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 "DialogFactory.h"
19 #include "DispatchResult.h"
20 #include "FileDialog.h"
21 #include "FontLoader.h"
22 #include "GuiApplication.h"
23 #include "GuiClickableLabel.h"
24 #include "GuiCompleter.h"
25 #include "GuiFontMetrics.h"
26 #include "GuiKeySymbol.h"
28 #include "GuiToolbar.h"
29 #include "GuiWorkArea.h"
30 #include "GuiProgress.h"
31 #include "LayoutBox.h"
35 #include "qt_helpers.h"
36 #include "support/filetools.h"
38 #include "frontends/alert.h"
39 #include "frontends/KeySymbol.h"
41 #include "buffer_funcs.h"
43 #include "BufferList.h"
44 #include "BufferParams.h"
45 #include "BufferView.h"
47 #include "Converter.h"
49 #include "CutAndPaste.h"
51 #include "ErrorList.h"
53 #include "FuncStatus.h"
54 #include "FuncRequest.h"
55 #include "KeySymbol.h"
57 #include "LayoutFile.h"
59 #include "LyXAction.h"
63 #include "Paragraph.h"
64 #include "SpellChecker.h"
71 #include "support/convert.h"
72 #include "support/debug.h"
73 #include "support/ExceptionMessage.h"
74 #include "support/FileName.h"
75 #include "support/gettext.h"
76 #include "support/ForkedCalls.h"
77 #include "support/lassert.h"
78 #include "support/lstrings.h"
79 #include "support/os.h"
80 #include "support/Package.h"
81 #include "support/PathChanger.h"
82 #include "support/Systemcall.h"
83 #include "support/Timeout.h"
84 #include "support/ProgressInterface.h"
87 #include <QApplication>
88 #include <QCloseEvent>
89 #include <QDragEnterEvent>
92 #include <QFutureWatcher>
104 #include <QShowEvent>
107 #include <QStackedWidget>
108 #include <QStatusBar>
109 #include <QSvgRenderer>
110 #include <QtConcurrentRun>
113 #include <QWindowStateChangeEvent>
116 // sync with GuiAlert.cpp
117 #define EXPORT_in_THREAD 1
120 #include "support/bind.h"
124 #ifdef HAVE_SYS_TIME_H
125 # include <sys/time.h>
133 using namespace lyx::support;
137 using support::addExtension;
138 using support::changeExtension;
139 using support::removeExtension;
145 class BackgroundWidget : public QWidget
148 BackgroundWidget(int width, int height)
149 : width_(width), height_(height)
151 LYXERR(Debug::GUI, "show banner: " << lyxrc.show_banner);
152 if (!lyxrc.show_banner)
154 /// The text to be written on top of the pixmap
155 QString const htext = qt_("The Document\nProcessor[[welcome banner]]");
156 QString const htextsize = qt_("1.0[[possibly scale the welcome banner text size]]");
157 /// The text to be written on top of the pixmap
158 QString const text = lyx_version ?
159 qt_("version ") + lyx_version : qt_("unknown version");
160 #if QT_VERSION >= 0x050000
161 QString imagedir = "images/";
162 FileName fname = imageLibFileSearch(imagedir, "banner", "svgz");
163 QSvgRenderer svgRenderer(toqstr(fname.absFileName()));
164 if (svgRenderer.isValid()) {
165 splash_ = QPixmap(splashSize());
166 QPainter painter(&splash_);
167 svgRenderer.render(&painter);
168 splash_.setDevicePixelRatio(pixelRatio());
170 splash_ = getPixmap("images/", "banner", "png");
173 splash_ = getPixmap("images/", "banner", "svgz,png");
176 QPainter pain(&splash_);
177 pain.setPen(QColor(0, 0, 0));
178 qreal const fsize = fontSize();
181 qreal locscale = htextsize.toFloat(&ok);
184 QPointF const position = textPosition(false);
185 QPointF const hposition = textPosition(true);
186 QRectF const hrect(hposition, splashSize());
188 "widget pixel ratio: " << pixelRatio() <<
189 " splash pixel ratio: " << splashPixelRatio() <<
190 " version text size,position: " << fsize << "@" << position.x() << "+" << position.y());
192 // The font used to display the version info
193 font.setStyleHint(QFont::SansSerif);
194 font.setWeight(QFont::Bold);
195 font.setPointSizeF(fsize);
197 pain.drawText(position, text);
198 // The font used to display the version info
199 font.setStyleHint(QFont::SansSerif);
200 font.setWeight(QFont::Normal);
201 font.setPointSizeF(hfsize);
202 // Check how long the logo gets with the current font
203 // and adapt if the font is running wider than what
205 GuiFontMetrics fm(font);
206 // Split the title into lines to measure the longest line
207 // in the current l7n.
208 QStringList titlesegs = htext.split('\n');
210 int hline = fm.maxHeight();
211 QStringList::const_iterator sit;
212 for (QString const & seg : titlesegs) {
213 if (fm.width(seg) > wline)
214 wline = fm.width(seg);
216 // The longest line in the reference font (for English)
217 // is 180. Calculate scale factor from that.
218 double const wscale = wline > 0 ? (180.0 / wline) : 1;
219 // Now do the same for the height (necessary for condensed fonts)
220 double const hscale = (34.0 / hline);
221 // take the lower of the two scale factors.
222 double const scale = min(wscale, hscale);
223 // Now rescale. Also consider l7n's offset factor.
224 font.setPointSizeF(hfsize * scale * locscale);
227 pain.drawText(hrect, Qt::AlignLeft, htext);
228 setFocusPolicy(Qt::StrongFocus);
231 void paintEvent(QPaintEvent *) override
233 int const w = width_;
234 int const h = height_;
235 int const x = (width() - w) / 2;
236 int const y = (height() - h) / 2;
238 "widget pixel ratio: " << pixelRatio() <<
239 " splash pixel ratio: " << splashPixelRatio() <<
240 " paint pixmap: " << w << "x" << h << "@" << x << "+" << y);
242 pain.drawPixmap(x, y, w, h, splash_);
245 void keyPressEvent(QKeyEvent * ev) override
248 setKeySymbol(&sym, ev);
250 guiApp->processKeySym(sym, q_key_state(ev->modifiers()));
262 /// Current ratio between physical pixels and device-independent pixels
263 double pixelRatio() const {
264 #if QT_VERSION >= 0x050000
265 return qt_scale_factor * devicePixelRatio();
271 qreal fontSize() const {
272 return toqstr(lyxrc.font_sizes[NORMAL_SIZE]).toDouble();
275 QPointF textPosition(bool const heading) const {
276 return heading ? QPointF(width_/2 - 18, height_/2 - 45)
277 : QPointF(width_/2 - 18, height_/2 + 45);
280 QSize splashSize() const {
282 static_cast<unsigned int>(width_ * pixelRatio()),
283 static_cast<unsigned int>(height_ * pixelRatio()));
286 /// Ratio between physical pixels and device-independent pixels of splash image
287 double splashPixelRatio() const {
288 #if QT_VERSION >= 0x050000
289 return splash_.devicePixelRatio();
297 /// Toolbar store providing access to individual toolbars by name.
298 typedef map<string, GuiToolbar *> ToolbarMap;
300 typedef shared_ptr<Dialog> DialogPtr;
305 class GuiView::GuiViewPrivate
308 GuiViewPrivate(GuiViewPrivate const &);
309 void operator=(GuiViewPrivate const &);
311 GuiViewPrivate(GuiView * gv)
312 : gv_(gv), current_work_area_(nullptr), current_main_work_area_(nullptr),
313 layout_(nullptr), autosave_timeout_(5000),
316 // hardcode here the platform specific icon size
317 smallIconSize = 16; // scaling problems
318 normalIconSize = 20; // ok, default if iconsize.png is missing
319 bigIconSize = 26; // better for some math icons
320 hugeIconSize = 32; // better for hires displays
323 // if it exists, use width of iconsize.png as normal size
324 QString const dir = toqstr(addPath("images", lyxrc.icon_set));
325 FileName const fn = lyx::libFileSearch(dir, "iconsize.png");
327 QImage image(toqstr(fn.absFileName()));
328 if (image.width() < int(smallIconSize))
329 normalIconSize = smallIconSize;
330 else if (image.width() > int(giantIconSize))
331 normalIconSize = giantIconSize;
333 normalIconSize = image.width();
336 splitter_ = new QSplitter;
337 bg_widget_ = new BackgroundWidget(400, 250);
338 stack_widget_ = new QStackedWidget;
339 stack_widget_->addWidget(bg_widget_);
340 stack_widget_->addWidget(splitter_);
343 // TODO cleanup, remove the singleton, handle multiple Windows?
344 progress_ = ProgressInterface::instance();
345 if (!dynamic_cast<GuiProgress*>(progress_)) {
346 progress_ = new GuiProgress; // TODO who deletes it
347 ProgressInterface::setInstance(progress_);
350 dynamic_cast<GuiProgress*>(progress_),
351 SIGNAL(updateStatusBarMessage(QString const&)),
352 gv, SLOT(updateStatusBarMessage(QString const&)));
354 dynamic_cast<GuiProgress*>(progress_),
355 SIGNAL(clearMessageText()),
356 gv, SLOT(clearMessageText()));
363 delete stack_widget_;
368 stack_widget_->setCurrentWidget(bg_widget_);
369 bg_widget_->setUpdatesEnabled(true);
370 bg_widget_->setFocus();
373 int tabWorkAreaCount()
375 return splitter_->count();
378 TabWorkArea * tabWorkArea(int i)
380 return dynamic_cast<TabWorkArea *>(splitter_->widget(i));
383 TabWorkArea * currentTabWorkArea()
385 int areas = tabWorkAreaCount();
387 // The first TabWorkArea is always the first one, if any.
388 return tabWorkArea(0);
390 for (int i = 0; i != areas; ++i) {
391 TabWorkArea * twa = tabWorkArea(i);
392 if (current_main_work_area_ == twa->currentWorkArea())
396 // None has the focus so we just take the first one.
397 return tabWorkArea(0);
400 int countWorkAreasOf(Buffer & buf)
402 int areas = tabWorkAreaCount();
404 for (int i = 0; i != areas; ++i) {
405 TabWorkArea * twa = tabWorkArea(i);
406 if (twa->workArea(buf))
412 void setPreviewFuture(QFuture<Buffer::ExportStatus> const & f)
414 if (processing_thread_watcher_.isRunning()) {
415 // we prefer to cancel this preview in order to keep a snappy
419 processing_thread_watcher_.setFuture(f);
422 QSize iconSize(docstring const & icon_size)
425 if (icon_size == "small")
426 size = smallIconSize;
427 else if (icon_size == "normal")
428 size = normalIconSize;
429 else if (icon_size == "big")
431 else if (icon_size == "huge")
433 else if (icon_size == "giant")
434 size = giantIconSize;
436 size = icon_size.empty() ? normalIconSize : convert<int>(icon_size);
438 if (size < smallIconSize)
439 size = smallIconSize;
441 return QSize(size, size);
444 QSize iconSize(QString const & icon_size)
446 return iconSize(qstring_to_ucs4(icon_size));
449 string & iconSize(QSize const & qsize)
451 LATTEST(qsize.width() == qsize.height());
453 static string icon_size;
455 unsigned int size = qsize.width();
457 if (size < smallIconSize)
458 size = smallIconSize;
460 if (size == smallIconSize)
462 else if (size == normalIconSize)
463 icon_size = "normal";
464 else if (size == bigIconSize)
466 else if (size == hugeIconSize)
468 else if (size == giantIconSize)
471 icon_size = convert<string>(size);
476 static Buffer::ExportStatus previewAndDestroy(Buffer const * orig,
477 Buffer * buffer, string const & format);
478 static Buffer::ExportStatus exportAndDestroy(Buffer const * orig,
479 Buffer * buffer, string const & format);
480 static Buffer::ExportStatus compileAndDestroy(Buffer const * orig,
481 Buffer * buffer, string const & format);
482 static docstring autosaveAndDestroy(Buffer const * orig, Buffer * buffer);
485 static Buffer::ExportStatus runAndDestroy(const T& func,
486 Buffer const * orig, Buffer * buffer, string const & format);
488 // TODO syncFunc/previewFunc: use bind
489 bool asyncBufferProcessing(string const & argument,
490 Buffer const * used_buffer,
491 docstring const & msg,
492 Buffer::ExportStatus (*asyncFunc)(Buffer const *, Buffer *, string const &),
493 Buffer::ExportStatus (Buffer::*syncFunc)(string const &, bool) const,
494 Buffer::ExportStatus (Buffer::*previewFunc)(string const &) const,
495 bool allow_async, bool use_tmpdir = false);
497 QVector<GuiWorkArea*> guiWorkAreas();
501 GuiWorkArea * current_work_area_;
502 GuiWorkArea * current_main_work_area_;
503 QSplitter * splitter_;
504 QStackedWidget * stack_widget_;
505 BackgroundWidget * bg_widget_;
507 ToolbarMap toolbars_;
508 ProgressInterface* progress_;
509 /// The main layout box.
511 * \warning Don't Delete! The layout box is actually owned by
512 * whichever toolbar contains it. All the GuiView class needs is a
513 * means of accessing it.
515 * FIXME: replace that with a proper model so that we are not limited
516 * to only one dialog.
521 map<string, DialogPtr> dialogs_;
524 QTimer statusbar_timer_;
525 /// auto-saving of buffers
526 Timeout autosave_timeout_;
529 TocModels toc_models_;
532 QFutureWatcher<docstring> autosave_watcher_;
533 QFutureWatcher<Buffer::ExportStatus> processing_thread_watcher_;
535 string last_export_format;
536 string processing_format;
538 static QSet<Buffer const *> busyBuffers;
540 unsigned int smallIconSize;
541 unsigned int normalIconSize;
542 unsigned int bigIconSize;
543 unsigned int hugeIconSize;
544 unsigned int giantIconSize;
546 /// flag against a race condition due to multiclicks, see bug #1119
550 QSet<Buffer const *> GuiView::GuiViewPrivate::busyBuffers;
553 GuiView::GuiView(int id)
554 : d(*new GuiViewPrivate(this)), id_(id), closing_(false), busy_(0),
555 command_execute_(false), minibuffer_focus_(false), toolbarsMovable_(true),
558 connect(this, SIGNAL(bufferViewChanged()),
559 this, SLOT(onBufferViewChanged()));
561 // GuiToolbars *must* be initialised before the menu bar.
562 setIconSize(QSize(d.normalIconSize, d.normalIconSize)); // at least on Mac the default is 32 otherwise, which is huge
565 // set ourself as the current view. This is needed for the menu bar
566 // filling, at least for the static special menu item on Mac. Otherwise
567 // they are greyed out.
568 guiApp->setCurrentView(this);
570 // Fill up the menu bar.
571 guiApp->menus().fillMenuBar(menuBar(), this, true);
573 setCentralWidget(d.stack_widget_);
575 // Start autosave timer
576 if (lyxrc.autosave) {
577 // The connection is closed when this is destroyed.
578 d.autosave_timeout_.timeout.connect([this](){ autoSave();});
579 d.autosave_timeout_.setTimeout(lyxrc.autosave * 1000);
580 d.autosave_timeout_.start();
582 connect(&d.statusbar_timer_, SIGNAL(timeout()),
583 this, SLOT(clearMessage()));
585 // We don't want to keep the window in memory if it is closed.
586 setAttribute(Qt::WA_DeleteOnClose, true);
588 #if !(defined(Q_OS_WIN) || defined(Q_CYGWIN_WIN)) && !defined(Q_OS_MAC)
589 // QIcon::fromTheme was introduced in Qt 4.6
590 #if (QT_VERSION >= 0x040600)
591 // assign an icon to main form. We do not do it under Qt/Win or Qt/Mac,
592 // since the icon is provided in the application bundle. We use a themed
593 // version when available and use the bundled one as fallback.
594 setWindowIcon(QIcon::fromTheme("lyx", getPixmap("images/", "lyx", "svg,png")));
596 setWindowIcon(getPixmap("images/", "lyx", "svg,png"));
602 // use tabbed dock area for multiple docks
603 // (such as "source" and "messages")
604 setDockOptions(QMainWindow::ForceTabbedDocks);
607 // use document mode tabs on docks
608 setDocumentMode(true);
612 setAcceptDrops(true);
614 // add busy indicator to statusbar
615 GuiClickableLabel * busylabel = new GuiClickableLabel(statusBar());
616 statusBar()->addPermanentWidget(busylabel);
617 search_mode mode = theGuiApp()->imageSearchMode();
618 QString fn = toqstr(lyx::libFileSearch("images", "busy", "gif", mode).absFileName());
619 QMovie * busyanim = new QMovie(fn, QByteArray(), busylabel);
620 busylabel->setMovie(busyanim);
624 connect(&d.processing_thread_watcher_, SIGNAL(started()),
625 busylabel, SLOT(show()));
626 connect(&d.processing_thread_watcher_, SIGNAL(finished()),
627 busylabel, SLOT(hide()));
628 connect(busylabel, SIGNAL(clicked()), this, SLOT(checkCancelBackground()));
630 QFontMetrics const fm(statusBar()->fontMetrics());
632 QSlider * zoomslider = new QSlider(Qt::Horizontal, statusBar());
633 #if (QT_VERSION >= QT_VERSION_CHECK(5, 11, 0))
634 zoomslider->setFixedWidth(fm.horizontalAdvance('x') * 15);
636 zoomslider->setFixedWidth(fm.width('x') * 15);
638 // Make the defaultZoom center
639 zoomslider->setRange(10, (lyxrc.defaultZoom * 2) - 10);
640 zoomslider->setValue(lyxrc.currentZoom);
641 zoomslider->setToolTip(qt_("Workarea zoom level. Drag, use Ctrl-+/- or Shift-Mousewheel to adjust."));
642 zoomslider->setTickPosition(QSlider::TicksBelow);
643 zoomslider->setTickInterval(lyxrc.defaultZoom - 10);
644 statusBar()->addPermanentWidget(zoomslider);
646 connect(zoomslider, SIGNAL(sliderMoved(int)), this, SLOT(zoomSliderMoved(int)));
647 connect(this, SIGNAL(currentZoomChanged(int)), zoomslider, SLOT(setValue(int)));
649 int const iconheight = max(int(d.normalIconSize), fm.height());
650 QSize const iconsize(iconheight, iconheight);
652 QPixmap shellescape = QIcon(getPixmap("images/", "emblem-shellescape", "svgz,png")).pixmap(iconsize);
653 shell_escape_ = new QLabel(statusBar());
654 shell_escape_->setPixmap(shellescape);
655 shell_escape_->setScaledContents(true);
656 shell_escape_->setAlignment(Qt::AlignCenter);
657 shell_escape_->setContextMenuPolicy(Qt::CustomContextMenu);
658 shell_escape_->setToolTip(qt_("WARNING: LaTeX is allowed to execute "
659 "external commands for this document. "
660 "Right click to change."));
661 SEMenu * menu = new SEMenu(this);
662 connect(shell_escape_, SIGNAL(customContextMenuRequested(QPoint)),
663 menu, SLOT(showMenu(QPoint)));
664 shell_escape_->hide();
665 statusBar()->addPermanentWidget(shell_escape_);
667 QPixmap readonly = QIcon(getPixmap("images/", "emblem-readonly", "svgz,png")).pixmap(iconsize);
668 read_only_ = new QLabel(statusBar());
669 read_only_->setPixmap(readonly);
670 read_only_->setScaledContents(true);
671 read_only_->setAlignment(Qt::AlignCenter);
673 statusBar()->addPermanentWidget(read_only_);
675 version_control_ = new QLabel(statusBar());
676 version_control_->setAlignment(Qt::AlignCenter);
677 version_control_->setFrameStyle(QFrame::StyledPanel);
678 version_control_->hide();
679 statusBar()->addPermanentWidget(version_control_);
681 statusBar()->setSizeGripEnabled(true);
684 connect(&d.autosave_watcher_, SIGNAL(finished()), this,
685 SLOT(autoSaveThreadFinished()));
687 connect(&d.processing_thread_watcher_, SIGNAL(started()), this,
688 SLOT(processingThreadStarted()));
689 connect(&d.processing_thread_watcher_, SIGNAL(finished()), this,
690 SLOT(processingThreadFinished()));
692 connect(this, SIGNAL(triggerShowDialog(QString const &, QString const &, Inset *)),
693 SLOT(doShowDialog(QString const &, QString const &, Inset *)));
695 // set custom application bars context menu, e.g. tool bar and menu bar
696 setContextMenuPolicy(Qt::CustomContextMenu);
697 connect(this, SIGNAL(customContextMenuRequested(const QPoint &)),
698 SLOT(toolBarPopup(const QPoint &)));
700 // Forbid too small unresizable window because it can happen
701 // with some window manager under X11.
702 setMinimumSize(300, 200);
704 if (lyxrc.allow_geometry_session) {
705 // Now take care of session management.
710 // no session handling, default to a sane size.
711 setGeometry(50, 50, 690, 510);
714 // clear session data if any.
716 settings.remove("views");
726 void GuiView::disableShellEscape()
728 BufferView * bv = documentBufferView();
731 theSession().shellescapeFiles().remove(bv->buffer().absFileName());
732 bv->buffer().params().shell_escape = false;
733 bv->processUpdateFlags(Update::Force);
737 void GuiView::checkCancelBackground()
739 docstring const ttl = _("Cancel Export?");
740 docstring const msg = _("Do you want to cancel the background export process?");
742 Alert::prompt(ttl, msg, 1, 1,
743 _("&Cancel export"), _("Co&ntinue"));
745 Systemcall::killscript();
749 void GuiView::zoomSliderMoved(int value)
752 dispatch(FuncRequest(LFUN_BUFFER_ZOOM, convert<string>(value)), dr);
753 currentWorkArea()->scheduleRedraw(true);
754 message(bformat(_("Zoom level is now %1$d% (default value: %2$d%)"),
755 value, lyxrc.defaultZoom));
759 QVector<GuiWorkArea*> GuiView::GuiViewPrivate::guiWorkAreas()
761 QVector<GuiWorkArea*> areas;
762 for (int i = 0; i < tabWorkAreaCount(); i++) {
763 TabWorkArea* ta = tabWorkArea(i);
764 for (int u = 0; u < ta->count(); u++) {
765 areas << ta->workArea(u);
771 static void handleExportStatus(GuiView * view, Buffer::ExportStatus status,
772 string const & format)
774 docstring const fmt = translateIfPossible(theFormats().prettyName(format));
777 case Buffer::ExportSuccess:
778 msg = bformat(_("Successful export to format: %1$s"), fmt);
780 case Buffer::ExportCancel:
781 msg = _("Document export cancelled.");
783 case Buffer::ExportError:
784 case Buffer::ExportNoPathToFormat:
785 case Buffer::ExportTexPathHasSpaces:
786 case Buffer::ExportConverterError:
787 msg = bformat(_("Error while exporting format: %1$s"), fmt);
789 case Buffer::PreviewSuccess:
790 msg = bformat(_("Successful preview of format: %1$s"), fmt);
792 case Buffer::PreviewError:
793 msg = bformat(_("Error while previewing format: %1$s"), fmt);
795 case Buffer::ExportKilled:
796 msg = bformat(_("Conversion cancelled while previewing format: %1$s"), fmt);
803 void GuiView::processingThreadStarted()
808 void GuiView::processingThreadFinished()
810 QFutureWatcher<Buffer::ExportStatus> const * watcher =
811 static_cast<QFutureWatcher<Buffer::ExportStatus> const *>(sender());
813 Buffer::ExportStatus const status = watcher->result();
814 handleExportStatus(this, status, d.processing_format);
817 BufferView const * const bv = currentBufferView();
818 if (bv && !bv->buffer().errorList("Export").empty()) {
823 bool const error = (status != Buffer::ExportSuccess &&
824 status != Buffer::PreviewSuccess &&
825 status != Buffer::ExportCancel);
827 ErrorList & el = bv->buffer().errorList(d.last_export_format);
828 // at this point, we do not know if buffer-view or
829 // master-buffer-view was called. If there was an export error,
830 // and the current buffer's error log is empty, we guess that
831 // it must be master-buffer-view that was called so we set
833 errors(d.last_export_format, el.empty());
838 void GuiView::autoSaveThreadFinished()
840 QFutureWatcher<docstring> const * watcher =
841 static_cast<QFutureWatcher<docstring> const *>(sender());
842 message(watcher->result());
847 void GuiView::saveLayout() const
850 settings.setValue("zoom_ratio", zoom_ratio_);
851 settings.setValue("devel_mode", devel_mode_);
852 settings.beginGroup("views");
853 settings.beginGroup(QString::number(id_));
854 if (guiApp->platformName() == "qt4x11" || guiApp->platformName() == "xcb") {
855 settings.setValue("pos", pos());
856 settings.setValue("size", size());
858 settings.setValue("geometry", saveGeometry());
859 settings.setValue("layout", saveState(0));
860 settings.setValue("icon_size", toqstr(d.iconSize(iconSize())));
864 void GuiView::saveUISettings() const
868 // Save the toolbar private states
869 for (auto const & tb_p : d.toolbars_)
870 tb_p.second->saveSession(settings);
871 // Now take care of all other dialogs
872 for (auto const & dlg_p : d.dialogs_)
873 dlg_p.second->saveSession(settings);
877 void GuiView::setCurrentZoom(const int v)
879 lyxrc.currentZoom = v;
880 Q_EMIT currentZoomChanged(v);
884 bool GuiView::restoreLayout()
887 zoom_ratio_ = settings.value("zoom_ratio", 1.0).toDouble();
888 // Actual zoom value: default zoom + fractional offset
889 int zoom = (int)(lyxrc.defaultZoom * zoom_ratio_);
890 if (zoom < static_cast<int>(zoom_min_))
892 setCurrentZoom(zoom);
893 devel_mode_ = settings.value("devel_mode", devel_mode_).toBool();
894 settings.beginGroup("views");
895 settings.beginGroup(QString::number(id_));
896 QString const icon_key = "icon_size";
897 if (!settings.contains(icon_key))
900 //code below is skipped when when ~/.config/LyX is (re)created
901 setIconSize(d.iconSize(settings.value(icon_key).toString()));
903 if (guiApp->platformName() == "qt4x11" || guiApp->platformName() == "xcb") {
904 QPoint pos = settings.value("pos", QPoint(50, 50)).toPoint();
905 QSize size = settings.value("size", QSize(690, 510)).toSize();
909 // Work-around for bug #6034: the window ends up in an undetermined
910 // state when trying to restore a maximized window when it is
911 // already maximized.
912 if (!(windowState() & Qt::WindowMaximized))
913 if (!restoreGeometry(settings.value("geometry").toByteArray()))
914 setGeometry(50, 50, 690, 510);
917 // Make sure layout is correctly oriented.
918 setLayoutDirection(qApp->layoutDirection());
920 // Allow the toc and view-source dock widget to be restored if needed.
922 if ((dialog = findOrBuild("toc", true)))
923 // see bug 5082. At least setup title and enabled state.
924 // Visibility will be adjusted by restoreState below.
925 dialog->prepareView();
926 if ((dialog = findOrBuild("view-source", true)))
927 dialog->prepareView();
928 if ((dialog = findOrBuild("progress", true)))
929 dialog->prepareView();
931 if (!restoreState(settings.value("layout").toByteArray(), 0))
934 // init the toolbars that have not been restored
935 for (auto const & tb_p : guiApp->toolbars()) {
936 GuiToolbar * tb = toolbar(tb_p.name);
937 if (tb && !tb->isRestored())
938 initToolbar(tb_p.name);
941 // update lock (all) toolbars positions
942 updateLockToolbars();
949 GuiToolbar * GuiView::toolbar(string const & name)
951 ToolbarMap::iterator it = d.toolbars_.find(name);
952 if (it != d.toolbars_.end())
955 LYXERR(Debug::GUI, "Toolbar::display: no toolbar named " << name);
960 void GuiView::updateLockToolbars()
962 toolbarsMovable_ = false;
963 for (ToolbarInfo const & info : guiApp->toolbars()) {
964 GuiToolbar * tb = toolbar(info.name);
965 if (tb && tb->isMovable())
966 toolbarsMovable_ = true;
971 void GuiView::constructToolbars()
973 for (auto const & tb_p : d.toolbars_)
977 // I don't like doing this here, but the standard toolbar
978 // destroys this object when it's destroyed itself (vfr)
979 d.layout_ = new LayoutBox(*this);
980 d.stack_widget_->addWidget(d.layout_);
981 d.layout_->move(0,0);
983 // extracts the toolbars from the backend
984 for (ToolbarInfo const & inf : guiApp->toolbars())
985 d.toolbars_[inf.name] = new GuiToolbar(inf, *this);
989 void GuiView::initToolbars()
991 // extracts the toolbars from the backend
992 for (ToolbarInfo const & inf : guiApp->toolbars())
993 initToolbar(inf.name);
997 void GuiView::initToolbar(string const & name)
999 GuiToolbar * tb = toolbar(name);
1002 int const visibility = guiApp->toolbars().defaultVisibility(name);
1003 bool newline = !(visibility & Toolbars::SAMEROW);
1004 tb->setVisible(false);
1005 tb->setVisibility(visibility);
1007 if (visibility & Toolbars::TOP) {
1009 addToolBarBreak(Qt::TopToolBarArea);
1010 addToolBar(Qt::TopToolBarArea, tb);
1013 if (visibility & Toolbars::BOTTOM) {
1015 addToolBarBreak(Qt::BottomToolBarArea);
1016 addToolBar(Qt::BottomToolBarArea, tb);
1019 if (visibility & Toolbars::LEFT) {
1021 addToolBarBreak(Qt::LeftToolBarArea);
1022 addToolBar(Qt::LeftToolBarArea, tb);
1025 if (visibility & Toolbars::RIGHT) {
1027 addToolBarBreak(Qt::RightToolBarArea);
1028 addToolBar(Qt::RightToolBarArea, tb);
1031 if (visibility & Toolbars::ON)
1032 tb->setVisible(true);
1034 tb->setMovable(true);
1038 TocModels & GuiView::tocModels()
1040 return d.toc_models_;
1044 void GuiView::setFocus()
1046 LYXERR(Debug::DEBUG, "GuiView::setFocus()" << this);
1047 QMainWindow::setFocus();
1051 bool GuiView::hasFocus() const
1053 if (currentWorkArea())
1054 return currentWorkArea()->hasFocus();
1055 if (currentMainWorkArea())
1056 return currentMainWorkArea()->hasFocus();
1057 return d.bg_widget_->hasFocus();
1061 void GuiView::focusInEvent(QFocusEvent * e)
1063 LYXERR(Debug::DEBUG, "GuiView::focusInEvent()" << this);
1064 QMainWindow::focusInEvent(e);
1065 // Make sure guiApp points to the correct view.
1066 guiApp->setCurrentView(this);
1067 if (currentWorkArea())
1068 currentWorkArea()->setFocus();
1069 else if (currentMainWorkArea())
1070 currentMainWorkArea()->setFocus();
1072 d.bg_widget_->setFocus();
1076 void GuiView::showEvent(QShowEvent * e)
1078 LYXERR(Debug::GUI, "Passed Geometry "
1079 << size().height() << "x" << size().width()
1080 << "+" << pos().x() << "+" << pos().y());
1082 if (d.splitter_->count() == 0)
1083 // No work area, switch to the background widget.
1087 QMainWindow::showEvent(e);
1091 bool GuiView::closeScheduled()
1098 bool GuiView::prepareAllBuffersForLogout()
1100 Buffer * first = theBufferList().first();
1104 // First, iterate over all buffers and ask the users if unsaved
1105 // changes should be saved.
1106 // We cannot use a for loop as the buffer list cycles.
1109 if (!saveBufferIfNeeded(const_cast<Buffer &>(*b), false))
1111 b = theBufferList().next(b);
1112 } while (b != first);
1114 // Next, save session state
1115 // When a view/window was closed before without quitting LyX, there
1116 // are already entries in the lastOpened list.
1117 theSession().lastOpened().clear();
1124 /** Destroy only all tabbed WorkAreas. Destruction of other WorkAreas
1125 ** is responsibility of the container (e.g., dialog)
1127 void GuiView::closeEvent(QCloseEvent * close_event)
1129 LYXERR(Debug::DEBUG, "GuiView::closeEvent()");
1131 if (!GuiViewPrivate::busyBuffers.isEmpty()) {
1132 Alert::warning(_("Exit LyX"),
1133 _("LyX could not be closed because documents are being processed by LyX."));
1134 close_event->setAccepted(false);
1138 // If the user pressed the x (so we didn't call closeView
1139 // programmatically), we want to clear all existing entries.
1141 theSession().lastOpened().clear();
1146 // it can happen that this event arrives without selecting the view,
1147 // e.g. when clicking the close button on a background window.
1149 if (!closeWorkAreaAll()) {
1151 close_event->ignore();
1155 // Make sure that nothing will use this to be closed View.
1156 guiApp->unregisterView(this);
1158 if (isFullScreen()) {
1159 // Switch off fullscreen before closing.
1164 // Make sure the timer time out will not trigger a statusbar update.
1165 d.statusbar_timer_.stop();
1167 // Saving fullscreen requires additional tweaks in the toolbar code.
1168 // It wouldn't also work under linux natively.
1169 if (lyxrc.allow_geometry_session) {
1174 close_event->accept();
1178 void GuiView::dragEnterEvent(QDragEnterEvent * event)
1180 if (event->mimeData()->hasUrls())
1182 /// \todo Ask lyx-devel is this is enough:
1183 /// if (event->mimeData()->hasFormat("text/plain"))
1184 /// event->acceptProposedAction();
1188 void GuiView::dropEvent(QDropEvent * event)
1190 QList<QUrl> files = event->mimeData()->urls();
1191 if (files.isEmpty())
1194 LYXERR(Debug::GUI, "GuiView::dropEvent: got URLs!");
1195 for (int i = 0; i != files.size(); ++i) {
1196 string const file = os::internal_path(fromqstr(
1197 files.at(i).toLocalFile()));
1201 string const ext = support::getExtension(file);
1202 vector<const Format *> found_formats;
1204 // Find all formats that have the correct extension.
1205 for (const Format * fmt : theConverters().importableFormats())
1206 if (fmt->hasExtension(ext))
1207 found_formats.push_back(fmt);
1210 if (!found_formats.empty()) {
1211 if (found_formats.size() > 1) {
1212 //FIXME: show a dialog to choose the correct importable format
1213 LYXERR(Debug::FILES,
1214 "Multiple importable formats found, selecting first");
1216 string const arg = found_formats[0]->name() + " " + file;
1217 cmd = FuncRequest(LFUN_BUFFER_IMPORT, arg);
1220 //FIXME: do we have to explicitly check whether it's a lyx file?
1221 LYXERR(Debug::FILES,
1222 "No formats found, trying to open it as a lyx file");
1223 cmd = FuncRequest(LFUN_FILE_OPEN, file);
1225 // add the functions to the queue
1226 guiApp->addToFuncRequestQueue(cmd);
1229 // now process the collected functions. We perform the events
1230 // asynchronously. This prevents potential problems in case the
1231 // BufferView is closed within an event.
1232 guiApp->processFuncRequestQueueAsync();
1236 void GuiView::message(docstring const & str)
1238 if (ForkedProcess::iAmAChild())
1241 // call is moved to GUI-thread by GuiProgress
1242 d.progress_->appendMessage(toqstr(str));
1246 void GuiView::clearMessageText()
1248 message(docstring());
1252 void GuiView::updateStatusBarMessage(QString const & str)
1254 statusBar()->showMessage(str);
1255 d.statusbar_timer_.stop();
1256 d.statusbar_timer_.start(3000);
1260 void GuiView::clearMessage()
1262 // FIXME: This code was introduced in r19643 to fix bug #4123. However,
1263 // the hasFocus function mostly returns false, even if the focus is on
1264 // a workarea in this view.
1268 d.statusbar_timer_.stop();
1272 void GuiView::updateWindowTitle(GuiWorkArea * wa)
1274 if (wa != d.current_work_area_
1275 || wa->bufferView().buffer().isInternal())
1277 Buffer const & buf = wa->bufferView().buffer();
1278 // Set the windows title
1279 docstring title = buf.fileName().displayName(130) + from_ascii("[*]");
1280 if (buf.notifiesExternalModification()) {
1281 title = bformat(_("%1$s (modified externally)"), title);
1282 // If the external modification status has changed, then maybe the status of
1283 // buffer-save has changed too.
1287 title += from_ascii(" - LyX");
1289 setWindowTitle(toqstr(title));
1290 // Sets the path for the window: this is used by OSX to
1291 // allow a context click on the title bar showing a menu
1292 // with the path up to the file
1293 setWindowFilePath(toqstr(buf.absFileName()));
1294 // Tell Qt whether the current document is changed
1295 setWindowModified(!buf.isClean());
1297 if (buf.params().shell_escape)
1298 shell_escape_->show();
1300 shell_escape_->hide();
1302 if (buf.hasReadonlyFlag())
1307 if (buf.lyxvc().inUse()) {
1308 version_control_->show();
1309 version_control_->setText(toqstr(buf.lyxvc().vcstatus()));
1311 version_control_->hide();
1315 void GuiView::on_currentWorkAreaChanged(GuiWorkArea * wa)
1317 if (d.current_work_area_)
1318 // disconnect the current work area from all slots
1319 QObject::disconnect(d.current_work_area_, nullptr, this, nullptr);
1321 disconnectBufferView();
1322 connectBufferView(wa->bufferView());
1323 connectBuffer(wa->bufferView().buffer());
1324 d.current_work_area_ = wa;
1325 QObject::connect(wa, SIGNAL(titleChanged(GuiWorkArea *)),
1326 this, SLOT(updateWindowTitle(GuiWorkArea *)));
1327 QObject::connect(wa, SIGNAL(busy(bool)),
1328 this, SLOT(setBusy(bool)));
1329 // connection of a signal to a signal
1330 QObject::connect(wa, SIGNAL(bufferViewChanged()),
1331 this, SIGNAL(bufferViewChanged()));
1332 Q_EMIT updateWindowTitle(wa);
1333 Q_EMIT bufferViewChanged();
1337 void GuiView::onBufferViewChanged()
1340 // Buffer-dependent dialogs must be updated. This is done here because
1341 // some dialogs require buffer()->text.
1346 void GuiView::on_lastWorkAreaRemoved()
1349 // We already are in a close event. Nothing more to do.
1352 if (d.splitter_->count() > 1)
1353 // We have a splitter so don't close anything.
1356 // Reset and updates the dialogs.
1357 Q_EMIT bufferViewChanged();
1362 if (lyxrc.open_buffers_in_tabs)
1363 // Nothing more to do, the window should stay open.
1366 if (guiApp->viewIds().size() > 1) {
1372 // On Mac we also close the last window because the application stay
1373 // resident in memory. On other platforms we don't close the last
1374 // window because this would quit the application.
1380 void GuiView::updateStatusBar()
1382 // let the user see the explicit message
1383 if (d.statusbar_timer_.isActive())
1390 void GuiView::showMessage()
1394 QString msg = toqstr(theGuiApp()->viewStatusMessage());
1395 if (msg.isEmpty()) {
1396 BufferView const * bv = currentBufferView();
1398 msg = toqstr(bv->cursor().currentState(devel_mode_));
1400 msg = qt_("Welcome to LyX!");
1402 statusBar()->showMessage(msg);
1406 bool GuiView::event(QEvent * e)
1410 // Useful debug code:
1411 //case QEvent::ActivationChange:
1412 //case QEvent::WindowDeactivate:
1413 //case QEvent::Paint:
1414 //case QEvent::Enter:
1415 //case QEvent::Leave:
1416 //case QEvent::HoverEnter:
1417 //case QEvent::HoverLeave:
1418 //case QEvent::HoverMove:
1419 //case QEvent::StatusTip:
1420 //case QEvent::DragEnter:
1421 //case QEvent::DragLeave:
1422 //case QEvent::Drop:
1425 case QEvent::WindowStateChange: {
1426 QWindowStateChangeEvent * ev = (QWindowStateChangeEvent*)e;
1427 bool ofstate = (ev->oldState() & Qt::WindowFullScreen);
1428 bool result = QMainWindow::event(e);
1429 bool nfstate = (windowState() & Qt::WindowFullScreen);
1430 if (!ofstate && nfstate) {
1431 LYXERR(Debug::DEBUG, "GuiView: WindowStateChange(): full-screen " << nfstate);
1432 // switch to full-screen state
1433 if (lyxrc.full_screen_statusbar)
1434 statusBar()->hide();
1435 if (lyxrc.full_screen_menubar)
1437 if (lyxrc.full_screen_toolbars) {
1438 for (auto const & tb_p : d.toolbars_)
1439 if (tb_p.second->isVisibiltyOn() && tb_p.second->isVisible())
1440 tb_p.second->hide();
1442 for (int i = 0; i != d.splitter_->count(); ++i)
1443 d.tabWorkArea(i)->setFullScreen(true);
1444 #if QT_VERSION > 0x050903
1445 //Qt's 5.9.4 ba44cdae38406c safe area measures won't allow us to go negative in margins
1446 setAttribute(Qt::WA_ContentsMarginsRespectsSafeArea, false);
1448 setContentsMargins(-2, -2, -2, -2);
1450 hideDialogs("prefs", nullptr);
1451 } else if (ofstate && !nfstate) {
1452 LYXERR(Debug::DEBUG, "GuiView: WindowStateChange(): full-screen " << nfstate);
1453 // switch back from full-screen state
1454 if (lyxrc.full_screen_statusbar && !statusBar()->isVisible())
1455 statusBar()->show();
1456 if (lyxrc.full_screen_menubar && !menuBar()->isVisible())
1458 if (lyxrc.full_screen_toolbars) {
1459 for (auto const & tb_p : d.toolbars_)
1460 if (tb_p.second->isVisibiltyOn() && !tb_p.second->isVisible())
1461 tb_p.second->show();
1464 for (int i = 0; i != d.splitter_->count(); ++i)
1465 d.tabWorkArea(i)->setFullScreen(false);
1466 #if QT_VERSION > 0x050903
1467 setAttribute(Qt::WA_ContentsMarginsRespectsSafeArea, true);
1469 setContentsMargins(0, 0, 0, 0);
1473 case QEvent::WindowActivate: {
1474 GuiView * old_view = guiApp->currentView();
1475 if (this == old_view) {
1477 return QMainWindow::event(e);
1479 if (old_view && old_view->currentBufferView()) {
1480 // save current selection to the selection buffer to allow
1481 // middle-button paste in this window.
1482 cap::saveSelection(old_view->currentBufferView()->cursor());
1484 guiApp->setCurrentView(this);
1485 if (d.current_work_area_)
1486 on_currentWorkAreaChanged(d.current_work_area_);
1490 return QMainWindow::event(e);
1493 case QEvent::ShortcutOverride: {
1495 if (isFullScreen() && menuBar()->isHidden()) {
1496 QKeyEvent * ke = static_cast<QKeyEvent*>(e);
1497 // FIXME: we should also try to detect special LyX shortcut such as
1498 // Alt-P and Alt-M. Right now there is a hack in
1499 // GuiWorkArea::processKeySym() that hides again the menubar for
1501 if (ke->modifiers() & Qt::AltModifier && ke->key() != Qt::Key_Alt) {
1503 return QMainWindow::event(e);
1506 return QMainWindow::event(e);
1509 case QEvent::ApplicationPaletteChange: {
1510 // runtime switch from/to dark mode
1512 return QMainWindow::event(e);
1516 return QMainWindow::event(e);
1520 void GuiView::resetWindowTitle()
1522 setWindowTitle(qt_("LyX"));
1525 bool GuiView::focusNextPrevChild(bool /*next*/)
1532 bool GuiView::busy() const
1538 void GuiView::setBusy(bool busy)
1540 bool const busy_before = busy_ > 0;
1541 busy ? ++busy_ : --busy_;
1542 if ((busy_ > 0) == busy_before)
1543 // busy state didn't change
1547 QApplication::setOverrideCursor(Qt::WaitCursor);
1550 QApplication::restoreOverrideCursor();
1555 void GuiView::resetCommandExecute()
1557 command_execute_ = false;
1562 double GuiView::pixelRatio() const
1564 #if QT_VERSION >= 0x050000
1565 return qt_scale_factor * devicePixelRatio();
1572 GuiWorkArea * GuiView::workArea(int index)
1574 if (TabWorkArea * twa = d.currentTabWorkArea())
1575 if (index < twa->count())
1576 return twa->workArea(index);
1581 GuiWorkArea * GuiView::workArea(Buffer & buffer)
1583 if (currentWorkArea()
1584 && ¤tWorkArea()->bufferView().buffer() == &buffer)
1585 return currentWorkArea();
1586 if (TabWorkArea * twa = d.currentTabWorkArea())
1587 return twa->workArea(buffer);
1592 GuiWorkArea * GuiView::addWorkArea(Buffer & buffer)
1594 // Automatically create a TabWorkArea if there are none yet.
1595 TabWorkArea * tab_widget = d.splitter_->count()
1596 ? d.currentTabWorkArea() : addTabWorkArea();
1597 return tab_widget->addWorkArea(buffer, *this);
1601 TabWorkArea * GuiView::addTabWorkArea()
1603 TabWorkArea * twa = new TabWorkArea;
1604 QObject::connect(twa, SIGNAL(currentWorkAreaChanged(GuiWorkArea *)),
1605 this, SLOT(on_currentWorkAreaChanged(GuiWorkArea *)));
1606 QObject::connect(twa, SIGNAL(lastWorkAreaRemoved()),
1607 this, SLOT(on_lastWorkAreaRemoved()));
1609 d.splitter_->addWidget(twa);
1610 d.stack_widget_->setCurrentWidget(d.splitter_);
1615 GuiWorkArea const * GuiView::currentWorkArea() const
1617 return d.current_work_area_;
1621 GuiWorkArea * GuiView::currentWorkArea()
1623 return d.current_work_area_;
1627 GuiWorkArea const * GuiView::currentMainWorkArea() const
1629 if (!d.currentTabWorkArea())
1631 return d.currentTabWorkArea()->currentWorkArea();
1635 GuiWorkArea * GuiView::currentMainWorkArea()
1637 if (!d.currentTabWorkArea())
1639 return d.currentTabWorkArea()->currentWorkArea();
1643 void GuiView::setCurrentWorkArea(GuiWorkArea * wa)
1645 LYXERR(Debug::DEBUG, "Setting current wa: " << wa << endl);
1647 d.current_work_area_ = nullptr;
1649 Q_EMIT bufferViewChanged();
1653 // FIXME: I've no clue why this is here and why it accesses
1654 // theGuiApp()->currentView, which might be 0 (bug 6464).
1655 // See also 27525 (vfr).
1656 if (theGuiApp()->currentView() == this
1657 && theGuiApp()->currentView()->currentWorkArea() == wa)
1660 if (currentBufferView())
1661 cap::saveSelection(currentBufferView()->cursor());
1663 theGuiApp()->setCurrentView(this);
1664 d.current_work_area_ = wa;
1666 // We need to reset this now, because it will need to be
1667 // right if the tabWorkArea gets reset in the for loop. We
1668 // will change it back if we aren't in that case.
1669 GuiWorkArea * const old_cmwa = d.current_main_work_area_;
1670 d.current_main_work_area_ = wa;
1672 for (int i = 0; i != d.splitter_->count(); ++i) {
1673 if (d.tabWorkArea(i)->setCurrentWorkArea(wa)) {
1674 LYXERR(Debug::DEBUG, "Current wa: " << currentWorkArea()
1675 << ", Current main wa: " << currentMainWorkArea());
1680 d.current_main_work_area_ = old_cmwa;
1682 LYXERR(Debug::DEBUG, "This is not a tabbed wa");
1683 on_currentWorkAreaChanged(wa);
1684 BufferView & bv = wa->bufferView();
1685 bv.cursor().fixIfBroken();
1687 wa->setUpdatesEnabled(true);
1688 LYXERR(Debug::DEBUG, "Current wa: " << currentWorkArea() << ", Current main wa: " << currentMainWorkArea());
1692 void GuiView::removeWorkArea(GuiWorkArea * wa)
1694 LASSERT(wa, return);
1695 if (wa == d.current_work_area_) {
1697 disconnectBufferView();
1698 d.current_work_area_ = nullptr;
1699 d.current_main_work_area_ = nullptr;
1702 bool found_twa = false;
1703 for (int i = 0; i != d.splitter_->count(); ++i) {
1704 TabWorkArea * twa = d.tabWorkArea(i);
1705 if (twa->removeWorkArea(wa)) {
1706 // Found in this tab group, and deleted the GuiWorkArea.
1708 if (twa->count() != 0) {
1709 if (d.current_work_area_ == nullptr)
1710 // This means that we are closing the current GuiWorkArea, so
1711 // switch to the next GuiWorkArea in the found TabWorkArea.
1712 setCurrentWorkArea(twa->currentWorkArea());
1714 // No more WorkAreas in this tab group, so delete it.
1721 // It is not a tabbed work area (i.e., the search work area), so it
1722 // should be deleted by other means.
1723 LASSERT(found_twa, return);
1725 if (d.current_work_area_ == nullptr) {
1726 if (d.splitter_->count() != 0) {
1727 TabWorkArea * twa = d.currentTabWorkArea();
1728 setCurrentWorkArea(twa->currentWorkArea());
1730 // No more work areas, switch to the background widget.
1731 setCurrentWorkArea(nullptr);
1737 LayoutBox * GuiView::getLayoutDialog() const
1743 void GuiView::updateLayoutList()
1746 d.layout_->updateContents(false);
1750 void GuiView::updateToolbars()
1752 if (d.current_work_area_) {
1754 if (d.current_work_area_->bufferView().cursor().inMathed()
1755 && !d.current_work_area_->bufferView().cursor().inRegexped())
1756 context |= Toolbars::MATH;
1757 if (lyx::getStatus(FuncRequest(LFUN_LAYOUT_TABULAR)).enabled())
1758 context |= Toolbars::TABLE;
1759 if (currentBufferView()->buffer().areChangesPresent()
1760 || (lyx::getStatus(FuncRequest(LFUN_CHANGES_TRACK)).enabled()
1761 && lyx::getStatus(FuncRequest(LFUN_CHANGES_TRACK)).onOff(true))
1762 || (lyx::getStatus(FuncRequest(LFUN_CHANGES_OUTPUT)).enabled()
1763 && lyx::getStatus(FuncRequest(LFUN_CHANGES_OUTPUT)).onOff(true)))
1764 context |= Toolbars::REVIEW;
1765 if (lyx::getStatus(FuncRequest(LFUN_IN_MATHMACROTEMPLATE)).enabled())
1766 context |= Toolbars::MATHMACROTEMPLATE;
1767 if (lyx::getStatus(FuncRequest(LFUN_IN_IPA)).enabled())
1768 context |= Toolbars::IPA;
1769 if (command_execute_)
1770 context |= Toolbars::MINIBUFFER;
1771 if (minibuffer_focus_) {
1772 context |= Toolbars::MINIBUFFER_FOCUS;
1773 minibuffer_focus_ = false;
1776 for (auto const & tb_p : d.toolbars_)
1777 tb_p.second->update(context);
1779 for (auto const & tb_p : d.toolbars_)
1780 tb_p.second->update();
1784 void GuiView::refillToolbars()
1786 for (auto const & tb_p : d.toolbars_)
1787 tb_p.second->refill();
1791 void GuiView::setBuffer(Buffer * newBuffer, bool switch_to)
1793 LYXERR(Debug::DEBUG, "Setting buffer: " << newBuffer << endl);
1794 LASSERT(newBuffer, return);
1796 GuiWorkArea * wa = workArea(*newBuffer);
1797 if (wa == nullptr) {
1799 newBuffer->masterBuffer()->updateBuffer();
1801 wa = addWorkArea(*newBuffer);
1802 // scroll to the position when the BufferView was last closed
1803 if (lyxrc.use_lastfilepos) {
1804 LastFilePosSection::FilePos filepos =
1805 theSession().lastFilePos().load(newBuffer->fileName());
1806 wa->bufferView().moveToPosition(filepos.pit, filepos.pos, 0, 0);
1809 //Disconnect the old buffer...there's no new one.
1812 connectBuffer(*newBuffer);
1813 connectBufferView(wa->bufferView());
1815 setCurrentWorkArea(wa);
1819 void GuiView::connectBuffer(Buffer & buf)
1821 buf.setGuiDelegate(this);
1825 void GuiView::disconnectBuffer()
1827 if (d.current_work_area_)
1828 d.current_work_area_->bufferView().buffer().setGuiDelegate(nullptr);
1832 void GuiView::connectBufferView(BufferView & bv)
1834 bv.setGuiDelegate(this);
1838 void GuiView::disconnectBufferView()
1840 if (d.current_work_area_)
1841 d.current_work_area_->bufferView().setGuiDelegate(nullptr);
1845 void GuiView::errors(string const & error_type, bool from_master)
1847 BufferView const * const bv = currentBufferView();
1851 ErrorList const & el = from_master ?
1852 bv->buffer().masterBuffer()->errorList(error_type) :
1853 bv->buffer().errorList(error_type);
1858 string err = error_type;
1860 err = "from_master|" + error_type;
1861 showDialog("errorlist", err);
1865 void GuiView::updateTocItem(string const & type, DocIterator const & dit)
1867 d.toc_models_.updateItem(toqstr(type), dit);
1871 void GuiView::structureChanged()
1873 // This is called from the Buffer, which has no way to ensure that cursors
1874 // in BufferView remain valid.
1875 if (documentBufferView())
1876 documentBufferView()->cursor().sanitize();
1877 // FIXME: This is slightly expensive, though less than the tocBackend update
1878 // (#9880). This also resets the view in the Toc Widget (#6675).
1879 d.toc_models_.reset(documentBufferView());
1880 // Navigator needs more than a simple update in this case. It needs to be
1882 updateDialog("toc", "");
1886 void GuiView::updateDialog(string const & name, string const & sdata)
1888 if (!isDialogVisible(name))
1891 map<string, DialogPtr>::const_iterator it = d.dialogs_.find(name);
1892 if (it == d.dialogs_.end())
1895 Dialog * const dialog = it->second.get();
1896 if (dialog->isVisibleView())
1897 dialog->initialiseParams(sdata);
1901 BufferView * GuiView::documentBufferView()
1903 return currentMainWorkArea()
1904 ? ¤tMainWorkArea()->bufferView()
1909 BufferView const * GuiView::documentBufferView() const
1911 return currentMainWorkArea()
1912 ? ¤tMainWorkArea()->bufferView()
1917 BufferView * GuiView::currentBufferView()
1919 return d.current_work_area_ ? &d.current_work_area_->bufferView() : nullptr;
1923 BufferView const * GuiView::currentBufferView() const
1925 return d.current_work_area_ ? &d.current_work_area_->bufferView() : nullptr;
1929 docstring GuiView::GuiViewPrivate::autosaveAndDestroy(
1930 Buffer const * orig, Buffer * clone)
1932 bool const success = clone->autoSave();
1934 busyBuffers.remove(orig);
1936 ? _("Automatic save done.")
1937 : _("Automatic save failed!");
1941 void GuiView::autoSave()
1943 LYXERR(Debug::INFO, "Running autoSave()");
1945 Buffer * buffer = documentBufferView()
1946 ? &documentBufferView()->buffer() : nullptr;
1948 resetAutosaveTimers();
1952 GuiViewPrivate::busyBuffers.insert(buffer);
1953 QFuture<docstring> f = QtConcurrent::run(GuiViewPrivate::autosaveAndDestroy,
1954 buffer, buffer->cloneBufferOnly());
1955 d.autosave_watcher_.setFuture(f);
1956 resetAutosaveTimers();
1960 void GuiView::resetAutosaveTimers()
1963 d.autosave_timeout_.restart();
1967 bool GuiView::getStatus(FuncRequest const & cmd, FuncStatus & flag)
1970 Buffer * buf = currentBufferView()
1971 ? ¤tBufferView()->buffer() : nullptr;
1972 Buffer * doc_buffer = documentBufferView()
1973 ? &(documentBufferView()->buffer()) : nullptr;
1976 /* In LyX/Mac, when a dialog is open, the menus of the
1977 application can still be accessed without giving focus to
1978 the main window. In this case, we want to disable the menu
1979 entries that are buffer-related.
1980 This code must not be used on Linux and Windows, since it
1981 would disable buffer-related entries when hovering over the
1982 menu (see bug #9574).
1984 if (cmd.origin() == FuncRequest::MENU && !hasFocus()) {
1990 // Check whether we need a buffer
1991 if (!lyxaction.funcHasFlag(cmd.action(), LyXAction::NoBuffer) && !buf) {
1992 // no, exit directly
1993 flag.message(from_utf8(N_("Command not allowed with"
1994 "out any document open")));
1995 flag.setEnabled(false);
1999 if (cmd.origin() == FuncRequest::TOC) {
2000 GuiToc * toc = static_cast<GuiToc*>(findOrBuild("toc", false));
2001 if (!toc || !toc->getStatus(documentBufferView()->cursor(), cmd, flag))
2002 flag.setEnabled(false);
2006 switch(cmd.action()) {
2007 case LFUN_BUFFER_IMPORT:
2010 case LFUN_MASTER_BUFFER_EXPORT:
2012 && (doc_buffer->parent() != nullptr
2013 || doc_buffer->hasChildren())
2014 && !d.processing_thread_watcher_.isRunning()
2015 // this launches a dialog, which would be in the wrong Buffer
2016 && !(::lyx::operator==(cmd.argument(), "custom"));
2019 case LFUN_MASTER_BUFFER_UPDATE:
2020 case LFUN_MASTER_BUFFER_VIEW:
2022 && (doc_buffer->parent() != nullptr
2023 || doc_buffer->hasChildren())
2024 && !d.processing_thread_watcher_.isRunning();
2027 case LFUN_BUFFER_UPDATE:
2028 case LFUN_BUFFER_VIEW: {
2029 if (!doc_buffer || d.processing_thread_watcher_.isRunning()) {
2033 string format = to_utf8(cmd.argument());
2034 if (cmd.argument().empty())
2035 format = doc_buffer->params().getDefaultOutputFormat();
2036 enable = doc_buffer->params().isExportable(format, true);
2040 case LFUN_BUFFER_RELOAD:
2041 enable = doc_buffer && !doc_buffer->isUnnamed()
2042 && doc_buffer->fileName().exists()
2043 && (!doc_buffer->isClean() || doc_buffer->notifiesExternalModification());
2046 case LFUN_BUFFER_RESET_EXPORT:
2047 enable = doc_buffer != nullptr;
2050 case LFUN_BUFFER_CHILD_OPEN:
2051 enable = doc_buffer != nullptr;
2054 case LFUN_MASTER_BUFFER_FORALL: {
2055 if (doc_buffer == nullptr) {
2056 flag.message(from_utf8(N_("Command not allowed without a buffer open")));
2060 FuncRequest const cmdToPass = lyxaction.lookupFunc(cmd.getLongArg(0));
2061 if (cmdToPass.action() == LFUN_UNKNOWN_ACTION) {
2062 flag.message(from_utf8(N_("Invalid argument of master-buffer-forall")));
2067 for (Buffer * buf : doc_buffer->allRelatives()) {
2068 GuiWorkArea * wa = workArea(*buf);
2071 if (wa->bufferView().getStatus(cmdToPass, flag)) {
2072 enable = flag.enabled();
2079 case LFUN_BUFFER_WRITE:
2080 enable = doc_buffer && (doc_buffer->isUnnamed()
2081 || (!doc_buffer->isClean()
2082 || cmd.argument() == "force"));
2085 //FIXME: This LFUN should be moved to GuiApplication.
2086 case LFUN_BUFFER_WRITE_ALL: {
2087 // We enable the command only if there are some modified buffers
2088 Buffer * first = theBufferList().first();
2093 // We cannot use a for loop as the buffer list is a cycle.
2095 if (!b->isClean()) {
2099 b = theBufferList().next(b);
2100 } while (b != first);
2104 case LFUN_BUFFER_EXTERNAL_MODIFICATION_CLEAR:
2105 enable = doc_buffer && doc_buffer->notifiesExternalModification();
2108 case LFUN_BUFFER_EXPORT: {
2109 if (!doc_buffer || d.processing_thread_watcher_.isRunning()) {
2113 return doc_buffer->getStatus(cmd, flag);
2116 case LFUN_BUFFER_EXPORT_AS:
2117 if (!doc_buffer || d.processing_thread_watcher_.isRunning()) {
2122 case LFUN_BUFFER_WRITE_AS:
2123 case LFUN_BUFFER_WRITE_AS_TEMPLATE:
2124 enable = doc_buffer != nullptr;
2127 case LFUN_EXPORT_CANCEL:
2128 enable = d.processing_thread_watcher_.isRunning();
2131 case LFUN_BUFFER_CLOSE:
2132 case LFUN_VIEW_CLOSE:
2133 enable = doc_buffer != nullptr;
2136 case LFUN_BUFFER_CLOSE_ALL:
2137 enable = theBufferList().last() != theBufferList().first();
2140 case LFUN_BUFFER_CHKTEX: {
2141 // hide if we have no checktex command
2142 if (lyxrc.chktex_command.empty()) {
2143 flag.setUnknown(true);
2147 if (!doc_buffer || !doc_buffer->params().isLatex()
2148 || d.processing_thread_watcher_.isRunning()) {
2149 // grey out, don't hide
2157 case LFUN_VIEW_SPLIT:
2158 if (cmd.getArg(0) == "vertical")
2159 enable = doc_buffer && (d.splitter_->count() == 1 ||
2160 d.splitter_->orientation() == Qt::Vertical);
2162 enable = doc_buffer && (d.splitter_->count() == 1 ||
2163 d.splitter_->orientation() == Qt::Horizontal);
2166 case LFUN_TAB_GROUP_CLOSE:
2167 enable = d.tabWorkAreaCount() > 1;
2170 case LFUN_DEVEL_MODE_TOGGLE:
2171 flag.setOnOff(devel_mode_);
2174 case LFUN_TOOLBAR_SET: {
2175 string const name = cmd.getArg(0);
2176 string const state = cmd.getArg(1);
2177 if (name.empty() || state.empty()) {
2179 docstring const msg =
2180 _("Function toolbar-set requires two arguments!");
2184 if (state != "on" && state != "off" && state != "auto") {
2186 docstring const msg =
2187 bformat(_("Invalid argument \"%1$s\" to function toolbar-set!"),
2192 if (GuiToolbar * t = toolbar(name)) {
2193 bool const autovis = t->visibility() & Toolbars::AUTO;
2195 flag.setOnOff(t->isVisible() && !autovis);
2196 else if (state == "off")
2197 flag.setOnOff(!t->isVisible() && !autovis);
2198 else if (state == "auto")
2199 flag.setOnOff(autovis);
2202 docstring const msg =
2203 bformat(_("Unknown toolbar \"%1$s\""), from_utf8(name));
2209 case LFUN_TOOLBAR_TOGGLE: {
2210 string const name = cmd.getArg(0);
2211 if (GuiToolbar * t = toolbar(name))
2212 flag.setOnOff(t->isVisible());
2215 docstring const msg =
2216 bformat(_("Unknown toolbar \"%1$s\""), from_utf8(name));
2222 case LFUN_TOOLBAR_MOVABLE: {
2223 string const name = cmd.getArg(0);
2224 // use negation since locked == !movable
2226 // toolbar name * locks all toolbars
2227 flag.setOnOff(!toolbarsMovable_);
2228 else if (GuiToolbar * t = toolbar(name))
2229 flag.setOnOff(!(t->isMovable()));
2232 docstring const msg =
2233 bformat(_("Unknown toolbar \"%1$s\""), from_utf8(name));
2239 case LFUN_ICON_SIZE:
2240 flag.setOnOff(d.iconSize(cmd.argument()) == iconSize());
2243 case LFUN_DROP_LAYOUTS_CHOICE:
2244 enable = buf != nullptr;
2247 case LFUN_UI_TOGGLE:
2248 flag.setOnOff(isFullScreen());
2251 case LFUN_DIALOG_DISCONNECT_INSET:
2254 case LFUN_DIALOG_HIDE:
2255 // FIXME: should we check if the dialog is shown?
2258 case LFUN_DIALOG_TOGGLE:
2259 flag.setOnOff(isDialogVisible(cmd.getArg(0)));
2262 case LFUN_DIALOG_SHOW: {
2263 string const name = cmd.getArg(0);
2265 enable = name == "aboutlyx"
2266 || name == "file" //FIXME: should be removed.
2267 || name == "lyxfiles"
2269 || name == "texinfo"
2270 || name == "progress"
2271 || name == "compare";
2272 else if (name == "character" || name == "symbols"
2273 || name == "mathdelimiter" || name == "mathmatrix") {
2274 if (!buf || buf->isReadonly())
2277 Cursor const & cur = currentBufferView()->cursor();
2278 enable = !(cur.inTexted() && cur.paragraph().isPassThru());
2281 else if (name == "latexlog")
2282 enable = FileName(doc_buffer->logName()).isReadableFile();
2283 else if (name == "spellchecker")
2284 enable = theSpellChecker()
2285 && !doc_buffer->text().empty();
2286 else if (name == "vclog")
2287 enable = doc_buffer->lyxvc().inUse();
2291 case LFUN_DIALOG_UPDATE: {
2292 string const name = cmd.getArg(0);
2294 enable = name == "prefs";
2298 case LFUN_COMMAND_EXECUTE:
2300 case LFUN_MENU_OPEN:
2301 // Nothing to check.
2304 case LFUN_COMPLETION_INLINE:
2305 if (!d.current_work_area_
2306 || !d.current_work_area_->completer().inlinePossible(
2307 currentBufferView()->cursor()))
2311 case LFUN_COMPLETION_POPUP:
2312 if (!d.current_work_area_
2313 || !d.current_work_area_->completer().popupPossible(
2314 currentBufferView()->cursor()))
2319 if (!d.current_work_area_
2320 || !d.current_work_area_->completer().inlinePossible(
2321 currentBufferView()->cursor()))
2325 case LFUN_COMPLETION_ACCEPT:
2326 if (!d.current_work_area_
2327 || (!d.current_work_area_->completer().popupVisible()
2328 && !d.current_work_area_->completer().inlineVisible()
2329 && !d.current_work_area_->completer().completionAvailable()))
2333 case LFUN_COMPLETION_CANCEL:
2334 if (!d.current_work_area_
2335 || (!d.current_work_area_->completer().popupVisible()
2336 && !d.current_work_area_->completer().inlineVisible()))
2340 case LFUN_BUFFER_ZOOM_OUT:
2341 case LFUN_BUFFER_ZOOM_IN: {
2342 // only diff between these two is that the default for ZOOM_OUT
2344 bool const neg_zoom =
2345 convert<int>(cmd.argument()) < 0 ||
2346 (cmd.action() == LFUN_BUFFER_ZOOM_OUT && cmd.argument().empty());
2347 if (lyxrc.currentZoom <= zoom_min_ && neg_zoom) {
2348 docstring const msg =
2349 bformat(_("Zoom level cannot be less than %1$d%."), zoom_min_);
2353 enable = doc_buffer;
2357 case LFUN_BUFFER_ZOOM: {
2358 bool const less_than_min_zoom =
2359 !cmd.argument().empty() && convert<int>(cmd.argument()) < zoom_min_;
2360 if (lyxrc.currentZoom <= zoom_min_ && less_than_min_zoom) {
2361 docstring const msg =
2362 bformat(_("Zoom level cannot be less than %1$d%."), zoom_min_);
2367 enable = doc_buffer;
2371 case LFUN_BUFFER_MOVE_NEXT:
2372 case LFUN_BUFFER_MOVE_PREVIOUS:
2373 // we do not cycle when moving
2374 case LFUN_BUFFER_NEXT:
2375 case LFUN_BUFFER_PREVIOUS:
2376 // because we cycle, it doesn't matter whether on first or last
2377 enable = (d.currentTabWorkArea()->count() > 1);
2379 case LFUN_BUFFER_SWITCH:
2380 // toggle on the current buffer, but do not toggle off
2381 // the other ones (is that a good idea?)
2383 && to_utf8(cmd.argument()) == doc_buffer->absFileName())
2384 flag.setOnOff(true);
2387 case LFUN_VC_REGISTER:
2388 enable = doc_buffer && !doc_buffer->lyxvc().inUse();
2390 case LFUN_VC_RENAME:
2391 enable = doc_buffer && doc_buffer->lyxvc().renameEnabled();
2394 enable = doc_buffer && doc_buffer->lyxvc().copyEnabled();
2396 case LFUN_VC_CHECK_IN:
2397 enable = doc_buffer && doc_buffer->lyxvc().checkInEnabled();
2399 case LFUN_VC_CHECK_OUT:
2400 enable = doc_buffer && doc_buffer->lyxvc().checkOutEnabled();
2402 case LFUN_VC_LOCKING_TOGGLE:
2403 enable = doc_buffer && !doc_buffer->hasReadonlyFlag()
2404 && doc_buffer->lyxvc().lockingToggleEnabled();
2405 flag.setOnOff(enable && doc_buffer->lyxvc().locking());
2407 case LFUN_VC_REVERT:
2408 enable = doc_buffer && doc_buffer->lyxvc().inUse()
2409 && !doc_buffer->hasReadonlyFlag();
2411 case LFUN_VC_UNDO_LAST:
2412 enable = doc_buffer && doc_buffer->lyxvc().undoLastEnabled();
2414 case LFUN_VC_REPO_UPDATE:
2415 enable = doc_buffer && doc_buffer->lyxvc().repoUpdateEnabled();
2417 case LFUN_VC_COMMAND: {
2418 if (cmd.argument().empty())
2420 if (!doc_buffer && contains(cmd.getArg(0), 'D'))
2424 case LFUN_VC_COMPARE:
2425 enable = doc_buffer && doc_buffer->lyxvc().prepareFileRevisionEnabled();
2428 case LFUN_SERVER_GOTO_FILE_ROW:
2429 case LFUN_LYX_ACTIVATE:
2430 case LFUN_WINDOW_RAISE:
2432 case LFUN_FORWARD_SEARCH:
2433 enable = !(lyxrc.forward_search_dvi.empty() && lyxrc.forward_search_pdf.empty());
2436 case LFUN_FILE_INSERT_PLAINTEXT:
2437 case LFUN_FILE_INSERT_PLAINTEXT_PARA:
2438 enable = documentBufferView() && documentBufferView()->cursor().inTexted();
2441 case LFUN_SPELLING_CONTINUOUSLY:
2442 flag.setOnOff(lyxrc.spellcheck_continuously);
2445 case LFUN_CITATION_OPEN:
2454 flag.setEnabled(false);
2460 static FileName selectTemplateFile()
2462 FileDialog dlg(qt_("Select template file"));
2463 dlg.setButton1(qt_("D&ocuments"), toqstr(lyxrc.document_path));
2464 dlg.setButton2(qt_("&Templates"), toqstr(lyxrc.template_path));
2466 FileDialog::Result result = dlg.open(toqstr(lyxrc.template_path),
2467 QStringList(qt_("LyX Documents (*.lyx)")));
2469 if (result.first == FileDialog::Later)
2471 if (result.second.isEmpty())
2473 return FileName(fromqstr(result.second));
2477 Buffer * GuiView::loadDocument(FileName const & filename, bool tolastfiles)
2481 Buffer * newBuffer = nullptr;
2483 newBuffer = checkAndLoadLyXFile(filename);
2484 } catch (ExceptionMessage const &) {
2491 message(_("Document not loaded."));
2495 setBuffer(newBuffer);
2496 newBuffer->errors("Parse");
2499 theSession().lastFiles().add(filename);
2500 theSession().writeFile();
2507 void GuiView::openDocument(string const & fname)
2509 string initpath = lyxrc.document_path;
2511 if (documentBufferView()) {
2512 string const trypath = documentBufferView()->buffer().filePath();
2513 // If directory is writeable, use this as default.
2514 if (FileName(trypath).isDirWritable())
2520 if (fname.empty()) {
2521 FileDialog dlg(qt_("Select document to open"));
2522 dlg.setButton1(qt_("D&ocuments"), toqstr(lyxrc.document_path));
2523 dlg.setButton2(qt_("&Examples"), toqstr(lyxrc.example_path));
2525 QStringList const filter(qt_("LyX Documents (*.lyx)"));
2526 FileDialog::Result result =
2527 dlg.open(toqstr(initpath), filter);
2529 if (result.first == FileDialog::Later)
2532 filename = fromqstr(result.second);
2534 // check selected filename
2535 if (filename.empty()) {
2536 message(_("Canceled."));
2542 // get absolute path of file and add ".lyx" to the filename if
2544 FileName const fullname =
2545 fileSearch(string(), filename, "lyx", support::may_not_exist);
2546 if (!fullname.empty())
2547 filename = fullname.absFileName();
2549 if (!fullname.onlyPath().isDirectory()) {
2550 Alert::warning(_("Invalid filename"),
2551 bformat(_("The directory in the given path\n%1$s\ndoes not exist."),
2552 from_utf8(fullname.absFileName())));
2556 // if the file doesn't exist and isn't already open (bug 6645),
2557 // let the user create one
2558 if (!fullname.exists() && !theBufferList().exists(fullname) &&
2559 !LyXVC::file_not_found_hook(fullname)) {
2560 // the user specifically chose this name. Believe him.
2561 Buffer * const b = newFile(filename, string(), true);
2567 docstring const disp_fn = makeDisplayPath(filename);
2568 message(bformat(_("Opening document %1$s..."), disp_fn));
2571 Buffer * buf = loadDocument(fullname);
2573 str2 = bformat(_("Document %1$s opened."), disp_fn);
2574 if (buf->lyxvc().inUse())
2575 str2 += " " + from_utf8(buf->lyxvc().versionString()) +
2576 " " + _("Version control detected.");
2578 str2 = bformat(_("Could not open document %1$s"), disp_fn);
2583 // FIXME: clean that
2584 static bool import(GuiView * lv, FileName const & filename,
2585 string const & format, ErrorList & errorList)
2587 FileName const lyxfile(support::changeExtension(filename.absFileName(), ".lyx"));
2589 string loader_format;
2590 vector<string> loaders = theConverters().loaders();
2591 if (find(loaders.begin(), loaders.end(), format) == loaders.end()) {
2592 for (string const & loader : loaders) {
2593 if (!theConverters().isReachable(format, loader))
2596 string const tofile =
2597 support::changeExtension(filename.absFileName(),
2598 theFormats().extension(loader));
2599 if (theConverters().convert(nullptr, filename, FileName(tofile),
2600 filename, format, loader, errorList) != Converters::SUCCESS)
2602 loader_format = loader;
2605 if (loader_format.empty()) {
2606 frontend::Alert::error(_("Couldn't import file"),
2607 bformat(_("No information for importing the format %1$s."),
2608 translateIfPossible(theFormats().prettyName(format))));
2612 loader_format = format;
2614 if (loader_format == "lyx") {
2615 Buffer * buf = lv->loadDocument(lyxfile);
2619 Buffer * const b = newFile(lyxfile.absFileName(), string(), true);
2623 bool as_paragraphs = loader_format == "textparagraph";
2624 string filename2 = (loader_format == format) ? filename.absFileName()
2625 : support::changeExtension(filename.absFileName(),
2626 theFormats().extension(loader_format));
2627 lv->currentBufferView()->insertPlaintextFile(FileName(filename2),
2629 guiApp->setCurrentView(lv);
2630 lyx::dispatch(FuncRequest(LFUN_MARK_OFF));
2637 void GuiView::importDocument(string const & argument)
2640 string filename = split(argument, format, ' ');
2642 LYXERR(Debug::INFO, format << " file: " << filename);
2644 // need user interaction
2645 if (filename.empty()) {
2646 string initpath = lyxrc.document_path;
2647 if (documentBufferView()) {
2648 string const trypath = documentBufferView()->buffer().filePath();
2649 // If directory is writeable, use this as default.
2650 if (FileName(trypath).isDirWritable())
2654 docstring const text = bformat(_("Select %1$s file to import"),
2655 translateIfPossible(theFormats().prettyName(format)));
2657 FileDialog dlg(toqstr(text));
2658 dlg.setButton1(qt_("D&ocuments"), toqstr(lyxrc.document_path));
2659 dlg.setButton2(qt_("&Examples"), toqstr(lyxrc.example_path));
2661 docstring filter = translateIfPossible(theFormats().prettyName(format));
2664 filter += from_utf8(theFormats().extensions(format));
2667 FileDialog::Result result =
2668 dlg.open(toqstr(initpath), fileFilters(toqstr(filter)));
2670 if (result.first == FileDialog::Later)
2673 filename = fromqstr(result.second);
2675 // check selected filename
2676 if (filename.empty())
2677 message(_("Canceled."));
2680 if (filename.empty())
2683 // get absolute path of file
2684 FileName const fullname(support::makeAbsPath(filename));
2686 // Can happen if the user entered a path into the dialog
2688 if (fullname.onlyFileName().empty()) {
2689 docstring msg = bformat(_("The file name '%1$s' is invalid!\n"
2690 "Aborting import."),
2691 from_utf8(fullname.absFileName()));
2692 frontend::Alert::error(_("File name error"), msg);
2693 message(_("Canceled."));
2698 FileName const lyxfile(support::changeExtension(fullname.absFileName(), ".lyx"));
2700 // Check if the document already is open
2701 Buffer * buf = theBufferList().getBuffer(lyxfile);
2704 if (!closeBuffer()) {
2705 message(_("Canceled."));
2710 docstring const displaypath = makeDisplayPath(lyxfile.absFileName(), 30);
2712 // if the file exists already, and we didn't do
2713 // -i lyx thefile.lyx, warn
2714 if (lyxfile.exists() && fullname != lyxfile) {
2716 docstring text = bformat(_("The document %1$s already exists.\n\n"
2717 "Do you want to overwrite that document?"), displaypath);
2718 int const ret = Alert::prompt(_("Overwrite document?"),
2719 text, 0, 1, _("&Overwrite"), _("&Cancel"));
2722 message(_("Canceled."));
2727 message(bformat(_("Importing %1$s..."), displaypath));
2728 ErrorList errorList;
2729 if (import(this, fullname, format, errorList))
2730 message(_("imported."));
2732 message(_("file not imported!"));
2734 // FIXME (Abdel 12/08/06): Is there a need to display the error list here?
2738 void GuiView::newDocument(string const & filename, string templatefile,
2741 FileName initpath(lyxrc.document_path);
2742 if (documentBufferView()) {
2743 FileName const trypath(documentBufferView()->buffer().filePath());
2744 // If directory is writeable, use this as default.
2745 if (trypath.isDirWritable())
2749 if (from_template) {
2750 if (templatefile.empty())
2751 templatefile = selectTemplateFile().absFileName();
2752 if (templatefile.empty())
2757 if (filename.empty())
2758 b = newUnnamedFile(initpath, to_utf8(_("newfile")), templatefile);
2760 b = newFile(filename, templatefile, true);
2765 // If no new document could be created, it is unsure
2766 // whether there is a valid BufferView.
2767 if (currentBufferView())
2768 // Ensure the cursor is correctly positioned on screen.
2769 currentBufferView()->showCursor();
2773 bool GuiView::insertLyXFile(docstring const & fname, bool ignorelang)
2775 BufferView * bv = documentBufferView();
2780 FileName filename(to_utf8(fname));
2781 if (filename.empty()) {
2782 // Launch a file browser
2784 string initpath = lyxrc.document_path;
2785 string const trypath = bv->buffer().filePath();
2786 // If directory is writeable, use this as default.
2787 if (FileName(trypath).isDirWritable())
2791 FileDialog dlg(qt_("Select LyX document to insert"));
2792 dlg.setButton1(qt_("D&ocuments"), toqstr(lyxrc.document_path));
2793 dlg.setButton2(qt_("&Examples"), toqstr(lyxrc.example_path));
2795 FileDialog::Result result = dlg.open(toqstr(initpath),
2796 QStringList(qt_("LyX Documents (*.lyx)")));
2798 if (result.first == FileDialog::Later)
2802 filename.set(fromqstr(result.second));
2804 // check selected filename
2805 if (filename.empty()) {
2806 // emit message signal.
2807 message(_("Canceled."));
2812 bv->insertLyXFile(filename, ignorelang);
2813 bv->buffer().errors("Parse");
2818 string const GuiView::getTemplatesPath(Buffer & b)
2820 // We start off with the user's templates path
2821 string result = addPath(package().user_support().absFileName(), "templates");
2822 // Check for the document language
2823 string const langcode = b.params().language->code();
2824 string const shortcode = langcode.substr(0, 2);
2825 if (!langcode.empty() && shortcode != "en") {
2826 string subpath = addPath(result, shortcode);
2827 string subpath_long = addPath(result, langcode);
2828 // If we have a subdirectory for the language already,
2830 FileName sp = FileName(subpath);
2831 if (sp.isDirectory())
2833 else if (FileName(subpath_long).isDirectory())
2834 result = subpath_long;
2836 // Ask whether we should create such a subdirectory
2837 docstring const text =
2838 bformat(_("It is suggested to save the template in a subdirectory\n"
2839 "appropriate to the document language (%1$s).\n"
2840 "This subdirectory does not exists yet.\n"
2841 "Do you want to create it?"),
2842 _(b.params().language->display()));
2843 if (Alert::prompt(_("Create Language Directory?"),
2844 text, 0, 1, _("&Yes, Create"), _("&No, Save Template in Parent Directory")) == 0) {
2845 // If the user agreed, we try to create it and report if this failed.
2846 if (!sp.createDirectory(0777))
2847 Alert::error(_("Subdirectory creation failed!"),
2848 _("Could not create subdirectory.\n"
2849 "The template will be saved in the parent directory."));
2855 // Do we have a layout category?
2856 string const cat = b.params().baseClass() ?
2857 b.params().baseClass()->category()
2860 string subpath = addPath(result, cat);
2861 // If we have a subdirectory for the category already,
2863 FileName sp = FileName(subpath);
2864 if (sp.isDirectory())
2867 // Ask whether we should create such a subdirectory
2868 docstring const text =
2869 bformat(_("It is suggested to save the template in a subdirectory\n"
2870 "appropriate to the layout category (%1$s).\n"
2871 "This subdirectory does not exists yet.\n"
2872 "Do you want to create it?"),
2874 if (Alert::prompt(_("Create Category Directory?"),
2875 text, 0, 1, _("&Yes, Create"), _("&No, Save Template in Parent Directory")) == 0) {
2876 // If the user agreed, we try to create it and report if this failed.
2877 if (!sp.createDirectory(0777))
2878 Alert::error(_("Subdirectory creation failed!"),
2879 _("Could not create subdirectory.\n"
2880 "The template will be saved in the parent directory."));
2890 bool GuiView::renameBuffer(Buffer & b, docstring const & newname, RenameKind kind)
2892 FileName fname = b.fileName();
2893 FileName const oldname = fname;
2894 bool const as_template = (kind == LV_WRITE_AS_TEMPLATE);
2896 if (!newname.empty()) {
2899 fname = support::makeAbsPath(to_utf8(newname), getTemplatesPath(b));
2901 fname = support::makeAbsPath(to_utf8(newname),
2902 oldname.onlyPath().absFileName());
2904 // Switch to this Buffer.
2907 // No argument? Ask user through dialog.
2909 QString const title = as_template ? qt_("Choose a filename to save template as")
2910 : qt_("Choose a filename to save document as");
2911 FileDialog dlg(title);
2912 dlg.setButton1(qt_("D&ocuments"), toqstr(lyxrc.document_path));
2913 dlg.setButton2(qt_("&Templates"), toqstr(lyxrc.template_path));
2915 if (!isLyXFileName(fname.absFileName()))
2916 fname.changeExtension(".lyx");
2918 string const path = as_template ?
2920 : fname.onlyPath().absFileName();
2921 FileDialog::Result result =
2922 dlg.save(toqstr(path),
2923 QStringList(qt_("LyX Documents (*.lyx)")),
2924 toqstr(fname.onlyFileName()));
2926 if (result.first == FileDialog::Later)
2929 fname.set(fromqstr(result.second));
2934 if (!isLyXFileName(fname.absFileName()))
2935 fname.changeExtension(".lyx");
2938 // fname is now the new Buffer location.
2940 // if there is already a Buffer open with this name, we do not want
2941 // to have another one. (the second test makes sure we're not just
2942 // trying to overwrite ourselves, which is fine.)
2943 if (theBufferList().exists(fname) && fname != oldname
2944 && theBufferList().getBuffer(fname) != &b) {
2945 docstring const text =
2946 bformat(_("The file\n%1$s\nis already open in your current session.\n"
2947 "Please close it before attempting to overwrite it.\n"
2948 "Do you want to choose a new filename?"),
2949 from_utf8(fname.absFileName()));
2950 int const ret = Alert::prompt(_("Chosen File Already Open"),
2951 text, 0, 1, _("&Rename"), _("&Cancel"));
2953 case 0: return renameBuffer(b, docstring(), kind);
2954 case 1: return false;
2959 bool const existsLocal = fname.exists();
2960 bool const existsInVC = LyXVC::fileInVC(fname);
2961 if (existsLocal || existsInVC) {
2962 docstring const file = makeDisplayPath(fname.absFileName(), 30);
2963 if (kind != LV_WRITE_AS && existsInVC) {
2964 // renaming to a name that is already in VC
2966 docstring text = bformat(_("The document %1$s "
2967 "is already registered.\n\n"
2968 "Do you want to choose a new name?"),
2970 docstring const title = (kind == LV_VC_RENAME) ?
2971 _("Rename document?") : _("Copy document?");
2972 docstring const button = (kind == LV_VC_RENAME) ?
2973 _("&Rename") : _("&Copy");
2974 int const ret = Alert::prompt(title, text, 0, 1,
2975 button, _("&Cancel"));
2977 case 0: return renameBuffer(b, docstring(), kind);
2978 case 1: return false;
2983 docstring text = bformat(_("The document %1$s "
2984 "already exists.\n\n"
2985 "Do you want to overwrite that document?"),
2987 int const ret = Alert::prompt(_("Overwrite document?"),
2988 text, 0, 2, _("&Overwrite"),
2989 _("&Rename"), _("&Cancel"));
2992 case 1: return renameBuffer(b, docstring(), kind);
2993 case 2: return false;
2999 case LV_VC_RENAME: {
3000 string msg = b.lyxvc().rename(fname);
3003 message(from_utf8(msg));
3007 string msg = b.lyxvc().copy(fname);
3010 message(from_utf8(msg));
3014 case LV_WRITE_AS_TEMPLATE:
3017 // LyXVC created the file already in case of LV_VC_RENAME or
3018 // LV_VC_COPY, but call saveBuffer() nevertheless to get
3019 // relative paths of included stuff right if we moved e.g. from
3020 // /a/b.lyx to /a/c/b.lyx.
3022 bool const saved = saveBuffer(b, fname);
3029 bool GuiView::exportBufferAs(Buffer & b, docstring const & iformat)
3031 FileName fname = b.fileName();
3033 FileDialog dlg(qt_("Choose a filename to export the document as"));
3034 dlg.setButton1(qt_("D&ocuments"), toqstr(lyxrc.document_path));
3037 QString const anyformat = qt_("Guess from extension (*.*)");
3040 vector<Format const *> export_formats;
3041 for (Format const & f : theFormats())
3042 if (f.documentFormat())
3043 export_formats.push_back(&f);
3044 sort(export_formats.begin(), export_formats.end(), Format::formatSorter);
3045 map<QString, string> fmap;
3048 for (Format const * f : export_formats) {
3049 docstring const loc_prettyname = translateIfPossible(f->prettyname());
3050 QString const loc_filter = toqstr(bformat(from_ascii("%1$s (*.%2$s)"),
3052 from_ascii(f->extension())));
3053 types << loc_filter;
3054 fmap[loc_filter] = f->name();
3055 if (from_ascii(f->name()) == iformat) {
3056 filter = loc_filter;
3057 ext = f->extension();
3060 string ofname = fname.onlyFileName();
3062 ofname = support::changeExtension(ofname, ext);
3063 FileDialog::Result result =
3064 dlg.save(toqstr(fname.onlyPath().absFileName()),
3068 if (result.first != FileDialog::Chosen)
3072 fname.set(fromqstr(result.second));
3073 if (filter == anyformat)
3074 fmt_name = theFormats().getFormatFromExtension(fname.extension());
3076 fmt_name = fmap[filter];
3077 LYXERR(Debug::FILES, "filter=" << fromqstr(filter)
3078 << ", fmt_name=" << fmt_name << ", fname=" << fname.absFileName());
3080 if (fmt_name.empty() || fname.empty())
3083 // fname is now the new Buffer location.
3084 if (FileName(fname).exists()) {
3085 docstring const file = makeDisplayPath(fname.absFileName(), 30);
3086 docstring text = bformat(_("The document %1$s already "
3087 "exists.\n\nDo you want to "
3088 "overwrite that document?"),
3090 int const ret = Alert::prompt(_("Overwrite document?"),
3091 text, 0, 2, _("&Overwrite"), _("&Rename"), _("&Cancel"));
3094 case 1: return exportBufferAs(b, from_ascii(fmt_name));
3095 case 2: return false;
3099 FuncRequest cmd(LFUN_BUFFER_EXPORT, fmt_name + " " + fname.absFileName());
3102 return dr.dispatched();
3106 bool GuiView::saveBuffer(Buffer & b)
3108 return saveBuffer(b, FileName());
3112 bool GuiView::saveBuffer(Buffer & b, FileName const & fn)
3114 if (workArea(b) && workArea(b)->inDialogMode())
3117 if (fn.empty() && b.isUnnamed())
3118 return renameBuffer(b, docstring());
3120 bool const success = (fn.empty() ? b.save() : b.saveAs(fn));
3122 theSession().lastFiles().add(b.fileName());
3123 theSession().writeFile();
3127 // Switch to this Buffer.
3130 // FIXME: we don't tell the user *WHY* the save failed !!
3131 docstring const file = makeDisplayPath(b.absFileName(), 30);
3132 docstring text = bformat(_("The document %1$s could not be saved.\n\n"
3133 "Do you want to rename the document and "
3134 "try again?"), file);
3135 int const ret = Alert::prompt(_("Rename and save?"),
3136 text, 0, 2, _("&Rename"), _("&Retry"), _("&Cancel"));
3139 if (!renameBuffer(b, docstring()))
3148 return saveBuffer(b, fn);
3152 bool GuiView::hideWorkArea(GuiWorkArea * wa)
3154 return closeWorkArea(wa, false);
3158 // We only want to close the buffer if it is not visible in other workareas
3159 // of the same view, nor in other views, and if this is not a child
3160 bool GuiView::closeWorkArea(GuiWorkArea * wa)
3162 Buffer & buf = wa->bufferView().buffer();
3164 bool last_wa = d.countWorkAreasOf(buf) == 1
3165 && !inOtherView(buf) && !buf.parent();
3167 bool close_buffer = last_wa;
3170 if (lyxrc.close_buffer_with_last_view == "yes")
3172 else if (lyxrc.close_buffer_with_last_view == "no")
3173 close_buffer = false;
3176 if (buf.isUnnamed())
3177 file = from_utf8(buf.fileName().onlyFileName());
3179 file = buf.fileName().displayName(30);
3180 docstring const text = bformat(
3181 _("Last view on document %1$s is being closed.\n"
3182 "Would you like to close or hide the document?\n"
3184 "Hidden documents can be displayed back through\n"
3185 "the menu: View->Hidden->...\n"
3187 "To remove this question, set your preference in:\n"
3188 " Tools->Preferences->Look&Feel->UserInterface\n"
3190 int ret = Alert::prompt(_("Close or hide document?"),
3191 text, 0, 2, _("&Close"), _("&Hide"), _("&Cancel"));
3194 close_buffer = (ret == 0);
3198 return closeWorkArea(wa, close_buffer);
3202 bool GuiView::closeBuffer()
3204 GuiWorkArea * wa = currentMainWorkArea();
3205 // coverity complained about this
3206 // it seems unnecessary, but perhaps is worth the check
3207 LASSERT(wa, return false);
3209 setCurrentWorkArea(wa);
3210 Buffer & buf = wa->bufferView().buffer();
3211 return closeWorkArea(wa, !buf.parent());
3215 void GuiView::writeSession() const {
3216 GuiWorkArea const * active_wa = currentMainWorkArea();
3217 for (int i = 0; i < d.splitter_->count(); ++i) {
3218 TabWorkArea * twa = d.tabWorkArea(i);
3219 for (int j = 0; j < twa->count(); ++j) {
3220 GuiWorkArea * wa = twa->workArea(j);
3221 Buffer & buf = wa->bufferView().buffer();
3222 theSession().lastOpened().add(buf.fileName(), wa == active_wa);
3228 bool GuiView::closeBufferAll()
3231 for (auto & buf : theBufferList()) {
3232 if (!saveBufferIfNeeded(*buf, false)) {
3233 // Closing has been cancelled, so abort.
3238 // Close the workareas in all other views
3239 QList<int> const ids = guiApp->viewIds();
3240 for (int i = 0; i != ids.size(); ++i) {
3241 if (id_ != ids[i] && !guiApp->view(ids[i]).closeWorkAreaAll())
3245 // Close our own workareas
3246 if (!closeWorkAreaAll())
3253 bool GuiView::closeWorkAreaAll()
3255 setCurrentWorkArea(currentMainWorkArea());
3257 // We might be in a situation that there is still a tabWorkArea, but
3258 // there are no tabs anymore. This can happen when we get here after a
3259 // TabWorkArea::lastWorkAreaRemoved() signal. Therefore we count how
3260 // many TabWorkArea's have no documents anymore.
3263 // We have to call count() each time, because it can happen that
3264 // more than one splitter will disappear in one iteration (bug 5998).
3265 while (d.splitter_->count() > empty_twa) {
3266 TabWorkArea * twa = d.tabWorkArea(empty_twa);
3268 if (twa->count() == 0)
3271 setCurrentWorkArea(twa->currentWorkArea());
3272 if (!closeTabWorkArea(twa))
3280 bool GuiView::closeWorkArea(GuiWorkArea * wa, bool close_buffer)
3285 Buffer & buf = wa->bufferView().buffer();
3287 if (GuiViewPrivate::busyBuffers.contains(&buf)) {
3288 Alert::warning(_("Close document"),
3289 _("Document could not be closed because it is being processed by LyX."));
3294 return closeBuffer(buf);
3296 if (!inMultiTabs(wa))
3297 if (!saveBufferIfNeeded(buf, true))
3305 bool GuiView::closeBuffer(Buffer & buf)
3307 bool success = true;
3308 for (Buffer * child_buf : buf.getChildren()) {
3309 if (theBufferList().isOthersChild(&buf, child_buf)) {
3310 child_buf->setParent(nullptr);
3314 // FIXME: should we look in other tabworkareas?
3315 // ANSWER: I don't think so. I've tested, and if the child is
3316 // open in some other window, it closes without a problem.
3317 GuiWorkArea * child_wa = workArea(*child_buf);
3320 // If we are in a close_event all children will be closed in some time,
3321 // so no need to do it here. This will ensure that the children end up
3322 // in the session file in the correct order. If we close the master
3323 // buffer, we can close or release the child buffers here too.
3325 success = closeWorkArea(child_wa, true);
3329 // In this case the child buffer is open but hidden.
3330 // Even in this case, children can be dirty (e.g.,
3331 // after a label change in the master, see #11405).
3332 // Therefore, check this
3333 if (closing_ && (child_buf->isClean() || child_buf->paragraphs().empty())) {
3334 // If we are in a close_event all children will be closed in some time,
3335 // so no need to do it here. This will ensure that the children end up
3336 // in the session file in the correct order. If we close the master
3337 // buffer, we can close or release the child buffers here too.
3340 // Save dirty buffers also if closing_!
3341 if (saveBufferIfNeeded(*child_buf, false)) {
3342 child_buf->removeAutosaveFile();
3343 theBufferList().release(child_buf);
3345 // Saving of dirty children has been cancelled.
3346 // Cancel the whole process.
3353 // goto bookmark to update bookmark pit.
3354 // FIXME: we should update only the bookmarks related to this buffer!
3355 // FIXME: this is done also in LFUN_WINDOW_CLOSE!
3356 LYXERR(Debug::DEBUG, "GuiView::closeBuffer()");
3357 for (unsigned int i = 1; i < theSession().bookmarks().size(); ++i)
3358 guiApp->gotoBookmark(i, false, false);
3360 if (saveBufferIfNeeded(buf, false)) {
3361 buf.removeAutosaveFile();
3362 theBufferList().release(&buf);
3366 // open all children again to avoid a crash because of dangling
3367 // pointers (bug 6603)
3373 bool GuiView::closeTabWorkArea(TabWorkArea * twa)
3375 while (twa == d.currentTabWorkArea()) {
3376 twa->setCurrentIndex(twa->count() - 1);
3378 GuiWorkArea * wa = twa->currentWorkArea();
3379 Buffer & b = wa->bufferView().buffer();
3381 // We only want to close the buffer if the same buffer is not visible
3382 // in another view, and if this is not a child and if we are closing
3383 // a view (not a tabgroup).
3384 bool const close_buffer =
3385 !inOtherView(b) && !b.parent() && closing_;
3387 if (!closeWorkArea(wa, close_buffer))
3394 bool GuiView::saveBufferIfNeeded(Buffer & buf, bool hiding)
3396 if (buf.isClean() || buf.paragraphs().empty())
3399 // Switch to this Buffer.
3405 if (buf.isUnnamed()) {
3406 file = from_utf8(buf.fileName().onlyFileName());
3409 FileName filename = buf.fileName();
3411 file = filename.displayName(30);
3412 exists = filename.exists();
3415 // Bring this window to top before asking questions.
3420 if (hiding && buf.isUnnamed()) {
3421 docstring const text = bformat(_("The document %1$s has not been "
3422 "saved yet.\n\nDo you want to save "
3423 "the document?"), file);
3424 ret = Alert::prompt(_("Save new document?"),
3425 text, 0, 1, _("&Save"), _("&Cancel"));
3429 docstring const text = exists ?
3430 bformat(_("The document %1$s has unsaved changes."
3431 "\n\nDo you want to save the document or "
3432 "discard the changes?"), file) :
3433 bformat(_("The document %1$s has not been saved yet."
3434 "\n\nDo you want to save the document or "
3435 "discard it entirely?"), file);
3436 docstring const title = exists ?
3437 _("Save changed document?") : _("Save document?");
3438 ret = Alert::prompt(title, text, 0, 2,
3439 _("&Save"), _("&Discard"), _("&Cancel"));
3444 if (!saveBuffer(buf))
3448 // If we crash after this we could have no autosave file
3449 // but I guess this is really improbable (Jug).
3450 // Sometimes improbable things happen:
3451 // - see bug http://www.lyx.org/trac/ticket/6587 (ps)
3452 // buf.removeAutosaveFile();
3454 // revert all changes
3465 bool GuiView::inMultiTabs(GuiWorkArea * wa)
3467 Buffer & buf = wa->bufferView().buffer();
3469 for (int i = 0; i != d.splitter_->count(); ++i) {
3470 GuiWorkArea * wa_ = d.tabWorkArea(i)->workArea(buf);
3471 if (wa_ && wa_ != wa)
3474 return inOtherView(buf);
3478 bool GuiView::inOtherView(Buffer & buf)
3480 QList<int> const ids = guiApp->viewIds();
3482 for (int i = 0; i != ids.size(); ++i) {
3486 if (guiApp->view(ids[i]).workArea(buf))
3493 void GuiView::gotoNextOrPreviousBuffer(NextOrPrevious np, bool const move)
3495 if (!documentBufferView())
3498 if (TabWorkArea * twa = d.currentTabWorkArea()) {
3499 Buffer * const curbuf = &documentBufferView()->buffer();
3500 int nwa = twa->count();
3501 for (int i = 0; i < nwa; ++i) {
3502 if (&workArea(i)->bufferView().buffer() == curbuf) {
3504 if (np == NEXTBUFFER)
3505 next_index = (i == nwa - 1 ? 0 : i + 1);
3507 next_index = (i == 0 ? nwa - 1 : i - 1);
3509 twa->moveTab(i, next_index);
3511 setBuffer(&workArea(next_index)->bufferView().buffer());
3519 /// make sure the document is saved
3520 static bool ensureBufferClean(Buffer * buffer)
3522 LASSERT(buffer, return false);
3523 if (buffer->isClean() && !buffer->isUnnamed())
3526 docstring const file = buffer->fileName().displayName(30);
3529 if (!buffer->isUnnamed()) {
3530 text = bformat(_("The document %1$s has unsaved "
3531 "changes.\n\nDo you want to save "
3532 "the document?"), file);
3533 title = _("Save changed document?");
3536 text = bformat(_("The document %1$s has not been "
3537 "saved yet.\n\nDo you want to save "
3538 "the document?"), file);
3539 title = _("Save new document?");
3541 int const ret = Alert::prompt(title, text, 0, 1, _("&Save"), _("&Cancel"));
3544 dispatch(FuncRequest(LFUN_BUFFER_WRITE));
3546 return buffer->isClean() && !buffer->isUnnamed();
3550 bool GuiView::reloadBuffer(Buffer & buf)
3552 currentBufferView()->cursor().reset();
3553 Buffer::ReadStatus status = buf.reload();
3554 return status == Buffer::ReadSuccess;
3558 void GuiView::checkExternallyModifiedBuffers()
3560 for (Buffer * buf : theBufferList()) {
3561 if (buf->fileName().exists() && buf->isChecksumModified()) {
3562 docstring text = bformat(_("Document \n%1$s\n has been externally modified."
3563 " Reload now? Any local changes will be lost."),
3564 from_utf8(buf->absFileName()));
3565 int const ret = Alert::prompt(_("Reload externally changed document?"),
3566 text, 0, 1, _("&Reload"), _("&Cancel"));
3574 void GuiView::dispatchVC(FuncRequest const & cmd, DispatchResult & dr)
3576 Buffer * buffer = documentBufferView()
3577 ? &(documentBufferView()->buffer()) : nullptr;
3579 switch (cmd.action()) {
3580 case LFUN_VC_REGISTER:
3581 if (!buffer || !ensureBufferClean(buffer))
3583 if (!buffer->lyxvc().inUse()) {
3584 if (buffer->lyxvc().registrer()) {
3585 reloadBuffer(*buffer);
3586 dr.clearMessageUpdate();
3591 case LFUN_VC_RENAME:
3592 case LFUN_VC_COPY: {
3593 if (!buffer || !ensureBufferClean(buffer))
3595 if (buffer->lyxvc().inUse() && !buffer->hasReadonlyFlag()) {
3596 if (buffer->lyxvc().isCheckInWithConfirmation()) {
3597 // Some changes are not yet committed.
3598 // We test here and not in getStatus(), since
3599 // this test is expensive.
3601 LyXVC::CommandResult ret =
3602 buffer->lyxvc().checkIn(log);
3604 if (ret == LyXVC::ErrorCommand ||
3605 ret == LyXVC::VCSuccess)
3606 reloadBuffer(*buffer);
3607 if (buffer->lyxvc().isCheckInWithConfirmation()) {
3608 frontend::Alert::error(
3609 _("Revision control error."),
3610 _("Document could not be checked in."));
3614 RenameKind const kind = (cmd.action() == LFUN_VC_RENAME) ?
3615 LV_VC_RENAME : LV_VC_COPY;
3616 renameBuffer(*buffer, cmd.argument(), kind);
3621 case LFUN_VC_CHECK_IN:
3622 if (!buffer || !ensureBufferClean(buffer))
3624 if (buffer->lyxvc().inUse() && !buffer->hasReadonlyFlag()) {
3626 LyXVC::CommandResult ret = buffer->lyxvc().checkIn(log);
3628 // Only skip reloading if the checkin was cancelled or
3629 // an error occurred before the real checkin VCS command
3630 // was executed, since the VCS might have changed the
3631 // file even if it could not checkin successfully.
3632 if (ret == LyXVC::ErrorCommand || ret == LyXVC::VCSuccess)
3633 reloadBuffer(*buffer);
3637 case LFUN_VC_CHECK_OUT:
3638 if (!buffer || !ensureBufferClean(buffer))
3640 if (buffer->lyxvc().inUse()) {
3641 dr.setMessage(buffer->lyxvc().checkOut());
3642 reloadBuffer(*buffer);
3646 case LFUN_VC_LOCKING_TOGGLE:
3647 if (!buffer || !ensureBufferClean(buffer) || buffer->hasReadonlyFlag())
3649 if (buffer->lyxvc().inUse()) {
3650 string res = buffer->lyxvc().lockingToggle();
3652 frontend::Alert::error(_("Revision control error."),
3653 _("Error when setting the locking property."));
3656 reloadBuffer(*buffer);
3661 case LFUN_VC_REVERT:
3664 if (buffer->lyxvc().revert()) {
3665 reloadBuffer(*buffer);
3666 dr.clearMessageUpdate();
3670 case LFUN_VC_UNDO_LAST:
3673 buffer->lyxvc().undoLast();
3674 reloadBuffer(*buffer);
3675 dr.clearMessageUpdate();
3678 case LFUN_VC_REPO_UPDATE:
3681 if (ensureBufferClean(buffer)) {
3682 dr.setMessage(buffer->lyxvc().repoUpdate());
3683 checkExternallyModifiedBuffers();
3687 case LFUN_VC_COMMAND: {
3688 string flag = cmd.getArg(0);
3689 if (buffer && contains(flag, 'R') && !ensureBufferClean(buffer))
3692 if (contains(flag, 'M')) {
3693 if (!Alert::askForText(message, _("LyX VC: Log Message")))
3696 string path = cmd.getArg(1);
3697 if (contains(path, "$$p") && buffer)
3698 path = subst(path, "$$p", buffer->filePath());
3699 LYXERR(Debug::LYXVC, "Directory: " << path);
3701 if (!pp.isReadableDirectory()) {
3702 lyxerr << _("Directory is not accessible.") << endl;
3705 support::PathChanger p(pp);
3707 string command = cmd.getArg(2);
3708 if (command.empty())
3711 command = subst(command, "$$i", buffer->absFileName());
3712 command = subst(command, "$$p", buffer->filePath());
3714 command = subst(command, "$$m", to_utf8(message));
3715 LYXERR(Debug::LYXVC, "Command: " << command);
3717 one.startscript(Systemcall::Wait, command);
3721 if (contains(flag, 'I'))
3722 buffer->markDirty();
3723 if (contains(flag, 'R'))
3724 reloadBuffer(*buffer);
3729 case LFUN_VC_COMPARE: {
3730 if (cmd.argument().empty()) {
3731 lyx::dispatch(FuncRequest(LFUN_DIALOG_SHOW, "comparehistory"));
3737 string rev1 = cmd.getArg(0);
3741 if (!buffer->lyxvc().prepareFileRevision(rev1, f1))
3744 if (isStrInt(rev1) && convert<int>(rev1) <= 0) {
3745 f2 = buffer->absFileName();
3747 string rev2 = cmd.getArg(1);
3751 if (!buffer->lyxvc().prepareFileRevision(rev2, f2))
3755 LYXERR(Debug::LYXVC, "Launching comparison for fetched revisions:\n" <<
3756 f1 << "\n" << f2 << "\n" );
3757 string par = "compare run " + quoteName(f1) + " " + quoteName(f2);
3758 lyx::dispatch(FuncRequest(LFUN_DIALOG_SHOW, par));
3768 void GuiView::openChildDocument(string const & fname)
3770 LASSERT(documentBufferView(), return);
3771 Buffer & buffer = documentBufferView()->buffer();
3772 FileName const filename = support::makeAbsPath(fname, buffer.filePath());
3773 documentBufferView()->saveBookmark(false);
3774 Buffer * child = nullptr;
3775 if (theBufferList().exists(filename)) {
3776 child = theBufferList().getBuffer(filename);
3779 message(bformat(_("Opening child document %1$s..."),
3780 makeDisplayPath(filename.absFileName())));
3781 child = loadDocument(filename, false);
3783 // Set the parent name of the child document.
3784 // This makes insertion of citations and references in the child work,
3785 // when the target is in the parent or another child document.
3787 child->setParent(&buffer);
3791 bool GuiView::goToFileRow(string const & argument)
3795 size_t i = argument.find_last_of(' ');
3796 if (i != string::npos) {
3797 file_name = os::internal_path(FileName(trim(argument.substr(0, i))).realPath());
3798 istringstream is(argument.substr(i + 1));
3803 if (i == string::npos) {
3804 LYXERR0("Wrong argument: " << argument);
3807 Buffer * buf = nullptr;
3808 string const realtmp = package().temp_dir().realPath();
3809 // We have to use os::path_prefix_is() here, instead of
3810 // simply prefixIs(), because the file name comes from
3811 // an external application and may need case adjustment.
3812 if (os::path_prefix_is(file_name, realtmp, os::CASE_ADJUSTED)) {
3813 buf = theBufferList().getBufferFromTmp(file_name, true);
3814 LYXERR(Debug::FILES, "goToFileRow: buffer lookup for " << file_name
3815 << (buf ? " success" : " failed"));
3817 // Must replace extension of the file to be .lyx
3818 // and get full path
3819 FileName const s = fileSearch(string(),
3820 support::changeExtension(file_name, ".lyx"), "lyx");
3821 // Either change buffer or load the file
3822 if (theBufferList().exists(s))
3823 buf = theBufferList().getBuffer(s);
3824 else if (s.exists()) {
3825 buf = loadDocument(s);
3830 _("File does not exist: %1$s"),
3831 makeDisplayPath(file_name)));
3837 _("No buffer for file: %1$s."),
3838 makeDisplayPath(file_name))
3843 bool success = documentBufferView()->setCursorFromRow(row);
3845 LYXERR(Debug::LATEX,
3846 "setCursorFromRow: invalid position for row " << row);
3847 frontend::Alert::error(_("Inverse Search Failed"),
3848 _("Invalid position requested by inverse search.\n"
3849 "You may need to update the viewed document."));
3855 void GuiView::toolBarPopup(const QPoint & /*pos*/)
3857 QMenu * menu = guiApp->menus().menu(toqstr("context-toolbars"), * this);
3858 menu->exec(QCursor::pos());
3863 Buffer::ExportStatus GuiView::GuiViewPrivate::runAndDestroy(const T& func,
3864 Buffer const * orig, Buffer * clone, string const & format)
3866 Buffer::ExportStatus const status = func(format);
3868 // the cloning operation will have produced a clone of the entire set of
3869 // documents, starting from the master. so we must delete those.
3870 Buffer * mbuf = const_cast<Buffer *>(clone->masterBuffer());
3872 busyBuffers.remove(orig);
3877 Buffer::ExportStatus GuiView::GuiViewPrivate::compileAndDestroy(
3878 Buffer const * orig, Buffer * clone, string const & format)
3880 Buffer::ExportStatus (Buffer::* mem_func)(std::string const &, bool) const =
3882 return runAndDestroy(lyx::bind(mem_func, clone, _1, true), orig, clone, format);
3886 Buffer::ExportStatus GuiView::GuiViewPrivate::exportAndDestroy(
3887 Buffer const * orig, Buffer * clone, string const & format)
3889 Buffer::ExportStatus (Buffer::* mem_func)(std::string const &, bool) const =
3891 return runAndDestroy(lyx::bind(mem_func, clone, _1, false), orig, clone, format);
3895 Buffer::ExportStatus GuiView::GuiViewPrivate::previewAndDestroy(
3896 Buffer const * orig, Buffer * clone, string const & format)
3898 Buffer::ExportStatus (Buffer::* mem_func)(std::string const &) const =
3900 return runAndDestroy(lyx::bind(mem_func, clone, _1), orig, clone, format);
3904 bool GuiView::GuiViewPrivate::asyncBufferProcessing(string const & argument,
3905 Buffer const * used_buffer,
3906 docstring const & msg,
3907 Buffer::ExportStatus (*asyncFunc)(Buffer const *, Buffer *, string const &),
3908 Buffer::ExportStatus (Buffer::*syncFunc)(string const &, bool) const,
3909 Buffer::ExportStatus (Buffer::*previewFunc)(string const &) const,
3910 bool allow_async, bool use_tmpdir)
3915 string format = argument;
3917 format = used_buffer->params().getDefaultOutputFormat();
3918 processing_format = format;
3920 progress_->clearMessages();
3923 #if EXPORT_in_THREAD
3925 GuiViewPrivate::busyBuffers.insert(used_buffer);
3926 Buffer * cloned_buffer = used_buffer->cloneWithChildren();
3927 if (!cloned_buffer) {
3928 Alert::error(_("Export Error"),
3929 _("Error cloning the Buffer."));
3932 QFuture<Buffer::ExportStatus> f = QtConcurrent::run(
3937 setPreviewFuture(f);
3938 last_export_format = used_buffer->params().bufferFormat();
3941 // We are asynchronous, so we don't know here anything about the success
3944 Buffer::ExportStatus status;
3946 status = (used_buffer->*syncFunc)(format, use_tmpdir);
3947 } else if (previewFunc) {
3948 status = (used_buffer->*previewFunc)(format);
3951 handleExportStatus(gv_, status, format);
3953 return (status == Buffer::ExportSuccess
3954 || status == Buffer::PreviewSuccess);
3958 Buffer::ExportStatus status;
3960 status = (used_buffer->*syncFunc)(format, true);
3961 } else if (previewFunc) {
3962 status = (used_buffer->*previewFunc)(format);
3965 handleExportStatus(gv_, status, format);
3967 return (status == Buffer::ExportSuccess
3968 || status == Buffer::PreviewSuccess);
3972 void GuiView::dispatchToBufferView(FuncRequest const & cmd, DispatchResult & dr)
3974 BufferView * bv = currentBufferView();
3975 LASSERT(bv, return);
3977 // Let the current BufferView dispatch its own actions.
3978 bv->dispatch(cmd, dr);
3979 if (dr.dispatched()) {
3980 if (cmd.action() == LFUN_REDO || cmd.action() == LFUN_UNDO)
3981 updateDialog("document", "");
3985 // Try with the document BufferView dispatch if any.
3986 BufferView * doc_bv = documentBufferView();
3987 if (doc_bv && doc_bv != bv) {
3988 doc_bv->dispatch(cmd, dr);
3989 if (dr.dispatched()) {
3990 if (cmd.action() == LFUN_REDO || cmd.action() == LFUN_UNDO)
3991 updateDialog("document", "");
3996 // Then let the current Cursor dispatch its own actions.
3997 bv->cursor().dispatch(cmd);
3999 // update completion. We do it here and not in
4000 // processKeySym to avoid another redraw just for a
4001 // changed inline completion
4002 if (cmd.origin() == FuncRequest::KEYBOARD) {
4003 if (cmd.action() == LFUN_SELF_INSERT
4004 || (cmd.action() == LFUN_ERT_INSERT && bv->cursor().inMathed()))
4005 updateCompletion(bv->cursor(), true, true);
4006 else if (cmd.action() == LFUN_CHAR_DELETE_BACKWARD)
4007 updateCompletion(bv->cursor(), false, true);
4009 updateCompletion(bv->cursor(), false, false);
4012 dr = bv->cursor().result();
4016 void GuiView::dispatch(FuncRequest const & cmd, DispatchResult & dr)
4018 BufferView * bv = currentBufferView();
4019 // By default we won't need any update.
4020 dr.screenUpdate(Update::None);
4021 // assume cmd will be dispatched
4022 dr.dispatched(true);
4024 Buffer * doc_buffer = documentBufferView()
4025 ? &(documentBufferView()->buffer()) : nullptr;
4027 if (cmd.origin() == FuncRequest::TOC) {
4028 GuiToc * toc = static_cast<GuiToc*>(findOrBuild("toc", false));
4029 toc->doDispatch(bv->cursor(), cmd, dr);
4033 string const argument = to_utf8(cmd.argument());
4035 switch(cmd.action()) {
4036 case LFUN_BUFFER_CHILD_OPEN:
4037 openChildDocument(to_utf8(cmd.argument()));
4040 case LFUN_BUFFER_IMPORT:
4041 importDocument(to_utf8(cmd.argument()));
4044 case LFUN_MASTER_BUFFER_EXPORT:
4046 doc_buffer = const_cast<Buffer *>(doc_buffer->masterBuffer());
4048 case LFUN_BUFFER_EXPORT: {
4051 // GCC only sees strfwd.h when building merged
4052 if (::lyx::operator==(cmd.argument(), "custom")) {
4053 // LFUN_MASTER_BUFFER_EXPORT is not enabled for this case,
4054 // so the following test should not be needed.
4055 // In principle, we could try to switch to such a view...
4056 // if (cmd.action() == LFUN_BUFFER_EXPORT)
4057 dispatch(FuncRequest(LFUN_DIALOG_SHOW, "sendto"), dr);
4061 string const dest = cmd.getArg(1);
4062 FileName target_dir;
4063 if (!dest.empty() && FileName::isAbsolute(dest))
4064 target_dir = FileName(support::onlyPath(dest));
4066 target_dir = doc_buffer->fileName().onlyPath();
4068 string const format = (argument.empty() || argument == "default") ?
4069 doc_buffer->params().getDefaultOutputFormat() : argument;
4071 if ((dest.empty() && doc_buffer->isUnnamed())
4072 || !target_dir.isDirWritable()) {
4073 exportBufferAs(*doc_buffer, from_utf8(format));
4076 /* TODO/Review: Is it a problem to also export the children?
4077 See the update_unincluded flag */
4078 d.asyncBufferProcessing(format,
4081 &GuiViewPrivate::exportAndDestroy,
4083 nullptr, cmd.allowAsync());
4084 // TODO Inform user about success
4088 case LFUN_BUFFER_EXPORT_AS: {
4089 LASSERT(doc_buffer, break);
4090 docstring f = cmd.argument();
4092 f = from_ascii(doc_buffer->params().getDefaultOutputFormat());
4093 exportBufferAs(*doc_buffer, f);
4097 case LFUN_BUFFER_UPDATE: {
4098 d.asyncBufferProcessing(argument,
4101 &GuiViewPrivate::compileAndDestroy,
4103 nullptr, cmd.allowAsync(), true);
4106 case LFUN_BUFFER_VIEW: {
4107 d.asyncBufferProcessing(argument,
4109 _("Previewing ..."),
4110 &GuiViewPrivate::previewAndDestroy,
4112 &Buffer::preview, cmd.allowAsync());
4115 case LFUN_MASTER_BUFFER_UPDATE: {
4116 d.asyncBufferProcessing(argument,
4117 (doc_buffer ? doc_buffer->masterBuffer() : nullptr),
4119 &GuiViewPrivate::compileAndDestroy,
4121 nullptr, cmd.allowAsync(), true);
4124 case LFUN_MASTER_BUFFER_VIEW: {
4125 d.asyncBufferProcessing(argument,
4126 (doc_buffer ? doc_buffer->masterBuffer() : nullptr),
4128 &GuiViewPrivate::previewAndDestroy,
4129 nullptr, &Buffer::preview, cmd.allowAsync());
4132 case LFUN_EXPORT_CANCEL: {
4133 Systemcall::killscript();
4136 case LFUN_BUFFER_SWITCH: {
4137 string const file_name = to_utf8(cmd.argument());
4138 if (!FileName::isAbsolute(file_name)) {
4140 dr.setMessage(_("Absolute filename expected."));
4144 Buffer * buffer = theBufferList().getBuffer(FileName(file_name));
4147 dr.setMessage(_("Document not loaded"));
4151 // Do we open or switch to the buffer in this view ?
4152 if (workArea(*buffer)
4153 || lyxrc.open_buffers_in_tabs || !documentBufferView()) {
4158 // Look for the buffer in other views
4159 QList<int> const ids = guiApp->viewIds();
4161 for (; i != ids.size(); ++i) {
4162 GuiView & gv = guiApp->view(ids[i]);
4163 if (gv.workArea(*buffer)) {
4165 gv.activateWindow();
4167 gv.setBuffer(buffer);
4172 // If necessary, open a new window as a last resort
4173 if (i == ids.size()) {
4174 lyx::dispatch(FuncRequest(LFUN_WINDOW_NEW));
4180 case LFUN_BUFFER_NEXT:
4181 gotoNextOrPreviousBuffer(NEXTBUFFER, false);
4184 case LFUN_BUFFER_MOVE_NEXT:
4185 gotoNextOrPreviousBuffer(NEXTBUFFER, true);
4188 case LFUN_BUFFER_PREVIOUS:
4189 gotoNextOrPreviousBuffer(PREVBUFFER, false);
4192 case LFUN_BUFFER_MOVE_PREVIOUS:
4193 gotoNextOrPreviousBuffer(PREVBUFFER, true);
4196 case LFUN_BUFFER_CHKTEX:
4197 LASSERT(doc_buffer, break);
4198 doc_buffer->runChktex();
4201 case LFUN_COMMAND_EXECUTE: {
4202 command_execute_ = true;
4203 minibuffer_focus_ = true;
4206 case LFUN_DROP_LAYOUTS_CHOICE:
4207 d.layout_->showPopup();
4210 case LFUN_MENU_OPEN:
4211 if (QMenu * menu = guiApp->menus().menu(toqstr(cmd.argument()), *this))
4212 menu->exec(QCursor::pos());
4215 case LFUN_FILE_INSERT: {
4216 bool const ignore_lang = cmd.getArg(1) == "ignorelang";
4217 if (insertLyXFile(from_utf8(cmd.getArg(0)), ignore_lang)) {
4218 dr.forceBufferUpdate();
4219 dr.screenUpdate(Update::Force);
4224 case LFUN_FILE_INSERT_PLAINTEXT:
4225 case LFUN_FILE_INSERT_PLAINTEXT_PARA: {
4226 string const fname = to_utf8(cmd.argument());
4227 if (!fname.empty() && !FileName::isAbsolute(fname)) {
4228 dr.setMessage(_("Absolute filename expected."));
4232 FileName filename(fname);
4233 if (fname.empty()) {
4234 FileDialog dlg(qt_("Select file to insert"));
4236 FileDialog::Result result = dlg.open(toqstr(bv->buffer().filePath()),
4237 QStringList(qt_("All Files (*)")));
4239 if (result.first == FileDialog::Later || result.second.isEmpty()) {
4240 dr.setMessage(_("Canceled."));
4244 filename.set(fromqstr(result.second));
4248 FuncRequest const new_cmd(cmd, filename.absoluteFilePath());
4249 bv->dispatch(new_cmd, dr);
4254 case LFUN_BUFFER_RELOAD: {
4255 LASSERT(doc_buffer, break);
4258 bool drop = (cmd.argument() == "dump");
4261 if (!drop && !doc_buffer->isClean()) {
4262 docstring const file =
4263 makeDisplayPath(doc_buffer->absFileName(), 20);
4264 if (doc_buffer->notifiesExternalModification()) {
4265 docstring text = _("The current version will be lost. "
4266 "Are you sure you want to load the version on disk "
4267 "of the document %1$s?");
4268 ret = Alert::prompt(_("Reload saved document?"),
4269 bformat(text, file), 1, 1,
4270 _("&Reload"), _("&Cancel"));
4272 docstring text = _("Any changes will be lost. "
4273 "Are you sure you want to revert to the saved version "
4274 "of the document %1$s?");
4275 ret = Alert::prompt(_("Revert to saved document?"),
4276 bformat(text, file), 1, 1,
4277 _("&Revert"), _("&Cancel"));
4282 doc_buffer->markClean();
4283 reloadBuffer(*doc_buffer);
4284 dr.forceBufferUpdate();
4289 case LFUN_BUFFER_RESET_EXPORT:
4290 LASSERT(doc_buffer, break);
4291 doc_buffer->requireFreshStart(true);
4292 dr.setMessage(_("Buffer export reset."));
4295 case LFUN_BUFFER_WRITE:
4296 LASSERT(doc_buffer, break);
4297 saveBuffer(*doc_buffer);
4300 case LFUN_BUFFER_WRITE_AS:
4301 LASSERT(doc_buffer, break);
4302 renameBuffer(*doc_buffer, cmd.argument());
4305 case LFUN_BUFFER_WRITE_AS_TEMPLATE:
4306 LASSERT(doc_buffer, break);
4307 renameBuffer(*doc_buffer, cmd.argument(),
4308 LV_WRITE_AS_TEMPLATE);
4311 case LFUN_BUFFER_WRITE_ALL: {
4312 Buffer * first = theBufferList().first();
4315 message(_("Saving all documents..."));
4316 // We cannot use a for loop as the buffer list cycles.
4319 if (!b->isClean()) {
4321 LYXERR(Debug::ACTION, "Saved " << b->absFileName());
4323 b = theBufferList().next(b);
4324 } while (b != first);
4325 dr.setMessage(_("All documents saved."));
4329 case LFUN_MASTER_BUFFER_FORALL: {
4333 FuncRequest funcToRun = lyxaction.lookupFunc(cmd.getLongArg(0));
4334 funcToRun.allowAsync(false);
4336 for (Buffer const * buf : doc_buffer->allRelatives()) {
4337 // Switch to other buffer view and resend cmd
4338 lyx::dispatch(FuncRequest(
4339 LFUN_BUFFER_SWITCH, buf->absFileName()));
4340 lyx::dispatch(funcToRun);
4343 lyx::dispatch(FuncRequest(
4344 LFUN_BUFFER_SWITCH, doc_buffer->absFileName()));
4348 case LFUN_BUFFER_EXTERNAL_MODIFICATION_CLEAR:
4349 LASSERT(doc_buffer, break);
4350 doc_buffer->clearExternalModification();
4353 case LFUN_BUFFER_CLOSE:
4357 case LFUN_BUFFER_CLOSE_ALL:
4361 case LFUN_DEVEL_MODE_TOGGLE:
4362 devel_mode_ = !devel_mode_;
4364 dr.setMessage(_("Developer mode is now enabled."));
4366 dr.setMessage(_("Developer mode is now disabled."));
4369 case LFUN_TOOLBAR_SET: {
4370 string const name = cmd.getArg(0);
4371 string const state = cmd.getArg(1);
4372 if (GuiToolbar * t = toolbar(name))
4377 case LFUN_TOOLBAR_TOGGLE: {
4378 string const name = cmd.getArg(0);
4379 if (GuiToolbar * t = toolbar(name))
4384 case LFUN_TOOLBAR_MOVABLE: {
4385 string const name = cmd.getArg(0);
4387 // toggle (all) toolbars movablility
4388 toolbarsMovable_ = !toolbarsMovable_;
4389 for (ToolbarInfo const & ti : guiApp->toolbars()) {
4390 GuiToolbar * tb = toolbar(ti.name);
4391 if (tb && tb->isMovable() != toolbarsMovable_)
4392 // toggle toolbar movablity if it does not fit lock
4393 // (all) toolbars positions state silent = true, since
4394 // status bar notifications are slow
4397 if (toolbarsMovable_)
4398 dr.setMessage(_("Toolbars unlocked."));
4400 dr.setMessage(_("Toolbars locked."));
4401 } else if (GuiToolbar * t = toolbar(name)) {
4402 // toggle current toolbar movablity
4404 // update lock (all) toolbars positions
4405 updateLockToolbars();
4410 case LFUN_ICON_SIZE: {
4411 QSize size = d.iconSize(cmd.argument());
4413 dr.setMessage(bformat(_("Icon size set to %1$dx%2$d."),
4414 size.width(), size.height()));
4418 case LFUN_DIALOG_UPDATE: {
4419 string const name = to_utf8(cmd.argument());
4420 if (name == "prefs" || name == "document")
4421 updateDialog(name, string());
4422 else if (name == "paragraph")
4423 lyx::dispatch(FuncRequest(LFUN_PARAGRAPH_UPDATE));
4424 else if (currentBufferView()) {
4425 Inset * inset = currentBufferView()->editedInset(name);
4426 // Can only update a dialog connected to an existing inset
4428 // FIXME: get rid of this indirection; GuiView ask the inset
4429 // if he is kind enough to update itself...
4430 FuncRequest fr(LFUN_INSET_DIALOG_UPDATE, cmd.argument());
4431 //FIXME: pass DispatchResult here?
4432 inset->dispatch(currentBufferView()->cursor(), fr);
4438 case LFUN_DIALOG_TOGGLE: {
4439 FuncCode const func_code = isDialogVisible(cmd.getArg(0))
4440 ? LFUN_DIALOG_HIDE : LFUN_DIALOG_SHOW;
4441 dispatch(FuncRequest(func_code, cmd.argument()), dr);
4445 case LFUN_DIALOG_DISCONNECT_INSET:
4446 disconnectDialog(to_utf8(cmd.argument()));
4449 case LFUN_DIALOG_HIDE: {
4450 guiApp->hideDialogs(to_utf8(cmd.argument()), nullptr);
4454 case LFUN_DIALOG_SHOW: {
4455 string const name = cmd.getArg(0);
4456 string sdata = trim(to_utf8(cmd.argument()).substr(name.size()));
4458 if (name == "latexlog") {
4459 // getStatus checks that
4460 LASSERT(doc_buffer, break);
4461 Buffer::LogType type;
4462 string const logfile = doc_buffer->logName(&type);
4464 case Buffer::latexlog:
4467 case Buffer::buildlog:
4468 sdata = "literate ";
4471 sdata += Lexer::quoteString(logfile);
4472 showDialog("log", sdata);
4473 } else if (name == "vclog") {
4474 // getStatus checks that
4475 LASSERT(doc_buffer, break);
4476 string const sdata2 = "vc " +
4477 Lexer::quoteString(doc_buffer->lyxvc().getLogFile());
4478 showDialog("log", sdata2);
4479 } else if (name == "symbols") {
4480 sdata = bv->cursor().getEncoding()->name();
4482 showDialog("symbols", sdata);
4483 } else if (name == "findreplace") {
4484 sdata = to_utf8(bv->cursor().selectionAsString(false));
4485 showDialog(name, sdata);
4487 } else if (name == "prefs" && isFullScreen()) {
4488 lfunUiToggle("fullscreen");
4489 showDialog("prefs", sdata);
4491 showDialog(name, sdata);
4496 dr.setMessage(cmd.argument());
4499 case LFUN_UI_TOGGLE: {
4500 string arg = cmd.getArg(0);
4501 if (!lfunUiToggle(arg)) {
4502 docstring const msg = "ui-toggle " + _("%1$s unknown command!");
4503 dr.setMessage(bformat(msg, from_utf8(arg)));
4505 // Make sure the keyboard focus stays in the work area.
4510 case LFUN_VIEW_SPLIT: {
4511 LASSERT(doc_buffer, break);
4512 string const orientation = cmd.getArg(0);
4513 d.splitter_->setOrientation(orientation == "vertical"
4514 ? Qt::Vertical : Qt::Horizontal);
4515 TabWorkArea * twa = addTabWorkArea();
4516 GuiWorkArea * wa = twa->addWorkArea(*doc_buffer, *this);
4517 setCurrentWorkArea(wa);
4520 case LFUN_TAB_GROUP_CLOSE:
4521 if (TabWorkArea * twa = d.currentTabWorkArea()) {
4522 closeTabWorkArea(twa);
4523 d.current_work_area_ = nullptr;
4524 twa = d.currentTabWorkArea();
4525 // Switch to the next GuiWorkArea in the found TabWorkArea.
4527 // Make sure the work area is up to date.
4528 setCurrentWorkArea(twa->currentWorkArea());
4530 setCurrentWorkArea(nullptr);
4535 case LFUN_VIEW_CLOSE:
4536 if (TabWorkArea * twa = d.currentTabWorkArea()) {
4537 closeWorkArea(twa->currentWorkArea());
4538 d.current_work_area_ = nullptr;
4539 twa = d.currentTabWorkArea();
4540 // Switch to the next GuiWorkArea in the found TabWorkArea.
4542 // Make sure the work area is up to date.
4543 setCurrentWorkArea(twa->currentWorkArea());
4545 setCurrentWorkArea(nullptr);
4550 case LFUN_COMPLETION_INLINE:
4551 if (d.current_work_area_)
4552 d.current_work_area_->completer().showInline();
4555 case LFUN_COMPLETION_POPUP:
4556 if (d.current_work_area_)
4557 d.current_work_area_->completer().showPopup();
4562 if (d.current_work_area_)
4563 d.current_work_area_->completer().tab();
4566 case LFUN_COMPLETION_CANCEL:
4567 if (d.current_work_area_) {
4568 if (d.current_work_area_->completer().popupVisible())
4569 d.current_work_area_->completer().hidePopup();
4571 d.current_work_area_->completer().hideInline();
4575 case LFUN_COMPLETION_ACCEPT:
4576 if (d.current_work_area_)
4577 d.current_work_area_->completer().activate();
4580 case LFUN_BUFFER_ZOOM_IN:
4581 case LFUN_BUFFER_ZOOM_OUT:
4582 case LFUN_BUFFER_ZOOM: {
4583 if (cmd.argument().empty()) {
4584 if (cmd.action() == LFUN_BUFFER_ZOOM)
4586 else if (cmd.action() == LFUN_BUFFER_ZOOM_IN)
4591 if (cmd.action() == LFUN_BUFFER_ZOOM)
4592 zoom_ratio_ = convert<int>(cmd.argument()) / double(lyxrc.defaultZoom);
4593 else if (cmd.action() == LFUN_BUFFER_ZOOM_IN)
4594 zoom_ratio_ += convert<int>(cmd.argument()) / 100.0;
4596 zoom_ratio_ -= convert<int>(cmd.argument()) / 100.0;
4599 // Actual zoom value: default zoom + fractional extra value
4600 int zoom = (int)(lyxrc.defaultZoom * zoom_ratio_);
4601 if (zoom < static_cast<int>(zoom_min_))
4604 setCurrentZoom(zoom);
4606 dr.setMessage(bformat(_("Zoom level is now %1$d% (default value: %2$d%)"),
4607 lyxrc.currentZoom, lyxrc.defaultZoom));
4609 guiApp->fontLoader().update();
4610 dr.screenUpdate(Update::ForceAll | Update::FitCursor);
4614 case LFUN_VC_REGISTER:
4615 case LFUN_VC_RENAME:
4617 case LFUN_VC_CHECK_IN:
4618 case LFUN_VC_CHECK_OUT:
4619 case LFUN_VC_REPO_UPDATE:
4620 case LFUN_VC_LOCKING_TOGGLE:
4621 case LFUN_VC_REVERT:
4622 case LFUN_VC_UNDO_LAST:
4623 case LFUN_VC_COMMAND:
4624 case LFUN_VC_COMPARE:
4625 dispatchVC(cmd, dr);
4628 case LFUN_SERVER_GOTO_FILE_ROW:
4629 if(goToFileRow(to_utf8(cmd.argument())))
4630 dr.screenUpdate(Update::Force | Update::FitCursor);
4633 case LFUN_LYX_ACTIVATE:
4637 case LFUN_WINDOW_RAISE:
4643 case LFUN_FORWARD_SEARCH: {
4644 // it seems safe to assume we have a document buffer, since
4645 // getStatus wants one.
4646 LASSERT(doc_buffer, break);
4647 Buffer const * doc_master = doc_buffer->masterBuffer();
4648 FileName const path(doc_master->temppath());
4649 string const texname = doc_master->isChild(doc_buffer)
4650 ? DocFileName(changeExtension(
4651 doc_buffer->absFileName(),
4652 "tex")).mangledFileName()
4653 : doc_buffer->latexName();
4654 string const fulltexname =
4655 support::makeAbsPath(texname, doc_master->temppath()).absFileName();
4656 string const mastername =
4657 removeExtension(doc_master->latexName());
4658 FileName const dviname(addName(path.absFileName(),
4659 addExtension(mastername, "dvi")));
4660 FileName const pdfname(addName(path.absFileName(),
4661 addExtension(mastername, "pdf")));
4662 bool const have_dvi = dviname.exists();
4663 bool const have_pdf = pdfname.exists();
4664 if (!have_dvi && !have_pdf) {
4665 dr.setMessage(_("Please, preview the document first."));
4668 string outname = dviname.onlyFileName();
4669 string command = lyxrc.forward_search_dvi;
4670 if (!have_dvi || (have_pdf &&
4671 pdfname.lastModified() > dviname.lastModified())) {
4672 outname = pdfname.onlyFileName();
4673 command = lyxrc.forward_search_pdf;
4676 DocIterator cur = bv->cursor();
4677 int row = doc_buffer->texrow().rowFromDocIterator(cur).first;
4678 LYXERR(Debug::ACTION, "Forward search: row:" << row
4680 if (row == -1 || command.empty()) {
4681 dr.setMessage(_("Couldn't proceed."));
4684 string texrow = convert<string>(row);
4686 command = subst(command, "$$n", texrow);
4687 command = subst(command, "$$f", fulltexname);
4688 command = subst(command, "$$t", texname);
4689 command = subst(command, "$$o", outname);
4691 volatile PathChanger p(path);
4693 one.startscript(Systemcall::DontWait, command);
4697 case LFUN_SPELLING_CONTINUOUSLY:
4698 lyxrc.spellcheck_continuously = !lyxrc.spellcheck_continuously;
4699 dr.screenUpdate(Update::Force);
4702 case LFUN_CITATION_OPEN: {
4704 if (theFormats().getFormat("pdf"))
4705 pdfv = theFormats().getFormat("pdf")->viewer();
4706 if (theFormats().getFormat("ps"))
4707 psv = theFormats().getFormat("ps")->viewer();
4708 frontend::showTarget(argument, pdfv, psv);
4713 // The LFUN must be for one of BufferView, Buffer or Cursor;
4715 dispatchToBufferView(cmd, dr);
4719 // Need to update bv because many LFUNs here might have destroyed it
4720 bv = currentBufferView();
4722 // Clear non-empty selections
4723 // (e.g. from a "char-forward-select" followed by "char-backward-select")
4725 Cursor & cur = bv->cursor();
4726 if ((cur.selection() && cur.selBegin() == cur.selEnd())) {
4727 cur.clearSelection();
4733 bool GuiView::lfunUiToggle(string const & ui_component)
4735 if (ui_component == "scrollbar") {
4736 // hide() is of no help
4737 if (d.current_work_area_->verticalScrollBarPolicy() ==
4738 Qt::ScrollBarAlwaysOff)
4740 d.current_work_area_->setVerticalScrollBarPolicy(
4741 Qt::ScrollBarAsNeeded);
4743 d.current_work_area_->setVerticalScrollBarPolicy(
4744 Qt::ScrollBarAlwaysOff);
4745 } else if (ui_component == "statusbar") {
4746 statusBar()->setVisible(!statusBar()->isVisible());
4747 } else if (ui_component == "menubar") {
4748 menuBar()->setVisible(!menuBar()->isVisible());
4750 if (ui_component == "frame") {
4751 int const l = contentsMargins().left();
4753 //are the frames in default state?
4754 d.current_work_area_->setFrameStyle(QFrame::NoFrame);
4756 #if QT_VERSION > 0x050903
4757 setAttribute(Qt::WA_ContentsMarginsRespectsSafeArea, false);
4759 setContentsMargins(-2, -2, -2, -2);
4761 #if QT_VERSION > 0x050903
4762 setAttribute(Qt::WA_ContentsMarginsRespectsSafeArea, true);
4764 setContentsMargins(0, 0, 0, 0);
4767 if (ui_component == "fullscreen") {
4775 void GuiView::toggleFullScreen()
4777 setWindowState(windowState() ^ Qt::WindowFullScreen);
4781 Buffer const * GuiView::updateInset(Inset const * inset)
4786 Buffer const * inset_buffer = &(inset->buffer());
4788 for (int i = 0; i != d.splitter_->count(); ++i) {
4789 GuiWorkArea * wa = d.tabWorkArea(i)->currentWorkArea();
4792 Buffer const * buffer = &(wa->bufferView().buffer());
4793 if (inset_buffer == buffer)
4794 wa->scheduleRedraw(true);
4796 return inset_buffer;
4800 void GuiView::restartCaret()
4802 /* When we move around, or type, it's nice to be able to see
4803 * the caret immediately after the keypress.
4805 if (d.current_work_area_)
4806 d.current_work_area_->startBlinkingCaret();
4808 // Take this occasion to update the other GUI elements.
4814 void GuiView::updateCompletion(Cursor & cur, bool start, bool keep)
4816 if (d.current_work_area_)
4817 d.current_work_area_->completer().updateVisibility(cur, start, keep);
4822 // This list should be kept in sync with the list of insets in
4823 // src/insets/Inset.cpp. I.e., if a dialog goes with an inset, the
4824 // dialog should have the same name as the inset.
4825 // Changes should be also recorded in LFUN_DIALOG_SHOW doxygen
4826 // docs in LyXAction.cpp.
4828 char const * const dialognames[] = {
4830 "aboutlyx", "bibitem", "bibtex", "box", "branch", "changes", "character",
4831 "citation", "compare", "comparehistory", "counter", "document", "errorlist", "ert",
4832 "external", "file", "findreplace", "findreplaceadv", "float", "graphics",
4833 "href", "include", "index", "index_print", "info", "listings", "label", "line",
4834 "log", "lyxfiles", "mathdelimiter", "mathmatrix", "mathspace", "nomenclature",
4835 "nomencl_print", "note", "paragraph", "phantom", "prefs", "ref",
4836 "sendto", "space", "spellchecker", "symbols", "tabular", "tabularcreate",
4837 "thesaurus", "texinfo", "toc", "view-source", "vspace", "wrap", "progress"};
4839 char const * const * const end_dialognames =
4840 dialognames + (sizeof(dialognames) / sizeof(char *));
4844 cmpCStr(char const * name) : name_(name) {}
4845 bool operator()(char const * other) {
4846 return strcmp(other, name_) == 0;
4853 bool isValidName(string const & name)
4855 return find_if(dialognames, end_dialognames,
4856 cmpCStr(name.c_str())) != end_dialognames;
4862 void GuiView::resetDialogs()
4864 // Make sure that no LFUN uses any GuiView.
4865 guiApp->setCurrentView(nullptr);
4869 constructToolbars();
4870 guiApp->menus().fillMenuBar(menuBar(), this, false);
4871 d.layout_->updateContents(true);
4872 // Now update controls with current buffer.
4873 guiApp->setCurrentView(this);
4879 void GuiView::flatGroupBoxes(const QObject * widget, bool flag)
4881 for (QObject * child: widget->children()) {
4882 if (child->inherits("QGroupBox")) {
4883 QGroupBox * box = (QGroupBox*) child;
4886 flatGroupBoxes(child, flag);
4892 Dialog * GuiView::findOrBuild(string const & name, bool hide_it)
4894 if (!isValidName(name))
4897 map<string, DialogPtr>::iterator it = d.dialogs_.find(name);
4899 if (it != d.dialogs_.end()) {
4901 it->second->hideView();
4902 return it->second.get();
4905 Dialog * dialog = build(name);
4906 d.dialogs_[name].reset(dialog);
4907 #if (QT_VERSION >= QT_VERSION_CHECK(5, 0, 0))
4908 // Force a uniform style for group boxes
4909 // On Mac non-flat works better, on Linux flat is standard
4910 flatGroupBoxes(dialog->asQWidget(), guiApp->platformName() != "cocoa");
4912 if (lyxrc.allow_geometry_session)
4913 dialog->restoreSession();
4920 void GuiView::showDialog(string const & name, string const & sdata,
4923 triggerShowDialog(toqstr(name), toqstr(sdata), inset);
4927 void GuiView::doShowDialog(QString const & qname, QString const & qdata,
4933 const string name = fromqstr(qname);
4934 const string sdata = fromqstr(qdata);
4938 Dialog * dialog = findOrBuild(name, false);
4940 bool const visible = dialog->isVisibleView();
4941 dialog->showData(sdata);
4942 if (currentBufferView())
4943 currentBufferView()->editInset(name, inset);
4944 // We only set the focus to the new dialog if it was not yet
4945 // visible in order not to change the existing previous behaviour
4947 // activateWindow is needed for floating dockviews
4948 dialog->asQWidget()->raise();
4949 dialog->asQWidget()->activateWindow();
4950 if (dialog->wantInitialFocus())
4951 dialog->asQWidget()->setFocus();
4955 catch (ExceptionMessage const &) {
4963 bool GuiView::isDialogVisible(string const & name) const
4965 map<string, DialogPtr>::const_iterator it = d.dialogs_.find(name);
4966 if (it == d.dialogs_.end())
4968 return it->second.get()->isVisibleView() && !it->second.get()->isClosing();
4972 void GuiView::hideDialog(string const & name, Inset * inset)
4974 map<string, DialogPtr>::const_iterator it = d.dialogs_.find(name);
4975 if (it == d.dialogs_.end())
4979 if (!currentBufferView())
4981 if (inset != currentBufferView()->editedInset(name))
4985 Dialog * const dialog = it->second.get();
4986 if (dialog->isVisibleView())
4988 if (currentBufferView())
4989 currentBufferView()->editInset(name, nullptr);
4993 void GuiView::disconnectDialog(string const & name)
4995 if (!isValidName(name))
4997 if (currentBufferView())
4998 currentBufferView()->editInset(name, nullptr);
5002 void GuiView::hideAll() const
5004 for(auto const & dlg_p : d.dialogs_)
5005 dlg_p.second->hideView();
5009 void GuiView::updateDialogs()
5011 for(auto const & dlg_p : d.dialogs_) {
5012 Dialog * dialog = dlg_p.second.get();
5014 if (dialog->needBufferOpen() && !documentBufferView())
5015 hideDialog(fromqstr(dialog->name()), nullptr);
5016 else if (dialog->isVisibleView())
5017 dialog->checkStatus();
5025 Dialog * GuiView::build(string const & name)
5027 return createDialog(*this, name);
5031 SEMenu::SEMenu(QWidget * parent)
5033 QAction * action = addAction(qt_("Disable Shell Escape"));
5034 connect(action, SIGNAL(triggered()),
5035 parent, SLOT(disableShellEscape()));
5038 } // namespace frontend
5041 #include "moc_GuiView.cpp"