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>
120 #include <QWindowStateChangeEvent>
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::WindowStateChange: {
1405 QWindowStateChangeEvent * ev = (QWindowStateChangeEvent*)e;
1406 bool ofstate = (ev->oldState() & Qt::WindowFullScreen);
1407 bool result = QMainWindow::event(e);
1408 bool nfstate = (windowState() & Qt::WindowFullScreen);
1409 if (!ofstate && nfstate) {
1410 LYXERR(Debug::DEBUG, "GuiView: WindowStateChange(): full-screen " << nfstate);
1411 // switch to full-screen state
1412 if (lyxrc.full_screen_statusbar)
1413 statusBar()->hide();
1414 if (lyxrc.full_screen_menubar)
1416 if (lyxrc.full_screen_toolbars) {
1417 ToolbarMap::iterator end = d.toolbars_.end();
1418 for (ToolbarMap::iterator it = d.toolbars_.begin(); it != end; ++it)
1419 if (it->second->isVisibiltyOn() && it->second->isVisible())
1422 for (int i = 0; i != d.splitter_->count(); ++i)
1423 d.tabWorkArea(i)->setFullScreen(true);
1424 setContentsMargins(-2, -2, -2, -2);
1426 hideDialogs("prefs", nullptr);
1427 } else if (ofstate && !nfstate) {
1428 LYXERR(Debug::DEBUG, "GuiView: WindowStateChange(): full-screen " << nfstate);
1429 // switch back from full-screen state
1430 if (lyxrc.full_screen_statusbar && !statusBar()->isVisible())
1431 statusBar()->show();
1432 if (lyxrc.full_screen_menubar && !menuBar()->isVisible())
1434 if (lyxrc.full_screen_toolbars) {
1435 ToolbarMap::iterator end = d.toolbars_.end();
1436 for (ToolbarMap::iterator it = d.toolbars_.begin(); it != end; ++it)
1437 if (it->second->isVisibiltyOn() && !it->second->isVisible())
1441 for (int i = 0; i != d.splitter_->count(); ++i)
1442 d.tabWorkArea(i)->setFullScreen(false);
1443 setContentsMargins(0, 0, 0, 0);
1447 case QEvent::WindowActivate: {
1448 GuiView * old_view = guiApp->currentView();
1449 if (this == old_view) {
1451 return QMainWindow::event(e);
1453 if (old_view && old_view->currentBufferView()) {
1454 // save current selection to the selection buffer to allow
1455 // middle-button paste in this window.
1456 cap::saveSelection(old_view->currentBufferView()->cursor());
1458 guiApp->setCurrentView(this);
1459 if (d.current_work_area_)
1460 on_currentWorkAreaChanged(d.current_work_area_);
1464 return QMainWindow::event(e);
1467 case QEvent::ShortcutOverride: {
1469 if (isFullScreen() && menuBar()->isHidden()) {
1470 QKeyEvent * ke = static_cast<QKeyEvent*>(e);
1471 // FIXME: we should also try to detect special LyX shortcut such as
1472 // Alt-P and Alt-M. Right now there is a hack in
1473 // GuiWorkArea::processKeySym() that hides again the menubar for
1475 if (ke->modifiers() & Qt::AltModifier && ke->key() != Qt::Key_Alt) {
1477 return QMainWindow::event(e);
1480 return QMainWindow::event(e);
1484 return QMainWindow::event(e);
1488 void GuiView::resetWindowTitle()
1490 setWindowTitle(qt_("LyX"));
1493 bool GuiView::focusNextPrevChild(bool /*next*/)
1500 bool GuiView::busy() const
1506 void GuiView::setBusy(bool busy)
1508 bool const busy_before = busy_ > 0;
1509 busy ? ++busy_ : --busy_;
1510 if ((busy_ > 0) == busy_before)
1511 // busy state didn't change
1515 QApplication::setOverrideCursor(Qt::WaitCursor);
1518 QApplication::restoreOverrideCursor();
1523 void GuiView::resetCommandExecute()
1525 command_execute_ = false;
1530 double GuiView::pixelRatio() const
1532 #if QT_VERSION >= 0x050000
1533 return qt_scale_factor * devicePixelRatio();
1540 GuiWorkArea * GuiView::workArea(int index)
1542 if (TabWorkArea * twa = d.currentTabWorkArea())
1543 if (index < twa->count())
1544 return twa->workArea(index);
1549 GuiWorkArea * GuiView::workArea(Buffer & buffer)
1551 if (currentWorkArea()
1552 && ¤tWorkArea()->bufferView().buffer() == &buffer)
1553 return currentWorkArea();
1554 if (TabWorkArea * twa = d.currentTabWorkArea())
1555 return twa->workArea(buffer);
1560 GuiWorkArea * GuiView::addWorkArea(Buffer & buffer)
1562 // Automatically create a TabWorkArea if there are none yet.
1563 TabWorkArea * tab_widget = d.splitter_->count()
1564 ? d.currentTabWorkArea() : addTabWorkArea();
1565 return tab_widget->addWorkArea(buffer, *this);
1569 TabWorkArea * GuiView::addTabWorkArea()
1571 TabWorkArea * twa = new TabWorkArea;
1572 QObject::connect(twa, SIGNAL(currentWorkAreaChanged(GuiWorkArea *)),
1573 this, SLOT(on_currentWorkAreaChanged(GuiWorkArea *)));
1574 QObject::connect(twa, SIGNAL(lastWorkAreaRemoved()),
1575 this, SLOT(on_lastWorkAreaRemoved()));
1577 d.splitter_->addWidget(twa);
1578 d.stack_widget_->setCurrentWidget(d.splitter_);
1583 GuiWorkArea const * GuiView::currentWorkArea() const
1585 return d.current_work_area_;
1589 GuiWorkArea * GuiView::currentWorkArea()
1591 return d.current_work_area_;
1595 GuiWorkArea const * GuiView::currentMainWorkArea() const
1597 if (!d.currentTabWorkArea())
1599 return d.currentTabWorkArea()->currentWorkArea();
1603 GuiWorkArea * GuiView::currentMainWorkArea()
1605 if (!d.currentTabWorkArea())
1607 return d.currentTabWorkArea()->currentWorkArea();
1611 void GuiView::setCurrentWorkArea(GuiWorkArea * wa)
1613 LYXERR(Debug::DEBUG, "Setting current wa: " << wa << endl);
1615 d.current_work_area_ = nullptr;
1617 Q_EMIT bufferViewChanged();
1621 // FIXME: I've no clue why this is here and why it accesses
1622 // theGuiApp()->currentView, which might be 0 (bug 6464).
1623 // See also 27525 (vfr).
1624 if (theGuiApp()->currentView() == this
1625 && theGuiApp()->currentView()->currentWorkArea() == wa)
1628 if (currentBufferView())
1629 cap::saveSelection(currentBufferView()->cursor());
1631 theGuiApp()->setCurrentView(this);
1632 d.current_work_area_ = wa;
1634 // We need to reset this now, because it will need to be
1635 // right if the tabWorkArea gets reset in the for loop. We
1636 // will change it back if we aren't in that case.
1637 GuiWorkArea * const old_cmwa = d.current_main_work_area_;
1638 d.current_main_work_area_ = wa;
1640 for (int i = 0; i != d.splitter_->count(); ++i) {
1641 if (d.tabWorkArea(i)->setCurrentWorkArea(wa)) {
1642 LYXERR(Debug::DEBUG, "Current wa: " << currentWorkArea()
1643 << ", Current main wa: " << currentMainWorkArea());
1648 d.current_main_work_area_ = old_cmwa;
1650 LYXERR(Debug::DEBUG, "This is not a tabbed wa");
1651 on_currentWorkAreaChanged(wa);
1652 BufferView & bv = wa->bufferView();
1653 bv.cursor().fixIfBroken();
1655 wa->setUpdatesEnabled(true);
1656 LYXERR(Debug::DEBUG, "Current wa: " << currentWorkArea() << ", Current main wa: " << currentMainWorkArea());
1660 void GuiView::removeWorkArea(GuiWorkArea * wa)
1662 LASSERT(wa, return);
1663 if (wa == d.current_work_area_) {
1665 disconnectBufferView();
1666 d.current_work_area_ = nullptr;
1667 d.current_main_work_area_ = nullptr;
1670 bool found_twa = false;
1671 for (int i = 0; i != d.splitter_->count(); ++i) {
1672 TabWorkArea * twa = d.tabWorkArea(i);
1673 if (twa->removeWorkArea(wa)) {
1674 // Found in this tab group, and deleted the GuiWorkArea.
1676 if (twa->count() != 0) {
1677 if (d.current_work_area_ == nullptr)
1678 // This means that we are closing the current GuiWorkArea, so
1679 // switch to the next GuiWorkArea in the found TabWorkArea.
1680 setCurrentWorkArea(twa->currentWorkArea());
1682 // No more WorkAreas in this tab group, so delete it.
1689 // It is not a tabbed work area (i.e., the search work area), so it
1690 // should be deleted by other means.
1691 LASSERT(found_twa, return);
1693 if (d.current_work_area_ == nullptr) {
1694 if (d.splitter_->count() != 0) {
1695 TabWorkArea * twa = d.currentTabWorkArea();
1696 setCurrentWorkArea(twa->currentWorkArea());
1698 // No more work areas, switch to the background widget.
1699 setCurrentWorkArea(nullptr);
1705 LayoutBox * GuiView::getLayoutDialog() const
1711 void GuiView::updateLayoutList()
1714 d.layout_->updateContents(false);
1718 void GuiView::updateToolbars()
1720 ToolbarMap::iterator end = d.toolbars_.end();
1721 if (d.current_work_area_) {
1723 if (d.current_work_area_->bufferView().cursor().inMathed()
1724 && !d.current_work_area_->bufferView().cursor().inRegexped())
1725 context |= Toolbars::MATH;
1726 if (lyx::getStatus(FuncRequest(LFUN_LAYOUT_TABULAR)).enabled())
1727 context |= Toolbars::TABLE;
1728 if (currentBufferView()->buffer().areChangesPresent()
1729 || (lyx::getStatus(FuncRequest(LFUN_CHANGES_TRACK)).enabled()
1730 && lyx::getStatus(FuncRequest(LFUN_CHANGES_TRACK)).onOff(true))
1731 || (lyx::getStatus(FuncRequest(LFUN_CHANGES_OUTPUT)).enabled()
1732 && lyx::getStatus(FuncRequest(LFUN_CHANGES_OUTPUT)).onOff(true)))
1733 context |= Toolbars::REVIEW;
1734 if (lyx::getStatus(FuncRequest(LFUN_IN_MATHMACROTEMPLATE)).enabled())
1735 context |= Toolbars::MATHMACROTEMPLATE;
1736 if (lyx::getStatus(FuncRequest(LFUN_IN_IPA)).enabled())
1737 context |= Toolbars::IPA;
1738 if (command_execute_)
1739 context |= Toolbars::MINIBUFFER;
1740 if (minibuffer_focus_) {
1741 context |= Toolbars::MINIBUFFER_FOCUS;
1742 minibuffer_focus_ = false;
1745 for (ToolbarMap::iterator it = d.toolbars_.begin(); it != end; ++it)
1746 it->second->update(context);
1748 for (ToolbarMap::iterator it = d.toolbars_.begin(); it != end; ++it)
1749 it->second->update();
1753 void GuiView::setBuffer(Buffer * newBuffer, bool switch_to)
1755 LYXERR(Debug::DEBUG, "Setting buffer: " << newBuffer << endl);
1756 LASSERT(newBuffer, return);
1758 GuiWorkArea * wa = workArea(*newBuffer);
1759 if (wa == nullptr) {
1761 newBuffer->masterBuffer()->updateBuffer();
1763 wa = addWorkArea(*newBuffer);
1764 // scroll to the position when the BufferView was last closed
1765 if (lyxrc.use_lastfilepos) {
1766 LastFilePosSection::FilePos filepos =
1767 theSession().lastFilePos().load(newBuffer->fileName());
1768 wa->bufferView().moveToPosition(filepos.pit, filepos.pos, 0, 0);
1771 //Disconnect the old buffer...there's no new one.
1774 connectBuffer(*newBuffer);
1775 connectBufferView(wa->bufferView());
1777 setCurrentWorkArea(wa);
1781 void GuiView::connectBuffer(Buffer & buf)
1783 buf.setGuiDelegate(this);
1787 void GuiView::disconnectBuffer()
1789 if (d.current_work_area_)
1790 d.current_work_area_->bufferView().buffer().setGuiDelegate(nullptr);
1794 void GuiView::connectBufferView(BufferView & bv)
1796 bv.setGuiDelegate(this);
1800 void GuiView::disconnectBufferView()
1802 if (d.current_work_area_)
1803 d.current_work_area_->bufferView().setGuiDelegate(nullptr);
1807 void GuiView::errors(string const & error_type, bool from_master)
1809 BufferView const * const bv = currentBufferView();
1813 ErrorList const & el = from_master ?
1814 bv->buffer().masterBuffer()->errorList(error_type) :
1815 bv->buffer().errorList(error_type);
1820 string err = error_type;
1822 err = "from_master|" + error_type;
1823 showDialog("errorlist", err);
1827 void GuiView::updateTocItem(string const & type, DocIterator const & dit)
1829 d.toc_models_.updateItem(toqstr(type), dit);
1833 void GuiView::structureChanged()
1835 // This is called from the Buffer, which has no way to ensure that cursors
1836 // in BufferView remain valid.
1837 if (documentBufferView())
1838 documentBufferView()->cursor().sanitize();
1839 // FIXME: This is slightly expensive, though less than the tocBackend update
1840 // (#9880). This also resets the view in the Toc Widget (#6675).
1841 d.toc_models_.reset(documentBufferView());
1842 // Navigator needs more than a simple update in this case. It needs to be
1844 updateDialog("toc", "");
1848 void GuiView::updateDialog(string const & name, string const & sdata)
1850 if (!isDialogVisible(name))
1853 map<string, DialogPtr>::const_iterator it = d.dialogs_.find(name);
1854 if (it == d.dialogs_.end())
1857 Dialog * const dialog = it->second.get();
1858 if (dialog->isVisibleView())
1859 dialog->initialiseParams(sdata);
1863 BufferView * GuiView::documentBufferView()
1865 return currentMainWorkArea()
1866 ? ¤tMainWorkArea()->bufferView()
1871 BufferView const * GuiView::documentBufferView() const
1873 return currentMainWorkArea()
1874 ? ¤tMainWorkArea()->bufferView()
1879 BufferView * GuiView::currentBufferView()
1881 return d.current_work_area_ ? &d.current_work_area_->bufferView() : nullptr;
1885 BufferView const * GuiView::currentBufferView() const
1887 return d.current_work_area_ ? &d.current_work_area_->bufferView() : nullptr;
1891 docstring GuiView::GuiViewPrivate::autosaveAndDestroy(
1892 Buffer const * orig, Buffer * clone)
1894 bool const success = clone->autoSave();
1896 busyBuffers.remove(orig);
1898 ? _("Automatic save done.")
1899 : _("Automatic save failed!");
1903 void GuiView::autoSave()
1905 LYXERR(Debug::INFO, "Running autoSave()");
1907 Buffer * buffer = documentBufferView()
1908 ? &documentBufferView()->buffer() : nullptr;
1910 resetAutosaveTimers();
1914 GuiViewPrivate::busyBuffers.insert(buffer);
1915 QFuture<docstring> f = QtConcurrent::run(GuiViewPrivate::autosaveAndDestroy,
1916 buffer, buffer->cloneBufferOnly());
1917 d.autosave_watcher_.setFuture(f);
1918 resetAutosaveTimers();
1922 void GuiView::resetAutosaveTimers()
1925 d.autosave_timeout_.restart();
1929 bool GuiView::getStatus(FuncRequest const & cmd, FuncStatus & flag)
1932 Buffer * buf = currentBufferView()
1933 ? ¤tBufferView()->buffer() : nullptr;
1934 Buffer * doc_buffer = documentBufferView()
1935 ? &(documentBufferView()->buffer()) : nullptr;
1938 /* In LyX/Mac, when a dialog is open, the menus of the
1939 application can still be accessed without giving focus to
1940 the main window. In this case, we want to disable the menu
1941 entries that are buffer-related.
1942 This code must not be used on Linux and Windows, since it
1943 would disable buffer-related entries when hovering over the
1944 menu (see bug #9574).
1946 if (cmd.origin() == FuncRequest::MENU && !hasFocus()) {
1952 // Check whether we need a buffer
1953 if (!lyxaction.funcHasFlag(cmd.action(), LyXAction::NoBuffer) && !buf) {
1954 // no, exit directly
1955 flag.message(from_utf8(N_("Command not allowed with"
1956 "out any document open")));
1957 flag.setEnabled(false);
1961 if (cmd.origin() == FuncRequest::TOC) {
1962 GuiToc * toc = static_cast<GuiToc*>(findOrBuild("toc", false));
1963 if (!toc || !toc->getStatus(documentBufferView()->cursor(), cmd, flag))
1964 flag.setEnabled(false);
1968 switch(cmd.action()) {
1969 case LFUN_BUFFER_IMPORT:
1972 case LFUN_MASTER_BUFFER_EXPORT:
1974 && (doc_buffer->parent() != nullptr
1975 || doc_buffer->hasChildren())
1976 && !d.processing_thread_watcher_.isRunning()
1977 // this launches a dialog, which would be in the wrong Buffer
1978 && !(::lyx::operator==(cmd.argument(), "custom"));
1981 case LFUN_MASTER_BUFFER_UPDATE:
1982 case LFUN_MASTER_BUFFER_VIEW:
1984 && (doc_buffer->parent() != nullptr
1985 || doc_buffer->hasChildren())
1986 && !d.processing_thread_watcher_.isRunning();
1989 case LFUN_BUFFER_UPDATE:
1990 case LFUN_BUFFER_VIEW: {
1991 if (!doc_buffer || d.processing_thread_watcher_.isRunning()) {
1995 string format = to_utf8(cmd.argument());
1996 if (cmd.argument().empty())
1997 format = doc_buffer->params().getDefaultOutputFormat();
1998 enable = doc_buffer->params().isExportable(format, true);
2002 case LFUN_BUFFER_RELOAD:
2003 enable = doc_buffer && !doc_buffer->isUnnamed()
2004 && doc_buffer->fileName().exists()
2005 && (!doc_buffer->isClean() || doc_buffer->notifiesExternalModification());
2008 case LFUN_BUFFER_RESET_EXPORT:
2009 enable = doc_buffer != nullptr;
2012 case LFUN_BUFFER_CHILD_OPEN:
2013 enable = doc_buffer != nullptr;
2016 case LFUN_MASTER_BUFFER_FORALL: {
2017 if (doc_buffer == nullptr) {
2018 flag.message(from_utf8(N_("Command not allowed without a buffer open")));
2022 FuncRequest const cmdToPass = lyxaction.lookupFunc(cmd.getLongArg(0));
2023 if (cmdToPass.action() == LFUN_UNKNOWN_ACTION) {
2024 flag.message(from_utf8(N_("Invalid argument of master-buffer-forall")));
2029 for (Buffer * buf : doc_buffer->allRelatives()) {
2030 GuiWorkArea * wa = workArea(*buf);
2033 if (wa->bufferView().getStatus(cmdToPass, flag)) {
2034 enable = flag.enabled();
2041 case LFUN_BUFFER_WRITE:
2042 enable = doc_buffer && (doc_buffer->isUnnamed()
2043 || (!doc_buffer->isClean()
2044 || cmd.argument() == "force"));
2047 //FIXME: This LFUN should be moved to GuiApplication.
2048 case LFUN_BUFFER_WRITE_ALL: {
2049 // We enable the command only if there are some modified buffers
2050 Buffer * first = theBufferList().first();
2055 // We cannot use a for loop as the buffer list is a cycle.
2057 if (!b->isClean()) {
2061 b = theBufferList().next(b);
2062 } while (b != first);
2066 case LFUN_BUFFER_EXTERNAL_MODIFICATION_CLEAR:
2067 enable = doc_buffer && doc_buffer->notifiesExternalModification();
2070 case LFUN_BUFFER_EXPORT: {
2071 if (!doc_buffer || d.processing_thread_watcher_.isRunning()) {
2075 return doc_buffer->getStatus(cmd, flag);
2078 case LFUN_BUFFER_EXPORT_AS:
2079 if (!doc_buffer || d.processing_thread_watcher_.isRunning()) {
2084 case LFUN_BUFFER_WRITE_AS:
2085 case LFUN_BUFFER_WRITE_AS_TEMPLATE:
2086 enable = doc_buffer != nullptr;
2089 case LFUN_EXPORT_CANCEL:
2090 enable = d.processing_thread_watcher_.isRunning();
2093 case LFUN_BUFFER_CLOSE:
2094 case LFUN_VIEW_CLOSE:
2095 enable = doc_buffer != nullptr;
2098 case LFUN_BUFFER_CLOSE_ALL:
2099 enable = theBufferList().last() != theBufferList().first();
2102 case LFUN_BUFFER_CHKTEX: {
2103 // hide if we have no checktex command
2104 if (lyxrc.chktex_command.empty()) {
2105 flag.setUnknown(true);
2109 if (!doc_buffer || !doc_buffer->params().isLatex()
2110 || d.processing_thread_watcher_.isRunning()) {
2111 // grey out, don't hide
2119 case LFUN_VIEW_SPLIT:
2120 if (cmd.getArg(0) == "vertical")
2121 enable = doc_buffer && (d.splitter_->count() == 1 ||
2122 d.splitter_->orientation() == Qt::Vertical);
2124 enable = doc_buffer && (d.splitter_->count() == 1 ||
2125 d.splitter_->orientation() == Qt::Horizontal);
2128 case LFUN_TAB_GROUP_CLOSE:
2129 enable = d.tabWorkAreaCount() > 1;
2132 case LFUN_DEVEL_MODE_TOGGLE:
2133 flag.setOnOff(devel_mode_);
2136 case LFUN_TOOLBAR_TOGGLE: {
2137 string const name = cmd.getArg(0);
2138 if (GuiToolbar * t = toolbar(name))
2139 flag.setOnOff(t->isVisible());
2142 docstring const msg =
2143 bformat(_("Unknown toolbar \"%1$s\""), from_utf8(name));
2149 case LFUN_TOOLBAR_MOVABLE: {
2150 string const name = cmd.getArg(0);
2151 // use negation since locked == !movable
2153 // toolbar name * locks all toolbars
2154 flag.setOnOff(!toolbarsMovable_);
2155 else if (GuiToolbar * t = toolbar(name))
2156 flag.setOnOff(!(t->isMovable()));
2159 docstring const msg =
2160 bformat(_("Unknown toolbar \"%1$s\""), from_utf8(name));
2166 case LFUN_ICON_SIZE:
2167 flag.setOnOff(d.iconSize(cmd.argument()) == iconSize());
2170 case LFUN_DROP_LAYOUTS_CHOICE:
2171 enable = buf != nullptr;
2174 case LFUN_UI_TOGGLE:
2175 flag.setOnOff(isFullScreen());
2178 case LFUN_DIALOG_DISCONNECT_INSET:
2181 case LFUN_DIALOG_HIDE:
2182 // FIXME: should we check if the dialog is shown?
2185 case LFUN_DIALOG_TOGGLE:
2186 flag.setOnOff(isDialogVisible(cmd.getArg(0)));
2189 case LFUN_DIALOG_SHOW: {
2190 string const name = cmd.getArg(0);
2192 enable = name == "aboutlyx"
2193 || name == "file" //FIXME: should be removed.
2194 || name == "lyxfiles"
2196 || name == "texinfo"
2197 || name == "progress"
2198 || name == "compare";
2199 else if (name == "character" || name == "symbols"
2200 || name == "mathdelimiter" || name == "mathmatrix") {
2201 if (!buf || buf->isReadonly())
2204 Cursor const & cur = currentBufferView()->cursor();
2205 enable = !(cur.inTexted() && cur.paragraph().isPassThru());
2208 else if (name == "latexlog")
2209 enable = FileName(doc_buffer->logName()).isReadableFile();
2210 else if (name == "spellchecker")
2211 enable = theSpellChecker()
2212 && !doc_buffer->isReadonly()
2213 && !doc_buffer->text().empty();
2214 else if (name == "vclog")
2215 enable = doc_buffer->lyxvc().inUse();
2219 case LFUN_DIALOG_UPDATE: {
2220 string const name = cmd.getArg(0);
2222 enable = name == "prefs";
2226 case LFUN_COMMAND_EXECUTE:
2228 case LFUN_MENU_OPEN:
2229 // Nothing to check.
2232 case LFUN_COMPLETION_INLINE:
2233 if (!d.current_work_area_
2234 || !d.current_work_area_->completer().inlinePossible(
2235 currentBufferView()->cursor()))
2239 case LFUN_COMPLETION_POPUP:
2240 if (!d.current_work_area_
2241 || !d.current_work_area_->completer().popupPossible(
2242 currentBufferView()->cursor()))
2247 if (!d.current_work_area_
2248 || !d.current_work_area_->completer().inlinePossible(
2249 currentBufferView()->cursor()))
2253 case LFUN_COMPLETION_ACCEPT:
2254 if (!d.current_work_area_
2255 || (!d.current_work_area_->completer().popupVisible()
2256 && !d.current_work_area_->completer().inlineVisible()
2257 && !d.current_work_area_->completer().completionAvailable()))
2261 case LFUN_COMPLETION_CANCEL:
2262 if (!d.current_work_area_
2263 || (!d.current_work_area_->completer().popupVisible()
2264 && !d.current_work_area_->completer().inlineVisible()))
2268 case LFUN_BUFFER_ZOOM_OUT:
2269 case LFUN_BUFFER_ZOOM_IN: {
2270 // only diff between these two is that the default for ZOOM_OUT
2272 bool const neg_zoom =
2273 convert<int>(cmd.argument()) < 0 ||
2274 (cmd.action() == LFUN_BUFFER_ZOOM_OUT && cmd.argument().empty());
2275 if (lyxrc.currentZoom <= zoom_min_ && neg_zoom) {
2276 docstring const msg =
2277 bformat(_("Zoom level cannot be less than %1$d%."), zoom_min_);
2281 enable = doc_buffer;
2285 case LFUN_BUFFER_ZOOM: {
2286 bool const less_than_min_zoom =
2287 !cmd.argument().empty() && convert<int>(cmd.argument()) < zoom_min_;
2288 if (lyxrc.currentZoom <= zoom_min_ && less_than_min_zoom) {
2289 docstring const msg =
2290 bformat(_("Zoom level cannot be less than %1$d%."), zoom_min_);
2295 enable = doc_buffer;
2299 case LFUN_BUFFER_MOVE_NEXT:
2300 case LFUN_BUFFER_MOVE_PREVIOUS:
2301 // we do not cycle when moving
2302 case LFUN_BUFFER_NEXT:
2303 case LFUN_BUFFER_PREVIOUS:
2304 // because we cycle, it doesn't matter whether on first or last
2305 enable = (d.currentTabWorkArea()->count() > 1);
2307 case LFUN_BUFFER_SWITCH:
2308 // toggle on the current buffer, but do not toggle off
2309 // the other ones (is that a good idea?)
2311 && to_utf8(cmd.argument()) == doc_buffer->absFileName())
2312 flag.setOnOff(true);
2315 case LFUN_VC_REGISTER:
2316 enable = doc_buffer && !doc_buffer->lyxvc().inUse();
2318 case LFUN_VC_RENAME:
2319 enable = doc_buffer && doc_buffer->lyxvc().renameEnabled();
2322 enable = doc_buffer && doc_buffer->lyxvc().copyEnabled();
2324 case LFUN_VC_CHECK_IN:
2325 enable = doc_buffer && doc_buffer->lyxvc().checkInEnabled();
2327 case LFUN_VC_CHECK_OUT:
2328 enable = doc_buffer && doc_buffer->lyxvc().checkOutEnabled();
2330 case LFUN_VC_LOCKING_TOGGLE:
2331 enable = doc_buffer && !doc_buffer->hasReadonlyFlag()
2332 && doc_buffer->lyxvc().lockingToggleEnabled();
2333 flag.setOnOff(enable && doc_buffer->lyxvc().locking());
2335 case LFUN_VC_REVERT:
2336 enable = doc_buffer && doc_buffer->lyxvc().inUse()
2337 && !doc_buffer->hasReadonlyFlag();
2339 case LFUN_VC_UNDO_LAST:
2340 enable = doc_buffer && doc_buffer->lyxvc().undoLastEnabled();
2342 case LFUN_VC_REPO_UPDATE:
2343 enable = doc_buffer && doc_buffer->lyxvc().repoUpdateEnabled();
2345 case LFUN_VC_COMMAND: {
2346 if (cmd.argument().empty())
2348 if (!doc_buffer && contains(cmd.getArg(0), 'D'))
2352 case LFUN_VC_COMPARE:
2353 enable = doc_buffer && doc_buffer->lyxvc().prepareFileRevisionEnabled();
2356 case LFUN_SERVER_GOTO_FILE_ROW:
2357 case LFUN_LYX_ACTIVATE:
2358 case LFUN_WINDOW_RAISE:
2360 case LFUN_FORWARD_SEARCH:
2361 enable = !(lyxrc.forward_search_dvi.empty() && lyxrc.forward_search_pdf.empty());
2364 case LFUN_FILE_INSERT_PLAINTEXT:
2365 case LFUN_FILE_INSERT_PLAINTEXT_PARA:
2366 enable = documentBufferView() && documentBufferView()->cursor().inTexted();
2369 case LFUN_SPELLING_CONTINUOUSLY:
2370 flag.setOnOff(lyxrc.spellcheck_continuously);
2378 flag.setEnabled(false);
2384 static FileName selectTemplateFile()
2386 FileDialog dlg(qt_("Select template file"));
2387 dlg.setButton1(qt_("D&ocuments"), toqstr(lyxrc.document_path));
2388 dlg.setButton2(qt_("&Templates"), toqstr(lyxrc.template_path));
2390 FileDialog::Result result = dlg.open(toqstr(lyxrc.template_path),
2391 QStringList(qt_("LyX Documents (*.lyx)")));
2393 if (result.first == FileDialog::Later)
2395 if (result.second.isEmpty())
2397 return FileName(fromqstr(result.second));
2401 Buffer * GuiView::loadDocument(FileName const & filename, bool tolastfiles)
2405 Buffer * newBuffer = nullptr;
2407 newBuffer = checkAndLoadLyXFile(filename);
2408 } catch (ExceptionMessage const & e) {
2415 message(_("Document not loaded."));
2419 setBuffer(newBuffer);
2420 newBuffer->errors("Parse");
2423 theSession().lastFiles().add(filename);
2424 theSession().writeFile();
2431 void GuiView::openDocument(string const & fname)
2433 string initpath = lyxrc.document_path;
2435 if (documentBufferView()) {
2436 string const trypath = documentBufferView()->buffer().filePath();
2437 // If directory is writeable, use this as default.
2438 if (FileName(trypath).isDirWritable())
2444 if (fname.empty()) {
2445 FileDialog dlg(qt_("Select document to open"));
2446 dlg.setButton1(qt_("D&ocuments"), toqstr(lyxrc.document_path));
2447 dlg.setButton2(qt_("&Examples"), toqstr(lyxrc.example_path));
2449 QStringList const filter(qt_("LyX Documents (*.lyx)"));
2450 FileDialog::Result result =
2451 dlg.open(toqstr(initpath), filter);
2453 if (result.first == FileDialog::Later)
2456 filename = fromqstr(result.second);
2458 // check selected filename
2459 if (filename.empty()) {
2460 message(_("Canceled."));
2466 // get absolute path of file and add ".lyx" to the filename if
2468 FileName const fullname =
2469 fileSearch(string(), filename, "lyx", support::may_not_exist);
2470 if (!fullname.empty())
2471 filename = fullname.absFileName();
2473 if (!fullname.onlyPath().isDirectory()) {
2474 Alert::warning(_("Invalid filename"),
2475 bformat(_("The directory in the given path\n%1$s\ndoes not exist."),
2476 from_utf8(fullname.absFileName())));
2480 // if the file doesn't exist and isn't already open (bug 6645),
2481 // let the user create one
2482 if (!fullname.exists() && !theBufferList().exists(fullname) &&
2483 !LyXVC::file_not_found_hook(fullname)) {
2484 // the user specifically chose this name. Believe him.
2485 Buffer * const b = newFile(filename, string(), true);
2491 docstring const disp_fn = makeDisplayPath(filename);
2492 message(bformat(_("Opening document %1$s..."), disp_fn));
2495 Buffer * buf = loadDocument(fullname);
2497 str2 = bformat(_("Document %1$s opened."), disp_fn);
2498 if (buf->lyxvc().inUse())
2499 str2 += " " + from_utf8(buf->lyxvc().versionString()) +
2500 " " + _("Version control detected.");
2502 str2 = bformat(_("Could not open document %1$s"), disp_fn);
2507 // FIXME: clean that
2508 static bool import(GuiView * lv, FileName const & filename,
2509 string const & format, ErrorList & errorList)
2511 FileName const lyxfile(support::changeExtension(filename.absFileName(), ".lyx"));
2513 string loader_format;
2514 vector<string> loaders = theConverters().loaders();
2515 if (find(loaders.begin(), loaders.end(), format) == loaders.end()) {
2516 vector<string>::const_iterator it = loaders.begin();
2517 vector<string>::const_iterator en = loaders.end();
2518 for (; it != en; ++it) {
2519 if (!theConverters().isReachable(format, *it))
2522 string const tofile =
2523 support::changeExtension(filename.absFileName(),
2524 theFormats().extension(*it));
2525 if (theConverters().convert(nullptr, filename, FileName(tofile),
2526 filename, format, *it, errorList) != Converters::SUCCESS)
2528 loader_format = *it;
2531 if (loader_format.empty()) {
2532 frontend::Alert::error(_("Couldn't import file"),
2533 bformat(_("No information for importing the format %1$s."),
2534 theFormats().prettyName(format)));
2538 loader_format = format;
2540 if (loader_format == "lyx") {
2541 Buffer * buf = lv->loadDocument(lyxfile);
2545 Buffer * const b = newFile(lyxfile.absFileName(), string(), true);
2549 bool as_paragraphs = loader_format == "textparagraph";
2550 string filename2 = (loader_format == format) ? filename.absFileName()
2551 : support::changeExtension(filename.absFileName(),
2552 theFormats().extension(loader_format));
2553 lv->currentBufferView()->insertPlaintextFile(FileName(filename2),
2555 guiApp->setCurrentView(lv);
2556 lyx::dispatch(FuncRequest(LFUN_MARK_OFF));
2563 void GuiView::importDocument(string const & argument)
2566 string filename = split(argument, format, ' ');
2568 LYXERR(Debug::INFO, format << " file: " << filename);
2570 // need user interaction
2571 if (filename.empty()) {
2572 string initpath = lyxrc.document_path;
2573 if (documentBufferView()) {
2574 string const trypath = documentBufferView()->buffer().filePath();
2575 // If directory is writeable, use this as default.
2576 if (FileName(trypath).isDirWritable())
2580 docstring const text = bformat(_("Select %1$s file to import"),
2581 theFormats().prettyName(format));
2583 FileDialog dlg(toqstr(text));
2584 dlg.setButton1(qt_("D&ocuments"), toqstr(lyxrc.document_path));
2585 dlg.setButton2(qt_("&Examples"), toqstr(lyxrc.example_path));
2587 docstring filter = theFormats().prettyName(format);
2590 filter += from_utf8(theFormats().extensions(format));
2593 FileDialog::Result result =
2594 dlg.open(toqstr(initpath), fileFilters(toqstr(filter)));
2596 if (result.first == FileDialog::Later)
2599 filename = fromqstr(result.second);
2601 // check selected filename
2602 if (filename.empty())
2603 message(_("Canceled."));
2606 if (filename.empty())
2609 // get absolute path of file
2610 FileName const fullname(support::makeAbsPath(filename));
2612 // Can happen if the user entered a path into the dialog
2614 if (fullname.onlyFileName().empty()) {
2615 docstring msg = bformat(_("The file name '%1$s' is invalid!\n"
2616 "Aborting import."),
2617 from_utf8(fullname.absFileName()));
2618 frontend::Alert::error(_("File name error"), msg);
2619 message(_("Canceled."));
2624 FileName const lyxfile(support::changeExtension(fullname.absFileName(), ".lyx"));
2626 // Check if the document already is open
2627 Buffer * buf = theBufferList().getBuffer(lyxfile);
2630 if (!closeBuffer()) {
2631 message(_("Canceled."));
2636 docstring const displaypath = makeDisplayPath(lyxfile.absFileName(), 30);
2638 // if the file exists already, and we didn't do
2639 // -i lyx thefile.lyx, warn
2640 if (lyxfile.exists() && fullname != lyxfile) {
2642 docstring text = bformat(_("The document %1$s already exists.\n\n"
2643 "Do you want to overwrite that document?"), displaypath);
2644 int const ret = Alert::prompt(_("Overwrite document?"),
2645 text, 0, 1, _("&Overwrite"), _("&Cancel"));
2648 message(_("Canceled."));
2653 message(bformat(_("Importing %1$s..."), displaypath));
2654 ErrorList errorList;
2655 if (import(this, fullname, format, errorList))
2656 message(_("imported."));
2658 message(_("file not imported!"));
2660 // FIXME (Abdel 12/08/06): Is there a need to display the error list here?
2664 void GuiView::newDocument(string const & filename, string templatefile,
2667 FileName initpath(lyxrc.document_path);
2668 if (documentBufferView()) {
2669 FileName const trypath(documentBufferView()->buffer().filePath());
2670 // If directory is writeable, use this as default.
2671 if (trypath.isDirWritable())
2675 if (from_template) {
2676 if (templatefile.empty())
2677 templatefile = selectTemplateFile().absFileName();
2678 if (templatefile.empty())
2683 if (filename.empty())
2684 b = newUnnamedFile(initpath, to_utf8(_("newfile")), templatefile);
2686 b = newFile(filename, templatefile, true);
2691 // If no new document could be created, it is unsure
2692 // whether there is a valid BufferView.
2693 if (currentBufferView())
2694 // Ensure the cursor is correctly positioned on screen.
2695 currentBufferView()->showCursor();
2699 void GuiView::insertLyXFile(docstring const & fname, bool ignorelang)
2701 BufferView * bv = documentBufferView();
2706 FileName filename(to_utf8(fname));
2707 if (filename.empty()) {
2708 // Launch a file browser
2710 string initpath = lyxrc.document_path;
2711 string const trypath = bv->buffer().filePath();
2712 // If directory is writeable, use this as default.
2713 if (FileName(trypath).isDirWritable())
2717 FileDialog dlg(qt_("Select LyX document to insert"));
2718 dlg.setButton1(qt_("D&ocuments"), toqstr(lyxrc.document_path));
2719 dlg.setButton2(qt_("&Examples"), toqstr(lyxrc.example_path));
2721 FileDialog::Result result = dlg.open(toqstr(initpath),
2722 QStringList(qt_("LyX Documents (*.lyx)")));
2724 if (result.first == FileDialog::Later)
2728 filename.set(fromqstr(result.second));
2730 // check selected filename
2731 if (filename.empty()) {
2732 // emit message signal.
2733 message(_("Canceled."));
2738 bv->insertLyXFile(filename, ignorelang);
2739 bv->buffer().errors("Parse");
2743 string const GuiView::getTemplatesPath(Buffer & b)
2745 // We start off with the user's templates path
2746 string result = addPath(package().user_support().absFileName(), "templates");
2747 // Check for the document language
2748 string const langcode = b.params().language->code();
2749 string const shortcode = langcode.substr(0, 2);
2750 if (!langcode.empty() && shortcode != "en") {
2751 string subpath = addPath(result, shortcode);
2752 string subpath_long = addPath(result, langcode);
2753 // If we have a subdirectory for the language already,
2755 FileName sp = FileName(subpath);
2756 if (sp.isDirectory())
2758 else if (FileName(subpath_long).isDirectory())
2759 result = subpath_long;
2761 // Ask whether we should create such a subdirectory
2762 docstring const text =
2763 bformat(_("It is suggested to save the template in a subdirectory\n"
2764 "appropriate to the document language (%1$s).\n"
2765 "This subdirectory does not exists yet.\n"
2766 "Do you want to create it?"),
2767 _(b.params().language->display()));
2768 if (Alert::prompt(_("Create Language Directory?"),
2769 text, 0, 1, _("&Yes, Create"), _("&No, Save Template in Parent Directory")) == 0) {
2770 // If the user agreed, we try to create it and report if this failed.
2771 if (!sp.createDirectory(0777))
2772 Alert::error(_("Subdirectory creation failed!"),
2773 _("Could not create subdirectory.\n"
2774 "The template will be saved in the parent directory."));
2780 // Do we have a layout category?
2781 string const cat = b.params().baseClass() ?
2782 b.params().baseClass()->category()
2785 string subpath = addPath(result, cat);
2786 // If we have a subdirectory for the category already,
2788 FileName sp = FileName(subpath);
2789 if (sp.isDirectory())
2792 // Ask whether we should create such a subdirectory
2793 docstring const text =
2794 bformat(_("It is suggested to save the template in a subdirectory\n"
2795 "appropriate to the layout category (%1$s).\n"
2796 "This subdirectory does not exists yet.\n"
2797 "Do you want to create it?"),
2799 if (Alert::prompt(_("Create Category Directory?"),
2800 text, 0, 1, _("&Yes, Create"), _("&No, Save Template in Parent Directory")) == 0) {
2801 // If the user agreed, we try to create it and report if this failed.
2802 if (!sp.createDirectory(0777))
2803 Alert::error(_("Subdirectory creation failed!"),
2804 _("Could not create subdirectory.\n"
2805 "The template will be saved in the parent directory."));
2815 bool GuiView::renameBuffer(Buffer & b, docstring const & newname, RenameKind kind)
2817 FileName fname = b.fileName();
2818 FileName const oldname = fname;
2819 bool const as_template = (kind == LV_WRITE_AS_TEMPLATE);
2821 if (!newname.empty()) {
2824 fname = support::makeAbsPath(to_utf8(newname), getTemplatesPath(b));
2826 fname = support::makeAbsPath(to_utf8(newname),
2827 oldname.onlyPath().absFileName());
2829 // Switch to this Buffer.
2832 // No argument? Ask user through dialog.
2834 QString const title = as_template ? qt_("Choose a filename to save template as")
2835 : qt_("Choose a filename to save document as");
2836 FileDialog dlg(title);
2837 dlg.setButton1(qt_("D&ocuments"), toqstr(lyxrc.document_path));
2838 dlg.setButton2(qt_("&Templates"), toqstr(lyxrc.template_path));
2840 if (!isLyXFileName(fname.absFileName()))
2841 fname.changeExtension(".lyx");
2843 string const path = as_template ?
2845 : fname.onlyPath().absFileName();
2846 FileDialog::Result result =
2847 dlg.save(toqstr(path),
2848 QStringList(qt_("LyX Documents (*.lyx)")),
2849 toqstr(fname.onlyFileName()));
2851 if (result.first == FileDialog::Later)
2854 fname.set(fromqstr(result.second));
2859 if (!isLyXFileName(fname.absFileName()))
2860 fname.changeExtension(".lyx");
2863 // fname is now the new Buffer location.
2865 // if there is already a Buffer open with this name, we do not want
2866 // to have another one. (the second test makes sure we're not just
2867 // trying to overwrite ourselves, which is fine.)
2868 if (theBufferList().exists(fname) && fname != oldname
2869 && theBufferList().getBuffer(fname) != &b) {
2870 docstring const text =
2871 bformat(_("The file\n%1$s\nis already open in your current session.\n"
2872 "Please close it before attempting to overwrite it.\n"
2873 "Do you want to choose a new filename?"),
2874 from_utf8(fname.absFileName()));
2875 int const ret = Alert::prompt(_("Chosen File Already Open"),
2876 text, 0, 1, _("&Rename"), _("&Cancel"));
2878 case 0: return renameBuffer(b, docstring(), kind);
2879 case 1: return false;
2884 bool const existsLocal = fname.exists();
2885 bool const existsInVC = LyXVC::fileInVC(fname);
2886 if (existsLocal || existsInVC) {
2887 docstring const file = makeDisplayPath(fname.absFileName(), 30);
2888 if (kind != LV_WRITE_AS && existsInVC) {
2889 // renaming to a name that is already in VC
2891 docstring text = bformat(_("The document %1$s "
2892 "is already registered.\n\n"
2893 "Do you want to choose a new name?"),
2895 docstring const title = (kind == LV_VC_RENAME) ?
2896 _("Rename document?") : _("Copy document?");
2897 docstring const button = (kind == LV_VC_RENAME) ?
2898 _("&Rename") : _("&Copy");
2899 int const ret = Alert::prompt(title, text, 0, 1,
2900 button, _("&Cancel"));
2902 case 0: return renameBuffer(b, docstring(), kind);
2903 case 1: return false;
2908 docstring text = bformat(_("The document %1$s "
2909 "already exists.\n\n"
2910 "Do you want to overwrite that document?"),
2912 int const ret = Alert::prompt(_("Overwrite document?"),
2913 text, 0, 2, _("&Overwrite"),
2914 _("&Rename"), _("&Cancel"));
2917 case 1: return renameBuffer(b, docstring(), kind);
2918 case 2: return false;
2924 case LV_VC_RENAME: {
2925 string msg = b.lyxvc().rename(fname);
2928 message(from_utf8(msg));
2932 string msg = b.lyxvc().copy(fname);
2935 message(from_utf8(msg));
2939 case LV_WRITE_AS_TEMPLATE:
2942 // LyXVC created the file already in case of LV_VC_RENAME or
2943 // LV_VC_COPY, but call saveBuffer() nevertheless to get
2944 // relative paths of included stuff right if we moved e.g. from
2945 // /a/b.lyx to /a/c/b.lyx.
2947 bool const saved = saveBuffer(b, fname);
2954 bool GuiView::exportBufferAs(Buffer & b, docstring const & iformat)
2956 FileName fname = b.fileName();
2958 FileDialog dlg(qt_("Choose a filename to export the document as"));
2959 dlg.setButton1(qt_("D&ocuments"), toqstr(lyxrc.document_path));
2962 QString const anyformat = qt_("Guess from extension (*.*)");
2965 vector<Format const *> export_formats;
2966 for (Format const & f : theFormats())
2967 if (f.documentFormat())
2968 export_formats.push_back(&f);
2969 sort(export_formats.begin(), export_formats.end(), Format::formatSorter);
2970 map<QString, string> fmap;
2973 for (Format const * f : export_formats) {
2974 docstring const loc_prettyname = translateIfPossible(f->prettyname());
2975 QString const loc_filter = toqstr(bformat(from_ascii("%1$s (*.%2$s)"),
2977 from_ascii(f->extension())));
2978 types << loc_filter;
2979 fmap[loc_filter] = f->name();
2980 if (from_ascii(f->name()) == iformat) {
2981 filter = loc_filter;
2982 ext = f->extension();
2985 string ofname = fname.onlyFileName();
2987 ofname = support::changeExtension(ofname, ext);
2988 FileDialog::Result result =
2989 dlg.save(toqstr(fname.onlyPath().absFileName()),
2993 if (result.first != FileDialog::Chosen)
2997 fname.set(fromqstr(result.second));
2998 if (filter == anyformat)
2999 fmt_name = theFormats().getFormatFromExtension(fname.extension());
3001 fmt_name = fmap[filter];
3002 LYXERR(Debug::FILES, "filter=" << fromqstr(filter)
3003 << ", fmt_name=" << fmt_name << ", fname=" << fname.absFileName());
3005 if (fmt_name.empty() || fname.empty())
3008 // fname is now the new Buffer location.
3009 if (FileName(fname).exists()) {
3010 docstring const file = makeDisplayPath(fname.absFileName(), 30);
3011 docstring text = bformat(_("The document %1$s already "
3012 "exists.\n\nDo you want to "
3013 "overwrite that document?"),
3015 int const ret = Alert::prompt(_("Overwrite document?"),
3016 text, 0, 2, _("&Overwrite"), _("&Rename"), _("&Cancel"));
3019 case 1: return exportBufferAs(b, from_ascii(fmt_name));
3020 case 2: return false;
3024 FuncRequest cmd(LFUN_BUFFER_EXPORT, fmt_name + " " + fname.absFileName());
3027 return dr.dispatched();
3031 bool GuiView::saveBuffer(Buffer & b)
3033 return saveBuffer(b, FileName());
3037 bool GuiView::saveBuffer(Buffer & b, FileName const & fn)
3039 if (workArea(b) && workArea(b)->inDialogMode())
3042 if (fn.empty() && b.isUnnamed())
3043 return renameBuffer(b, docstring());
3045 bool const success = (fn.empty() ? b.save() : b.saveAs(fn));
3047 theSession().lastFiles().add(b.fileName());
3048 theSession().writeFile();
3052 // Switch to this Buffer.
3055 // FIXME: we don't tell the user *WHY* the save failed !!
3056 docstring const file = makeDisplayPath(b.absFileName(), 30);
3057 docstring text = bformat(_("The document %1$s could not be saved.\n\n"
3058 "Do you want to rename the document and "
3059 "try again?"), file);
3060 int const ret = Alert::prompt(_("Rename and save?"),
3061 text, 0, 2, _("&Rename"), _("&Retry"), _("&Cancel"));
3064 if (!renameBuffer(b, docstring()))
3073 return saveBuffer(b, fn);
3077 bool GuiView::hideWorkArea(GuiWorkArea * wa)
3079 return closeWorkArea(wa, false);
3083 // We only want to close the buffer if it is not visible in other workareas
3084 // of the same view, nor in other views, and if this is not a child
3085 bool GuiView::closeWorkArea(GuiWorkArea * wa)
3087 Buffer & buf = wa->bufferView().buffer();
3089 bool last_wa = d.countWorkAreasOf(buf) == 1
3090 && !inOtherView(buf) && !buf.parent();
3092 bool close_buffer = last_wa;
3095 if (lyxrc.close_buffer_with_last_view == "yes")
3097 else if (lyxrc.close_buffer_with_last_view == "no")
3098 close_buffer = false;
3101 if (buf.isUnnamed())
3102 file = from_utf8(buf.fileName().onlyFileName());
3104 file = buf.fileName().displayName(30);
3105 docstring const text = bformat(
3106 _("Last view on document %1$s is being closed.\n"
3107 "Would you like to close or hide the document?\n"
3109 "Hidden documents can be displayed back through\n"
3110 "the menu: View->Hidden->...\n"
3112 "To remove this question, set your preference in:\n"
3113 " Tools->Preferences->Look&Feel->UserInterface\n"
3115 int ret = Alert::prompt(_("Close or hide document?"),
3116 text, 0, 2, _("&Close"), _("&Hide"), _("&Cancel"));
3119 close_buffer = (ret == 0);
3123 return closeWorkArea(wa, close_buffer);
3127 bool GuiView::closeBuffer()
3129 GuiWorkArea * wa = currentMainWorkArea();
3130 // coverity complained about this
3131 // it seems unnecessary, but perhaps is worth the check
3132 LASSERT(wa, return false);
3134 setCurrentWorkArea(wa);
3135 Buffer & buf = wa->bufferView().buffer();
3136 return closeWorkArea(wa, !buf.parent());
3140 void GuiView::writeSession() const {
3141 GuiWorkArea const * active_wa = currentMainWorkArea();
3142 for (int i = 0; i < d.splitter_->count(); ++i) {
3143 TabWorkArea * twa = d.tabWorkArea(i);
3144 for (int j = 0; j < twa->count(); ++j) {
3145 GuiWorkArea * wa = twa->workArea(j);
3146 Buffer & buf = wa->bufferView().buffer();
3147 theSession().lastOpened().add(buf.fileName(), wa == active_wa);
3153 bool GuiView::closeBufferAll()
3156 for (auto & buf : theBufferList()) {
3157 if (!saveBufferIfNeeded(*buf, false)) {
3158 // Closing has been cancelled, so abort.
3163 // Close the workareas in all other views
3164 QList<int> const ids = guiApp->viewIds();
3165 for (int i = 0; i != ids.size(); ++i) {
3166 if (id_ != ids[i] && !guiApp->view(ids[i]).closeWorkAreaAll())
3170 // Close our own workareas
3171 if (!closeWorkAreaAll())
3178 bool GuiView::closeWorkAreaAll()
3180 setCurrentWorkArea(currentMainWorkArea());
3182 // We might be in a situation that there is still a tabWorkArea, but
3183 // there are no tabs anymore. This can happen when we get here after a
3184 // TabWorkArea::lastWorkAreaRemoved() signal. Therefore we count how
3185 // many TabWorkArea's have no documents anymore.
3188 // We have to call count() each time, because it can happen that
3189 // more than one splitter will disappear in one iteration (bug 5998).
3190 while (d.splitter_->count() > empty_twa) {
3191 TabWorkArea * twa = d.tabWorkArea(empty_twa);
3193 if (twa->count() == 0)
3196 setCurrentWorkArea(twa->currentWorkArea());
3197 if (!closeTabWorkArea(twa))
3205 bool GuiView::closeWorkArea(GuiWorkArea * wa, bool close_buffer)
3210 Buffer & buf = wa->bufferView().buffer();
3212 if (GuiViewPrivate::busyBuffers.contains(&buf)) {
3213 Alert::warning(_("Close document"),
3214 _("Document could not be closed because it is being processed by LyX."));
3219 return closeBuffer(buf);
3221 if (!inMultiTabs(wa))
3222 if (!saveBufferIfNeeded(buf, true))
3230 bool GuiView::closeBuffer(Buffer & buf)
3232 bool success = true;
3233 ListOfBuffers clist = buf.getChildren();
3234 ListOfBuffers::const_iterator it = clist.begin();
3235 ListOfBuffers::const_iterator const bend = clist.end();
3236 for (; it != bend; ++it) {
3237 Buffer * child_buf = *it;
3238 if (theBufferList().isOthersChild(&buf, child_buf)) {
3239 child_buf->setParent(nullptr);
3243 // FIXME: should we look in other tabworkareas?
3244 // ANSWER: I don't think so. I've tested, and if the child is
3245 // open in some other window, it closes without a problem.
3246 GuiWorkArea * child_wa = workArea(*child_buf);
3249 // If we are in a close_event all children will be closed in some time,
3250 // so no need to do it here. This will ensure that the children end up
3251 // in the session file in the correct order. If we close the master
3252 // buffer, we can close or release the child buffers here too.
3254 success = closeWorkArea(child_wa, true);
3258 // In this case the child buffer is open but hidden.
3259 // Even in this case, children can be dirty (e.g.,
3260 // after a label change in the master, see #11405).
3261 // Therefore, check this
3262 if (closing_ && (child_buf->isClean() || child_buf->paragraphs().empty())) {
3263 // If we are in a close_event all children will be closed in some time,
3264 // so no need to do it here. This will ensure that the children end up
3265 // in the session file in the correct order. If we close the master
3266 // buffer, we can close or release the child buffers here too.
3269 // Save dirty buffers also if closing_!
3270 if (saveBufferIfNeeded(*child_buf, false)) {
3271 child_buf->removeAutosaveFile();
3272 theBufferList().release(child_buf);
3274 // Saving of dirty children has been cancelled.
3275 // Cancel the whole process.
3282 // goto bookmark to update bookmark pit.
3283 // FIXME: we should update only the bookmarks related to this buffer!
3284 LYXERR(Debug::DEBUG, "GuiView::closeBuffer()");
3285 for (unsigned int i = 0; i < theSession().bookmarks().size(); ++i)
3286 guiApp->gotoBookmark(i + 1, false, false);
3288 if (saveBufferIfNeeded(buf, false)) {
3289 buf.removeAutosaveFile();
3290 theBufferList().release(&buf);
3294 // open all children again to avoid a crash because of dangling
3295 // pointers (bug 6603)
3301 bool GuiView::closeTabWorkArea(TabWorkArea * twa)
3303 while (twa == d.currentTabWorkArea()) {
3304 twa->setCurrentIndex(twa->count() - 1);
3306 GuiWorkArea * wa = twa->currentWorkArea();
3307 Buffer & b = wa->bufferView().buffer();
3309 // We only want to close the buffer if the same buffer is not visible
3310 // in another view, and if this is not a child and if we are closing
3311 // a view (not a tabgroup).
3312 bool const close_buffer =
3313 !inOtherView(b) && !b.parent() && closing_;
3315 if (!closeWorkArea(wa, close_buffer))
3322 bool GuiView::saveBufferIfNeeded(Buffer & buf, bool hiding)
3324 if (buf.isClean() || buf.paragraphs().empty())
3327 // Switch to this Buffer.
3333 if (buf.isUnnamed()) {
3334 file = from_utf8(buf.fileName().onlyFileName());
3337 FileName filename = buf.fileName();
3339 file = filename.displayName(30);
3340 exists = filename.exists();
3343 // Bring this window to top before asking questions.
3348 if (hiding && buf.isUnnamed()) {
3349 docstring const text = bformat(_("The document %1$s has not been "
3350 "saved yet.\n\nDo you want to save "
3351 "the document?"), file);
3352 ret = Alert::prompt(_("Save new document?"),
3353 text, 0, 1, _("&Save"), _("&Cancel"));
3357 docstring const text = exists ?
3358 bformat(_("The document %1$s has unsaved changes."
3359 "\n\nDo you want to save the document or "
3360 "discard the changes?"), file) :
3361 bformat(_("The document %1$s has not been saved yet."
3362 "\n\nDo you want to save the document or "
3363 "discard it entirely?"), file);
3364 docstring const title = exists ?
3365 _("Save changed document?") : _("Save document?");
3366 ret = Alert::prompt(title, text, 0, 2,
3367 _("&Save"), _("&Discard"), _("&Cancel"));
3372 if (!saveBuffer(buf))
3376 // If we crash after this we could have no autosave file
3377 // but I guess this is really improbable (Jug).
3378 // Sometimes improbable things happen:
3379 // - see bug http://www.lyx.org/trac/ticket/6587 (ps)
3380 // buf.removeAutosaveFile();
3382 // revert all changes
3393 bool GuiView::inMultiTabs(GuiWorkArea * wa)
3395 Buffer & buf = wa->bufferView().buffer();
3397 for (int i = 0; i != d.splitter_->count(); ++i) {
3398 GuiWorkArea * wa_ = d.tabWorkArea(i)->workArea(buf);
3399 if (wa_ && wa_ != wa)
3402 return inOtherView(buf);
3406 bool GuiView::inOtherView(Buffer & buf)
3408 QList<int> const ids = guiApp->viewIds();
3410 for (int i = 0; i != ids.size(); ++i) {
3414 if (guiApp->view(ids[i]).workArea(buf))
3421 void GuiView::gotoNextOrPreviousBuffer(NextOrPrevious np, bool const move)
3423 if (!documentBufferView())
3426 if (TabWorkArea * twa = d.currentTabWorkArea()) {
3427 Buffer * const curbuf = &documentBufferView()->buffer();
3428 int nwa = twa->count();
3429 for (int i = 0; i < nwa; ++i) {
3430 if (&workArea(i)->bufferView().buffer() == curbuf) {
3432 if (np == NEXTBUFFER)
3433 next_index = (i == nwa - 1 ? 0 : i + 1);
3435 next_index = (i == 0 ? nwa - 1 : i - 1);
3437 twa->moveTab(i, next_index);
3439 setBuffer(&workArea(next_index)->bufferView().buffer());
3447 /// make sure the document is saved
3448 static bool ensureBufferClean(Buffer * buffer)
3450 LASSERT(buffer, return false);
3451 if (buffer->isClean() && !buffer->isUnnamed())
3454 docstring const file = buffer->fileName().displayName(30);
3457 if (!buffer->isUnnamed()) {
3458 text = bformat(_("The document %1$s has unsaved "
3459 "changes.\n\nDo you want to save "
3460 "the document?"), file);
3461 title = _("Save changed document?");
3464 text = bformat(_("The document %1$s has not been "
3465 "saved yet.\n\nDo you want to save "
3466 "the document?"), file);
3467 title = _("Save new document?");
3469 int const ret = Alert::prompt(title, text, 0, 1, _("&Save"), _("&Cancel"));
3472 dispatch(FuncRequest(LFUN_BUFFER_WRITE));
3474 return buffer->isClean() && !buffer->isUnnamed();
3478 bool GuiView::reloadBuffer(Buffer & buf)
3480 currentBufferView()->cursor().reset();
3481 Buffer::ReadStatus status = buf.reload();
3482 return status == Buffer::ReadSuccess;
3486 void GuiView::checkExternallyModifiedBuffers()
3488 BufferList::iterator bit = theBufferList().begin();
3489 BufferList::iterator const bend = theBufferList().end();
3490 for (; bit != bend; ++bit) {
3491 Buffer * buf = *bit;
3492 if (buf->fileName().exists() && buf->isChecksumModified()) {
3493 docstring text = bformat(_("Document \n%1$s\n has been externally modified."
3494 " Reload now? Any local changes will be lost."),
3495 from_utf8(buf->absFileName()));
3496 int const ret = Alert::prompt(_("Reload externally changed document?"),
3497 text, 0, 1, _("&Reload"), _("&Cancel"));
3505 void GuiView::dispatchVC(FuncRequest const & cmd, DispatchResult & dr)
3507 Buffer * buffer = documentBufferView()
3508 ? &(documentBufferView()->buffer()) : nullptr;
3510 switch (cmd.action()) {
3511 case LFUN_VC_REGISTER:
3512 if (!buffer || !ensureBufferClean(buffer))
3514 if (!buffer->lyxvc().inUse()) {
3515 if (buffer->lyxvc().registrer()) {
3516 reloadBuffer(*buffer);
3517 dr.clearMessageUpdate();
3522 case LFUN_VC_RENAME:
3523 case LFUN_VC_COPY: {
3524 if (!buffer || !ensureBufferClean(buffer))
3526 if (buffer->lyxvc().inUse() && !buffer->hasReadonlyFlag()) {
3527 if (buffer->lyxvc().isCheckInWithConfirmation()) {
3528 // Some changes are not yet committed.
3529 // We test here and not in getStatus(), since
3530 // this test is expensive.
3532 LyXVC::CommandResult ret =
3533 buffer->lyxvc().checkIn(log);
3535 if (ret == LyXVC::ErrorCommand ||
3536 ret == LyXVC::VCSuccess)
3537 reloadBuffer(*buffer);
3538 if (buffer->lyxvc().isCheckInWithConfirmation()) {
3539 frontend::Alert::error(
3540 _("Revision control error."),
3541 _("Document could not be checked in."));
3545 RenameKind const kind = (cmd.action() == LFUN_VC_RENAME) ?
3546 LV_VC_RENAME : LV_VC_COPY;
3547 renameBuffer(*buffer, cmd.argument(), kind);
3552 case LFUN_VC_CHECK_IN:
3553 if (!buffer || !ensureBufferClean(buffer))
3555 if (buffer->lyxvc().inUse() && !buffer->hasReadonlyFlag()) {
3557 LyXVC::CommandResult ret = buffer->lyxvc().checkIn(log);
3559 // Only skip reloading if the checkin was cancelled or
3560 // an error occurred before the real checkin VCS command
3561 // was executed, since the VCS might have changed the
3562 // file even if it could not checkin successfully.
3563 if (ret == LyXVC::ErrorCommand || ret == LyXVC::VCSuccess)
3564 reloadBuffer(*buffer);
3568 case LFUN_VC_CHECK_OUT:
3569 if (!buffer || !ensureBufferClean(buffer))
3571 if (buffer->lyxvc().inUse()) {
3572 dr.setMessage(buffer->lyxvc().checkOut());
3573 reloadBuffer(*buffer);
3577 case LFUN_VC_LOCKING_TOGGLE:
3578 if (!buffer || !ensureBufferClean(buffer) || buffer->hasReadonlyFlag())
3580 if (buffer->lyxvc().inUse()) {
3581 string res = buffer->lyxvc().lockingToggle();
3583 frontend::Alert::error(_("Revision control error."),
3584 _("Error when setting the locking property."));
3587 reloadBuffer(*buffer);
3592 case LFUN_VC_REVERT:
3595 if (buffer->lyxvc().revert()) {
3596 reloadBuffer(*buffer);
3597 dr.clearMessageUpdate();
3601 case LFUN_VC_UNDO_LAST:
3604 buffer->lyxvc().undoLast();
3605 reloadBuffer(*buffer);
3606 dr.clearMessageUpdate();
3609 case LFUN_VC_REPO_UPDATE:
3612 if (ensureBufferClean(buffer)) {
3613 dr.setMessage(buffer->lyxvc().repoUpdate());
3614 checkExternallyModifiedBuffers();
3618 case LFUN_VC_COMMAND: {
3619 string flag = cmd.getArg(0);
3620 if (buffer && contains(flag, 'R') && !ensureBufferClean(buffer))
3623 if (contains(flag, 'M')) {
3624 if (!Alert::askForText(message, _("LyX VC: Log Message")))
3627 string path = cmd.getArg(1);
3628 if (contains(path, "$$p") && buffer)
3629 path = subst(path, "$$p", buffer->filePath());
3630 LYXERR(Debug::LYXVC, "Directory: " << path);
3632 if (!pp.isReadableDirectory()) {
3633 lyxerr << _("Directory is not accessible.") << endl;
3636 support::PathChanger p(pp);
3638 string command = cmd.getArg(2);
3639 if (command.empty())
3642 command = subst(command, "$$i", buffer->absFileName());
3643 command = subst(command, "$$p", buffer->filePath());
3645 command = subst(command, "$$m", to_utf8(message));
3646 LYXERR(Debug::LYXVC, "Command: " << command);
3648 one.startscript(Systemcall::Wait, command);
3652 if (contains(flag, 'I'))
3653 buffer->markDirty();
3654 if (contains(flag, 'R'))
3655 reloadBuffer(*buffer);
3660 case LFUN_VC_COMPARE: {
3661 if (cmd.argument().empty()) {
3662 lyx::dispatch(FuncRequest(LFUN_DIALOG_SHOW, "comparehistory"));
3668 string rev1 = cmd.getArg(0);
3672 if (!buffer->lyxvc().prepareFileRevision(rev1, f1))
3675 if (isStrInt(rev1) && convert<int>(rev1) <= 0) {
3676 f2 = buffer->absFileName();
3678 string rev2 = cmd.getArg(1);
3682 if (!buffer->lyxvc().prepareFileRevision(rev2, f2))
3686 LYXERR(Debug::LYXVC, "Launching comparison for fetched revisions:\n" <<
3687 f1 << "\n" << f2 << "\n" );
3688 string par = "compare run " + quoteName(f1) + " " + quoteName(f2);
3689 lyx::dispatch(FuncRequest(LFUN_DIALOG_SHOW, par));
3699 void GuiView::openChildDocument(string const & fname)
3701 LASSERT(documentBufferView(), return);
3702 Buffer & buffer = documentBufferView()->buffer();
3703 FileName const filename = support::makeAbsPath(fname, buffer.filePath());
3704 documentBufferView()->saveBookmark(false);
3705 Buffer * child = nullptr;
3706 if (theBufferList().exists(filename)) {
3707 child = theBufferList().getBuffer(filename);
3710 message(bformat(_("Opening child document %1$s..."),
3711 makeDisplayPath(filename.absFileName())));
3712 child = loadDocument(filename, false);
3714 // Set the parent name of the child document.
3715 // This makes insertion of citations and references in the child work,
3716 // when the target is in the parent or another child document.
3718 child->setParent(&buffer);
3722 bool GuiView::goToFileRow(string const & argument)
3726 size_t i = argument.find_last_of(' ');
3727 if (i != string::npos) {
3728 file_name = os::internal_path(FileName(trim(argument.substr(0, i))).realPath());
3729 istringstream is(argument.substr(i + 1));
3734 if (i == string::npos) {
3735 LYXERR0("Wrong argument: " << argument);
3738 Buffer * buf = nullptr;
3739 string const realtmp = package().temp_dir().realPath();
3740 // We have to use os::path_prefix_is() here, instead of
3741 // simply prefixIs(), because the file name comes from
3742 // an external application and may need case adjustment.
3743 if (os::path_prefix_is(file_name, realtmp, os::CASE_ADJUSTED)) {
3744 buf = theBufferList().getBufferFromTmp(file_name, true);
3745 LYXERR(Debug::FILES, "goToFileRow: buffer lookup for " << file_name
3746 << (buf ? " success" : " failed"));
3748 // Must replace extension of the file to be .lyx
3749 // and get full path
3750 FileName const s = fileSearch(string(),
3751 support::changeExtension(file_name, ".lyx"), "lyx");
3752 // Either change buffer or load the file
3753 if (theBufferList().exists(s))
3754 buf = theBufferList().getBuffer(s);
3755 else if (s.exists()) {
3756 buf = loadDocument(s);
3761 _("File does not exist: %1$s"),
3762 makeDisplayPath(file_name)));
3768 _("No buffer for file: %1$s."),
3769 makeDisplayPath(file_name))
3774 bool success = documentBufferView()->setCursorFromRow(row);
3776 LYXERR(Debug::LATEX,
3777 "setCursorFromRow: invalid position for row " << row);
3778 frontend::Alert::error(_("Inverse Search Failed"),
3779 _("Invalid position requested by inverse search.\n"
3780 "You may need to update the viewed document."));
3786 void GuiView::toolBarPopup(const QPoint & /*pos*/)
3788 QMenu * menu = guiApp->menus().menu(toqstr("context-toolbars"), * this);
3789 menu->exec(QCursor::pos());
3794 Buffer::ExportStatus GuiView::GuiViewPrivate::runAndDestroy(const T& func,
3795 Buffer const * orig, Buffer * clone, string const & format)
3797 Buffer::ExportStatus const status = func(format);
3799 // the cloning operation will have produced a clone of the entire set of
3800 // documents, starting from the master. so we must delete those.
3801 Buffer * mbuf = const_cast<Buffer *>(clone->masterBuffer());
3803 busyBuffers.remove(orig);
3808 Buffer::ExportStatus GuiView::GuiViewPrivate::compileAndDestroy(
3809 Buffer const * orig, Buffer * clone, string const & format)
3811 Buffer::ExportStatus (Buffer::* mem_func)(std::string const &, bool) const =
3813 return runAndDestroy(lyx::bind(mem_func, clone, _1, true), orig, clone, format);
3817 Buffer::ExportStatus GuiView::GuiViewPrivate::exportAndDestroy(
3818 Buffer const * orig, Buffer * clone, string const & format)
3820 Buffer::ExportStatus (Buffer::* mem_func)(std::string const &, bool) const =
3822 return runAndDestroy(lyx::bind(mem_func, clone, _1, false), orig, clone, format);
3826 Buffer::ExportStatus GuiView::GuiViewPrivate::previewAndDestroy(
3827 Buffer const * orig, Buffer * clone, string const & format)
3829 Buffer::ExportStatus (Buffer::* mem_func)(std::string const &) const =
3831 return runAndDestroy(lyx::bind(mem_func, clone, _1), orig, clone, format);
3835 bool GuiView::GuiViewPrivate::asyncBufferProcessing(
3836 string const & argument,
3837 Buffer const * used_buffer,
3838 docstring const & msg,
3839 Buffer::ExportStatus (*asyncFunc)(Buffer const *, Buffer *, string const &),
3840 Buffer::ExportStatus (Buffer::*syncFunc)(string const &, bool) const,
3841 Buffer::ExportStatus (Buffer::*previewFunc)(string const &) const,
3847 string format = argument;
3849 format = used_buffer->params().getDefaultOutputFormat();
3850 processing_format = format;
3852 progress_->clearMessages();
3855 #if EXPORT_in_THREAD
3857 GuiViewPrivate::busyBuffers.insert(used_buffer);
3858 Buffer * cloned_buffer = used_buffer->cloneWithChildren();
3859 if (!cloned_buffer) {
3860 Alert::error(_("Export Error"),
3861 _("Error cloning the Buffer."));
3864 QFuture<Buffer::ExportStatus> f = QtConcurrent::run(
3869 setPreviewFuture(f);
3870 last_export_format = used_buffer->params().bufferFormat();
3873 // We are asynchronous, so we don't know here anything about the success
3876 Buffer::ExportStatus status;
3878 status = (used_buffer->*syncFunc)(format, false);
3879 } else if (previewFunc) {
3880 status = (used_buffer->*previewFunc)(format);
3883 handleExportStatus(gv_, status, format);
3885 return (status == Buffer::ExportSuccess
3886 || status == Buffer::PreviewSuccess);
3890 Buffer::ExportStatus status;
3892 status = (used_buffer->*syncFunc)(format, true);
3893 } else if (previewFunc) {
3894 status = (used_buffer->*previewFunc)(format);
3897 handleExportStatus(gv_, status, format);
3899 return (status == Buffer::ExportSuccess
3900 || status == Buffer::PreviewSuccess);
3904 void GuiView::dispatchToBufferView(FuncRequest const & cmd, DispatchResult & dr)
3906 BufferView * bv = currentBufferView();
3907 LASSERT(bv, return);
3909 // Let the current BufferView dispatch its own actions.
3910 bv->dispatch(cmd, dr);
3911 if (dr.dispatched()) {
3912 if (cmd.action() == LFUN_REDO || cmd.action() == LFUN_UNDO)
3913 updateDialog("document", "");
3917 // Try with the document BufferView dispatch if any.
3918 BufferView * doc_bv = documentBufferView();
3919 if (doc_bv && doc_bv != bv) {
3920 doc_bv->dispatch(cmd, dr);
3921 if (dr.dispatched()) {
3922 if (cmd.action() == LFUN_REDO || cmd.action() == LFUN_UNDO)
3923 updateDialog("document", "");
3928 // Then let the current Cursor dispatch its own actions.
3929 bv->cursor().dispatch(cmd);
3931 // update completion. We do it here and not in
3932 // processKeySym to avoid another redraw just for a
3933 // changed inline completion
3934 if (cmd.origin() == FuncRequest::KEYBOARD) {
3935 if (cmd.action() == LFUN_SELF_INSERT
3936 || (cmd.action() == LFUN_ERT_INSERT && bv->cursor().inMathed()))
3937 updateCompletion(bv->cursor(), true, true);
3938 else if (cmd.action() == LFUN_CHAR_DELETE_BACKWARD)
3939 updateCompletion(bv->cursor(), false, true);
3941 updateCompletion(bv->cursor(), false, false);
3944 dr = bv->cursor().result();
3948 void GuiView::dispatch(FuncRequest const & cmd, DispatchResult & dr)
3950 BufferView * bv = currentBufferView();
3951 // By default we won't need any update.
3952 dr.screenUpdate(Update::None);
3953 // assume cmd will be dispatched
3954 dr.dispatched(true);
3956 Buffer * doc_buffer = documentBufferView()
3957 ? &(documentBufferView()->buffer()) : nullptr;
3959 if (cmd.origin() == FuncRequest::TOC) {
3960 GuiToc * toc = static_cast<GuiToc*>(findOrBuild("toc", false));
3961 toc->doDispatch(bv->cursor(), cmd, dr);
3965 string const argument = to_utf8(cmd.argument());
3967 switch(cmd.action()) {
3968 case LFUN_BUFFER_CHILD_OPEN:
3969 openChildDocument(to_utf8(cmd.argument()));
3972 case LFUN_BUFFER_IMPORT:
3973 importDocument(to_utf8(cmd.argument()));
3976 case LFUN_MASTER_BUFFER_EXPORT:
3978 doc_buffer = const_cast<Buffer *>(doc_buffer->masterBuffer());
3980 case LFUN_BUFFER_EXPORT: {
3983 // GCC only sees strfwd.h when building merged
3984 if (::lyx::operator==(cmd.argument(), "custom")) {
3985 // LFUN_MASTER_BUFFER_EXPORT is not enabled for this case,
3986 // so the following test should not be needed.
3987 // In principle, we could try to switch to such a view...
3988 // if (cmd.action() == LFUN_BUFFER_EXPORT)
3989 dispatch(FuncRequest(LFUN_DIALOG_SHOW, "sendto"), dr);
3993 string const dest = cmd.getArg(1);
3994 FileName target_dir;
3995 if (!dest.empty() && FileName::isAbsolute(dest))
3996 target_dir = FileName(support::onlyPath(dest));
3998 target_dir = doc_buffer->fileName().onlyPath();
4000 string const format = (argument.empty() || argument == "default") ?
4001 doc_buffer->params().getDefaultOutputFormat() : argument;
4003 if ((dest.empty() && doc_buffer->isUnnamed())
4004 || !target_dir.isDirWritable()) {
4005 exportBufferAs(*doc_buffer, from_utf8(format));
4008 /* TODO/Review: Is it a problem to also export the children?
4009 See the update_unincluded flag */
4010 d.asyncBufferProcessing(format,
4013 &GuiViewPrivate::exportAndDestroy,
4015 nullptr, cmd.allowAsync());
4016 // TODO Inform user about success
4020 case LFUN_BUFFER_EXPORT_AS: {
4021 LASSERT(doc_buffer, break);
4022 docstring f = cmd.argument();
4024 f = from_ascii(doc_buffer->params().getDefaultOutputFormat());
4025 exportBufferAs(*doc_buffer, f);
4029 case LFUN_BUFFER_UPDATE: {
4030 d.asyncBufferProcessing(argument,
4033 &GuiViewPrivate::compileAndDestroy,
4035 nullptr, cmd.allowAsync());
4038 case LFUN_BUFFER_VIEW: {
4039 d.asyncBufferProcessing(argument,
4041 _("Previewing ..."),
4042 &GuiViewPrivate::previewAndDestroy,
4044 &Buffer::preview, cmd.allowAsync());
4047 case LFUN_MASTER_BUFFER_UPDATE: {
4048 d.asyncBufferProcessing(argument,
4049 (doc_buffer ? doc_buffer->masterBuffer() : nullptr),
4051 &GuiViewPrivate::compileAndDestroy,
4053 nullptr, cmd.allowAsync());
4056 case LFUN_MASTER_BUFFER_VIEW: {
4057 d.asyncBufferProcessing(argument,
4058 (doc_buffer ? doc_buffer->masterBuffer() : nullptr),
4060 &GuiViewPrivate::previewAndDestroy,
4061 nullptr, &Buffer::preview, cmd.allowAsync());
4064 case LFUN_EXPORT_CANCEL: {
4065 Systemcall::killscript();
4068 case LFUN_BUFFER_SWITCH: {
4069 string const file_name = to_utf8(cmd.argument());
4070 if (!FileName::isAbsolute(file_name)) {
4072 dr.setMessage(_("Absolute filename expected."));
4076 Buffer * buffer = theBufferList().getBuffer(FileName(file_name));
4079 dr.setMessage(_("Document not loaded"));
4083 // Do we open or switch to the buffer in this view ?
4084 if (workArea(*buffer)
4085 || lyxrc.open_buffers_in_tabs || !documentBufferView()) {
4090 // Look for the buffer in other views
4091 QList<int> const ids = guiApp->viewIds();
4093 for (; i != ids.size(); ++i) {
4094 GuiView & gv = guiApp->view(ids[i]);
4095 if (gv.workArea(*buffer)) {
4097 gv.activateWindow();
4099 gv.setBuffer(buffer);
4104 // If necessary, open a new window as a last resort
4105 if (i == ids.size()) {
4106 lyx::dispatch(FuncRequest(LFUN_WINDOW_NEW));
4112 case LFUN_BUFFER_NEXT:
4113 gotoNextOrPreviousBuffer(NEXTBUFFER, false);
4116 case LFUN_BUFFER_MOVE_NEXT:
4117 gotoNextOrPreviousBuffer(NEXTBUFFER, true);
4120 case LFUN_BUFFER_PREVIOUS:
4121 gotoNextOrPreviousBuffer(PREVBUFFER, false);
4124 case LFUN_BUFFER_MOVE_PREVIOUS:
4125 gotoNextOrPreviousBuffer(PREVBUFFER, true);
4128 case LFUN_BUFFER_CHKTEX:
4129 LASSERT(doc_buffer, break);
4130 doc_buffer->runChktex();
4133 case LFUN_COMMAND_EXECUTE: {
4134 command_execute_ = true;
4135 minibuffer_focus_ = true;
4138 case LFUN_DROP_LAYOUTS_CHOICE:
4139 d.layout_->showPopup();
4142 case LFUN_MENU_OPEN:
4143 if (QMenu * menu = guiApp->menus().menu(toqstr(cmd.argument()), *this))
4144 menu->exec(QCursor::pos());
4147 case LFUN_FILE_INSERT: {
4148 if (cmd.getArg(1) == "ignorelang")
4149 insertLyXFile(from_utf8(cmd.getArg(0)), true);
4151 insertLyXFile(cmd.argument());
4155 case LFUN_FILE_INSERT_PLAINTEXT:
4156 case LFUN_FILE_INSERT_PLAINTEXT_PARA: {
4157 string const fname = to_utf8(cmd.argument());
4158 if (!fname.empty() && !FileName::isAbsolute(fname)) {
4159 dr.setMessage(_("Absolute filename expected."));
4163 FileName filename(fname);
4164 if (fname.empty()) {
4165 FileDialog dlg(qt_("Select file to insert"));
4167 FileDialog::Result result = dlg.open(toqstr(bv->buffer().filePath()),
4168 QStringList(qt_("All Files (*)")));
4170 if (result.first == FileDialog::Later || result.second.isEmpty()) {
4171 dr.setMessage(_("Canceled."));
4175 filename.set(fromqstr(result.second));
4179 FuncRequest const new_cmd(cmd, filename.absoluteFilePath());
4180 bv->dispatch(new_cmd, dr);
4185 case LFUN_BUFFER_RELOAD: {
4186 LASSERT(doc_buffer, break);
4189 bool drop = (cmd.argument() == "dump");
4192 if (!drop && !doc_buffer->isClean()) {
4193 docstring const file =
4194 makeDisplayPath(doc_buffer->absFileName(), 20);
4195 if (doc_buffer->notifiesExternalModification()) {
4196 docstring text = _("The current version will be lost. "
4197 "Are you sure you want to load the version on disk "
4198 "of the document %1$s?");
4199 ret = Alert::prompt(_("Reload saved document?"),
4200 bformat(text, file), 1, 1,
4201 _("&Reload"), _("&Cancel"));
4203 docstring text = _("Any changes will be lost. "
4204 "Are you sure you want to revert to the saved version "
4205 "of the document %1$s?");
4206 ret = Alert::prompt(_("Revert to saved document?"),
4207 bformat(text, file), 1, 1,
4208 _("&Revert"), _("&Cancel"));
4213 doc_buffer->markClean();
4214 reloadBuffer(*doc_buffer);
4215 dr.forceBufferUpdate();
4220 case LFUN_BUFFER_RESET_EXPORT:
4221 LASSERT(doc_buffer, break);
4222 doc_buffer->requireFreshStart(true);
4223 dr.setMessage(_("Buffer export reset."));
4226 case LFUN_BUFFER_WRITE:
4227 LASSERT(doc_buffer, break);
4228 saveBuffer(*doc_buffer);
4231 case LFUN_BUFFER_WRITE_AS:
4232 LASSERT(doc_buffer, break);
4233 renameBuffer(*doc_buffer, cmd.argument());
4236 case LFUN_BUFFER_WRITE_AS_TEMPLATE:
4237 LASSERT(doc_buffer, break);
4238 renameBuffer(*doc_buffer, cmd.argument(),
4239 LV_WRITE_AS_TEMPLATE);
4242 case LFUN_BUFFER_WRITE_ALL: {
4243 Buffer * first = theBufferList().first();
4246 message(_("Saving all documents..."));
4247 // We cannot use a for loop as the buffer list cycles.
4250 if (!b->isClean()) {
4252 LYXERR(Debug::ACTION, "Saved " << b->absFileName());
4254 b = theBufferList().next(b);
4255 } while (b != first);
4256 dr.setMessage(_("All documents saved."));
4260 case LFUN_MASTER_BUFFER_FORALL: {
4264 FuncRequest funcToRun = lyxaction.lookupFunc(cmd.getLongArg(0));
4265 funcToRun.allowAsync(false);
4267 for (Buffer const * buf : doc_buffer->allRelatives()) {
4268 // Switch to other buffer view and resend cmd
4269 lyx::dispatch(FuncRequest(
4270 LFUN_BUFFER_SWITCH, buf->absFileName()));
4271 lyx::dispatch(funcToRun);
4274 lyx::dispatch(FuncRequest(
4275 LFUN_BUFFER_SWITCH, doc_buffer->absFileName()));
4279 case LFUN_BUFFER_EXTERNAL_MODIFICATION_CLEAR:
4280 LASSERT(doc_buffer, break);
4281 doc_buffer->clearExternalModification();
4284 case LFUN_BUFFER_CLOSE:
4288 case LFUN_BUFFER_CLOSE_ALL:
4292 case LFUN_DEVEL_MODE_TOGGLE:
4293 devel_mode_ = !devel_mode_;
4295 dr.setMessage(_("Developer mode is now enabled."));
4297 dr.setMessage(_("Developer mode is now disabled."));
4300 case LFUN_TOOLBAR_TOGGLE: {
4301 string const name = cmd.getArg(0);
4302 if (GuiToolbar * t = toolbar(name))
4307 case LFUN_TOOLBAR_MOVABLE: {
4308 string const name = cmd.getArg(0);
4310 // toggle (all) toolbars movablility
4311 toolbarsMovable_ = !toolbarsMovable_;
4312 for (ToolbarInfo const & ti : guiApp->toolbars()) {
4313 GuiToolbar * tb = toolbar(ti.name);
4314 if (tb && tb->isMovable() != toolbarsMovable_)
4315 // toggle toolbar movablity if it does not fit lock
4316 // (all) toolbars positions state silent = true, since
4317 // status bar notifications are slow
4320 if (toolbarsMovable_)
4321 dr.setMessage(_("Toolbars unlocked."));
4323 dr.setMessage(_("Toolbars locked."));
4324 } else if (GuiToolbar * t = toolbar(name)) {
4325 // toggle current toolbar movablity
4327 // update lock (all) toolbars positions
4328 updateLockToolbars();
4333 case LFUN_ICON_SIZE: {
4334 QSize size = d.iconSize(cmd.argument());
4336 dr.setMessage(bformat(_("Icon size set to %1$dx%2$d."),
4337 size.width(), size.height()));
4341 case LFUN_DIALOG_UPDATE: {
4342 string const name = to_utf8(cmd.argument());
4343 if (name == "prefs" || name == "document")
4344 updateDialog(name, string());
4345 else if (name == "paragraph")
4346 lyx::dispatch(FuncRequest(LFUN_PARAGRAPH_UPDATE));
4347 else if (currentBufferView()) {
4348 Inset * inset = currentBufferView()->editedInset(name);
4349 // Can only update a dialog connected to an existing inset
4351 // FIXME: get rid of this indirection; GuiView ask the inset
4352 // if he is kind enough to update itself...
4353 FuncRequest fr(LFUN_INSET_DIALOG_UPDATE, cmd.argument());
4354 //FIXME: pass DispatchResult here?
4355 inset->dispatch(currentBufferView()->cursor(), fr);
4361 case LFUN_DIALOG_TOGGLE: {
4362 FuncCode const func_code = isDialogVisible(cmd.getArg(0))
4363 ? LFUN_DIALOG_HIDE : LFUN_DIALOG_SHOW;
4364 dispatch(FuncRequest(func_code, cmd.argument()), dr);
4368 case LFUN_DIALOG_DISCONNECT_INSET:
4369 disconnectDialog(to_utf8(cmd.argument()));
4372 case LFUN_DIALOG_HIDE: {
4373 guiApp->hideDialogs(to_utf8(cmd.argument()), nullptr);
4377 case LFUN_DIALOG_SHOW: {
4378 string const name = cmd.getArg(0);
4379 string sdata = trim(to_utf8(cmd.argument()).substr(name.size()));
4381 if (name == "latexlog") {
4382 // getStatus checks that
4383 LASSERT(doc_buffer, break);
4384 Buffer::LogType type;
4385 string const logfile = doc_buffer->logName(&type);
4387 case Buffer::latexlog:
4390 case Buffer::buildlog:
4391 sdata = "literate ";
4394 sdata += Lexer::quoteString(logfile);
4395 showDialog("log", sdata);
4396 } else if (name == "vclog") {
4397 // getStatus checks that
4398 LASSERT(doc_buffer, break);
4399 string const sdata2 = "vc " +
4400 Lexer::quoteString(doc_buffer->lyxvc().getLogFile());
4401 showDialog("log", sdata2);
4402 } else if (name == "symbols") {
4403 sdata = bv->cursor().getEncoding()->name();
4405 showDialog("symbols", sdata);
4407 } else if (name == "prefs" && isFullScreen()) {
4408 lfunUiToggle("fullscreen");
4409 showDialog("prefs", sdata);
4411 showDialog(name, sdata);
4416 dr.setMessage(cmd.argument());
4419 case LFUN_UI_TOGGLE: {
4420 string arg = cmd.getArg(0);
4421 if (!lfunUiToggle(arg)) {
4422 docstring const msg = "ui-toggle " + _("%1$s unknown command!");
4423 dr.setMessage(bformat(msg, from_utf8(arg)));
4425 // Make sure the keyboard focus stays in the work area.
4430 case LFUN_VIEW_SPLIT: {
4431 LASSERT(doc_buffer, break);
4432 string const orientation = cmd.getArg(0);
4433 d.splitter_->setOrientation(orientation == "vertical"
4434 ? Qt::Vertical : Qt::Horizontal);
4435 TabWorkArea * twa = addTabWorkArea();
4436 GuiWorkArea * wa = twa->addWorkArea(*doc_buffer, *this);
4437 setCurrentWorkArea(wa);
4440 case LFUN_TAB_GROUP_CLOSE:
4441 if (TabWorkArea * twa = d.currentTabWorkArea()) {
4442 closeTabWorkArea(twa);
4443 d.current_work_area_ = nullptr;
4444 twa = d.currentTabWorkArea();
4445 // Switch to the next GuiWorkArea in the found TabWorkArea.
4447 // Make sure the work area is up to date.
4448 setCurrentWorkArea(twa->currentWorkArea());
4450 setCurrentWorkArea(nullptr);
4455 case LFUN_VIEW_CLOSE:
4456 if (TabWorkArea * twa = d.currentTabWorkArea()) {
4457 closeWorkArea(twa->currentWorkArea());
4458 d.current_work_area_ = nullptr;
4459 twa = d.currentTabWorkArea();
4460 // Switch to the next GuiWorkArea in the found TabWorkArea.
4462 // Make sure the work area is up to date.
4463 setCurrentWorkArea(twa->currentWorkArea());
4465 setCurrentWorkArea(nullptr);
4470 case LFUN_COMPLETION_INLINE:
4471 if (d.current_work_area_)
4472 d.current_work_area_->completer().showInline();
4475 case LFUN_COMPLETION_POPUP:
4476 if (d.current_work_area_)
4477 d.current_work_area_->completer().showPopup();
4482 if (d.current_work_area_)
4483 d.current_work_area_->completer().tab();
4486 case LFUN_COMPLETION_CANCEL:
4487 if (d.current_work_area_) {
4488 if (d.current_work_area_->completer().popupVisible())
4489 d.current_work_area_->completer().hidePopup();
4491 d.current_work_area_->completer().hideInline();
4495 case LFUN_COMPLETION_ACCEPT:
4496 if (d.current_work_area_)
4497 d.current_work_area_->completer().activate();
4500 case LFUN_BUFFER_ZOOM_IN:
4501 case LFUN_BUFFER_ZOOM_OUT:
4502 case LFUN_BUFFER_ZOOM: {
4503 if (cmd.argument().empty()) {
4504 if (cmd.action() == LFUN_BUFFER_ZOOM)
4506 else if (cmd.action() == LFUN_BUFFER_ZOOM_IN)
4511 if (cmd.action() == LFUN_BUFFER_ZOOM)
4512 zoom_ratio_ = convert<int>(cmd.argument()) / double(lyxrc.defaultZoom);
4513 else if (cmd.action() == LFUN_BUFFER_ZOOM_IN)
4514 zoom_ratio_ += convert<int>(cmd.argument()) / 100.0;
4516 zoom_ratio_ -= convert<int>(cmd.argument()) / 100.0;
4519 // Actual zoom value: default zoom + fractional extra value
4520 int zoom = (int)(lyxrc.defaultZoom * zoom_ratio_);
4521 if (zoom < static_cast<int>(zoom_min_))
4524 lyxrc.currentZoom = zoom;
4526 dr.setMessage(bformat(_("Zoom level is now %1$d% (default value: %2$d%)"),
4527 lyxrc.currentZoom, lyxrc.defaultZoom));
4529 guiApp->fontLoader().update();
4530 dr.screenUpdate(Update::Force | Update::FitCursor);
4534 case LFUN_VC_REGISTER:
4535 case LFUN_VC_RENAME:
4537 case LFUN_VC_CHECK_IN:
4538 case LFUN_VC_CHECK_OUT:
4539 case LFUN_VC_REPO_UPDATE:
4540 case LFUN_VC_LOCKING_TOGGLE:
4541 case LFUN_VC_REVERT:
4542 case LFUN_VC_UNDO_LAST:
4543 case LFUN_VC_COMMAND:
4544 case LFUN_VC_COMPARE:
4545 dispatchVC(cmd, dr);
4548 case LFUN_SERVER_GOTO_FILE_ROW:
4549 if(goToFileRow(to_utf8(cmd.argument())))
4550 dr.screenUpdate(Update::Force | Update::FitCursor);
4553 case LFUN_LYX_ACTIVATE:
4557 case LFUN_WINDOW_RAISE:
4563 case LFUN_FORWARD_SEARCH: {
4564 // it seems safe to assume we have a document buffer, since
4565 // getStatus wants one.
4566 LASSERT(doc_buffer, break);
4567 Buffer const * doc_master = doc_buffer->masterBuffer();
4568 FileName const path(doc_master->temppath());
4569 string const texname = doc_master->isChild(doc_buffer)
4570 ? DocFileName(changeExtension(
4571 doc_buffer->absFileName(),
4572 "tex")).mangledFileName()
4573 : doc_buffer->latexName();
4574 string const fulltexname =
4575 support::makeAbsPath(texname, doc_master->temppath()).absFileName();
4576 string const mastername =
4577 removeExtension(doc_master->latexName());
4578 FileName const dviname(addName(path.absFileName(),
4579 addExtension(mastername, "dvi")));
4580 FileName const pdfname(addName(path.absFileName(),
4581 addExtension(mastername, "pdf")));
4582 bool const have_dvi = dviname.exists();
4583 bool const have_pdf = pdfname.exists();
4584 if (!have_dvi && !have_pdf) {
4585 dr.setMessage(_("Please, preview the document first."));
4588 string outname = dviname.onlyFileName();
4589 string command = lyxrc.forward_search_dvi;
4590 if (!have_dvi || (have_pdf &&
4591 pdfname.lastModified() > dviname.lastModified())) {
4592 outname = pdfname.onlyFileName();
4593 command = lyxrc.forward_search_pdf;
4596 DocIterator cur = bv->cursor();
4597 int row = doc_buffer->texrow().rowFromDocIterator(cur).first;
4598 LYXERR(Debug::ACTION, "Forward search: row:" << row
4600 if (row == -1 || command.empty()) {
4601 dr.setMessage(_("Couldn't proceed."));
4604 string texrow = convert<string>(row);
4606 command = subst(command, "$$n", texrow);
4607 command = subst(command, "$$f", fulltexname);
4608 command = subst(command, "$$t", texname);
4609 command = subst(command, "$$o", outname);
4611 volatile PathChanger p(path);
4613 one.startscript(Systemcall::DontWait, command);
4617 case LFUN_SPELLING_CONTINUOUSLY:
4618 lyxrc.spellcheck_continuously = !lyxrc.spellcheck_continuously;
4619 dr.screenUpdate(Update::Force);
4623 // The LFUN must be for one of BufferView, Buffer or Cursor;
4625 dispatchToBufferView(cmd, dr);
4629 // Part of automatic menu appearance feature.
4630 if (isFullScreen()) {
4631 if (menuBar()->isVisible() && lyxrc.full_screen_menubar)
4635 // Need to update bv because many LFUNs here might have destroyed it
4636 bv = currentBufferView();
4638 // Clear non-empty selections
4639 // (e.g. from a "char-forward-select" followed by "char-backward-select")
4641 Cursor & cur = bv->cursor();
4642 if ((cur.selection() && cur.selBegin() == cur.selEnd())) {
4643 cur.clearSelection();
4649 bool GuiView::lfunUiToggle(string const & ui_component)
4651 if (ui_component == "scrollbar") {
4652 // hide() is of no help
4653 if (d.current_work_area_->verticalScrollBarPolicy() ==
4654 Qt::ScrollBarAlwaysOff)
4656 d.current_work_area_->setVerticalScrollBarPolicy(
4657 Qt::ScrollBarAsNeeded);
4659 d.current_work_area_->setVerticalScrollBarPolicy(
4660 Qt::ScrollBarAlwaysOff);
4661 } else if (ui_component == "statusbar") {
4662 statusBar()->setVisible(!statusBar()->isVisible());
4663 } else if (ui_component == "menubar") {
4664 menuBar()->setVisible(!menuBar()->isVisible());
4666 if (ui_component == "frame") {
4667 int const l = contentsMargins().left();
4669 //are the frames in default state?
4670 d.current_work_area_->setFrameStyle(QFrame::NoFrame);
4672 setContentsMargins(-2, -2, -2, -2);
4674 setContentsMargins(0, 0, 0, 0);
4677 if (ui_component == "fullscreen") {
4685 void GuiView::toggleFullScreen()
4687 setWindowState(windowState() ^ Qt::WindowFullScreen);
4691 Buffer const * GuiView::updateInset(Inset const * inset)
4696 Buffer const * inset_buffer = &(inset->buffer());
4698 for (int i = 0; i != d.splitter_->count(); ++i) {
4699 GuiWorkArea * wa = d.tabWorkArea(i)->currentWorkArea();
4702 Buffer const * buffer = &(wa->bufferView().buffer());
4703 if (inset_buffer == buffer)
4704 wa->scheduleRedraw(true);
4706 return inset_buffer;
4710 void GuiView::restartCaret()
4712 /* When we move around, or type, it's nice to be able to see
4713 * the caret immediately after the keypress.
4715 if (d.current_work_area_)
4716 d.current_work_area_->startBlinkingCaret();
4718 // Take this occasion to update the other GUI elements.
4724 void GuiView::updateCompletion(Cursor & cur, bool start, bool keep)
4726 if (d.current_work_area_)
4727 d.current_work_area_->completer().updateVisibility(cur, start, keep);
4732 // This list should be kept in sync with the list of insets in
4733 // src/insets/Inset.cpp. I.e., if a dialog goes with an inset, the
4734 // dialog should have the same name as the inset.
4735 // Changes should be also recorded in LFUN_DIALOG_SHOW doxygen
4736 // docs in LyXAction.cpp.
4738 char const * const dialognames[] = {
4740 "aboutlyx", "bibitem", "bibtex", "box", "branch", "changes", "character",
4741 "citation", "compare", "comparehistory", "counter", "document", "errorlist", "ert",
4742 "external", "file", "findreplace", "findreplaceadv", "float", "graphics",
4743 "href", "include", "index", "index_print", "info", "listings", "label", "line",
4744 "log", "lyxfiles", "mathdelimiter", "mathmatrix", "mathspace", "nomenclature",
4745 "nomencl_print", "note", "paragraph", "phantom", "prefs", "ref",
4746 "sendto", "space", "spellchecker", "symbols", "tabular", "tabularcreate",
4747 "thesaurus", "texinfo", "toc", "view-source", "vspace", "wrap", "progress"};
4749 char const * const * const end_dialognames =
4750 dialognames + (sizeof(dialognames) / sizeof(char *));
4754 cmpCStr(char const * name) : name_(name) {}
4755 bool operator()(char const * other) {
4756 return strcmp(other, name_) == 0;
4763 bool isValidName(string const & name)
4765 return find_if(dialognames, end_dialognames,
4766 cmpCStr(name.c_str())) != end_dialognames;
4772 void GuiView::resetDialogs()
4774 // Make sure that no LFUN uses any GuiView.
4775 guiApp->setCurrentView(nullptr);
4779 constructToolbars();
4780 guiApp->menus().fillMenuBar(menuBar(), this, false);
4781 d.layout_->updateContents(true);
4782 // Now update controls with current buffer.
4783 guiApp->setCurrentView(this);
4789 Dialog * GuiView::findOrBuild(string const & name, bool hide_it)
4791 if (!isValidName(name))
4794 map<string, DialogPtr>::iterator it = d.dialogs_.find(name);
4796 if (it != d.dialogs_.end()) {
4798 it->second->hideView();
4799 return it->second.get();
4802 Dialog * dialog = build(name);
4803 d.dialogs_[name].reset(dialog);
4804 if (lyxrc.allow_geometry_session)
4805 dialog->restoreSession();
4812 void GuiView::showDialog(string const & name, string const & sdata,
4815 triggerShowDialog(toqstr(name), toqstr(sdata), inset);
4819 void GuiView::doShowDialog(QString const & qname, QString const & qdata,
4825 const string name = fromqstr(qname);
4826 const string sdata = fromqstr(qdata);
4830 Dialog * dialog = findOrBuild(name, false);
4832 bool const visible = dialog->isVisibleView();
4833 dialog->showData(sdata);
4834 if (currentBufferView())
4835 currentBufferView()->editInset(name, inset);
4836 // We only set the focus to the new dialog if it was not yet
4837 // visible in order not to change the existing previous behaviour
4839 // activateWindow is needed for floating dockviews
4840 dialog->asQWidget()->raise();
4841 dialog->asQWidget()->activateWindow();
4842 dialog->asQWidget()->setFocus();
4846 catch (ExceptionMessage const & ex) {
4854 bool GuiView::isDialogVisible(string const & name) const
4856 map<string, DialogPtr>::const_iterator it = d.dialogs_.find(name);
4857 if (it == d.dialogs_.end())
4859 return it->second.get()->isVisibleView() && !it->second.get()->isClosing();
4863 void GuiView::hideDialog(string const & name, Inset * inset)
4865 map<string, DialogPtr>::const_iterator it = d.dialogs_.find(name);
4866 if (it == d.dialogs_.end())
4870 if (!currentBufferView())
4872 if (inset != currentBufferView()->editedInset(name))
4876 Dialog * const dialog = it->second.get();
4877 if (dialog->isVisibleView())
4879 if (currentBufferView())
4880 currentBufferView()->editInset(name, nullptr);
4884 void GuiView::disconnectDialog(string const & name)
4886 if (!isValidName(name))
4888 if (currentBufferView())
4889 currentBufferView()->editInset(name, nullptr);
4893 void GuiView::hideAll() const
4895 map<string, DialogPtr>::const_iterator it = d.dialogs_.begin();
4896 map<string, DialogPtr>::const_iterator end = d.dialogs_.end();
4898 for(; it != end; ++it)
4899 it->second->hideView();
4903 void GuiView::updateDialogs()
4905 map<string, DialogPtr>::const_iterator it = d.dialogs_.begin();
4906 map<string, DialogPtr>::const_iterator end = d.dialogs_.end();
4908 for(; it != end; ++it) {
4909 Dialog * dialog = it->second.get();
4911 if (dialog->needBufferOpen() && !documentBufferView())
4912 hideDialog(fromqstr(dialog->name()), nullptr);
4913 else if (dialog->isVisibleView())
4914 dialog->checkStatus();
4921 Dialog * createDialog(GuiView & lv, string const & name);
4923 // will be replaced by a proper factory...
4924 Dialog * createGuiAbout(GuiView & lv);
4925 Dialog * createGuiBibtex(GuiView & lv);
4926 Dialog * createGuiChanges(GuiView & lv);
4927 Dialog * createGuiCharacter(GuiView & lv);
4928 Dialog * createGuiCitation(GuiView & lv);
4929 Dialog * createGuiCompare(GuiView & lv);
4930 Dialog * createGuiCompareHistory(GuiView & lv);
4931 Dialog * createGuiDelimiter(GuiView & lv);
4932 Dialog * createGuiDocument(GuiView & lv);
4933 Dialog * createGuiErrorList(GuiView & lv);
4934 Dialog * createGuiExternal(GuiView & lv);
4935 Dialog * createGuiGraphics(GuiView & lv);
4936 Dialog * createGuiInclude(GuiView & lv);
4937 Dialog * createGuiIndex(GuiView & lv);
4938 Dialog * createGuiListings(GuiView & lv);
4939 Dialog * createGuiLog(GuiView & lv);
4940 Dialog * createGuiLyXFiles(GuiView & lv);
4941 Dialog * createGuiMathMatrix(GuiView & lv);
4942 Dialog * createGuiNote(GuiView & lv);
4943 Dialog * createGuiParagraph(GuiView & lv);
4944 Dialog * createGuiPhantom(GuiView & lv);
4945 Dialog * createGuiPreferences(GuiView & lv);
4946 Dialog * createGuiPrint(GuiView & lv);
4947 Dialog * createGuiPrintindex(GuiView & lv);
4948 Dialog * createGuiRef(GuiView & lv);
4949 Dialog * createGuiSearch(GuiView & lv);
4950 Dialog * createGuiSearchAdv(GuiView & lv);
4951 Dialog * createGuiSendTo(GuiView & lv);
4952 Dialog * createGuiShowFile(GuiView & lv);
4953 Dialog * createGuiSpellchecker(GuiView & lv);
4954 Dialog * createGuiSymbols(GuiView & lv);
4955 Dialog * createGuiTabularCreate(GuiView & lv);
4956 Dialog * createGuiTexInfo(GuiView & lv);
4957 Dialog * createGuiToc(GuiView & lv);
4958 Dialog * createGuiThesaurus(GuiView & lv);
4959 Dialog * createGuiViewSource(GuiView & lv);
4960 Dialog * createGuiWrap(GuiView & lv);
4961 Dialog * createGuiProgressView(GuiView & lv);
4965 Dialog * GuiView::build(string const & name)
4967 LASSERT(isValidName(name), return nullptr);
4969 Dialog * dialog = createDialog(*this, name);
4973 if (name == "aboutlyx")
4974 return createGuiAbout(*this);
4975 if (name == "bibtex")
4976 return createGuiBibtex(*this);
4977 if (name == "changes")
4978 return createGuiChanges(*this);
4979 if (name == "character")
4980 return createGuiCharacter(*this);
4981 if (name == "citation")
4982 return createGuiCitation(*this);
4983 if (name == "compare")
4984 return createGuiCompare(*this);
4985 if (name == "comparehistory")
4986 return createGuiCompareHistory(*this);
4987 if (name == "document")
4988 return createGuiDocument(*this);
4989 if (name == "errorlist")
4990 return createGuiErrorList(*this);
4991 if (name == "external")
4992 return createGuiExternal(*this);
4994 return createGuiShowFile(*this);
4995 if (name == "findreplace")
4996 return createGuiSearch(*this);
4997 if (name == "findreplaceadv")
4998 return createGuiSearchAdv(*this);
4999 if (name == "graphics")
5000 return createGuiGraphics(*this);
5001 if (name == "include")
5002 return createGuiInclude(*this);
5003 if (name == "index")
5004 return createGuiIndex(*this);
5005 if (name == "index_print")
5006 return createGuiPrintindex(*this);
5007 if (name == "listings")
5008 return createGuiListings(*this);
5010 return createGuiLog(*this);
5011 if (name == "lyxfiles")
5012 return createGuiLyXFiles(*this);
5013 if (name == "mathdelimiter")
5014 return createGuiDelimiter(*this);
5015 if (name == "mathmatrix")
5016 return createGuiMathMatrix(*this);
5018 return createGuiNote(*this);
5019 if (name == "paragraph")
5020 return createGuiParagraph(*this);
5021 if (name == "phantom")
5022 return createGuiPhantom(*this);
5023 if (name == "prefs")
5024 return createGuiPreferences(*this);
5026 return createGuiRef(*this);
5027 if (name == "sendto")
5028 return createGuiSendTo(*this);
5029 if (name == "spellchecker")
5030 return createGuiSpellchecker(*this);
5031 if (name == "symbols")
5032 return createGuiSymbols(*this);
5033 if (name == "tabularcreate")
5034 return createGuiTabularCreate(*this);
5035 if (name == "texinfo")
5036 return createGuiTexInfo(*this);
5037 if (name == "thesaurus")
5038 return createGuiThesaurus(*this);
5040 return createGuiToc(*this);
5041 if (name == "view-source")
5042 return createGuiViewSource(*this);
5044 return createGuiWrap(*this);
5045 if (name == "progress")
5046 return createGuiProgressView(*this);
5052 SEMenu::SEMenu(QWidget * parent)
5054 QAction * action = addAction(qt_("Disable Shell Escape"));
5055 connect(action, SIGNAL(triggered()),
5056 parent, SLOT(disableShellEscape()));
5059 } // namespace frontend
5062 #include "moc_GuiView.cpp"