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 "GuiClickableLabel.h"
23 #include "GuiCommandBuffer.h"
24 #include "GuiCompleter.h"
25 #include "GuiKeySymbol.h"
27 #include "GuiToolbar.h"
28 #include "GuiWorkArea.h"
29 #include "GuiProgress.h"
30 #include "LayoutBox.h"
34 #include "qt_helpers.h"
35 #include "support/filetools.h"
37 #include "frontends/alert.h"
38 #include "frontends/KeySymbol.h"
40 #include "buffer_funcs.h"
42 #include "BufferList.h"
43 #include "BufferParams.h"
44 #include "BufferView.h"
46 #include "Converter.h"
48 #include "CutAndPaste.h"
50 #include "ErrorList.h"
52 #include "FuncStatus.h"
53 #include "FuncRequest.h"
57 #include "LayoutFile.h"
59 #include "LyXAction.h"
63 #include "Paragraph.h"
64 #include "SpellChecker.h"
67 #include "TextClass.h"
72 #include "support/convert.h"
73 #include "support/debug.h"
74 #include "support/ExceptionMessage.h"
75 #include "support/FileName.h"
76 #include "support/filetools.h"
77 #include "support/gettext.h"
78 #include "support/filetools.h"
79 #include "support/ForkedCalls.h"
80 #include "support/lassert.h"
81 #include "support/lstrings.h"
82 #include "support/os.h"
83 #include "support/Package.h"
84 #include "support/PathChanger.h"
85 #include "support/Systemcall.h"
86 #include "support/Timeout.h"
87 #include "support/ProgressInterface.h"
90 #include <QApplication>
91 #include <QCloseEvent>
93 #include <QDesktopWidget>
94 #include <QDragEnterEvent>
97 #include <QFutureWatcher>
107 #include <QPushButton>
108 #include <QScrollBar>
110 #include <QShowEvent>
112 #include <QStackedWidget>
113 #include <QStatusBar>
114 #include <QSvgRenderer>
115 #include <QtConcurrentRun>
123 // sync with GuiAlert.cpp
124 #define EXPORT_in_THREAD 1
127 #include "support/bind.h"
131 #ifdef HAVE_SYS_TIME_H
132 # include <sys/time.h>
140 using namespace lyx::support;
144 using support::addExtension;
145 using support::changeExtension;
146 using support::removeExtension;
152 class BackgroundWidget : public QWidget
155 BackgroundWidget(int width, int height)
156 : width_(width), height_(height)
158 LYXERR(Debug::GUI, "show banner: " << lyxrc.show_banner);
159 if (!lyxrc.show_banner)
161 /// The text to be written on top of the pixmap
162 QString const htext = qt_("The Document\nProcessor[[welcome banner]]");
163 QString const htextsize = qt_("1.0[[possibly scale the welcome banner text size]]");
164 /// The text to be written on top of the pixmap
165 QString const text = lyx_version ?
166 qt_("version ") + lyx_version : qt_("unknown version");
167 #if QT_VERSION >= 0x050000
168 QString imagedir = "images/";
169 FileName fname = imageLibFileSearch(imagedir, "banner", "svgz");
170 QSvgRenderer svgRenderer(toqstr(fname.absFileName()));
171 if (svgRenderer.isValid()) {
172 splash_ = QPixmap(splashSize());
173 QPainter painter(&splash_);
174 svgRenderer.render(&painter);
175 splash_.setDevicePixelRatio(pixelRatio());
177 splash_ = getPixmap("images/", "banner", "png");
180 splash_ = getPixmap("images/", "banner", "svgz,png");
183 QPainter pain(&splash_);
184 pain.setPen(QColor(0, 0, 0));
185 qreal const fsize = fontSize();
188 qreal locscale = htextsize.toFloat(&ok);
191 QPointF const position = textPosition(false);
192 QPointF const hposition = textPosition(true);
193 QRectF const hrect(hposition, splashSize());
195 "widget pixel ratio: " << pixelRatio() <<
196 " splash pixel ratio: " << splashPixelRatio() <<
197 " version text size,position: " << fsize << "@" << position.x() << "+" << position.y());
199 // The font used to display the version info
200 font.setStyleHint(QFont::SansSerif);
201 font.setWeight(QFont::Bold);
202 font.setPointSizeF(fsize);
204 pain.drawText(position, text);
205 // The font used to display the version info
206 font.setStyleHint(QFont::SansSerif);
207 font.setWeight(QFont::Normal);
208 font.setPointSizeF(hfsize);
209 // Check how long the logo gets with the current font
210 // and adapt if the font is running wider than what
212 QFontMetrics fm(font);
213 // Split the title into lines to measure the longest line
214 // in the current l7n.
215 QStringList titlesegs = htext.split('\n');
217 int hline = fm.height();
218 QStringList::const_iterator sit;
219 for (sit = titlesegs.constBegin(); sit != titlesegs.constEnd(); ++sit) {
220 if (fm.width(*sit) > wline)
221 wline = fm.width(*sit);
223 // The longest line in the reference font (for English)
224 // is 180. Calculate scale factor from that.
225 double const wscale = wline > 0 ? (180.0 / wline) : 1;
226 // Now do the same for the height (necessary for condensed fonts)
227 double const hscale = (34.0 / hline);
228 // take the lower of the two scale factors.
229 double const scale = min(wscale, hscale);
230 // Now rescale. Also consider l7n's offset factor.
231 font.setPointSizeF(hfsize * scale * locscale);
234 pain.drawText(hrect, Qt::AlignLeft, htext);
235 setFocusPolicy(Qt::StrongFocus);
238 void paintEvent(QPaintEvent *)
240 int const w = width_;
241 int const h = height_;
242 int const x = (width() - w) / 2;
243 int const y = (height() - h) / 2;
245 "widget pixel ratio: " << pixelRatio() <<
246 " splash pixel ratio: " << splashPixelRatio() <<
247 " paint pixmap: " << w << "x" << h << "@" << x << "+" << y);
249 pain.drawPixmap(x, y, w, h, splash_);
252 void keyPressEvent(QKeyEvent * ev)
255 setKeySymbol(&sym, ev);
257 guiApp->processKeySym(sym, q_key_state(ev->modifiers()));
269 /// Current ratio between physical pixels and device-independent pixels
270 double pixelRatio() const {
271 #if QT_VERSION >= 0x050000
272 return qt_scale_factor * devicePixelRatio();
278 qreal fontSize() const {
279 return toqstr(lyxrc.font_sizes[NORMAL_SIZE]).toDouble();
282 QPointF textPosition(bool const heading) const {
283 return heading ? QPointF(width_/2 - 18, height_/2 - 45)
284 : QPointF(width_/2 - 18, height_/2 + 45);
287 QSize splashSize() const {
289 static_cast<unsigned int>(width_ * pixelRatio()),
290 static_cast<unsigned int>(height_ * pixelRatio()));
293 /// Ratio between physical pixels and device-independent pixels of splash image
294 double splashPixelRatio() const {
295 #if QT_VERSION >= 0x050000
296 return splash_.devicePixelRatio();
304 /// Toolbar store providing access to individual toolbars by name.
305 typedef map<string, GuiToolbar *> ToolbarMap;
307 typedef shared_ptr<Dialog> DialogPtr;
312 class GuiView::GuiViewPrivate
315 GuiViewPrivate(GuiViewPrivate const &);
316 void operator=(GuiViewPrivate const &);
318 GuiViewPrivate(GuiView * gv)
319 : gv_(gv), current_work_area_(nullptr), current_main_work_area_(nullptr),
320 layout_(nullptr), autosave_timeout_(5000),
323 // hardcode here the platform specific icon size
324 smallIconSize = 16; // scaling problems
325 normalIconSize = 20; // ok, default if iconsize.png is missing
326 bigIconSize = 26; // better for some math icons
327 hugeIconSize = 32; // better for hires displays
330 // if it exists, use width of iconsize.png as normal size
331 QString const dir = toqstr(addPath("images", lyxrc.icon_set));
332 FileName const fn = lyx::libFileSearch(dir, "iconsize.png");
334 QImage image(toqstr(fn.absFileName()));
335 if (image.width() < int(smallIconSize))
336 normalIconSize = smallIconSize;
337 else if (image.width() > int(giantIconSize))
338 normalIconSize = giantIconSize;
340 normalIconSize = image.width();
343 splitter_ = new QSplitter;
344 bg_widget_ = new BackgroundWidget(400, 250);
345 stack_widget_ = new QStackedWidget;
346 stack_widget_->addWidget(bg_widget_);
347 stack_widget_->addWidget(splitter_);
350 // TODO cleanup, remove the singleton, handle multiple Windows?
351 progress_ = ProgressInterface::instance();
352 if (!dynamic_cast<GuiProgress*>(progress_)) {
353 progress_ = new GuiProgress; // TODO who deletes it
354 ProgressInterface::setInstance(progress_);
357 dynamic_cast<GuiProgress*>(progress_),
358 SIGNAL(updateStatusBarMessage(QString const&)),
359 gv, SLOT(updateStatusBarMessage(QString const&)));
361 dynamic_cast<GuiProgress*>(progress_),
362 SIGNAL(clearMessageText()),
363 gv, SLOT(clearMessageText()));
370 delete stack_widget_;
375 stack_widget_->setCurrentWidget(bg_widget_);
376 bg_widget_->setUpdatesEnabled(true);
377 bg_widget_->setFocus();
380 int tabWorkAreaCount()
382 return splitter_->count();
385 TabWorkArea * tabWorkArea(int i)
387 return dynamic_cast<TabWorkArea *>(splitter_->widget(i));
390 TabWorkArea * currentTabWorkArea()
392 int areas = tabWorkAreaCount();
394 // The first TabWorkArea is always the first one, if any.
395 return tabWorkArea(0);
397 for (int i = 0; i != areas; ++i) {
398 TabWorkArea * twa = tabWorkArea(i);
399 if (current_main_work_area_ == twa->currentWorkArea())
403 // None has the focus so we just take the first one.
404 return tabWorkArea(0);
407 int countWorkAreasOf(Buffer & buf)
409 int areas = tabWorkAreaCount();
411 for (int i = 0; i != areas; ++i) {
412 TabWorkArea * twa = tabWorkArea(i);
413 if (twa->workArea(buf))
419 void setPreviewFuture(QFuture<Buffer::ExportStatus> const & f)
421 if (processing_thread_watcher_.isRunning()) {
422 // we prefer to cancel this preview in order to keep a snappy
426 processing_thread_watcher_.setFuture(f);
429 QSize iconSize(docstring const & icon_size)
432 if (icon_size == "small")
433 size = smallIconSize;
434 else if (icon_size == "normal")
435 size = normalIconSize;
436 else if (icon_size == "big")
438 else if (icon_size == "huge")
440 else if (icon_size == "giant")
441 size = giantIconSize;
443 size = icon_size.empty() ? normalIconSize : convert<int>(icon_size);
445 if (size < smallIconSize)
446 size = smallIconSize;
448 return QSize(size, size);
451 QSize iconSize(QString const & icon_size)
453 return iconSize(qstring_to_ucs4(icon_size));
456 string & iconSize(QSize const & qsize)
458 LATTEST(qsize.width() == qsize.height());
460 static string icon_size;
462 unsigned int size = qsize.width();
464 if (size < smallIconSize)
465 size = smallIconSize;
467 if (size == smallIconSize)
469 else if (size == normalIconSize)
470 icon_size = "normal";
471 else if (size == bigIconSize)
473 else if (size == hugeIconSize)
475 else if (size == giantIconSize)
478 icon_size = convert<string>(size);
483 static Buffer::ExportStatus previewAndDestroy(Buffer const * orig,
484 Buffer * buffer, string const & format);
485 static Buffer::ExportStatus exportAndDestroy(Buffer const * orig,
486 Buffer * buffer, string const & format);
487 static Buffer::ExportStatus compileAndDestroy(Buffer const * orig,
488 Buffer * buffer, string const & format);
489 static docstring autosaveAndDestroy(Buffer const * orig, Buffer * buffer);
492 static Buffer::ExportStatus runAndDestroy(const T& func,
493 Buffer const * orig, Buffer * buffer, string const & format);
495 // TODO syncFunc/previewFunc: use bind
496 bool asyncBufferProcessing(string const & argument,
497 Buffer const * used_buffer,
498 docstring const & msg,
499 Buffer::ExportStatus (*asyncFunc)(Buffer const *, Buffer *, string const &),
500 Buffer::ExportStatus (Buffer::*syncFunc)(string const &, bool) const,
501 Buffer::ExportStatus (Buffer::*previewFunc)(string const &) const,
504 QVector<GuiWorkArea*> guiWorkAreas();
508 GuiWorkArea * current_work_area_;
509 GuiWorkArea * current_main_work_area_;
510 QSplitter * splitter_;
511 QStackedWidget * stack_widget_;
512 BackgroundWidget * bg_widget_;
514 ToolbarMap toolbars_;
515 ProgressInterface* progress_;
516 /// The main layout box.
518 * \warning Don't Delete! The layout box is actually owned by
519 * whichever toolbar contains it. All the GuiView class needs is a
520 * means of accessing it.
522 * FIXME: replace that with a proper model so that we are not limited
523 * to only one dialog.
528 map<string, DialogPtr> dialogs_;
531 QTimer statusbar_timer_;
532 /// auto-saving of buffers
533 Timeout autosave_timeout_;
536 TocModels toc_models_;
539 QFutureWatcher<docstring> autosave_watcher_;
540 QFutureWatcher<Buffer::ExportStatus> processing_thread_watcher_;
542 string last_export_format;
543 string processing_format;
545 static QSet<Buffer const *> busyBuffers;
547 unsigned int smallIconSize;
548 unsigned int normalIconSize;
549 unsigned int bigIconSize;
550 unsigned int hugeIconSize;
551 unsigned int giantIconSize;
553 /// flag against a race condition due to multiclicks, see bug #1119
557 QSet<Buffer const *> GuiView::GuiViewPrivate::busyBuffers;
560 GuiView::GuiView(int id)
561 : d(*new GuiViewPrivate(this)), id_(id), closing_(false), busy_(0),
562 command_execute_(false), minibuffer_focus_(false), toolbarsMovable_(true),
565 connect(this, SIGNAL(bufferViewChanged()),
566 this, SLOT(onBufferViewChanged()));
568 // GuiToolbars *must* be initialised before the menu bar.
569 setIconSize(QSize(d.normalIconSize, d.normalIconSize)); // at least on Mac the default is 32 otherwise, which is huge
572 // set ourself as the current view. This is needed for the menu bar
573 // filling, at least for the static special menu item on Mac. Otherwise
574 // they are greyed out.
575 guiApp->setCurrentView(this);
577 // Fill up the menu bar.
578 guiApp->menus().fillMenuBar(menuBar(), this, true);
580 setCentralWidget(d.stack_widget_);
582 // Start autosave timer
583 if (lyxrc.autosave) {
584 // The connection is closed when this is destroyed.
585 d.autosave_timeout_.timeout.connect([this](){ autoSave();});
586 d.autosave_timeout_.setTimeout(lyxrc.autosave * 1000);
587 d.autosave_timeout_.start();
589 connect(&d.statusbar_timer_, SIGNAL(timeout()),
590 this, SLOT(clearMessage()));
592 // We don't want to keep the window in memory if it is closed.
593 setAttribute(Qt::WA_DeleteOnClose, true);
595 #if !(defined(Q_OS_WIN) || defined(Q_CYGWIN_WIN)) && !defined(Q_OS_MAC)
596 // QIcon::fromTheme was introduced in Qt 4.6
597 #if (QT_VERSION >= 0x040600)
598 // assign an icon to main form. We do not do it under Qt/Win or Qt/Mac,
599 // since the icon is provided in the application bundle. We use a themed
600 // version when available and use the bundled one as fallback.
601 setWindowIcon(QIcon::fromTheme("lyx", getPixmap("images/", "lyx", "svg,png")));
603 setWindowIcon(getPixmap("images/", "lyx", "svg,png"));
609 // use tabbed dock area for multiple docks
610 // (such as "source" and "messages")
611 setDockOptions(QMainWindow::ForceTabbedDocks);
614 setAcceptDrops(true);
616 // add busy indicator to statusbar
617 GuiClickableLabel * busylabel = new GuiClickableLabel(statusBar());
618 statusBar()->addPermanentWidget(busylabel);
619 search_mode mode = theGuiApp()->imageSearchMode();
620 QString fn = toqstr(lyx::libFileSearch("images", "busy", "gif", mode).absFileName());
621 QMovie * busyanim = new QMovie(fn, QByteArray(), busylabel);
622 busylabel->setMovie(busyanim);
626 connect(&d.processing_thread_watcher_, SIGNAL(started()),
627 busylabel, SLOT(show()));
628 connect(&d.processing_thread_watcher_, SIGNAL(finished()),
629 busylabel, SLOT(hide()));
630 connect(busylabel, SIGNAL(clicked()), this, SLOT(checkCancelBackground()));
632 QFontMetrics const fm(statusBar()->fontMetrics());
633 int const iconheight = max(int(d.normalIconSize), fm.height());
634 QSize const iconsize(iconheight, iconheight);
636 QPixmap shellescape = QIcon(getPixmap("images/", "emblem-shellescape", "svgz,png")).pixmap(iconsize);
637 shell_escape_ = new QLabel(statusBar());
638 shell_escape_->setPixmap(shellescape);
639 shell_escape_->setScaledContents(true);
640 shell_escape_->setAlignment(Qt::AlignCenter);
641 shell_escape_->setContextMenuPolicy(Qt::CustomContextMenu);
642 shell_escape_->setToolTip(qt_("WARNING: LaTeX is allowed to execute "
643 "external commands for this document. "
644 "Right click to change."));
645 SEMenu * menu = new SEMenu(this);
646 connect(shell_escape_, SIGNAL(customContextMenuRequested(QPoint)),
647 menu, SLOT(showMenu(QPoint)));
648 shell_escape_->hide();
649 statusBar()->addPermanentWidget(shell_escape_);
651 QPixmap readonly = QIcon(getPixmap("images/", "emblem-readonly", "svgz,png")).pixmap(iconsize);
652 read_only_ = new QLabel(statusBar());
653 read_only_->setPixmap(readonly);
654 read_only_->setScaledContents(true);
655 read_only_->setAlignment(Qt::AlignCenter);
657 statusBar()->addPermanentWidget(read_only_);
659 version_control_ = new QLabel(statusBar());
660 version_control_->setAlignment(Qt::AlignCenter);
661 version_control_->setFrameStyle(QFrame::StyledPanel);
662 version_control_->hide();
663 statusBar()->addPermanentWidget(version_control_);
665 statusBar()->setSizeGripEnabled(true);
668 connect(&d.autosave_watcher_, SIGNAL(finished()), this,
669 SLOT(autoSaveThreadFinished()));
671 connect(&d.processing_thread_watcher_, SIGNAL(started()), this,
672 SLOT(processingThreadStarted()));
673 connect(&d.processing_thread_watcher_, SIGNAL(finished()), this,
674 SLOT(processingThreadFinished()));
676 connect(this, SIGNAL(triggerShowDialog(QString const &, QString const &, Inset *)),
677 SLOT(doShowDialog(QString const &, QString const &, Inset *)));
679 // set custom application bars context menu, e.g. tool bar and menu bar
680 setContextMenuPolicy(Qt::CustomContextMenu);
681 connect(this, SIGNAL(customContextMenuRequested(const QPoint &)),
682 SLOT(toolBarPopup(const QPoint &)));
684 // Forbid too small unresizable window because it can happen
685 // with some window manager under X11.
686 setMinimumSize(300, 200);
688 if (lyxrc.allow_geometry_session) {
689 // Now take care of session management.
694 // no session handling, default to a sane size.
695 setGeometry(50, 50, 690, 510);
698 // clear session data if any.
700 settings.remove("views");
710 void GuiView::disableShellEscape()
712 BufferView * bv = documentBufferView();
715 theSession().shellescapeFiles().remove(bv->buffer().absFileName());
716 bv->buffer().params().shell_escape = false;
717 bv->processUpdateFlags(Update::Force);
721 void GuiView::checkCancelBackground()
723 docstring const ttl = _("Cancel Export?");
724 docstring const msg = _("Do you want to cancel the background export process?");
726 Alert::prompt(ttl, msg, 1, 1,
727 _("&Cancel export"), _("Co&ntinue"));
729 Systemcall::killscript();
733 QVector<GuiWorkArea*> GuiView::GuiViewPrivate::guiWorkAreas()
735 QVector<GuiWorkArea*> areas;
736 for (int i = 0; i < tabWorkAreaCount(); i++) {
737 TabWorkArea* ta = tabWorkArea(i);
738 for (int u = 0; u < ta->count(); u++) {
739 areas << ta->workArea(u);
745 static void handleExportStatus(GuiView * view, Buffer::ExportStatus status,
746 string const & format)
748 docstring const fmt = theFormats().prettyName(format);
751 case Buffer::ExportSuccess:
752 msg = bformat(_("Successful export to format: %1$s"), fmt);
754 case Buffer::ExportCancel:
755 msg = _("Document export cancelled.");
757 case Buffer::ExportError:
758 case Buffer::ExportNoPathToFormat:
759 case Buffer::ExportTexPathHasSpaces:
760 case Buffer::ExportConverterError:
761 msg = bformat(_("Error while exporting format: %1$s"), fmt);
763 case Buffer::PreviewSuccess:
764 msg = bformat(_("Successful preview of format: %1$s"), fmt);
766 case Buffer::PreviewError:
767 msg = bformat(_("Error while previewing format: %1$s"), fmt);
769 case Buffer::ExportKilled:
770 msg = bformat(_("Conversion cancelled while previewing format: %1$s"), fmt);
777 void GuiView::processingThreadStarted()
782 void GuiView::processingThreadFinished()
784 QFutureWatcher<Buffer::ExportStatus> const * watcher =
785 static_cast<QFutureWatcher<Buffer::ExportStatus> const *>(sender());
787 Buffer::ExportStatus const status = watcher->result();
788 handleExportStatus(this, status, d.processing_format);
791 BufferView const * const bv = currentBufferView();
792 if (bv && !bv->buffer().errorList("Export").empty()) {
797 bool const error = (status != Buffer::ExportSuccess &&
798 status != Buffer::PreviewSuccess &&
799 status != Buffer::ExportCancel);
801 ErrorList & el = bv->buffer().errorList(d.last_export_format);
802 // at this point, we do not know if buffer-view or
803 // master-buffer-view was called. If there was an export error,
804 // and the current buffer's error log is empty, we guess that
805 // it must be master-buffer-view that was called so we set
807 errors(d.last_export_format, el.empty());
812 void GuiView::autoSaveThreadFinished()
814 QFutureWatcher<docstring> const * watcher =
815 static_cast<QFutureWatcher<docstring> const *>(sender());
816 message(watcher->result());
821 void GuiView::saveLayout() const
824 settings.setValue("zoom_ratio", zoom_ratio_);
825 settings.setValue("devel_mode", devel_mode_);
826 settings.beginGroup("views");
827 settings.beginGroup(QString::number(id_));
828 #if defined(Q_WS_X11) || defined(QPA_XCB)
829 settings.setValue("pos", pos());
830 settings.setValue("size", size());
832 settings.setValue("geometry", saveGeometry());
834 settings.setValue("layout", saveState(0));
835 settings.setValue("icon_size", toqstr(d.iconSize(iconSize())));
839 void GuiView::saveUISettings() const
843 // Save the toolbar private states
844 ToolbarMap::iterator end = d.toolbars_.end();
845 for (ToolbarMap::iterator it = d.toolbars_.begin(); it != end; ++it)
846 it->second->saveSession(settings);
847 // Now take care of all other dialogs
848 map<string, DialogPtr>::const_iterator it = d.dialogs_.begin();
849 for (; it!= d.dialogs_.end(); ++it)
850 it->second->saveSession(settings);
854 bool GuiView::restoreLayout()
857 zoom_ratio_ = settings.value("zoom_ratio", 1.0).toDouble();
858 // Actual zoom value: default zoom + fractional offset
859 int zoom = (int)(lyxrc.defaultZoom * zoom_ratio_);
860 if (zoom < static_cast<int>(zoom_min_))
862 lyxrc.currentZoom = zoom;
863 devel_mode_ = settings.value("devel_mode", devel_mode_).toBool();
864 settings.beginGroup("views");
865 settings.beginGroup(QString::number(id_));
866 QString const icon_key = "icon_size";
867 if (!settings.contains(icon_key))
870 //code below is skipped when when ~/.config/LyX is (re)created
871 setIconSize(d.iconSize(settings.value(icon_key).toString()));
873 #if defined(Q_WS_X11) || defined(QPA_XCB)
874 QPoint pos = settings.value("pos", QPoint(50, 50)).toPoint();
875 QSize size = settings.value("size", QSize(690, 510)).toSize();
879 // Work-around for bug #6034: the window ends up in an undetermined
880 // state when trying to restore a maximized window when it is
881 // already maximized.
882 if (!(windowState() & Qt::WindowMaximized))
883 if (!restoreGeometry(settings.value("geometry").toByteArray()))
884 setGeometry(50, 50, 690, 510);
886 // Make sure layout is correctly oriented.
887 setLayoutDirection(qApp->layoutDirection());
889 // Allow the toc and view-source dock widget to be restored if needed.
891 if ((dialog = findOrBuild("toc", true)))
892 // see bug 5082. At least setup title and enabled state.
893 // Visibility will be adjusted by restoreState below.
894 dialog->prepareView();
895 if ((dialog = findOrBuild("view-source", true)))
896 dialog->prepareView();
897 if ((dialog = findOrBuild("progress", true)))
898 dialog->prepareView();
900 if (!restoreState(settings.value("layout").toByteArray(), 0))
903 // init the toolbars that have not been restored
904 Toolbars::Infos::iterator cit = guiApp->toolbars().begin();
905 Toolbars::Infos::iterator end = guiApp->toolbars().end();
906 for (; cit != end; ++cit) {
907 GuiToolbar * tb = toolbar(cit->name);
908 if (tb && !tb->isRestored())
909 initToolbar(cit->name);
912 // update lock (all) toolbars positions
913 updateLockToolbars();
920 GuiToolbar * GuiView::toolbar(string const & name)
922 ToolbarMap::iterator it = d.toolbars_.find(name);
923 if (it != d.toolbars_.end())
926 LYXERR(Debug::GUI, "Toolbar::display: no toolbar named " << name);
931 void GuiView::updateLockToolbars()
933 toolbarsMovable_ = false;
934 for (ToolbarInfo const & info : guiApp->toolbars()) {
935 GuiToolbar * tb = toolbar(info.name);
936 if (tb && tb->isMovable())
937 toolbarsMovable_ = true;
942 void GuiView::constructToolbars()
944 ToolbarMap::iterator it = d.toolbars_.begin();
945 for (; it != d.toolbars_.end(); ++it)
949 // I don't like doing this here, but the standard toolbar
950 // destroys this object when it's destroyed itself (vfr)
951 d.layout_ = new LayoutBox(*this);
952 d.stack_widget_->addWidget(d.layout_);
953 d.layout_->move(0,0);
955 // extracts the toolbars from the backend
956 Toolbars::Infos::iterator cit = guiApp->toolbars().begin();
957 Toolbars::Infos::iterator end = guiApp->toolbars().end();
958 for (; cit != end; ++cit)
959 d.toolbars_[cit->name] = new GuiToolbar(*cit, *this);
963 void GuiView::initToolbars()
965 // extracts the toolbars from the backend
966 Toolbars::Infos::iterator cit = guiApp->toolbars().begin();
967 Toolbars::Infos::iterator end = guiApp->toolbars().end();
968 for (; cit != end; ++cit)
969 initToolbar(cit->name);
973 void GuiView::initToolbar(string const & name)
975 GuiToolbar * tb = toolbar(name);
978 int const visibility = guiApp->toolbars().defaultVisibility(name);
979 bool newline = !(visibility & Toolbars::SAMEROW);
980 tb->setVisible(false);
981 tb->setVisibility(visibility);
983 if (visibility & Toolbars::TOP) {
985 addToolBarBreak(Qt::TopToolBarArea);
986 addToolBar(Qt::TopToolBarArea, tb);
989 if (visibility & Toolbars::BOTTOM) {
991 addToolBarBreak(Qt::BottomToolBarArea);
992 addToolBar(Qt::BottomToolBarArea, tb);
995 if (visibility & Toolbars::LEFT) {
997 addToolBarBreak(Qt::LeftToolBarArea);
998 addToolBar(Qt::LeftToolBarArea, tb);
1001 if (visibility & Toolbars::RIGHT) {
1003 addToolBarBreak(Qt::RightToolBarArea);
1004 addToolBar(Qt::RightToolBarArea, tb);
1007 if (visibility & Toolbars::ON)
1008 tb->setVisible(true);
1010 tb->setMovable(true);
1014 TocModels & GuiView::tocModels()
1016 return d.toc_models_;
1020 void GuiView::setFocus()
1022 LYXERR(Debug::DEBUG, "GuiView::setFocus()" << this);
1023 QMainWindow::setFocus();
1027 bool GuiView::hasFocus() const
1029 if (currentWorkArea())
1030 return currentWorkArea()->hasFocus();
1031 if (currentMainWorkArea())
1032 return currentMainWorkArea()->hasFocus();
1033 return d.bg_widget_->hasFocus();
1037 void GuiView::focusInEvent(QFocusEvent * e)
1039 LYXERR(Debug::DEBUG, "GuiView::focusInEvent()" << this);
1040 QMainWindow::focusInEvent(e);
1041 // Make sure guiApp points to the correct view.
1042 guiApp->setCurrentView(this);
1043 if (currentWorkArea())
1044 currentWorkArea()->setFocus();
1045 else if (currentMainWorkArea())
1046 currentMainWorkArea()->setFocus();
1048 d.bg_widget_->setFocus();
1052 void GuiView::showEvent(QShowEvent * e)
1054 LYXERR(Debug::GUI, "Passed Geometry "
1055 << size().height() << "x" << size().width()
1056 << "+" << pos().x() << "+" << pos().y());
1058 if (d.splitter_->count() == 0)
1059 // No work area, switch to the background widget.
1063 QMainWindow::showEvent(e);
1067 bool GuiView::closeScheduled()
1074 bool GuiView::prepareAllBuffersForLogout()
1076 Buffer * first = theBufferList().first();
1080 // First, iterate over all buffers and ask the users if unsaved
1081 // changes should be saved.
1082 // We cannot use a for loop as the buffer list cycles.
1085 if (!saveBufferIfNeeded(const_cast<Buffer &>(*b), false))
1087 b = theBufferList().next(b);
1088 } while (b != first);
1090 // Next, save session state
1091 // When a view/window was closed before without quitting LyX, there
1092 // are already entries in the lastOpened list.
1093 theSession().lastOpened().clear();
1100 /** Destroy only all tabbed WorkAreas. Destruction of other WorkAreas
1101 ** is responsibility of the container (e.g., dialog)
1103 void GuiView::closeEvent(QCloseEvent * close_event)
1105 LYXERR(Debug::DEBUG, "GuiView::closeEvent()");
1107 if (!GuiViewPrivate::busyBuffers.isEmpty()) {
1108 Alert::warning(_("Exit LyX"),
1109 _("LyX could not be closed because documents are being processed by LyX."));
1110 close_event->setAccepted(false);
1114 // If the user pressed the x (so we didn't call closeView
1115 // programmatically), we want to clear all existing entries.
1117 theSession().lastOpened().clear();
1122 // it can happen that this event arrives without selecting the view,
1123 // e.g. when clicking the close button on a background window.
1125 if (!closeWorkAreaAll()) {
1127 close_event->ignore();
1131 // Make sure that nothing will use this to be closed View.
1132 guiApp->unregisterView(this);
1134 if (isFullScreen()) {
1135 // Switch off fullscreen before closing.
1140 // Make sure the timer time out will not trigger a statusbar update.
1141 d.statusbar_timer_.stop();
1143 // Saving fullscreen requires additional tweaks in the toolbar code.
1144 // It wouldn't also work under linux natively.
1145 if (lyxrc.allow_geometry_session) {
1150 close_event->accept();
1154 void GuiView::dragEnterEvent(QDragEnterEvent * event)
1156 if (event->mimeData()->hasUrls())
1158 /// \todo Ask lyx-devel is this is enough:
1159 /// if (event->mimeData()->hasFormat("text/plain"))
1160 /// event->acceptProposedAction();
1164 void GuiView::dropEvent(QDropEvent * event)
1166 QList<QUrl> files = event->mimeData()->urls();
1167 if (files.isEmpty())
1170 LYXERR(Debug::GUI, "GuiView::dropEvent: got URLs!");
1171 for (int i = 0; i != files.size(); ++i) {
1172 string const file = os::internal_path(fromqstr(
1173 files.at(i).toLocalFile()));
1177 string const ext = support::getExtension(file);
1178 vector<const Format *> found_formats;
1180 // Find all formats that have the correct extension.
1181 vector<const Format *> const & import_formats
1182 = theConverters().importableFormats();
1183 vector<const Format *>::const_iterator it = import_formats.begin();
1184 for (; it != import_formats.end(); ++it)
1185 if ((*it)->hasExtension(ext))
1186 found_formats.push_back(*it);
1189 if (found_formats.size() >= 1) {
1190 if (found_formats.size() > 1) {
1191 //FIXME: show a dialog to choose the correct importable format
1192 LYXERR(Debug::FILES,
1193 "Multiple importable formats found, selecting first");
1195 string const arg = found_formats[0]->name() + " " + file;
1196 cmd = FuncRequest(LFUN_BUFFER_IMPORT, arg);
1199 //FIXME: do we have to explicitly check whether it's a lyx file?
1200 LYXERR(Debug::FILES,
1201 "No formats found, trying to open it as a lyx file");
1202 cmd = FuncRequest(LFUN_FILE_OPEN, file);
1204 // add the functions to the queue
1205 guiApp->addToFuncRequestQueue(cmd);
1208 // now process the collected functions. We perform the events
1209 // asynchronously. This prevents potential problems in case the
1210 // BufferView is closed within an event.
1211 guiApp->processFuncRequestQueueAsync();
1215 void GuiView::message(docstring const & str)
1217 if (ForkedProcess::iAmAChild())
1220 // call is moved to GUI-thread by GuiProgress
1221 d.progress_->appendMessage(toqstr(str));
1225 void GuiView::clearMessageText()
1227 message(docstring());
1231 void GuiView::updateStatusBarMessage(QString const & str)
1233 statusBar()->showMessage(str);
1234 d.statusbar_timer_.stop();
1235 d.statusbar_timer_.start(3000);
1239 void GuiView::clearMessage()
1241 // FIXME: This code was introduced in r19643 to fix bug #4123. However,
1242 // the hasFocus function mostly returns false, even if the focus is on
1243 // a workarea in this view.
1247 d.statusbar_timer_.stop();
1251 void GuiView::updateWindowTitle(GuiWorkArea * wa)
1253 if (wa != d.current_work_area_
1254 || wa->bufferView().buffer().isInternal())
1256 Buffer const & buf = wa->bufferView().buffer();
1257 // Set the windows title
1258 docstring title = buf.fileName().displayName(130) + from_ascii("[*]");
1259 if (buf.notifiesExternalModification()) {
1260 title = bformat(_("%1$s (modified externally)"), title);
1261 // If the external modification status has changed, then maybe the status of
1262 // buffer-save has changed too.
1266 title += from_ascii(" - LyX");
1268 setWindowTitle(toqstr(title));
1269 // Sets the path for the window: this is used by OSX to
1270 // allow a context click on the title bar showing a menu
1271 // with the path up to the file
1272 setWindowFilePath(toqstr(buf.absFileName()));
1273 // Tell Qt whether the current document is changed
1274 setWindowModified(!buf.isClean());
1276 if (buf.params().shell_escape)
1277 shell_escape_->show();
1279 shell_escape_->hide();
1281 if (buf.hasReadonlyFlag())
1286 if (buf.lyxvc().inUse()) {
1287 version_control_->show();
1288 version_control_->setText(toqstr(buf.lyxvc().vcstatus()));
1290 version_control_->hide();
1294 void GuiView::on_currentWorkAreaChanged(GuiWorkArea * wa)
1296 if (d.current_work_area_)
1297 // disconnect the current work area from all slots
1298 QObject::disconnect(d.current_work_area_, nullptr, this, nullptr);
1300 disconnectBufferView();
1301 connectBufferView(wa->bufferView());
1302 connectBuffer(wa->bufferView().buffer());
1303 d.current_work_area_ = wa;
1304 QObject::connect(wa, SIGNAL(titleChanged(GuiWorkArea *)),
1305 this, SLOT(updateWindowTitle(GuiWorkArea *)));
1306 QObject::connect(wa, SIGNAL(busy(bool)),
1307 this, SLOT(setBusy(bool)));
1308 // connection of a signal to a signal
1309 QObject::connect(wa, SIGNAL(bufferViewChanged()),
1310 this, SIGNAL(bufferViewChanged()));
1311 Q_EMIT updateWindowTitle(wa);
1312 Q_EMIT bufferViewChanged();
1316 void GuiView::onBufferViewChanged()
1319 // Buffer-dependent dialogs must be updated. This is done here because
1320 // some dialogs require buffer()->text.
1325 void GuiView::on_lastWorkAreaRemoved()
1328 // We already are in a close event. Nothing more to do.
1331 if (d.splitter_->count() > 1)
1332 // We have a splitter so don't close anything.
1335 // Reset and updates the dialogs.
1336 Q_EMIT bufferViewChanged();
1341 if (lyxrc.open_buffers_in_tabs)
1342 // Nothing more to do, the window should stay open.
1345 if (guiApp->viewIds().size() > 1) {
1351 // On Mac we also close the last window because the application stay
1352 // resident in memory. On other platforms we don't close the last
1353 // window because this would quit the application.
1359 void GuiView::updateStatusBar()
1361 // let the user see the explicit message
1362 if (d.statusbar_timer_.isActive())
1369 void GuiView::showMessage()
1373 QString msg = toqstr(theGuiApp()->viewStatusMessage());
1374 if (msg.isEmpty()) {
1375 BufferView const * bv = currentBufferView();
1377 msg = toqstr(bv->cursor().currentState(devel_mode_));
1379 msg = qt_("Welcome to LyX!");
1381 statusBar()->showMessage(msg);
1385 bool GuiView::event(QEvent * e)
1389 // Useful debug code:
1390 //case QEvent::ActivationChange:
1391 //case QEvent::WindowDeactivate:
1392 //case QEvent::Paint:
1393 //case QEvent::Enter:
1394 //case QEvent::Leave:
1395 //case QEvent::HoverEnter:
1396 //case QEvent::HoverLeave:
1397 //case QEvent::HoverMove:
1398 //case QEvent::StatusTip:
1399 //case QEvent::DragEnter:
1400 //case QEvent::DragLeave:
1401 //case QEvent::Drop:
1404 case QEvent::WindowActivate: {
1405 GuiView * old_view = guiApp->currentView();
1406 if (this == old_view) {
1408 return QMainWindow::event(e);
1410 if (old_view && old_view->currentBufferView()) {
1411 // save current selection to the selection buffer to allow
1412 // middle-button paste in this window.
1413 cap::saveSelection(old_view->currentBufferView()->cursor());
1415 guiApp->setCurrentView(this);
1416 if (d.current_work_area_)
1417 on_currentWorkAreaChanged(d.current_work_area_);
1421 return QMainWindow::event(e);
1424 case QEvent::ShortcutOverride: {
1426 if (isFullScreen() && menuBar()->isHidden()) {
1427 QKeyEvent * ke = static_cast<QKeyEvent*>(e);
1428 // FIXME: we should also try to detect special LyX shortcut such as
1429 // Alt-P and Alt-M. Right now there is a hack in
1430 // GuiWorkArea::processKeySym() that hides again the menubar for
1432 if (ke->modifiers() & Qt::AltModifier && ke->key() != Qt::Key_Alt) {
1434 return QMainWindow::event(e);
1437 return QMainWindow::event(e);
1441 return QMainWindow::event(e);
1445 void GuiView::resetWindowTitle()
1447 setWindowTitle(qt_("LyX"));
1450 bool GuiView::focusNextPrevChild(bool /*next*/)
1457 bool GuiView::busy() const
1463 void GuiView::setBusy(bool busy)
1465 bool const busy_before = busy_ > 0;
1466 busy ? ++busy_ : --busy_;
1467 if ((busy_ > 0) == busy_before)
1468 // busy state didn't change
1472 QApplication::setOverrideCursor(Qt::WaitCursor);
1475 QApplication::restoreOverrideCursor();
1480 void GuiView::resetCommandExecute()
1482 command_execute_ = false;
1487 double GuiView::pixelRatio() const
1489 #if QT_VERSION >= 0x050000
1490 return qt_scale_factor * devicePixelRatio();
1497 GuiWorkArea * GuiView::workArea(int index)
1499 if (TabWorkArea * twa = d.currentTabWorkArea())
1500 if (index < twa->count())
1501 return twa->workArea(index);
1506 GuiWorkArea * GuiView::workArea(Buffer & buffer)
1508 if (currentWorkArea()
1509 && ¤tWorkArea()->bufferView().buffer() == &buffer)
1510 return currentWorkArea();
1511 if (TabWorkArea * twa = d.currentTabWorkArea())
1512 return twa->workArea(buffer);
1517 GuiWorkArea * GuiView::addWorkArea(Buffer & buffer)
1519 // Automatically create a TabWorkArea if there are none yet.
1520 TabWorkArea * tab_widget = d.splitter_->count()
1521 ? d.currentTabWorkArea() : addTabWorkArea();
1522 return tab_widget->addWorkArea(buffer, *this);
1526 TabWorkArea * GuiView::addTabWorkArea()
1528 TabWorkArea * twa = new TabWorkArea;
1529 QObject::connect(twa, SIGNAL(currentWorkAreaChanged(GuiWorkArea *)),
1530 this, SLOT(on_currentWorkAreaChanged(GuiWorkArea *)));
1531 QObject::connect(twa, SIGNAL(lastWorkAreaRemoved()),
1532 this, SLOT(on_lastWorkAreaRemoved()));
1534 d.splitter_->addWidget(twa);
1535 d.stack_widget_->setCurrentWidget(d.splitter_);
1540 GuiWorkArea const * GuiView::currentWorkArea() const
1542 return d.current_work_area_;
1546 GuiWorkArea * GuiView::currentWorkArea()
1548 return d.current_work_area_;
1552 GuiWorkArea const * GuiView::currentMainWorkArea() const
1554 if (!d.currentTabWorkArea())
1556 return d.currentTabWorkArea()->currentWorkArea();
1560 GuiWorkArea * GuiView::currentMainWorkArea()
1562 if (!d.currentTabWorkArea())
1564 return d.currentTabWorkArea()->currentWorkArea();
1568 void GuiView::setCurrentWorkArea(GuiWorkArea * wa)
1570 LYXERR(Debug::DEBUG, "Setting current wa: " << wa << endl);
1572 d.current_work_area_ = nullptr;
1574 Q_EMIT bufferViewChanged();
1578 // FIXME: I've no clue why this is here and why it accesses
1579 // theGuiApp()->currentView, which might be 0 (bug 6464).
1580 // See also 27525 (vfr).
1581 if (theGuiApp()->currentView() == this
1582 && theGuiApp()->currentView()->currentWorkArea() == wa)
1585 if (currentBufferView())
1586 cap::saveSelection(currentBufferView()->cursor());
1588 theGuiApp()->setCurrentView(this);
1589 d.current_work_area_ = wa;
1591 // We need to reset this now, because it will need to be
1592 // right if the tabWorkArea gets reset in the for loop. We
1593 // will change it back if we aren't in that case.
1594 GuiWorkArea * const old_cmwa = d.current_main_work_area_;
1595 d.current_main_work_area_ = wa;
1597 for (int i = 0; i != d.splitter_->count(); ++i) {
1598 if (d.tabWorkArea(i)->setCurrentWorkArea(wa)) {
1599 LYXERR(Debug::DEBUG, "Current wa: " << currentWorkArea()
1600 << ", Current main wa: " << currentMainWorkArea());
1605 d.current_main_work_area_ = old_cmwa;
1607 LYXERR(Debug::DEBUG, "This is not a tabbed wa");
1608 on_currentWorkAreaChanged(wa);
1609 BufferView & bv = wa->bufferView();
1610 bv.cursor().fixIfBroken();
1612 wa->setUpdatesEnabled(true);
1613 LYXERR(Debug::DEBUG, "Current wa: " << currentWorkArea() << ", Current main wa: " << currentMainWorkArea());
1617 void GuiView::removeWorkArea(GuiWorkArea * wa)
1619 LASSERT(wa, return);
1620 if (wa == d.current_work_area_) {
1622 disconnectBufferView();
1623 d.current_work_area_ = nullptr;
1624 d.current_main_work_area_ = nullptr;
1627 bool found_twa = false;
1628 for (int i = 0; i != d.splitter_->count(); ++i) {
1629 TabWorkArea * twa = d.tabWorkArea(i);
1630 if (twa->removeWorkArea(wa)) {
1631 // Found in this tab group, and deleted the GuiWorkArea.
1633 if (twa->count() != 0) {
1634 if (d.current_work_area_ == nullptr)
1635 // This means that we are closing the current GuiWorkArea, so
1636 // switch to the next GuiWorkArea in the found TabWorkArea.
1637 setCurrentWorkArea(twa->currentWorkArea());
1639 // No more WorkAreas in this tab group, so delete it.
1646 // It is not a tabbed work area (i.e., the search work area), so it
1647 // should be deleted by other means.
1648 LASSERT(found_twa, return);
1650 if (d.current_work_area_ == nullptr) {
1651 if (d.splitter_->count() != 0) {
1652 TabWorkArea * twa = d.currentTabWorkArea();
1653 setCurrentWorkArea(twa->currentWorkArea());
1655 // No more work areas, switch to the background widget.
1656 setCurrentWorkArea(nullptr);
1662 LayoutBox * GuiView::getLayoutDialog() const
1668 void GuiView::updateLayoutList()
1671 d.layout_->updateContents(false);
1675 void GuiView::updateToolbars()
1677 ToolbarMap::iterator end = d.toolbars_.end();
1678 if (d.current_work_area_) {
1680 if (d.current_work_area_->bufferView().cursor().inMathed()
1681 && !d.current_work_area_->bufferView().cursor().inRegexped())
1682 context |= Toolbars::MATH;
1683 if (lyx::getStatus(FuncRequest(LFUN_LAYOUT_TABULAR)).enabled())
1684 context |= Toolbars::TABLE;
1685 if (currentBufferView()->buffer().areChangesPresent()
1686 || (lyx::getStatus(FuncRequest(LFUN_CHANGES_TRACK)).enabled()
1687 && lyx::getStatus(FuncRequest(LFUN_CHANGES_TRACK)).onOff(true))
1688 || (lyx::getStatus(FuncRequest(LFUN_CHANGES_OUTPUT)).enabled()
1689 && lyx::getStatus(FuncRequest(LFUN_CHANGES_OUTPUT)).onOff(true)))
1690 context |= Toolbars::REVIEW;
1691 if (lyx::getStatus(FuncRequest(LFUN_IN_MATHMACROTEMPLATE)).enabled())
1692 context |= Toolbars::MATHMACROTEMPLATE;
1693 if (lyx::getStatus(FuncRequest(LFUN_IN_IPA)).enabled())
1694 context |= Toolbars::IPA;
1695 if (command_execute_)
1696 context |= Toolbars::MINIBUFFER;
1697 if (minibuffer_focus_) {
1698 context |= Toolbars::MINIBUFFER_FOCUS;
1699 minibuffer_focus_ = false;
1702 for (ToolbarMap::iterator it = d.toolbars_.begin(); it != end; ++it)
1703 it->second->update(context);
1705 for (ToolbarMap::iterator it = d.toolbars_.begin(); it != end; ++it)
1706 it->second->update();
1710 void GuiView::setBuffer(Buffer * newBuffer, bool switch_to)
1712 LYXERR(Debug::DEBUG, "Setting buffer: " << newBuffer << endl);
1713 LASSERT(newBuffer, return);
1715 GuiWorkArea * wa = workArea(*newBuffer);
1716 if (wa == nullptr) {
1718 newBuffer->masterBuffer()->updateBuffer();
1720 wa = addWorkArea(*newBuffer);
1721 // scroll to the position when the BufferView was last closed
1722 if (lyxrc.use_lastfilepos) {
1723 LastFilePosSection::FilePos filepos =
1724 theSession().lastFilePos().load(newBuffer->fileName());
1725 wa->bufferView().moveToPosition(filepos.pit, filepos.pos, 0, 0);
1728 //Disconnect the old buffer...there's no new one.
1731 connectBuffer(*newBuffer);
1732 connectBufferView(wa->bufferView());
1734 setCurrentWorkArea(wa);
1738 void GuiView::connectBuffer(Buffer & buf)
1740 buf.setGuiDelegate(this);
1744 void GuiView::disconnectBuffer()
1746 if (d.current_work_area_)
1747 d.current_work_area_->bufferView().buffer().setGuiDelegate(nullptr);
1751 void GuiView::connectBufferView(BufferView & bv)
1753 bv.setGuiDelegate(this);
1757 void GuiView::disconnectBufferView()
1759 if (d.current_work_area_)
1760 d.current_work_area_->bufferView().setGuiDelegate(nullptr);
1764 void GuiView::errors(string const & error_type, bool from_master)
1766 BufferView const * const bv = currentBufferView();
1770 ErrorList const & el = from_master ?
1771 bv->buffer().masterBuffer()->errorList(error_type) :
1772 bv->buffer().errorList(error_type);
1777 string err = error_type;
1779 err = "from_master|" + error_type;
1780 showDialog("errorlist", err);
1784 void GuiView::updateTocItem(string const & type, DocIterator const & dit)
1786 d.toc_models_.updateItem(toqstr(type), dit);
1790 void GuiView::structureChanged()
1792 // This is called from the Buffer, which has no way to ensure that cursors
1793 // in BufferView remain valid.
1794 if (documentBufferView())
1795 documentBufferView()->cursor().sanitize();
1796 // FIXME: This is slightly expensive, though less than the tocBackend update
1797 // (#9880). This also resets the view in the Toc Widget (#6675).
1798 d.toc_models_.reset(documentBufferView());
1799 // Navigator needs more than a simple update in this case. It needs to be
1801 updateDialog("toc", "");
1805 void GuiView::updateDialog(string const & name, string const & sdata)
1807 if (!isDialogVisible(name))
1810 map<string, DialogPtr>::const_iterator it = d.dialogs_.find(name);
1811 if (it == d.dialogs_.end())
1814 Dialog * const dialog = it->second.get();
1815 if (dialog->isVisibleView())
1816 dialog->initialiseParams(sdata);
1820 BufferView * GuiView::documentBufferView()
1822 return currentMainWorkArea()
1823 ? ¤tMainWorkArea()->bufferView()
1828 BufferView const * GuiView::documentBufferView() const
1830 return currentMainWorkArea()
1831 ? ¤tMainWorkArea()->bufferView()
1836 BufferView * GuiView::currentBufferView()
1838 return d.current_work_area_ ? &d.current_work_area_->bufferView() : nullptr;
1842 BufferView const * GuiView::currentBufferView() const
1844 return d.current_work_area_ ? &d.current_work_area_->bufferView() : nullptr;
1848 docstring GuiView::GuiViewPrivate::autosaveAndDestroy(
1849 Buffer const * orig, Buffer * clone)
1851 bool const success = clone->autoSave();
1853 busyBuffers.remove(orig);
1855 ? _("Automatic save done.")
1856 : _("Automatic save failed!");
1860 void GuiView::autoSave()
1862 LYXERR(Debug::INFO, "Running autoSave()");
1864 Buffer * buffer = documentBufferView()
1865 ? &documentBufferView()->buffer() : nullptr;
1867 resetAutosaveTimers();
1871 GuiViewPrivate::busyBuffers.insert(buffer);
1872 QFuture<docstring> f = QtConcurrent::run(GuiViewPrivate::autosaveAndDestroy,
1873 buffer, buffer->cloneBufferOnly());
1874 d.autosave_watcher_.setFuture(f);
1875 resetAutosaveTimers();
1879 void GuiView::resetAutosaveTimers()
1882 d.autosave_timeout_.restart();
1886 bool GuiView::getStatus(FuncRequest const & cmd, FuncStatus & flag)
1889 Buffer * buf = currentBufferView()
1890 ? ¤tBufferView()->buffer() : nullptr;
1891 Buffer * doc_buffer = documentBufferView()
1892 ? &(documentBufferView()->buffer()) : nullptr;
1895 /* In LyX/Mac, when a dialog is open, the menus of the
1896 application can still be accessed without giving focus to
1897 the main window. In this case, we want to disable the menu
1898 entries that are buffer-related.
1899 This code must not be used on Linux and Windows, since it
1900 would disable buffer-related entries when hovering over the
1901 menu (see bug #9574).
1903 if (cmd.origin() == FuncRequest::MENU && !hasFocus()) {
1909 // Check whether we need a buffer
1910 if (!lyxaction.funcHasFlag(cmd.action(), LyXAction::NoBuffer) && !buf) {
1911 // no, exit directly
1912 flag.message(from_utf8(N_("Command not allowed with"
1913 "out any document open")));
1914 flag.setEnabled(false);
1918 if (cmd.origin() == FuncRequest::TOC) {
1919 GuiToc * toc = static_cast<GuiToc*>(findOrBuild("toc", false));
1920 if (!toc || !toc->getStatus(documentBufferView()->cursor(), cmd, flag))
1921 flag.setEnabled(false);
1925 switch(cmd.action()) {
1926 case LFUN_BUFFER_IMPORT:
1929 case LFUN_MASTER_BUFFER_EXPORT:
1931 && (doc_buffer->parent() != nullptr
1932 || doc_buffer->hasChildren())
1933 && !d.processing_thread_watcher_.isRunning()
1934 // this launches a dialog, which would be in the wrong Buffer
1935 && !(::lyx::operator==(cmd.argument(), "custom"));
1938 case LFUN_MASTER_BUFFER_UPDATE:
1939 case LFUN_MASTER_BUFFER_VIEW:
1941 && (doc_buffer->parent() != nullptr
1942 || doc_buffer->hasChildren())
1943 && !d.processing_thread_watcher_.isRunning();
1946 case LFUN_BUFFER_UPDATE:
1947 case LFUN_BUFFER_VIEW: {
1948 if (!doc_buffer || d.processing_thread_watcher_.isRunning()) {
1952 string format = to_utf8(cmd.argument());
1953 if (cmd.argument().empty())
1954 format = doc_buffer->params().getDefaultOutputFormat();
1955 enable = doc_buffer->params().isExportable(format, true);
1959 case LFUN_BUFFER_RELOAD:
1960 enable = doc_buffer && !doc_buffer->isUnnamed()
1961 && doc_buffer->fileName().exists()
1962 && (!doc_buffer->isClean() || doc_buffer->notifiesExternalModification());
1965 case LFUN_BUFFER_RESET_EXPORT:
1966 enable = doc_buffer != nullptr;
1969 case LFUN_BUFFER_CHILD_OPEN:
1970 enable = doc_buffer != nullptr;
1973 case LFUN_MASTER_BUFFER_FORALL: {
1974 if (doc_buffer == nullptr) {
1975 flag.message(from_utf8(N_("Command not allowed without a buffer open")));
1979 FuncRequest const cmdToPass = lyxaction.lookupFunc(cmd.getLongArg(0));
1980 if (cmdToPass.action() == LFUN_UNKNOWN_ACTION) {
1981 flag.message(from_utf8(N_("Invalid argument of master-buffer-forall")));
1986 for (Buffer * buf : doc_buffer->allRelatives()) {
1987 GuiWorkArea * wa = workArea(*buf);
1990 if (wa->bufferView().getStatus(cmdToPass, flag)) {
1991 enable = flag.enabled();
1998 case LFUN_BUFFER_WRITE:
1999 enable = doc_buffer && (doc_buffer->isUnnamed()
2000 || (!doc_buffer->isClean()
2001 || cmd.argument() == "force"));
2004 //FIXME: This LFUN should be moved to GuiApplication.
2005 case LFUN_BUFFER_WRITE_ALL: {
2006 // We enable the command only if there are some modified buffers
2007 Buffer * first = theBufferList().first();
2012 // We cannot use a for loop as the buffer list is a cycle.
2014 if (!b->isClean()) {
2018 b = theBufferList().next(b);
2019 } while (b != first);
2023 case LFUN_BUFFER_EXTERNAL_MODIFICATION_CLEAR:
2024 enable = doc_buffer && doc_buffer->notifiesExternalModification();
2027 case LFUN_BUFFER_EXPORT: {
2028 if (!doc_buffer || d.processing_thread_watcher_.isRunning()) {
2032 return doc_buffer->getStatus(cmd, flag);
2035 case LFUN_BUFFER_EXPORT_AS:
2036 if (!doc_buffer || d.processing_thread_watcher_.isRunning()) {
2041 case LFUN_BUFFER_WRITE_AS:
2042 case LFUN_BUFFER_WRITE_AS_TEMPLATE:
2043 enable = doc_buffer != nullptr;
2046 case LFUN_EXPORT_CANCEL:
2047 enable = d.processing_thread_watcher_.isRunning();
2050 case LFUN_BUFFER_CLOSE:
2051 case LFUN_VIEW_CLOSE:
2052 enable = doc_buffer != nullptr;
2055 case LFUN_BUFFER_CLOSE_ALL:
2056 enable = theBufferList().last() != theBufferList().first();
2059 case LFUN_BUFFER_CHKTEX: {
2060 // hide if we have no checktex command
2061 if (lyxrc.chktex_command.empty()) {
2062 flag.setUnknown(true);
2066 if (!doc_buffer || !doc_buffer->params().isLatex()
2067 || d.processing_thread_watcher_.isRunning()) {
2068 // grey out, don't hide
2076 case LFUN_VIEW_SPLIT:
2077 if (cmd.getArg(0) == "vertical")
2078 enable = doc_buffer && (d.splitter_->count() == 1 ||
2079 d.splitter_->orientation() == Qt::Vertical);
2081 enable = doc_buffer && (d.splitter_->count() == 1 ||
2082 d.splitter_->orientation() == Qt::Horizontal);
2085 case LFUN_TAB_GROUP_CLOSE:
2086 enable = d.tabWorkAreaCount() > 1;
2089 case LFUN_DEVEL_MODE_TOGGLE:
2090 flag.setOnOff(devel_mode_);
2093 case LFUN_TOOLBAR_TOGGLE: {
2094 string const name = cmd.getArg(0);
2095 if (GuiToolbar * t = toolbar(name))
2096 flag.setOnOff(t->isVisible());
2099 docstring const msg =
2100 bformat(_("Unknown toolbar \"%1$s\""), from_utf8(name));
2106 case LFUN_TOOLBAR_MOVABLE: {
2107 string const name = cmd.getArg(0);
2108 // use negation since locked == !movable
2110 // toolbar name * locks all toolbars
2111 flag.setOnOff(!toolbarsMovable_);
2112 else if (GuiToolbar * t = toolbar(name))
2113 flag.setOnOff(!(t->isMovable()));
2116 docstring const msg =
2117 bformat(_("Unknown toolbar \"%1$s\""), from_utf8(name));
2123 case LFUN_ICON_SIZE:
2124 flag.setOnOff(d.iconSize(cmd.argument()) == iconSize());
2127 case LFUN_DROP_LAYOUTS_CHOICE:
2128 enable = buf != nullptr;
2131 case LFUN_UI_TOGGLE:
2132 flag.setOnOff(isFullScreen());
2135 case LFUN_DIALOG_DISCONNECT_INSET:
2138 case LFUN_DIALOG_HIDE:
2139 // FIXME: should we check if the dialog is shown?
2142 case LFUN_DIALOG_TOGGLE:
2143 flag.setOnOff(isDialogVisible(cmd.getArg(0)));
2146 case LFUN_DIALOG_SHOW: {
2147 string const name = cmd.getArg(0);
2149 enable = name == "aboutlyx"
2150 || name == "file" //FIXME: should be removed.
2151 || name == "lyxfiles"
2153 || name == "texinfo"
2154 || name == "progress"
2155 || name == "compare";
2156 else if (name == "character" || name == "symbols"
2157 || name == "mathdelimiter" || name == "mathmatrix") {
2158 if (!buf || buf->isReadonly())
2161 Cursor const & cur = currentBufferView()->cursor();
2162 enable = !(cur.inTexted() && cur.paragraph().isPassThru());
2165 else if (name == "latexlog")
2166 enable = FileName(doc_buffer->logName()).isReadableFile();
2167 else if (name == "spellchecker")
2168 enable = theSpellChecker()
2169 && !doc_buffer->isReadonly()
2170 && !doc_buffer->text().empty();
2171 else if (name == "vclog")
2172 enable = doc_buffer->lyxvc().inUse();
2176 case LFUN_DIALOG_UPDATE: {
2177 string const name = cmd.getArg(0);
2179 enable = name == "prefs";
2183 case LFUN_COMMAND_EXECUTE:
2185 case LFUN_MENU_OPEN:
2186 // Nothing to check.
2189 case LFUN_COMPLETION_INLINE:
2190 if (!d.current_work_area_
2191 || !d.current_work_area_->completer().inlinePossible(
2192 currentBufferView()->cursor()))
2196 case LFUN_COMPLETION_POPUP:
2197 if (!d.current_work_area_
2198 || !d.current_work_area_->completer().popupPossible(
2199 currentBufferView()->cursor()))
2204 if (!d.current_work_area_
2205 || !d.current_work_area_->completer().inlinePossible(
2206 currentBufferView()->cursor()))
2210 case LFUN_COMPLETION_ACCEPT:
2211 if (!d.current_work_area_
2212 || (!d.current_work_area_->completer().popupVisible()
2213 && !d.current_work_area_->completer().inlineVisible()
2214 && !d.current_work_area_->completer().completionAvailable()))
2218 case LFUN_COMPLETION_CANCEL:
2219 if (!d.current_work_area_
2220 || (!d.current_work_area_->completer().popupVisible()
2221 && !d.current_work_area_->completer().inlineVisible()))
2225 case LFUN_BUFFER_ZOOM_OUT:
2226 case LFUN_BUFFER_ZOOM_IN: {
2227 // only diff between these two is that the default for ZOOM_OUT
2229 bool const neg_zoom =
2230 convert<int>(cmd.argument()) < 0 ||
2231 (cmd.action() == LFUN_BUFFER_ZOOM_OUT && cmd.argument().empty());
2232 if (lyxrc.currentZoom <= zoom_min_ && neg_zoom) {
2233 docstring const msg =
2234 bformat(_("Zoom level cannot be less than %1$d%."), zoom_min_);
2238 enable = doc_buffer;
2242 case LFUN_BUFFER_ZOOM: {
2243 bool const less_than_min_zoom =
2244 !cmd.argument().empty() && convert<int>(cmd.argument()) < zoom_min_;
2245 if (lyxrc.currentZoom <= zoom_min_ && less_than_min_zoom) {
2246 docstring const msg =
2247 bformat(_("Zoom level cannot be less than %1$d%."), zoom_min_);
2252 enable = doc_buffer;
2256 case LFUN_BUFFER_MOVE_NEXT:
2257 case LFUN_BUFFER_MOVE_PREVIOUS:
2258 // we do not cycle when moving
2259 case LFUN_BUFFER_NEXT:
2260 case LFUN_BUFFER_PREVIOUS:
2261 // because we cycle, it doesn't matter whether on first or last
2262 enable = (d.currentTabWorkArea()->count() > 1);
2264 case LFUN_BUFFER_SWITCH:
2265 // toggle on the current buffer, but do not toggle off
2266 // the other ones (is that a good idea?)
2268 && to_utf8(cmd.argument()) == doc_buffer->absFileName())
2269 flag.setOnOff(true);
2272 case LFUN_VC_REGISTER:
2273 enable = doc_buffer && !doc_buffer->lyxvc().inUse();
2275 case LFUN_VC_RENAME:
2276 enable = doc_buffer && doc_buffer->lyxvc().renameEnabled();
2279 enable = doc_buffer && doc_buffer->lyxvc().copyEnabled();
2281 case LFUN_VC_CHECK_IN:
2282 enable = doc_buffer && doc_buffer->lyxvc().checkInEnabled();
2284 case LFUN_VC_CHECK_OUT:
2285 enable = doc_buffer && doc_buffer->lyxvc().checkOutEnabled();
2287 case LFUN_VC_LOCKING_TOGGLE:
2288 enable = doc_buffer && !doc_buffer->hasReadonlyFlag()
2289 && doc_buffer->lyxvc().lockingToggleEnabled();
2290 flag.setOnOff(enable && doc_buffer->lyxvc().locking());
2292 case LFUN_VC_REVERT:
2293 enable = doc_buffer && doc_buffer->lyxvc().inUse()
2294 && !doc_buffer->hasReadonlyFlag();
2296 case LFUN_VC_UNDO_LAST:
2297 enable = doc_buffer && doc_buffer->lyxvc().undoLastEnabled();
2299 case LFUN_VC_REPO_UPDATE:
2300 enable = doc_buffer && doc_buffer->lyxvc().repoUpdateEnabled();
2302 case LFUN_VC_COMMAND: {
2303 if (cmd.argument().empty())
2305 if (!doc_buffer && contains(cmd.getArg(0), 'D'))
2309 case LFUN_VC_COMPARE:
2310 enable = doc_buffer && doc_buffer->lyxvc().prepareFileRevisionEnabled();
2313 case LFUN_SERVER_GOTO_FILE_ROW:
2314 case LFUN_LYX_ACTIVATE:
2315 case LFUN_WINDOW_RAISE:
2317 case LFUN_FORWARD_SEARCH:
2318 enable = !(lyxrc.forward_search_dvi.empty() && lyxrc.forward_search_pdf.empty());
2321 case LFUN_FILE_INSERT_PLAINTEXT:
2322 case LFUN_FILE_INSERT_PLAINTEXT_PARA:
2323 enable = documentBufferView() && documentBufferView()->cursor().inTexted();
2326 case LFUN_SPELLING_CONTINUOUSLY:
2327 flag.setOnOff(lyxrc.spellcheck_continuously);
2335 flag.setEnabled(false);
2341 static FileName selectTemplateFile()
2343 FileDialog dlg(qt_("Select template file"));
2344 dlg.setButton1(qt_("D&ocuments"), toqstr(lyxrc.document_path));
2345 dlg.setButton2(qt_("&Templates"), toqstr(lyxrc.template_path));
2347 FileDialog::Result result = dlg.open(toqstr(lyxrc.template_path),
2348 QStringList(qt_("LyX Documents (*.lyx)")));
2350 if (result.first == FileDialog::Later)
2352 if (result.second.isEmpty())
2354 return FileName(fromqstr(result.second));
2358 Buffer * GuiView::loadDocument(FileName const & filename, bool tolastfiles)
2362 Buffer * newBuffer = nullptr;
2364 newBuffer = checkAndLoadLyXFile(filename);
2365 } catch (ExceptionMessage const & e) {
2372 message(_("Document not loaded."));
2376 setBuffer(newBuffer);
2377 newBuffer->errors("Parse");
2380 theSession().lastFiles().add(filename);
2381 theSession().writeFile();
2388 void GuiView::openDocument(string const & fname)
2390 string initpath = lyxrc.document_path;
2392 if (documentBufferView()) {
2393 string const trypath = documentBufferView()->buffer().filePath();
2394 // If directory is writeable, use this as default.
2395 if (FileName(trypath).isDirWritable())
2401 if (fname.empty()) {
2402 FileDialog dlg(qt_("Select document to open"));
2403 dlg.setButton1(qt_("D&ocuments"), toqstr(lyxrc.document_path));
2404 dlg.setButton2(qt_("&Examples"), toqstr(lyxrc.example_path));
2406 QStringList const filter(qt_("LyX Documents (*.lyx)"));
2407 FileDialog::Result result =
2408 dlg.open(toqstr(initpath), filter);
2410 if (result.first == FileDialog::Later)
2413 filename = fromqstr(result.second);
2415 // check selected filename
2416 if (filename.empty()) {
2417 message(_("Canceled."));
2423 // get absolute path of file and add ".lyx" to the filename if
2425 FileName const fullname =
2426 fileSearch(string(), filename, "lyx", support::may_not_exist);
2427 if (!fullname.empty())
2428 filename = fullname.absFileName();
2430 if (!fullname.onlyPath().isDirectory()) {
2431 Alert::warning(_("Invalid filename"),
2432 bformat(_("The directory in the given path\n%1$s\ndoes not exist."),
2433 from_utf8(fullname.absFileName())));
2437 // if the file doesn't exist and isn't already open (bug 6645),
2438 // let the user create one
2439 if (!fullname.exists() && !theBufferList().exists(fullname) &&
2440 !LyXVC::file_not_found_hook(fullname)) {
2441 // the user specifically chose this name. Believe him.
2442 Buffer * const b = newFile(filename, string(), true);
2448 docstring const disp_fn = makeDisplayPath(filename);
2449 message(bformat(_("Opening document %1$s..."), disp_fn));
2452 Buffer * buf = loadDocument(fullname);
2454 str2 = bformat(_("Document %1$s opened."), disp_fn);
2455 if (buf->lyxvc().inUse())
2456 str2 += " " + from_utf8(buf->lyxvc().versionString()) +
2457 " " + _("Version control detected.");
2459 str2 = bformat(_("Could not open document %1$s"), disp_fn);
2464 // FIXME: clean that
2465 static bool import(GuiView * lv, FileName const & filename,
2466 string const & format, ErrorList & errorList)
2468 FileName const lyxfile(support::changeExtension(filename.absFileName(), ".lyx"));
2470 string loader_format;
2471 vector<string> loaders = theConverters().loaders();
2472 if (find(loaders.begin(), loaders.end(), format) == loaders.end()) {
2473 vector<string>::const_iterator it = loaders.begin();
2474 vector<string>::const_iterator en = loaders.end();
2475 for (; it != en; ++it) {
2476 if (!theConverters().isReachable(format, *it))
2479 string const tofile =
2480 support::changeExtension(filename.absFileName(),
2481 theFormats().extension(*it));
2482 if (theConverters().convert(nullptr, filename, FileName(tofile),
2483 filename, format, *it, errorList) != Converters::SUCCESS)
2485 loader_format = *it;
2488 if (loader_format.empty()) {
2489 frontend::Alert::error(_("Couldn't import file"),
2490 bformat(_("No information for importing the format %1$s."),
2491 theFormats().prettyName(format)));
2495 loader_format = format;
2497 if (loader_format == "lyx") {
2498 Buffer * buf = lv->loadDocument(lyxfile);
2502 Buffer * const b = newFile(lyxfile.absFileName(), string(), true);
2506 bool as_paragraphs = loader_format == "textparagraph";
2507 string filename2 = (loader_format == format) ? filename.absFileName()
2508 : support::changeExtension(filename.absFileName(),
2509 theFormats().extension(loader_format));
2510 lv->currentBufferView()->insertPlaintextFile(FileName(filename2),
2512 guiApp->setCurrentView(lv);
2513 lyx::dispatch(FuncRequest(LFUN_MARK_OFF));
2520 void GuiView::importDocument(string const & argument)
2523 string filename = split(argument, format, ' ');
2525 LYXERR(Debug::INFO, format << " file: " << filename);
2527 // need user interaction
2528 if (filename.empty()) {
2529 string initpath = lyxrc.document_path;
2530 if (documentBufferView()) {
2531 string const trypath = documentBufferView()->buffer().filePath();
2532 // If directory is writeable, use this as default.
2533 if (FileName(trypath).isDirWritable())
2537 docstring const text = bformat(_("Select %1$s file to import"),
2538 theFormats().prettyName(format));
2540 FileDialog dlg(toqstr(text));
2541 dlg.setButton1(qt_("D&ocuments"), toqstr(lyxrc.document_path));
2542 dlg.setButton2(qt_("&Examples"), toqstr(lyxrc.example_path));
2544 docstring filter = theFormats().prettyName(format);
2547 filter += from_utf8(theFormats().extensions(format));
2550 FileDialog::Result result =
2551 dlg.open(toqstr(initpath), fileFilters(toqstr(filter)));
2553 if (result.first == FileDialog::Later)
2556 filename = fromqstr(result.second);
2558 // check selected filename
2559 if (filename.empty())
2560 message(_("Canceled."));
2563 if (filename.empty())
2566 // get absolute path of file
2567 FileName const fullname(support::makeAbsPath(filename));
2569 // Can happen if the user entered a path into the dialog
2571 if (fullname.onlyFileName().empty()) {
2572 docstring msg = bformat(_("The file name '%1$s' is invalid!\n"
2573 "Aborting import."),
2574 from_utf8(fullname.absFileName()));
2575 frontend::Alert::error(_("File name error"), msg);
2576 message(_("Canceled."));
2581 FileName const lyxfile(support::changeExtension(fullname.absFileName(), ".lyx"));
2583 // Check if the document already is open
2584 Buffer * buf = theBufferList().getBuffer(lyxfile);
2587 if (!closeBuffer()) {
2588 message(_("Canceled."));
2593 docstring const displaypath = makeDisplayPath(lyxfile.absFileName(), 30);
2595 // if the file exists already, and we didn't do
2596 // -i lyx thefile.lyx, warn
2597 if (lyxfile.exists() && fullname != lyxfile) {
2599 docstring text = bformat(_("The document %1$s already exists.\n\n"
2600 "Do you want to overwrite that document?"), displaypath);
2601 int const ret = Alert::prompt(_("Overwrite document?"),
2602 text, 0, 1, _("&Overwrite"), _("&Cancel"));
2605 message(_("Canceled."));
2610 message(bformat(_("Importing %1$s..."), displaypath));
2611 ErrorList errorList;
2612 if (import(this, fullname, format, errorList))
2613 message(_("imported."));
2615 message(_("file not imported!"));
2617 // FIXME (Abdel 12/08/06): Is there a need to display the error list here?
2621 void GuiView::newDocument(string const & filename, string templatefile,
2624 FileName initpath(lyxrc.document_path);
2625 if (documentBufferView()) {
2626 FileName const trypath(documentBufferView()->buffer().filePath());
2627 // If directory is writeable, use this as default.
2628 if (trypath.isDirWritable())
2632 if (from_template) {
2633 if (templatefile.empty())
2634 templatefile = selectTemplateFile().absFileName();
2635 if (templatefile.empty())
2640 if (filename.empty())
2641 b = newUnnamedFile(initpath, to_utf8(_("newfile")), templatefile);
2643 b = newFile(filename, templatefile, true);
2648 // If no new document could be created, it is unsure
2649 // whether there is a valid BufferView.
2650 if (currentBufferView())
2651 // Ensure the cursor is correctly positioned on screen.
2652 currentBufferView()->showCursor();
2656 void GuiView::insertLyXFile(docstring const & fname, bool ignorelang)
2658 BufferView * bv = documentBufferView();
2663 FileName filename(to_utf8(fname));
2664 if (filename.empty()) {
2665 // Launch a file browser
2667 string initpath = lyxrc.document_path;
2668 string const trypath = bv->buffer().filePath();
2669 // If directory is writeable, use this as default.
2670 if (FileName(trypath).isDirWritable())
2674 FileDialog dlg(qt_("Select LyX document to insert"));
2675 dlg.setButton1(qt_("D&ocuments"), toqstr(lyxrc.document_path));
2676 dlg.setButton2(qt_("&Examples"), toqstr(lyxrc.example_path));
2678 FileDialog::Result result = dlg.open(toqstr(initpath),
2679 QStringList(qt_("LyX Documents (*.lyx)")));
2681 if (result.first == FileDialog::Later)
2685 filename.set(fromqstr(result.second));
2687 // check selected filename
2688 if (filename.empty()) {
2689 // emit message signal.
2690 message(_("Canceled."));
2695 bv->insertLyXFile(filename, ignorelang);
2696 bv->buffer().errors("Parse");
2700 string const GuiView::getTemplatesPath(Buffer & b)
2702 // We start off with the user's templates path
2703 string result = addPath(package().user_support().absFileName(), "templates");
2704 // Check for the document language
2705 string const langcode = b.params().language->code();
2706 string const shortcode = langcode.substr(0, 2);
2707 if (!langcode.empty() && shortcode != "en") {
2708 string subpath = addPath(result, shortcode);
2709 string subpath_long = addPath(result, langcode);
2710 // If we have a subdirectory for the language already,
2712 FileName sp = FileName(subpath);
2713 if (sp.isDirectory())
2715 else if (FileName(subpath_long).isDirectory())
2716 result = subpath_long;
2718 // Ask whether we should create such a subdirectory
2719 docstring const text =
2720 bformat(_("It is suggested to save the template in a subdirectory\n"
2721 "appropriate to the document language (%1$s).\n"
2722 "This subdirectory does not exists yet.\n"
2723 "Do you want to create it?"),
2724 _(b.params().language->display()));
2725 if (Alert::prompt(_("Create Language Directory?"),
2726 text, 0, 1, _("&Yes, Create"), _("&No, Save Template in Parent Directory")) == 0) {
2727 // If the user agreed, we try to create it and report if this failed.
2728 if (!sp.createDirectory(0777))
2729 Alert::error(_("Subdirectory creation failed!"),
2730 _("Could not create subdirectory.\n"
2731 "The template will be saved in the parent directory."));
2737 // Do we have a layout category?
2738 string const cat = b.params().baseClass() ?
2739 b.params().baseClass()->category()
2742 string subpath = addPath(result, cat);
2743 // If we have a subdirectory for the category already,
2745 FileName sp = FileName(subpath);
2746 if (sp.isDirectory())
2749 // Ask whether we should create such a subdirectory
2750 docstring const text =
2751 bformat(_("It is suggested to save the template in a subdirectory\n"
2752 "appropriate to the layout category (%1$s).\n"
2753 "This subdirectory does not exists yet.\n"
2754 "Do you want to create it?"),
2756 if (Alert::prompt(_("Create Category Directory?"),
2757 text, 0, 1, _("&Yes, Create"), _("&No, Save Template in Parent Directory")) == 0) {
2758 // If the user agreed, we try to create it and report if this failed.
2759 if (!sp.createDirectory(0777))
2760 Alert::error(_("Subdirectory creation failed!"),
2761 _("Could not create subdirectory.\n"
2762 "The template will be saved in the parent directory."));
2772 bool GuiView::renameBuffer(Buffer & b, docstring const & newname, RenameKind kind)
2774 FileName fname = b.fileName();
2775 FileName const oldname = fname;
2776 bool const as_template = (kind == LV_WRITE_AS_TEMPLATE);
2778 if (!newname.empty()) {
2781 fname = support::makeAbsPath(to_utf8(newname), getTemplatesPath(b));
2783 fname = support::makeAbsPath(to_utf8(newname),
2784 oldname.onlyPath().absFileName());
2786 // Switch to this Buffer.
2789 // No argument? Ask user through dialog.
2791 QString const title = as_template ? qt_("Choose a filename to save template as")
2792 : qt_("Choose a filename to save document as");
2793 FileDialog dlg(title);
2794 dlg.setButton1(qt_("D&ocuments"), toqstr(lyxrc.document_path));
2795 dlg.setButton2(qt_("&Templates"), toqstr(lyxrc.template_path));
2797 if (!isLyXFileName(fname.absFileName()))
2798 fname.changeExtension(".lyx");
2800 string const path = as_template ?
2802 : fname.onlyPath().absFileName();
2803 FileDialog::Result result =
2804 dlg.save(toqstr(path),
2805 QStringList(qt_("LyX Documents (*.lyx)")),
2806 toqstr(fname.onlyFileName()));
2808 if (result.first == FileDialog::Later)
2811 fname.set(fromqstr(result.second));
2816 if (!isLyXFileName(fname.absFileName()))
2817 fname.changeExtension(".lyx");
2820 // fname is now the new Buffer location.
2822 // if there is already a Buffer open with this name, we do not want
2823 // to have another one. (the second test makes sure we're not just
2824 // trying to overwrite ourselves, which is fine.)
2825 if (theBufferList().exists(fname) && fname != oldname
2826 && theBufferList().getBuffer(fname) != &b) {
2827 docstring const text =
2828 bformat(_("The file\n%1$s\nis already open in your current session.\n"
2829 "Please close it before attempting to overwrite it.\n"
2830 "Do you want to choose a new filename?"),
2831 from_utf8(fname.absFileName()));
2832 int const ret = Alert::prompt(_("Chosen File Already Open"),
2833 text, 0, 1, _("&Rename"), _("&Cancel"));
2835 case 0: return renameBuffer(b, docstring(), kind);
2836 case 1: return false;
2841 bool const existsLocal = fname.exists();
2842 bool const existsInVC = LyXVC::fileInVC(fname);
2843 if (existsLocal || existsInVC) {
2844 docstring const file = makeDisplayPath(fname.absFileName(), 30);
2845 if (kind != LV_WRITE_AS && existsInVC) {
2846 // renaming to a name that is already in VC
2848 docstring text = bformat(_("The document %1$s "
2849 "is already registered.\n\n"
2850 "Do you want to choose a new name?"),
2852 docstring const title = (kind == LV_VC_RENAME) ?
2853 _("Rename document?") : _("Copy document?");
2854 docstring const button = (kind == LV_VC_RENAME) ?
2855 _("&Rename") : _("&Copy");
2856 int const ret = Alert::prompt(title, text, 0, 1,
2857 button, _("&Cancel"));
2859 case 0: return renameBuffer(b, docstring(), kind);
2860 case 1: return false;
2865 docstring text = bformat(_("The document %1$s "
2866 "already exists.\n\n"
2867 "Do you want to overwrite that document?"),
2869 int const ret = Alert::prompt(_("Overwrite document?"),
2870 text, 0, 2, _("&Overwrite"),
2871 _("&Rename"), _("&Cancel"));
2874 case 1: return renameBuffer(b, docstring(), kind);
2875 case 2: return false;
2881 case LV_VC_RENAME: {
2882 string msg = b.lyxvc().rename(fname);
2885 message(from_utf8(msg));
2889 string msg = b.lyxvc().copy(fname);
2892 message(from_utf8(msg));
2896 case LV_WRITE_AS_TEMPLATE:
2899 // LyXVC created the file already in case of LV_VC_RENAME or
2900 // LV_VC_COPY, but call saveBuffer() nevertheless to get
2901 // relative paths of included stuff right if we moved e.g. from
2902 // /a/b.lyx to /a/c/b.lyx.
2904 bool const saved = saveBuffer(b, fname);
2911 bool GuiView::exportBufferAs(Buffer & b, docstring const & iformat)
2913 FileName fname = b.fileName();
2915 FileDialog dlg(qt_("Choose a filename to export the document as"));
2916 dlg.setButton1(qt_("D&ocuments"), toqstr(lyxrc.document_path));
2919 QString const anyformat = qt_("Guess from extension (*.*)");
2922 vector<Format const *> export_formats;
2923 for (Format const & f : theFormats())
2924 if (f.documentFormat())
2925 export_formats.push_back(&f);
2926 sort(export_formats.begin(), export_formats.end(), Format::formatSorter);
2927 map<QString, string> fmap;
2930 for (Format const * f : export_formats) {
2931 docstring const loc_prettyname = translateIfPossible(f->prettyname());
2932 QString const loc_filter = toqstr(bformat(from_ascii("%1$s (*.%2$s)"),
2934 from_ascii(f->extension())));
2935 types << loc_filter;
2936 fmap[loc_filter] = f->name();
2937 if (from_ascii(f->name()) == iformat) {
2938 filter = loc_filter;
2939 ext = f->extension();
2942 string ofname = fname.onlyFileName();
2944 ofname = support::changeExtension(ofname, ext);
2945 FileDialog::Result result =
2946 dlg.save(toqstr(fname.onlyPath().absFileName()),
2950 if (result.first != FileDialog::Chosen)
2954 fname.set(fromqstr(result.second));
2955 if (filter == anyformat)
2956 fmt_name = theFormats().getFormatFromExtension(fname.extension());
2958 fmt_name = fmap[filter];
2959 LYXERR(Debug::FILES, "filter=" << fromqstr(filter)
2960 << ", fmt_name=" << fmt_name << ", fname=" << fname.absFileName());
2962 if (fmt_name.empty() || fname.empty())
2965 // fname is now the new Buffer location.
2966 if (FileName(fname).exists()) {
2967 docstring const file = makeDisplayPath(fname.absFileName(), 30);
2968 docstring text = bformat(_("The document %1$s already "
2969 "exists.\n\nDo you want to "
2970 "overwrite that document?"),
2972 int const ret = Alert::prompt(_("Overwrite document?"),
2973 text, 0, 2, _("&Overwrite"), _("&Rename"), _("&Cancel"));
2976 case 1: return exportBufferAs(b, from_ascii(fmt_name));
2977 case 2: return false;
2981 FuncRequest cmd(LFUN_BUFFER_EXPORT, fmt_name + " " + fname.absFileName());
2984 return dr.dispatched();
2988 bool GuiView::saveBuffer(Buffer & b)
2990 return saveBuffer(b, FileName());
2994 bool GuiView::saveBuffer(Buffer & b, FileName const & fn)
2996 if (workArea(b) && workArea(b)->inDialogMode())
2999 if (fn.empty() && b.isUnnamed())
3000 return renameBuffer(b, docstring());
3002 bool const success = (fn.empty() ? b.save() : b.saveAs(fn));
3004 theSession().lastFiles().add(b.fileName());
3005 theSession().writeFile();
3009 // Switch to this Buffer.
3012 // FIXME: we don't tell the user *WHY* the save failed !!
3013 docstring const file = makeDisplayPath(b.absFileName(), 30);
3014 docstring text = bformat(_("The document %1$s could not be saved.\n\n"
3015 "Do you want to rename the document and "
3016 "try again?"), file);
3017 int const ret = Alert::prompt(_("Rename and save?"),
3018 text, 0, 2, _("&Rename"), _("&Retry"), _("&Cancel"));
3021 if (!renameBuffer(b, docstring()))
3030 return saveBuffer(b, fn);
3034 bool GuiView::hideWorkArea(GuiWorkArea * wa)
3036 return closeWorkArea(wa, false);
3040 // We only want to close the buffer if it is not visible in other workareas
3041 // of the same view, nor in other views, and if this is not a child
3042 bool GuiView::closeWorkArea(GuiWorkArea * wa)
3044 Buffer & buf = wa->bufferView().buffer();
3046 bool last_wa = d.countWorkAreasOf(buf) == 1
3047 && !inOtherView(buf) && !buf.parent();
3049 bool close_buffer = last_wa;
3052 if (lyxrc.close_buffer_with_last_view == "yes")
3054 else if (lyxrc.close_buffer_with_last_view == "no")
3055 close_buffer = false;
3058 if (buf.isUnnamed())
3059 file = from_utf8(buf.fileName().onlyFileName());
3061 file = buf.fileName().displayName(30);
3062 docstring const text = bformat(
3063 _("Last view on document %1$s is being closed.\n"
3064 "Would you like to close or hide the document?\n"
3066 "Hidden documents can be displayed back through\n"
3067 "the menu: View->Hidden->...\n"
3069 "To remove this question, set your preference in:\n"
3070 " Tools->Preferences->Look&Feel->UserInterface\n"
3072 int ret = Alert::prompt(_("Close or hide document?"),
3073 text, 0, 2, _("&Close"), _("&Hide"), _("&Cancel"));
3076 close_buffer = (ret == 0);
3080 return closeWorkArea(wa, close_buffer);
3084 bool GuiView::closeBuffer()
3086 GuiWorkArea * wa = currentMainWorkArea();
3087 // coverity complained about this
3088 // it seems unnecessary, but perhaps is worth the check
3089 LASSERT(wa, return false);
3091 setCurrentWorkArea(wa);
3092 Buffer & buf = wa->bufferView().buffer();
3093 return closeWorkArea(wa, !buf.parent());
3097 void GuiView::writeSession() const {
3098 GuiWorkArea const * active_wa = currentMainWorkArea();
3099 for (int i = 0; i < d.splitter_->count(); ++i) {
3100 TabWorkArea * twa = d.tabWorkArea(i);
3101 for (int j = 0; j < twa->count(); ++j) {
3102 GuiWorkArea * wa = twa->workArea(j);
3103 Buffer & buf = wa->bufferView().buffer();
3104 theSession().lastOpened().add(buf.fileName(), wa == active_wa);
3110 bool GuiView::closeBufferAll()
3113 for (auto & buf : theBufferList()) {
3114 if (!saveBufferIfNeeded(*buf, false)) {
3115 // Closing has been cancelled, so abort.
3120 // Close the workareas in all other views
3121 QList<int> const ids = guiApp->viewIds();
3122 for (int i = 0; i != ids.size(); ++i) {
3123 if (id_ != ids[i] && !guiApp->view(ids[i]).closeWorkAreaAll())
3127 // Close our own workareas
3128 if (!closeWorkAreaAll())
3135 bool GuiView::closeWorkAreaAll()
3137 setCurrentWorkArea(currentMainWorkArea());
3139 // We might be in a situation that there is still a tabWorkArea, but
3140 // there are no tabs anymore. This can happen when we get here after a
3141 // TabWorkArea::lastWorkAreaRemoved() signal. Therefore we count how
3142 // many TabWorkArea's have no documents anymore.
3145 // We have to call count() each time, because it can happen that
3146 // more than one splitter will disappear in one iteration (bug 5998).
3147 while (d.splitter_->count() > empty_twa) {
3148 TabWorkArea * twa = d.tabWorkArea(empty_twa);
3150 if (twa->count() == 0)
3153 setCurrentWorkArea(twa->currentWorkArea());
3154 if (!closeTabWorkArea(twa))
3162 bool GuiView::closeWorkArea(GuiWorkArea * wa, bool close_buffer)
3167 Buffer & buf = wa->bufferView().buffer();
3169 if (GuiViewPrivate::busyBuffers.contains(&buf)) {
3170 Alert::warning(_("Close document"),
3171 _("Document could not be closed because it is being processed by LyX."));
3176 return closeBuffer(buf);
3178 if (!inMultiTabs(wa))
3179 if (!saveBufferIfNeeded(buf, true))
3187 bool GuiView::closeBuffer(Buffer & buf)
3189 bool success = true;
3190 ListOfBuffers clist = buf.getChildren();
3191 ListOfBuffers::const_iterator it = clist.begin();
3192 ListOfBuffers::const_iterator const bend = clist.end();
3193 for (; it != bend; ++it) {
3194 Buffer * child_buf = *it;
3195 if (theBufferList().isOthersChild(&buf, child_buf)) {
3196 child_buf->setParent(nullptr);
3200 // FIXME: should we look in other tabworkareas?
3201 // ANSWER: I don't think so. I've tested, and if the child is
3202 // open in some other window, it closes without a problem.
3203 GuiWorkArea * child_wa = workArea(*child_buf);
3206 // If we are in a close_event all children will be closed in some time,
3207 // so no need to do it here. This will ensure that the children end up
3208 // in the session file in the correct order. If we close the master
3209 // buffer, we can close or release the child buffers here too.
3211 success = closeWorkArea(child_wa, true);
3215 // In this case the child buffer is open but hidden.
3216 // Even in this case, children can be dirty (e.g.,
3217 // after a label change in the master, see #11405).
3218 // Therefore, check this
3219 if (closing_ && (child_buf->isClean() || child_buf->paragraphs().empty())) {
3220 // If we are in a close_event all children will be closed in some time,
3221 // so no need to do it here. This will ensure that the children end up
3222 // in the session file in the correct order. If we close the master
3223 // buffer, we can close or release the child buffers here too.
3226 // Save dirty buffers also if closing_!
3227 if (saveBufferIfNeeded(*child_buf, false)) {
3228 child_buf->removeAutosaveFile();
3229 theBufferList().release(child_buf);
3231 // Saving of dirty children has been cancelled.
3232 // Cancel the whole process.
3239 // goto bookmark to update bookmark pit.
3240 // FIXME: we should update only the bookmarks related to this buffer!
3241 LYXERR(Debug::DEBUG, "GuiView::closeBuffer()");
3242 for (unsigned int i = 0; i < theSession().bookmarks().size(); ++i)
3243 guiApp->gotoBookmark(i + 1, false, false);
3245 if (saveBufferIfNeeded(buf, false)) {
3246 buf.removeAutosaveFile();
3247 theBufferList().release(&buf);
3251 // open all children again to avoid a crash because of dangling
3252 // pointers (bug 6603)
3258 bool GuiView::closeTabWorkArea(TabWorkArea * twa)
3260 while (twa == d.currentTabWorkArea()) {
3261 twa->setCurrentIndex(twa->count() - 1);
3263 GuiWorkArea * wa = twa->currentWorkArea();
3264 Buffer & b = wa->bufferView().buffer();
3266 // We only want to close the buffer if the same buffer is not visible
3267 // in another view, and if this is not a child and if we are closing
3268 // a view (not a tabgroup).
3269 bool const close_buffer =
3270 !inOtherView(b) && !b.parent() && closing_;
3272 if (!closeWorkArea(wa, close_buffer))
3279 bool GuiView::saveBufferIfNeeded(Buffer & buf, bool hiding)
3281 if (buf.isClean() || buf.paragraphs().empty())
3284 // Switch to this Buffer.
3290 if (buf.isUnnamed()) {
3291 file = from_utf8(buf.fileName().onlyFileName());
3294 FileName filename = buf.fileName();
3296 file = filename.displayName(30);
3297 exists = filename.exists();
3300 // Bring this window to top before asking questions.
3305 if (hiding && buf.isUnnamed()) {
3306 docstring const text = bformat(_("The document %1$s has not been "
3307 "saved yet.\n\nDo you want to save "
3308 "the document?"), file);
3309 ret = Alert::prompt(_("Save new document?"),
3310 text, 0, 1, _("&Save"), _("&Cancel"));
3314 docstring const text = exists ?
3315 bformat(_("The document %1$s has unsaved changes."
3316 "\n\nDo you want to save the document or "
3317 "discard the changes?"), file) :
3318 bformat(_("The document %1$s has not been saved yet."
3319 "\n\nDo you want to save the document or "
3320 "discard it entirely?"), file);
3321 docstring const title = exists ?
3322 _("Save changed document?") : _("Save document?");
3323 ret = Alert::prompt(title, text, 0, 2,
3324 _("&Save"), _("&Discard"), _("&Cancel"));
3329 if (!saveBuffer(buf))
3333 // If we crash after this we could have no autosave file
3334 // but I guess this is really improbable (Jug).
3335 // Sometimes improbable things happen:
3336 // - see bug http://www.lyx.org/trac/ticket/6587 (ps)
3337 // buf.removeAutosaveFile();
3339 // revert all changes
3350 bool GuiView::inMultiTabs(GuiWorkArea * wa)
3352 Buffer & buf = wa->bufferView().buffer();
3354 for (int i = 0; i != d.splitter_->count(); ++i) {
3355 GuiWorkArea * wa_ = d.tabWorkArea(i)->workArea(buf);
3356 if (wa_ && wa_ != wa)
3359 return inOtherView(buf);
3363 bool GuiView::inOtherView(Buffer & buf)
3365 QList<int> const ids = guiApp->viewIds();
3367 for (int i = 0; i != ids.size(); ++i) {
3371 if (guiApp->view(ids[i]).workArea(buf))
3378 void GuiView::gotoNextOrPreviousBuffer(NextOrPrevious np, bool const move)
3380 if (!documentBufferView())
3383 if (TabWorkArea * twa = d.currentTabWorkArea()) {
3384 Buffer * const curbuf = &documentBufferView()->buffer();
3385 int nwa = twa->count();
3386 for (int i = 0; i < nwa; ++i) {
3387 if (&workArea(i)->bufferView().buffer() == curbuf) {
3389 if (np == NEXTBUFFER)
3390 next_index = (i == nwa - 1 ? 0 : i + 1);
3392 next_index = (i == 0 ? nwa - 1 : i - 1);
3394 twa->moveTab(i, next_index);
3396 setBuffer(&workArea(next_index)->bufferView().buffer());
3404 /// make sure the document is saved
3405 static bool ensureBufferClean(Buffer * buffer)
3407 LASSERT(buffer, return false);
3408 if (buffer->isClean() && !buffer->isUnnamed())
3411 docstring const file = buffer->fileName().displayName(30);
3414 if (!buffer->isUnnamed()) {
3415 text = bformat(_("The document %1$s has unsaved "
3416 "changes.\n\nDo you want to save "
3417 "the document?"), file);
3418 title = _("Save changed document?");
3421 text = bformat(_("The document %1$s has not been "
3422 "saved yet.\n\nDo you want to save "
3423 "the document?"), file);
3424 title = _("Save new document?");
3426 int const ret = Alert::prompt(title, text, 0, 1, _("&Save"), _("&Cancel"));
3429 dispatch(FuncRequest(LFUN_BUFFER_WRITE));
3431 return buffer->isClean() && !buffer->isUnnamed();
3435 bool GuiView::reloadBuffer(Buffer & buf)
3437 currentBufferView()->cursor().reset();
3438 Buffer::ReadStatus status = buf.reload();
3439 return status == Buffer::ReadSuccess;
3443 void GuiView::checkExternallyModifiedBuffers()
3445 BufferList::iterator bit = theBufferList().begin();
3446 BufferList::iterator const bend = theBufferList().end();
3447 for (; bit != bend; ++bit) {
3448 Buffer * buf = *bit;
3449 if (buf->fileName().exists() && buf->isChecksumModified()) {
3450 docstring text = bformat(_("Document \n%1$s\n has been externally modified."
3451 " Reload now? Any local changes will be lost."),
3452 from_utf8(buf->absFileName()));
3453 int const ret = Alert::prompt(_("Reload externally changed document?"),
3454 text, 0, 1, _("&Reload"), _("&Cancel"));
3462 void GuiView::dispatchVC(FuncRequest const & cmd, DispatchResult & dr)
3464 Buffer * buffer = documentBufferView()
3465 ? &(documentBufferView()->buffer()) : nullptr;
3467 switch (cmd.action()) {
3468 case LFUN_VC_REGISTER:
3469 if (!buffer || !ensureBufferClean(buffer))
3471 if (!buffer->lyxvc().inUse()) {
3472 if (buffer->lyxvc().registrer()) {
3473 reloadBuffer(*buffer);
3474 dr.clearMessageUpdate();
3479 case LFUN_VC_RENAME:
3480 case LFUN_VC_COPY: {
3481 if (!buffer || !ensureBufferClean(buffer))
3483 if (buffer->lyxvc().inUse() && !buffer->hasReadonlyFlag()) {
3484 if (buffer->lyxvc().isCheckInWithConfirmation()) {
3485 // Some changes are not yet committed.
3486 // We test here and not in getStatus(), since
3487 // this test is expensive.
3489 LyXVC::CommandResult ret =
3490 buffer->lyxvc().checkIn(log);
3492 if (ret == LyXVC::ErrorCommand ||
3493 ret == LyXVC::VCSuccess)
3494 reloadBuffer(*buffer);
3495 if (buffer->lyxvc().isCheckInWithConfirmation()) {
3496 frontend::Alert::error(
3497 _("Revision control error."),
3498 _("Document could not be checked in."));
3502 RenameKind const kind = (cmd.action() == LFUN_VC_RENAME) ?
3503 LV_VC_RENAME : LV_VC_COPY;
3504 renameBuffer(*buffer, cmd.argument(), kind);
3509 case LFUN_VC_CHECK_IN:
3510 if (!buffer || !ensureBufferClean(buffer))
3512 if (buffer->lyxvc().inUse() && !buffer->hasReadonlyFlag()) {
3514 LyXVC::CommandResult ret = buffer->lyxvc().checkIn(log);
3516 // Only skip reloading if the checkin was cancelled or
3517 // an error occurred before the real checkin VCS command
3518 // was executed, since the VCS might have changed the
3519 // file even if it could not checkin successfully.
3520 if (ret == LyXVC::ErrorCommand || ret == LyXVC::VCSuccess)
3521 reloadBuffer(*buffer);
3525 case LFUN_VC_CHECK_OUT:
3526 if (!buffer || !ensureBufferClean(buffer))
3528 if (buffer->lyxvc().inUse()) {
3529 dr.setMessage(buffer->lyxvc().checkOut());
3530 reloadBuffer(*buffer);
3534 case LFUN_VC_LOCKING_TOGGLE:
3535 if (!buffer || !ensureBufferClean(buffer) || buffer->hasReadonlyFlag())
3537 if (buffer->lyxvc().inUse()) {
3538 string res = buffer->lyxvc().lockingToggle();
3540 frontend::Alert::error(_("Revision control error."),
3541 _("Error when setting the locking property."));
3544 reloadBuffer(*buffer);
3549 case LFUN_VC_REVERT:
3552 if (buffer->lyxvc().revert()) {
3553 reloadBuffer(*buffer);
3554 dr.clearMessageUpdate();
3558 case LFUN_VC_UNDO_LAST:
3561 buffer->lyxvc().undoLast();
3562 reloadBuffer(*buffer);
3563 dr.clearMessageUpdate();
3566 case LFUN_VC_REPO_UPDATE:
3569 if (ensureBufferClean(buffer)) {
3570 dr.setMessage(buffer->lyxvc().repoUpdate());
3571 checkExternallyModifiedBuffers();
3575 case LFUN_VC_COMMAND: {
3576 string flag = cmd.getArg(0);
3577 if (buffer && contains(flag, 'R') && !ensureBufferClean(buffer))
3580 if (contains(flag, 'M')) {
3581 if (!Alert::askForText(message, _("LyX VC: Log Message")))
3584 string path = cmd.getArg(1);
3585 if (contains(path, "$$p") && buffer)
3586 path = subst(path, "$$p", buffer->filePath());
3587 LYXERR(Debug::LYXVC, "Directory: " << path);
3589 if (!pp.isReadableDirectory()) {
3590 lyxerr << _("Directory is not accessible.") << endl;
3593 support::PathChanger p(pp);
3595 string command = cmd.getArg(2);
3596 if (command.empty())
3599 command = subst(command, "$$i", buffer->absFileName());
3600 command = subst(command, "$$p", buffer->filePath());
3602 command = subst(command, "$$m", to_utf8(message));
3603 LYXERR(Debug::LYXVC, "Command: " << command);
3605 one.startscript(Systemcall::Wait, command);
3609 if (contains(flag, 'I'))
3610 buffer->markDirty();
3611 if (contains(flag, 'R'))
3612 reloadBuffer(*buffer);
3617 case LFUN_VC_COMPARE: {
3618 if (cmd.argument().empty()) {
3619 lyx::dispatch(FuncRequest(LFUN_DIALOG_SHOW, "comparehistory"));
3625 string rev1 = cmd.getArg(0);
3629 if (!buffer->lyxvc().prepareFileRevision(rev1, f1))
3632 if (isStrInt(rev1) && convert<int>(rev1) <= 0) {
3633 f2 = buffer->absFileName();
3635 string rev2 = cmd.getArg(1);
3639 if (!buffer->lyxvc().prepareFileRevision(rev2, f2))
3643 LYXERR(Debug::LYXVC, "Launching comparison for fetched revisions:\n" <<
3644 f1 << "\n" << f2 << "\n" );
3645 string par = "compare run " + quoteName(f1) + " " + quoteName(f2);
3646 lyx::dispatch(FuncRequest(LFUN_DIALOG_SHOW, par));
3656 void GuiView::openChildDocument(string const & fname)
3658 LASSERT(documentBufferView(), return);
3659 Buffer & buffer = documentBufferView()->buffer();
3660 FileName const filename = support::makeAbsPath(fname, buffer.filePath());
3661 documentBufferView()->saveBookmark(false);
3662 Buffer * child = nullptr;
3663 if (theBufferList().exists(filename)) {
3664 child = theBufferList().getBuffer(filename);
3667 message(bformat(_("Opening child document %1$s..."),
3668 makeDisplayPath(filename.absFileName())));
3669 child = loadDocument(filename, false);
3671 // Set the parent name of the child document.
3672 // This makes insertion of citations and references in the child work,
3673 // when the target is in the parent or another child document.
3675 child->setParent(&buffer);
3679 bool GuiView::goToFileRow(string const & argument)
3683 size_t i = argument.find_last_of(' ');
3684 if (i != string::npos) {
3685 file_name = os::internal_path(FileName(trim(argument.substr(0, i))).realPath());
3686 istringstream is(argument.substr(i + 1));
3691 if (i == string::npos) {
3692 LYXERR0("Wrong argument: " << argument);
3695 Buffer * buf = nullptr;
3696 string const realtmp = package().temp_dir().realPath();
3697 // We have to use os::path_prefix_is() here, instead of
3698 // simply prefixIs(), because the file name comes from
3699 // an external application and may need case adjustment.
3700 if (os::path_prefix_is(file_name, realtmp, os::CASE_ADJUSTED)) {
3701 buf = theBufferList().getBufferFromTmp(file_name, true);
3702 LYXERR(Debug::FILES, "goToFileRow: buffer lookup for " << file_name
3703 << (buf ? " success" : " failed"));
3705 // Must replace extension of the file to be .lyx
3706 // and get full path
3707 FileName const s = fileSearch(string(),
3708 support::changeExtension(file_name, ".lyx"), "lyx");
3709 // Either change buffer or load the file
3710 if (theBufferList().exists(s))
3711 buf = theBufferList().getBuffer(s);
3712 else if (s.exists()) {
3713 buf = loadDocument(s);
3718 _("File does not exist: %1$s"),
3719 makeDisplayPath(file_name)));
3725 _("No buffer for file: %1$s."),
3726 makeDisplayPath(file_name))
3731 bool success = documentBufferView()->setCursorFromRow(row);
3733 LYXERR(Debug::LATEX,
3734 "setCursorFromRow: invalid position for row " << row);
3735 frontend::Alert::error(_("Inverse Search Failed"),
3736 _("Invalid position requested by inverse search.\n"
3737 "You may need to update the viewed document."));
3743 void GuiView::toolBarPopup(const QPoint & /*pos*/)
3745 QMenu * menu = guiApp->menus().menu(toqstr("context-toolbars"), * this);
3746 menu->exec(QCursor::pos());
3751 Buffer::ExportStatus GuiView::GuiViewPrivate::runAndDestroy(const T& func,
3752 Buffer const * orig, Buffer * clone, string const & format)
3754 Buffer::ExportStatus const status = func(format);
3756 // the cloning operation will have produced a clone of the entire set of
3757 // documents, starting from the master. so we must delete those.
3758 Buffer * mbuf = const_cast<Buffer *>(clone->masterBuffer());
3760 busyBuffers.remove(orig);
3765 Buffer::ExportStatus GuiView::GuiViewPrivate::compileAndDestroy(
3766 Buffer const * orig, Buffer * clone, string const & format)
3768 Buffer::ExportStatus (Buffer::* mem_func)(std::string const &, bool) const =
3770 return runAndDestroy(lyx::bind(mem_func, clone, _1, true), orig, clone, format);
3774 Buffer::ExportStatus GuiView::GuiViewPrivate::exportAndDestroy(
3775 Buffer const * orig, Buffer * clone, string const & format)
3777 Buffer::ExportStatus (Buffer::* mem_func)(std::string const &, bool) const =
3779 return runAndDestroy(lyx::bind(mem_func, clone, _1, false), orig, clone, format);
3783 Buffer::ExportStatus GuiView::GuiViewPrivate::previewAndDestroy(
3784 Buffer const * orig, Buffer * clone, string const & format)
3786 Buffer::ExportStatus (Buffer::* mem_func)(std::string const &) const =
3788 return runAndDestroy(lyx::bind(mem_func, clone, _1), orig, clone, format);
3792 bool GuiView::GuiViewPrivate::asyncBufferProcessing(
3793 string const & argument,
3794 Buffer const * used_buffer,
3795 docstring const & msg,
3796 Buffer::ExportStatus (*asyncFunc)(Buffer const *, Buffer *, string const &),
3797 Buffer::ExportStatus (Buffer::*syncFunc)(string const &, bool) const,
3798 Buffer::ExportStatus (Buffer::*previewFunc)(string const &) const,
3804 string format = argument;
3806 format = used_buffer->params().getDefaultOutputFormat();
3807 processing_format = format;
3809 progress_->clearMessages();
3812 #if EXPORT_in_THREAD
3814 GuiViewPrivate::busyBuffers.insert(used_buffer);
3815 Buffer * cloned_buffer = used_buffer->cloneWithChildren();
3816 if (!cloned_buffer) {
3817 Alert::error(_("Export Error"),
3818 _("Error cloning the Buffer."));
3821 QFuture<Buffer::ExportStatus> f = QtConcurrent::run(
3826 setPreviewFuture(f);
3827 last_export_format = used_buffer->params().bufferFormat();
3830 // We are asynchronous, so we don't know here anything about the success
3833 Buffer::ExportStatus status;
3835 status = (used_buffer->*syncFunc)(format, false);
3836 } else if (previewFunc) {
3837 status = (used_buffer->*previewFunc)(format);
3840 handleExportStatus(gv_, status, format);
3842 return (status == Buffer::ExportSuccess
3843 || status == Buffer::PreviewSuccess);
3847 Buffer::ExportStatus status;
3849 status = (used_buffer->*syncFunc)(format, true);
3850 } else if (previewFunc) {
3851 status = (used_buffer->*previewFunc)(format);
3854 handleExportStatus(gv_, status, format);
3856 return (status == Buffer::ExportSuccess
3857 || status == Buffer::PreviewSuccess);
3861 void GuiView::dispatchToBufferView(FuncRequest const & cmd, DispatchResult & dr)
3863 BufferView * bv = currentBufferView();
3864 LASSERT(bv, return);
3866 // Let the current BufferView dispatch its own actions.
3867 bv->dispatch(cmd, dr);
3868 if (dr.dispatched()) {
3869 if (cmd.action() == LFUN_REDO || cmd.action() == LFUN_UNDO)
3870 updateDialog("document", "");
3874 // Try with the document BufferView dispatch if any.
3875 BufferView * doc_bv = documentBufferView();
3876 if (doc_bv && doc_bv != bv) {
3877 doc_bv->dispatch(cmd, dr);
3878 if (dr.dispatched()) {
3879 if (cmd.action() == LFUN_REDO || cmd.action() == LFUN_UNDO)
3880 updateDialog("document", "");
3885 // Then let the current Cursor dispatch its own actions.
3886 bv->cursor().dispatch(cmd);
3888 // update completion. We do it here and not in
3889 // processKeySym to avoid another redraw just for a
3890 // changed inline completion
3891 if (cmd.origin() == FuncRequest::KEYBOARD) {
3892 if (cmd.action() == LFUN_SELF_INSERT
3893 || (cmd.action() == LFUN_ERT_INSERT && bv->cursor().inMathed()))
3894 updateCompletion(bv->cursor(), true, true);
3895 else if (cmd.action() == LFUN_CHAR_DELETE_BACKWARD)
3896 updateCompletion(bv->cursor(), false, true);
3898 updateCompletion(bv->cursor(), false, false);
3901 dr = bv->cursor().result();
3905 void GuiView::dispatch(FuncRequest const & cmd, DispatchResult & dr)
3907 BufferView * bv = currentBufferView();
3908 // By default we won't need any update.
3909 dr.screenUpdate(Update::None);
3910 // assume cmd will be dispatched
3911 dr.dispatched(true);
3913 Buffer * doc_buffer = documentBufferView()
3914 ? &(documentBufferView()->buffer()) : nullptr;
3916 if (cmd.origin() == FuncRequest::TOC) {
3917 GuiToc * toc = static_cast<GuiToc*>(findOrBuild("toc", false));
3918 toc->doDispatch(bv->cursor(), cmd, dr);
3922 string const argument = to_utf8(cmd.argument());
3924 switch(cmd.action()) {
3925 case LFUN_BUFFER_CHILD_OPEN:
3926 openChildDocument(to_utf8(cmd.argument()));
3929 case LFUN_BUFFER_IMPORT:
3930 importDocument(to_utf8(cmd.argument()));
3933 case LFUN_MASTER_BUFFER_EXPORT:
3935 doc_buffer = const_cast<Buffer *>(doc_buffer->masterBuffer());
3937 case LFUN_BUFFER_EXPORT: {
3940 // GCC only sees strfwd.h when building merged
3941 if (::lyx::operator==(cmd.argument(), "custom")) {
3942 // LFUN_MASTER_BUFFER_EXPORT is not enabled for this case,
3943 // so the following test should not be needed.
3944 // In principle, we could try to switch to such a view...
3945 // if (cmd.action() == LFUN_BUFFER_EXPORT)
3946 dispatch(FuncRequest(LFUN_DIALOG_SHOW, "sendto"), dr);
3950 string const dest = cmd.getArg(1);
3951 FileName target_dir;
3952 if (!dest.empty() && FileName::isAbsolute(dest))
3953 target_dir = FileName(support::onlyPath(dest));
3955 target_dir = doc_buffer->fileName().onlyPath();
3957 string const format = (argument.empty() || argument == "default") ?
3958 doc_buffer->params().getDefaultOutputFormat() : argument;
3960 if ((dest.empty() && doc_buffer->isUnnamed())
3961 || !target_dir.isDirWritable()) {
3962 exportBufferAs(*doc_buffer, from_utf8(format));
3965 /* TODO/Review: Is it a problem to also export the children?
3966 See the update_unincluded flag */
3967 d.asyncBufferProcessing(format,
3970 &GuiViewPrivate::exportAndDestroy,
3972 nullptr, cmd.allowAsync());
3973 // TODO Inform user about success
3977 case LFUN_BUFFER_EXPORT_AS: {
3978 LASSERT(doc_buffer, break);
3979 docstring f = cmd.argument();
3981 f = from_ascii(doc_buffer->params().getDefaultOutputFormat());
3982 exportBufferAs(*doc_buffer, f);
3986 case LFUN_BUFFER_UPDATE: {
3987 d.asyncBufferProcessing(argument,
3990 &GuiViewPrivate::compileAndDestroy,
3992 nullptr, cmd.allowAsync());
3995 case LFUN_BUFFER_VIEW: {
3996 d.asyncBufferProcessing(argument,
3998 _("Previewing ..."),
3999 &GuiViewPrivate::previewAndDestroy,
4001 &Buffer::preview, cmd.allowAsync());
4004 case LFUN_MASTER_BUFFER_UPDATE: {
4005 d.asyncBufferProcessing(argument,
4006 (doc_buffer ? doc_buffer->masterBuffer() : nullptr),
4008 &GuiViewPrivate::compileAndDestroy,
4010 nullptr, cmd.allowAsync());
4013 case LFUN_MASTER_BUFFER_VIEW: {
4014 d.asyncBufferProcessing(argument,
4015 (doc_buffer ? doc_buffer->masterBuffer() : nullptr),
4017 &GuiViewPrivate::previewAndDestroy,
4018 nullptr, &Buffer::preview, cmd.allowAsync());
4021 case LFUN_EXPORT_CANCEL: {
4022 Systemcall::killscript();
4025 case LFUN_BUFFER_SWITCH: {
4026 string const file_name = to_utf8(cmd.argument());
4027 if (!FileName::isAbsolute(file_name)) {
4029 dr.setMessage(_("Absolute filename expected."));
4033 Buffer * buffer = theBufferList().getBuffer(FileName(file_name));
4036 dr.setMessage(_("Document not loaded"));
4040 // Do we open or switch to the buffer in this view ?
4041 if (workArea(*buffer)
4042 || lyxrc.open_buffers_in_tabs || !documentBufferView()) {
4047 // Look for the buffer in other views
4048 QList<int> const ids = guiApp->viewIds();
4050 for (; i != ids.size(); ++i) {
4051 GuiView & gv = guiApp->view(ids[i]);
4052 if (gv.workArea(*buffer)) {
4054 gv.activateWindow();
4056 gv.setBuffer(buffer);
4061 // If necessary, open a new window as a last resort
4062 if (i == ids.size()) {
4063 lyx::dispatch(FuncRequest(LFUN_WINDOW_NEW));
4069 case LFUN_BUFFER_NEXT:
4070 gotoNextOrPreviousBuffer(NEXTBUFFER, false);
4073 case LFUN_BUFFER_MOVE_NEXT:
4074 gotoNextOrPreviousBuffer(NEXTBUFFER, true);
4077 case LFUN_BUFFER_PREVIOUS:
4078 gotoNextOrPreviousBuffer(PREVBUFFER, false);
4081 case LFUN_BUFFER_MOVE_PREVIOUS:
4082 gotoNextOrPreviousBuffer(PREVBUFFER, true);
4085 case LFUN_BUFFER_CHKTEX:
4086 LASSERT(doc_buffer, break);
4087 doc_buffer->runChktex();
4090 case LFUN_COMMAND_EXECUTE: {
4091 command_execute_ = true;
4092 minibuffer_focus_ = true;
4095 case LFUN_DROP_LAYOUTS_CHOICE:
4096 d.layout_->showPopup();
4099 case LFUN_MENU_OPEN:
4100 if (QMenu * menu = guiApp->menus().menu(toqstr(cmd.argument()), *this))
4101 menu->exec(QCursor::pos());
4104 case LFUN_FILE_INSERT: {
4105 if (cmd.getArg(1) == "ignorelang")
4106 insertLyXFile(from_utf8(cmd.getArg(0)), true);
4108 insertLyXFile(cmd.argument());
4112 case LFUN_FILE_INSERT_PLAINTEXT:
4113 case LFUN_FILE_INSERT_PLAINTEXT_PARA: {
4114 string const fname = to_utf8(cmd.argument());
4115 if (!fname.empty() && !FileName::isAbsolute(fname)) {
4116 dr.setMessage(_("Absolute filename expected."));
4120 FileName filename(fname);
4121 if (fname.empty()) {
4122 FileDialog dlg(qt_("Select file to insert"));
4124 FileDialog::Result result = dlg.open(toqstr(bv->buffer().filePath()),
4125 QStringList(qt_("All Files (*)")));
4127 if (result.first == FileDialog::Later || result.second.isEmpty()) {
4128 dr.setMessage(_("Canceled."));
4132 filename.set(fromqstr(result.second));
4136 FuncRequest const new_cmd(cmd, filename.absoluteFilePath());
4137 bv->dispatch(new_cmd, dr);
4142 case LFUN_BUFFER_RELOAD: {
4143 LASSERT(doc_buffer, break);
4146 bool drop = (cmd.argument() == "dump");
4149 if (!drop && !doc_buffer->isClean()) {
4150 docstring const file =
4151 makeDisplayPath(doc_buffer->absFileName(), 20);
4152 if (doc_buffer->notifiesExternalModification()) {
4153 docstring text = _("The current version will be lost. "
4154 "Are you sure you want to load the version on disk "
4155 "of the document %1$s?");
4156 ret = Alert::prompt(_("Reload saved document?"),
4157 bformat(text, file), 1, 1,
4158 _("&Reload"), _("&Cancel"));
4160 docstring text = _("Any changes will be lost. "
4161 "Are you sure you want to revert to the saved version "
4162 "of the document %1$s?");
4163 ret = Alert::prompt(_("Revert to saved document?"),
4164 bformat(text, file), 1, 1,
4165 _("&Revert"), _("&Cancel"));
4170 doc_buffer->markClean();
4171 reloadBuffer(*doc_buffer);
4172 dr.forceBufferUpdate();
4177 case LFUN_BUFFER_RESET_EXPORT:
4178 LASSERT(doc_buffer, break);
4179 doc_buffer->requireFreshStart(true);
4180 dr.setMessage(_("Buffer export reset."));
4183 case LFUN_BUFFER_WRITE:
4184 LASSERT(doc_buffer, break);
4185 saveBuffer(*doc_buffer);
4188 case LFUN_BUFFER_WRITE_AS:
4189 LASSERT(doc_buffer, break);
4190 renameBuffer(*doc_buffer, cmd.argument());
4193 case LFUN_BUFFER_WRITE_AS_TEMPLATE:
4194 LASSERT(doc_buffer, break);
4195 renameBuffer(*doc_buffer, cmd.argument(),
4196 LV_WRITE_AS_TEMPLATE);
4199 case LFUN_BUFFER_WRITE_ALL: {
4200 Buffer * first = theBufferList().first();
4203 message(_("Saving all documents..."));
4204 // We cannot use a for loop as the buffer list cycles.
4207 if (!b->isClean()) {
4209 LYXERR(Debug::ACTION, "Saved " << b->absFileName());
4211 b = theBufferList().next(b);
4212 } while (b != first);
4213 dr.setMessage(_("All documents saved."));
4217 case LFUN_MASTER_BUFFER_FORALL: {
4221 FuncRequest funcToRun = lyxaction.lookupFunc(cmd.getLongArg(0));
4222 funcToRun.allowAsync(false);
4224 for (Buffer const * buf : doc_buffer->allRelatives()) {
4225 // Switch to other buffer view and resend cmd
4226 lyx::dispatch(FuncRequest(
4227 LFUN_BUFFER_SWITCH, buf->absFileName()));
4228 lyx::dispatch(funcToRun);
4231 lyx::dispatch(FuncRequest(
4232 LFUN_BUFFER_SWITCH, doc_buffer->absFileName()));
4236 case LFUN_BUFFER_EXTERNAL_MODIFICATION_CLEAR:
4237 LASSERT(doc_buffer, break);
4238 doc_buffer->clearExternalModification();
4241 case LFUN_BUFFER_CLOSE:
4245 case LFUN_BUFFER_CLOSE_ALL:
4249 case LFUN_DEVEL_MODE_TOGGLE:
4250 devel_mode_ = !devel_mode_;
4252 dr.setMessage(_("Developer mode is now enabled."));
4254 dr.setMessage(_("Developer mode is now disabled."));
4257 case LFUN_TOOLBAR_TOGGLE: {
4258 string const name = cmd.getArg(0);
4259 if (GuiToolbar * t = toolbar(name))
4264 case LFUN_TOOLBAR_MOVABLE: {
4265 string const name = cmd.getArg(0);
4267 // toggle (all) toolbars movablility
4268 toolbarsMovable_ = !toolbarsMovable_;
4269 for (ToolbarInfo const & ti : guiApp->toolbars()) {
4270 GuiToolbar * tb = toolbar(ti.name);
4271 if (tb && tb->isMovable() != toolbarsMovable_)
4272 // toggle toolbar movablity if it does not fit lock
4273 // (all) toolbars positions state silent = true, since
4274 // status bar notifications are slow
4277 if (toolbarsMovable_)
4278 dr.setMessage(_("Toolbars unlocked."));
4280 dr.setMessage(_("Toolbars locked."));
4281 } else if (GuiToolbar * t = toolbar(name)) {
4282 // toggle current toolbar movablity
4284 // update lock (all) toolbars positions
4285 updateLockToolbars();
4290 case LFUN_ICON_SIZE: {
4291 QSize size = d.iconSize(cmd.argument());
4293 dr.setMessage(bformat(_("Icon size set to %1$dx%2$d."),
4294 size.width(), size.height()));
4298 case LFUN_DIALOG_UPDATE: {
4299 string const name = to_utf8(cmd.argument());
4300 if (name == "prefs" || name == "document")
4301 updateDialog(name, string());
4302 else if (name == "paragraph")
4303 lyx::dispatch(FuncRequest(LFUN_PARAGRAPH_UPDATE));
4304 else if (currentBufferView()) {
4305 Inset * inset = currentBufferView()->editedInset(name);
4306 // Can only update a dialog connected to an existing inset
4308 // FIXME: get rid of this indirection; GuiView ask the inset
4309 // if he is kind enough to update itself...
4310 FuncRequest fr(LFUN_INSET_DIALOG_UPDATE, cmd.argument());
4311 //FIXME: pass DispatchResult here?
4312 inset->dispatch(currentBufferView()->cursor(), fr);
4318 case LFUN_DIALOG_TOGGLE: {
4319 FuncCode const func_code = isDialogVisible(cmd.getArg(0))
4320 ? LFUN_DIALOG_HIDE : LFUN_DIALOG_SHOW;
4321 dispatch(FuncRequest(func_code, cmd.argument()), dr);
4325 case LFUN_DIALOG_DISCONNECT_INSET:
4326 disconnectDialog(to_utf8(cmd.argument()));
4329 case LFUN_DIALOG_HIDE: {
4330 guiApp->hideDialogs(to_utf8(cmd.argument()), nullptr);
4334 case LFUN_DIALOG_SHOW: {
4335 string const name = cmd.getArg(0);
4336 string sdata = trim(to_utf8(cmd.argument()).substr(name.size()));
4338 if (name == "latexlog") {
4339 // getStatus checks that
4340 LASSERT(doc_buffer, break);
4341 Buffer::LogType type;
4342 string const logfile = doc_buffer->logName(&type);
4344 case Buffer::latexlog:
4347 case Buffer::buildlog:
4348 sdata = "literate ";
4351 sdata += Lexer::quoteString(logfile);
4352 showDialog("log", sdata);
4353 } else if (name == "vclog") {
4354 // getStatus checks that
4355 LASSERT(doc_buffer, break);
4356 string const sdata2 = "vc " +
4357 Lexer::quoteString(doc_buffer->lyxvc().getLogFile());
4358 showDialog("log", sdata2);
4359 } else if (name == "symbols") {
4360 sdata = bv->cursor().getEncoding()->name();
4362 showDialog("symbols", sdata);
4364 } else if (name == "prefs" && isFullScreen()) {
4365 lfunUiToggle("fullscreen");
4366 showDialog("prefs", sdata);
4368 showDialog(name, sdata);
4373 dr.setMessage(cmd.argument());
4376 case LFUN_UI_TOGGLE: {
4377 string arg = cmd.getArg(0);
4378 if (!lfunUiToggle(arg)) {
4379 docstring const msg = "ui-toggle " + _("%1$s unknown command!");
4380 dr.setMessage(bformat(msg, from_utf8(arg)));
4382 // Make sure the keyboard focus stays in the work area.
4387 case LFUN_VIEW_SPLIT: {
4388 LASSERT(doc_buffer, break);
4389 string const orientation = cmd.getArg(0);
4390 d.splitter_->setOrientation(orientation == "vertical"
4391 ? Qt::Vertical : Qt::Horizontal);
4392 TabWorkArea * twa = addTabWorkArea();
4393 GuiWorkArea * wa = twa->addWorkArea(*doc_buffer, *this);
4394 setCurrentWorkArea(wa);
4397 case LFUN_TAB_GROUP_CLOSE:
4398 if (TabWorkArea * twa = d.currentTabWorkArea()) {
4399 closeTabWorkArea(twa);
4400 d.current_work_area_ = nullptr;
4401 twa = d.currentTabWorkArea();
4402 // Switch to the next GuiWorkArea in the found TabWorkArea.
4404 // Make sure the work area is up to date.
4405 setCurrentWorkArea(twa->currentWorkArea());
4407 setCurrentWorkArea(nullptr);
4412 case LFUN_VIEW_CLOSE:
4413 if (TabWorkArea * twa = d.currentTabWorkArea()) {
4414 closeWorkArea(twa->currentWorkArea());
4415 d.current_work_area_ = nullptr;
4416 twa = d.currentTabWorkArea();
4417 // Switch to the next GuiWorkArea in the found TabWorkArea.
4419 // Make sure the work area is up to date.
4420 setCurrentWorkArea(twa->currentWorkArea());
4422 setCurrentWorkArea(nullptr);
4427 case LFUN_COMPLETION_INLINE:
4428 if (d.current_work_area_)
4429 d.current_work_area_->completer().showInline();
4432 case LFUN_COMPLETION_POPUP:
4433 if (d.current_work_area_)
4434 d.current_work_area_->completer().showPopup();
4439 if (d.current_work_area_)
4440 d.current_work_area_->completer().tab();
4443 case LFUN_COMPLETION_CANCEL:
4444 if (d.current_work_area_) {
4445 if (d.current_work_area_->completer().popupVisible())
4446 d.current_work_area_->completer().hidePopup();
4448 d.current_work_area_->completer().hideInline();
4452 case LFUN_COMPLETION_ACCEPT:
4453 if (d.current_work_area_)
4454 d.current_work_area_->completer().activate();
4457 case LFUN_BUFFER_ZOOM_IN:
4458 case LFUN_BUFFER_ZOOM_OUT:
4459 case LFUN_BUFFER_ZOOM: {
4460 if (cmd.argument().empty()) {
4461 if (cmd.action() == LFUN_BUFFER_ZOOM)
4463 else if (cmd.action() == LFUN_BUFFER_ZOOM_IN)
4468 if (cmd.action() == LFUN_BUFFER_ZOOM)
4469 zoom_ratio_ = convert<int>(cmd.argument()) / double(lyxrc.defaultZoom);
4470 else if (cmd.action() == LFUN_BUFFER_ZOOM_IN)
4471 zoom_ratio_ += convert<int>(cmd.argument()) / 100.0;
4473 zoom_ratio_ -= convert<int>(cmd.argument()) / 100.0;
4476 // Actual zoom value: default zoom + fractional extra value
4477 int zoom = (int)(lyxrc.defaultZoom * zoom_ratio_);
4478 if (zoom < static_cast<int>(zoom_min_))
4481 lyxrc.currentZoom = zoom;
4483 dr.setMessage(bformat(_("Zoom level is now %1$d% (default value: %2$d%)"),
4484 lyxrc.currentZoom, lyxrc.defaultZoom));
4486 guiApp->fontLoader().update();
4487 dr.screenUpdate(Update::Force | Update::FitCursor);
4491 case LFUN_VC_REGISTER:
4492 case LFUN_VC_RENAME:
4494 case LFUN_VC_CHECK_IN:
4495 case LFUN_VC_CHECK_OUT:
4496 case LFUN_VC_REPO_UPDATE:
4497 case LFUN_VC_LOCKING_TOGGLE:
4498 case LFUN_VC_REVERT:
4499 case LFUN_VC_UNDO_LAST:
4500 case LFUN_VC_COMMAND:
4501 case LFUN_VC_COMPARE:
4502 dispatchVC(cmd, dr);
4505 case LFUN_SERVER_GOTO_FILE_ROW:
4506 if(goToFileRow(to_utf8(cmd.argument())))
4507 dr.screenUpdate(Update::Force | Update::FitCursor);
4510 case LFUN_LYX_ACTIVATE:
4514 case LFUN_WINDOW_RAISE:
4520 case LFUN_FORWARD_SEARCH: {
4521 // it seems safe to assume we have a document buffer, since
4522 // getStatus wants one.
4523 LASSERT(doc_buffer, break);
4524 Buffer const * doc_master = doc_buffer->masterBuffer();
4525 FileName const path(doc_master->temppath());
4526 string const texname = doc_master->isChild(doc_buffer)
4527 ? DocFileName(changeExtension(
4528 doc_buffer->absFileName(),
4529 "tex")).mangledFileName()
4530 : doc_buffer->latexName();
4531 string const fulltexname =
4532 support::makeAbsPath(texname, doc_master->temppath()).absFileName();
4533 string const mastername =
4534 removeExtension(doc_master->latexName());
4535 FileName const dviname(addName(path.absFileName(),
4536 addExtension(mastername, "dvi")));
4537 FileName const pdfname(addName(path.absFileName(),
4538 addExtension(mastername, "pdf")));
4539 bool const have_dvi = dviname.exists();
4540 bool const have_pdf = pdfname.exists();
4541 if (!have_dvi && !have_pdf) {
4542 dr.setMessage(_("Please, preview the document first."));
4545 string outname = dviname.onlyFileName();
4546 string command = lyxrc.forward_search_dvi;
4547 if (!have_dvi || (have_pdf &&
4548 pdfname.lastModified() > dviname.lastModified())) {
4549 outname = pdfname.onlyFileName();
4550 command = lyxrc.forward_search_pdf;
4553 DocIterator cur = bv->cursor();
4554 int row = doc_buffer->texrow().rowFromDocIterator(cur).first;
4555 LYXERR(Debug::ACTION, "Forward search: row:" << row
4557 if (row == -1 || command.empty()) {
4558 dr.setMessage(_("Couldn't proceed."));
4561 string texrow = convert<string>(row);
4563 command = subst(command, "$$n", texrow);
4564 command = subst(command, "$$f", fulltexname);
4565 command = subst(command, "$$t", texname);
4566 command = subst(command, "$$o", outname);
4568 volatile PathChanger p(path);
4570 one.startscript(Systemcall::DontWait, command);
4574 case LFUN_SPELLING_CONTINUOUSLY:
4575 lyxrc.spellcheck_continuously = !lyxrc.spellcheck_continuously;
4576 dr.screenUpdate(Update::Force);
4580 // The LFUN must be for one of BufferView, Buffer or Cursor;
4582 dispatchToBufferView(cmd, dr);
4586 // Part of automatic menu appearance feature.
4587 if (isFullScreen()) {
4588 if (menuBar()->isVisible() && lyxrc.full_screen_menubar)
4592 // Need to update bv because many LFUNs here might have destroyed it
4593 bv = currentBufferView();
4595 // Clear non-empty selections
4596 // (e.g. from a "char-forward-select" followed by "char-backward-select")
4598 Cursor & cur = bv->cursor();
4599 if ((cur.selection() && cur.selBegin() == cur.selEnd())) {
4600 cur.clearSelection();
4606 bool GuiView::lfunUiToggle(string const & ui_component)
4608 if (ui_component == "scrollbar") {
4609 // hide() is of no help
4610 if (d.current_work_area_->verticalScrollBarPolicy() ==
4611 Qt::ScrollBarAlwaysOff)
4613 d.current_work_area_->setVerticalScrollBarPolicy(
4614 Qt::ScrollBarAsNeeded);
4616 d.current_work_area_->setVerticalScrollBarPolicy(
4617 Qt::ScrollBarAlwaysOff);
4618 } else if (ui_component == "statusbar") {
4619 statusBar()->setVisible(!statusBar()->isVisible());
4620 } else if (ui_component == "menubar") {
4621 menuBar()->setVisible(!menuBar()->isVisible());
4623 if (ui_component == "frame") {
4624 int const l = contentsMargins().left();
4626 //are the frames in default state?
4627 d.current_work_area_->setFrameStyle(QFrame::NoFrame);
4629 setContentsMargins(-2, -2, -2, -2);
4631 setContentsMargins(0, 0, 0, 0);
4634 if (ui_component == "fullscreen") {
4642 void GuiView::toggleFullScreen()
4644 if (isFullScreen()) {
4645 for (int i = 0; i != d.splitter_->count(); ++i)
4646 d.tabWorkArea(i)->setFullScreen(false);
4647 setContentsMargins(0, 0, 0, 0);
4648 setWindowState(windowState() ^ Qt::WindowFullScreen);
4651 statusBar()->show();
4654 hideDialogs("prefs", nullptr);
4655 for (int i = 0; i != d.splitter_->count(); ++i)
4656 d.tabWorkArea(i)->setFullScreen(true);
4657 setContentsMargins(-2, -2, -2, -2);
4659 setWindowState(windowState() ^ Qt::WindowFullScreen);
4660 if (lyxrc.full_screen_statusbar)
4661 statusBar()->hide();
4662 if (lyxrc.full_screen_menubar)
4664 if (lyxrc.full_screen_toolbars) {
4665 ToolbarMap::iterator end = d.toolbars_.end();
4666 for (ToolbarMap::iterator it = d.toolbars_.begin(); it != end; ++it)
4671 // give dialogs like the TOC a chance to adapt
4676 Buffer const * GuiView::updateInset(Inset const * inset)
4681 Buffer const * inset_buffer = &(inset->buffer());
4683 for (int i = 0; i != d.splitter_->count(); ++i) {
4684 GuiWorkArea * wa = d.tabWorkArea(i)->currentWorkArea();
4687 Buffer const * buffer = &(wa->bufferView().buffer());
4688 if (inset_buffer == buffer)
4689 wa->scheduleRedraw(true);
4691 return inset_buffer;
4695 void GuiView::restartCaret()
4697 /* When we move around, or type, it's nice to be able to see
4698 * the caret immediately after the keypress.
4700 if (d.current_work_area_)
4701 d.current_work_area_->startBlinkingCaret();
4703 // Take this occasion to update the other GUI elements.
4709 void GuiView::updateCompletion(Cursor & cur, bool start, bool keep)
4711 if (d.current_work_area_)
4712 d.current_work_area_->completer().updateVisibility(cur, start, keep);
4717 // This list should be kept in sync with the list of insets in
4718 // src/insets/Inset.cpp. I.e., if a dialog goes with an inset, the
4719 // dialog should have the same name as the inset.
4720 // Changes should be also recorded in LFUN_DIALOG_SHOW doxygen
4721 // docs in LyXAction.cpp.
4723 char const * const dialognames[] = {
4725 "aboutlyx", "bibitem", "bibtex", "box", "branch", "changes", "character",
4726 "citation", "compare", "comparehistory", "counter", "document", "errorlist", "ert",
4727 "external", "file", "findreplace", "findreplaceadv", "float", "graphics",
4728 "href", "include", "index", "index_print", "info", "listings", "label", "line",
4729 "log", "lyxfiles", "mathdelimiter", "mathmatrix", "mathspace", "nomenclature",
4730 "nomencl_print", "note", "paragraph", "phantom", "prefs", "ref",
4731 "sendto", "space", "spellchecker", "symbols", "tabular", "tabularcreate",
4732 "thesaurus", "texinfo", "toc", "view-source", "vspace", "wrap", "progress"};
4734 char const * const * const end_dialognames =
4735 dialognames + (sizeof(dialognames) / sizeof(char *));
4739 cmpCStr(char const * name) : name_(name) {}
4740 bool operator()(char const * other) {
4741 return strcmp(other, name_) == 0;
4748 bool isValidName(string const & name)
4750 return find_if(dialognames, end_dialognames,
4751 cmpCStr(name.c_str())) != end_dialognames;
4757 void GuiView::resetDialogs()
4759 // Make sure that no LFUN uses any GuiView.
4760 guiApp->setCurrentView(nullptr);
4764 constructToolbars();
4765 guiApp->menus().fillMenuBar(menuBar(), this, false);
4766 d.layout_->updateContents(true);
4767 // Now update controls with current buffer.
4768 guiApp->setCurrentView(this);
4774 Dialog * GuiView::findOrBuild(string const & name, bool hide_it)
4776 if (!isValidName(name))
4779 map<string, DialogPtr>::iterator it = d.dialogs_.find(name);
4781 if (it != d.dialogs_.end()) {
4783 it->second->hideView();
4784 return it->second.get();
4787 Dialog * dialog = build(name);
4788 d.dialogs_[name].reset(dialog);
4789 if (lyxrc.allow_geometry_session)
4790 dialog->restoreSession();
4797 void GuiView::showDialog(string const & name, string const & sdata,
4800 triggerShowDialog(toqstr(name), toqstr(sdata), inset);
4804 void GuiView::doShowDialog(QString const & qname, QString const & qdata,
4810 const string name = fromqstr(qname);
4811 const string sdata = fromqstr(qdata);
4815 Dialog * dialog = findOrBuild(name, false);
4817 bool const visible = dialog->isVisibleView();
4818 dialog->showData(sdata);
4819 if (currentBufferView())
4820 currentBufferView()->editInset(name, inset);
4821 // We only set the focus to the new dialog if it was not yet
4822 // visible in order not to change the existing previous behaviour
4824 // activateWindow is needed for floating dockviews
4825 dialog->asQWidget()->raise();
4826 dialog->asQWidget()->activateWindow();
4827 dialog->asQWidget()->setFocus();
4831 catch (ExceptionMessage const & ex) {
4839 bool GuiView::isDialogVisible(string const & name) const
4841 map<string, DialogPtr>::const_iterator it = d.dialogs_.find(name);
4842 if (it == d.dialogs_.end())
4844 return it->second.get()->isVisibleView() && !it->second.get()->isClosing();
4848 void GuiView::hideDialog(string const & name, Inset * inset)
4850 map<string, DialogPtr>::const_iterator it = d.dialogs_.find(name);
4851 if (it == d.dialogs_.end())
4855 if (!currentBufferView())
4857 if (inset != currentBufferView()->editedInset(name))
4861 Dialog * const dialog = it->second.get();
4862 if (dialog->isVisibleView())
4864 if (currentBufferView())
4865 currentBufferView()->editInset(name, nullptr);
4869 void GuiView::disconnectDialog(string const & name)
4871 if (!isValidName(name))
4873 if (currentBufferView())
4874 currentBufferView()->editInset(name, nullptr);
4878 void GuiView::hideAll() const
4880 map<string, DialogPtr>::const_iterator it = d.dialogs_.begin();
4881 map<string, DialogPtr>::const_iterator end = d.dialogs_.end();
4883 for(; it != end; ++it)
4884 it->second->hideView();
4888 void GuiView::updateDialogs()
4890 map<string, DialogPtr>::const_iterator it = d.dialogs_.begin();
4891 map<string, DialogPtr>::const_iterator end = d.dialogs_.end();
4893 for(; it != end; ++it) {
4894 Dialog * dialog = it->second.get();
4896 if (dialog->needBufferOpen() && !documentBufferView())
4897 hideDialog(fromqstr(dialog->name()), nullptr);
4898 else if (dialog->isVisibleView())
4899 dialog->checkStatus();
4906 Dialog * createDialog(GuiView & lv, string const & name);
4908 // will be replaced by a proper factory...
4909 Dialog * createGuiAbout(GuiView & lv);
4910 Dialog * createGuiBibtex(GuiView & lv);
4911 Dialog * createGuiChanges(GuiView & lv);
4912 Dialog * createGuiCharacter(GuiView & lv);
4913 Dialog * createGuiCitation(GuiView & lv);
4914 Dialog * createGuiCompare(GuiView & lv);
4915 Dialog * createGuiCompareHistory(GuiView & lv);
4916 Dialog * createGuiDelimiter(GuiView & lv);
4917 Dialog * createGuiDocument(GuiView & lv);
4918 Dialog * createGuiErrorList(GuiView & lv);
4919 Dialog * createGuiExternal(GuiView & lv);
4920 Dialog * createGuiGraphics(GuiView & lv);
4921 Dialog * createGuiInclude(GuiView & lv);
4922 Dialog * createGuiIndex(GuiView & lv);
4923 Dialog * createGuiListings(GuiView & lv);
4924 Dialog * createGuiLog(GuiView & lv);
4925 Dialog * createGuiLyXFiles(GuiView & lv);
4926 Dialog * createGuiMathMatrix(GuiView & lv);
4927 Dialog * createGuiNote(GuiView & lv);
4928 Dialog * createGuiParagraph(GuiView & lv);
4929 Dialog * createGuiPhantom(GuiView & lv);
4930 Dialog * createGuiPreferences(GuiView & lv);
4931 Dialog * createGuiPrint(GuiView & lv);
4932 Dialog * createGuiPrintindex(GuiView & lv);
4933 Dialog * createGuiRef(GuiView & lv);
4934 Dialog * createGuiSearch(GuiView & lv);
4935 Dialog * createGuiSearchAdv(GuiView & lv);
4936 Dialog * createGuiSendTo(GuiView & lv);
4937 Dialog * createGuiShowFile(GuiView & lv);
4938 Dialog * createGuiSpellchecker(GuiView & lv);
4939 Dialog * createGuiSymbols(GuiView & lv);
4940 Dialog * createGuiTabularCreate(GuiView & lv);
4941 Dialog * createGuiTexInfo(GuiView & lv);
4942 Dialog * createGuiToc(GuiView & lv);
4943 Dialog * createGuiThesaurus(GuiView & lv);
4944 Dialog * createGuiViewSource(GuiView & lv);
4945 Dialog * createGuiWrap(GuiView & lv);
4946 Dialog * createGuiProgressView(GuiView & lv);
4950 Dialog * GuiView::build(string const & name)
4952 LASSERT(isValidName(name), return nullptr);
4954 Dialog * dialog = createDialog(*this, name);
4958 if (name == "aboutlyx")
4959 return createGuiAbout(*this);
4960 if (name == "bibtex")
4961 return createGuiBibtex(*this);
4962 if (name == "changes")
4963 return createGuiChanges(*this);
4964 if (name == "character")
4965 return createGuiCharacter(*this);
4966 if (name == "citation")
4967 return createGuiCitation(*this);
4968 if (name == "compare")
4969 return createGuiCompare(*this);
4970 if (name == "comparehistory")
4971 return createGuiCompareHistory(*this);
4972 if (name == "document")
4973 return createGuiDocument(*this);
4974 if (name == "errorlist")
4975 return createGuiErrorList(*this);
4976 if (name == "external")
4977 return createGuiExternal(*this);
4979 return createGuiShowFile(*this);
4980 if (name == "findreplace")
4981 return createGuiSearch(*this);
4982 if (name == "findreplaceadv")
4983 return createGuiSearchAdv(*this);
4984 if (name == "graphics")
4985 return createGuiGraphics(*this);
4986 if (name == "include")
4987 return createGuiInclude(*this);
4988 if (name == "index")
4989 return createGuiIndex(*this);
4990 if (name == "index_print")
4991 return createGuiPrintindex(*this);
4992 if (name == "listings")
4993 return createGuiListings(*this);
4995 return createGuiLog(*this);
4996 if (name == "lyxfiles")
4997 return createGuiLyXFiles(*this);
4998 if (name == "mathdelimiter")
4999 return createGuiDelimiter(*this);
5000 if (name == "mathmatrix")
5001 return createGuiMathMatrix(*this);
5003 return createGuiNote(*this);
5004 if (name == "paragraph")
5005 return createGuiParagraph(*this);
5006 if (name == "phantom")
5007 return createGuiPhantom(*this);
5008 if (name == "prefs")
5009 return createGuiPreferences(*this);
5011 return createGuiRef(*this);
5012 if (name == "sendto")
5013 return createGuiSendTo(*this);
5014 if (name == "spellchecker")
5015 return createGuiSpellchecker(*this);
5016 if (name == "symbols")
5017 return createGuiSymbols(*this);
5018 if (name == "tabularcreate")
5019 return createGuiTabularCreate(*this);
5020 if (name == "texinfo")
5021 return createGuiTexInfo(*this);
5022 if (name == "thesaurus")
5023 return createGuiThesaurus(*this);
5025 return createGuiToc(*this);
5026 if (name == "view-source")
5027 return createGuiViewSource(*this);
5029 return createGuiWrap(*this);
5030 if (name == "progress")
5031 return createGuiProgressView(*this);
5037 SEMenu::SEMenu(QWidget * parent)
5039 QAction * action = addAction(qt_("Disable Shell Escape"));
5040 connect(action, SIGNAL(triggered()),
5041 parent, SLOT(disableShellEscape()));
5044 } // namespace frontend
5047 #include "moc_GuiView.cpp"