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 "FindAndReplace.h"
22 #include "FontLoader.h"
23 #include "GuiApplication.h"
24 #include "GuiClickableLabel.h"
25 #include "GuiCompleter.h"
26 #include "GuiFontMetrics.h"
27 #include "GuiKeySymbol.h"
29 #include "GuiToolbar.h"
30 #include "GuiWorkArea.h"
31 #include "GuiProgress.h"
32 #include "LayoutBox.h"
36 #include "qt_helpers.h"
37 #include "support/filetools.h"
39 #include "frontends/alert.h"
40 #include "frontends/KeySymbol.h"
42 #include "buffer_funcs.h"
44 #include "BufferList.h"
45 #include "BufferParams.h"
46 #include "BufferView.h"
48 #include "Converter.h"
50 #include "CutAndPaste.h"
52 #include "ErrorList.h"
54 #include "FuncStatus.h"
55 #include "FuncRequest.h"
56 #include "KeySymbol.h"
58 #include "LayoutFile.h"
60 #include "LyXAction.h"
64 #include "Paragraph.h"
65 #include "SpellChecker.h"
72 #include "graphics/PreviewLoader.h"
74 #include "support/convert.h"
75 #include "support/debug.h"
76 #include "support/ExceptionMessage.h"
77 #include "support/FileName.h"
78 #include "support/gettext.h"
79 #include "support/ForkedCalls.h"
80 #include "support/lassert.h"
81 #include "support/lstrings.h"
82 #include "support/os.h"
83 #include "support/Package.h"
84 #include "support/PathChanger.h"
85 #include "support/Systemcall.h"
86 #include "support/Timeout.h"
87 #include "support/ProgressInterface.h"
90 #include <QApplication>
91 #include <QCloseEvent>
92 #include <QDragEnterEvent>
95 #include <QFutureWatcher>
106 #include <QShowEvent>
109 #include <QStackedWidget>
110 #include <QStatusBar>
111 #include <QSvgRenderer>
112 #include <QtConcurrentRun>
115 #include <QWindowStateChangeEvent>
116 #include <QGestureEvent>
117 #include <QPinchGesture>
120 // sync with GuiAlert.cpp
121 #define EXPORT_in_THREAD 1
124 #include "support/bind.h"
128 #ifdef HAVE_SYS_TIME_H
129 # include <sys/time.h>
137 using namespace lyx::support;
141 using support::addExtension;
142 using support::changeExtension;
143 using support::removeExtension;
149 class BackgroundWidget : public QWidget
152 BackgroundWidget(int width, int height)
153 : width_(width), height_(height)
155 LYXERR(Debug::GUI, "show banner: " << lyxrc.show_banner);
156 if (!lyxrc.show_banner)
158 /// The text to be written on top of the pixmap
159 QString const htext = qt_("The Document\nProcessor[[welcome banner]]");
160 QString const htextsize = qt_("1.0[[translating this to different value scales the welcome banner text size for your language]]");
161 /// The text to be written on top of the pixmap
162 QString const text = lyx_version ?
163 qt_("version ") + lyx_version : qt_("unknown version");
164 QString imagedir = "images/";
165 FileName fname = imageLibFileSearch(imagedir, "banner", "svgz");
166 QSvgRenderer svgRenderer(toqstr(fname.absFileName()));
167 if (svgRenderer.isValid()) {
168 splash_ = QPixmap(splashSize());
169 QPainter painter(&splash_);
170 svgRenderer.render(&painter);
171 splash_.setDevicePixelRatio(pixelRatio());
173 splash_ = getPixmap("images/", "banner", "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 for (QString const & seg : titlesegs) {
212 if (fm.width(seg) > wline)
213 wline = fm.width(seg);
215 // The longest line in the reference font (for English)
216 // is 180. Calculate scale factor from that.
217 double const wscale = wline > 0 ? (180.0 / wline) : 1;
218 // Now do the same for the height (necessary for condensed fonts)
219 double const hscale = (34.0 / hline);
220 // take the lower of the two scale factors.
221 double const scale = min(wscale, hscale);
222 // Now rescale. Also consider l7n's offset factor.
223 font.setPointSizeF(hfsize * scale * locscale);
226 pain.drawText(hrect, Qt::AlignLeft, htext);
227 setFocusPolicy(Qt::StrongFocus);
230 void paintEvent(QPaintEvent *) override
232 int const w = width_;
233 int const h = height_;
234 int const x = (width() - w) / 2;
235 int const y = (height() - h) / 2;
237 "widget pixel ratio: " << pixelRatio() <<
238 " splash pixel ratio: " << splashPixelRatio() <<
239 " paint pixmap: " << w << "x" << h << "@" << x << "+" << y);
241 pain.drawPixmap(x, y, w, h, splash_);
244 void keyPressEvent(QKeyEvent * ev) override
247 setKeySymbol(&sym, ev);
249 guiApp->processKeySym(sym, q_key_state(ev->modifiers()));
261 /// Current ratio between physical pixels and device-independent pixels
262 double pixelRatio() const {
263 return qt_scale_factor * devicePixelRatio();
266 qreal fontSize() const {
267 return toqstr(lyxrc.font_sizes[NORMAL_SIZE]).toDouble();
270 QPointF textPosition(bool const heading) const {
271 return heading ? QPointF(width_/2 - 18, height_/2 - 45)
272 : QPointF(width_/2 - 18, height_/2 + 45);
275 QSize splashSize() const {
277 static_cast<unsigned int>(width_ * pixelRatio()),
278 static_cast<unsigned int>(height_ * pixelRatio()));
281 /// Ratio between physical pixels and device-independent pixels of splash image
282 double splashPixelRatio() const {
283 return splash_.devicePixelRatio();
288 /// Toolbar store providing access to individual toolbars by name.
289 typedef map<string, GuiToolbar *> ToolbarMap;
291 typedef shared_ptr<Dialog> DialogPtr;
296 class GuiView::GuiViewPrivate
299 GuiViewPrivate(GuiViewPrivate const &);
300 void operator=(GuiViewPrivate const &);
302 GuiViewPrivate(GuiView * gv)
303 : gv_(gv), current_work_area_(nullptr), current_main_work_area_(nullptr),
304 layout_(nullptr), autosave_timeout_(5000),
307 // hardcode here the platform specific icon size
308 smallIconSize = 16; // scaling problems
309 normalIconSize = 20; // ok, default if iconsize.png is missing
310 bigIconSize = 26; // better for some math icons
311 hugeIconSize = 32; // better for hires displays
314 // if it exists, use width of iconsize.png as normal size
315 QString const dir = toqstr(addPath("images", lyxrc.icon_set));
316 FileName const fn = lyx::libFileSearch(dir, "iconsize.png");
318 QImage image(toqstr(fn.absFileName()));
319 if (image.width() < int(smallIconSize))
320 normalIconSize = smallIconSize;
321 else if (image.width() > int(giantIconSize))
322 normalIconSize = giantIconSize;
324 normalIconSize = image.width();
327 splitter_ = new QSplitter;
328 bg_widget_ = new BackgroundWidget(400, 250);
329 stack_widget_ = new QStackedWidget;
330 stack_widget_->addWidget(bg_widget_);
331 stack_widget_->addWidget(splitter_);
334 // TODO cleanup, remove the singleton, handle multiple Windows?
335 progress_ = ProgressInterface::instance();
336 if (!dynamic_cast<GuiProgress*>(progress_)) {
337 progress_ = new GuiProgress; // TODO who deletes it
338 ProgressInterface::setInstance(progress_);
341 dynamic_cast<GuiProgress*>(progress_),
342 SIGNAL(updateStatusBarMessage(QString const&)),
343 gv, SLOT(updateStatusBarMessage(QString const&)));
345 dynamic_cast<GuiProgress*>(progress_),
346 SIGNAL(clearMessageText()),
347 gv, SLOT(clearMessageText()));
354 delete stack_widget_;
359 stack_widget_->setCurrentWidget(bg_widget_);
360 bg_widget_->setUpdatesEnabled(true);
361 bg_widget_->setFocus();
364 int tabWorkAreaCount()
366 return splitter_->count();
369 TabWorkArea * tabWorkArea(int i)
371 return dynamic_cast<TabWorkArea *>(splitter_->widget(i));
374 TabWorkArea * currentTabWorkArea()
376 int areas = tabWorkAreaCount();
378 // The first TabWorkArea is always the first one, if any.
379 return tabWorkArea(0);
381 for (int i = 0; i != areas; ++i) {
382 TabWorkArea * twa = tabWorkArea(i);
383 if (current_main_work_area_ == twa->currentWorkArea())
387 // None has the focus so we just take the first one.
388 return tabWorkArea(0);
391 int countWorkAreasOf(Buffer & buf)
393 int areas = tabWorkAreaCount();
395 for (int i = 0; i != areas; ++i) {
396 TabWorkArea * twa = tabWorkArea(i);
397 if (twa->workArea(buf))
403 void setPreviewFuture(QFuture<Buffer::ExportStatus> const & f)
405 if (processing_thread_watcher_.isRunning()) {
406 // we prefer to cancel this preview in order to keep a snappy
410 processing_thread_watcher_.setFuture(f);
413 QSize iconSize(docstring const & icon_size)
416 if (icon_size == "small")
417 size = smallIconSize;
418 else if (icon_size == "normal")
419 size = normalIconSize;
420 else if (icon_size == "big")
422 else if (icon_size == "huge")
424 else if (icon_size == "giant")
425 size = giantIconSize;
427 size = icon_size.empty() ? normalIconSize : convert<int>(icon_size);
429 if (size < smallIconSize)
430 size = smallIconSize;
432 return QSize(size, size);
435 QSize iconSize(QString const & icon_size)
437 return iconSize(qstring_to_ucs4(icon_size));
440 string & iconSize(QSize const & qsize)
442 LATTEST(qsize.width() == qsize.height());
444 static string icon_size;
446 unsigned int size = qsize.width();
448 if (size < smallIconSize)
449 size = smallIconSize;
451 if (size == smallIconSize)
453 else if (size == normalIconSize)
454 icon_size = "normal";
455 else if (size == bigIconSize)
457 else if (size == hugeIconSize)
459 else if (size == giantIconSize)
462 icon_size = convert<string>(size);
467 static Buffer::ExportStatus previewAndDestroy(Buffer const * orig,
468 Buffer * buffer, string const & format);
469 static Buffer::ExportStatus exportAndDestroy(Buffer const * orig,
470 Buffer * buffer, string const & format);
471 static Buffer::ExportStatus compileAndDestroy(Buffer const * orig,
472 Buffer * buffer, string const & format);
473 static docstring autosaveAndDestroy(Buffer const * orig, Buffer * buffer);
476 static Buffer::ExportStatus runAndDestroy(const T& func,
477 Buffer const * orig, Buffer * buffer, string const & format);
479 // TODO syncFunc/previewFunc: use bind
480 bool asyncBufferProcessing(string const & argument,
481 Buffer const * used_buffer,
482 docstring const & msg,
483 Buffer::ExportStatus (*asyncFunc)(Buffer const *, Buffer *, string const &),
484 Buffer::ExportStatus (Buffer::*syncFunc)(string const &, bool) const,
485 Buffer::ExportStatus (Buffer::*previewFunc)(string const &) const,
486 bool allow_async, bool use_tmpdir = false);
488 QVector<GuiWorkArea*> guiWorkAreas();
492 GuiWorkArea * current_work_area_;
493 GuiWorkArea * current_main_work_area_;
494 QSplitter * splitter_;
495 QStackedWidget * stack_widget_;
496 BackgroundWidget * bg_widget_;
498 ToolbarMap toolbars_;
499 ProgressInterface* progress_;
500 /// The main layout box.
502 * \warning Don't Delete! The layout box is actually owned by
503 * whichever toolbar contains it. All the GuiView class needs is a
504 * means of accessing it.
506 * FIXME: replace that with a proper model so that we are not limited
507 * to only one dialog.
512 map<string, DialogPtr> dialogs_;
515 QTimer statusbar_timer_;
516 QTimer statusbar_stats_timer_;
517 /// auto-saving of buffers
518 Timeout autosave_timeout_;
521 TocModels toc_models_;
524 QFutureWatcher<docstring> autosave_watcher_;
525 QFutureWatcher<Buffer::ExportStatus> processing_thread_watcher_;
527 string last_export_format;
528 string processing_format;
530 // Buffers that are being exported
531 static QSet<Buffer const *> busyBuffers;
533 unsigned int smallIconSize;
534 unsigned int normalIconSize;
535 unsigned int bigIconSize;
536 unsigned int hugeIconSize;
537 unsigned int giantIconSize;
539 /// flag against a race condition due to multiclicks, see bug #1119
542 // Timers for statistic updates in buffer
543 /// Current time left to the nearest info update
544 int time_to_update = 1000;
545 ///Basic step for timer in ms. Basically reaction time for short selections
546 int const timer_rate = 500;
547 /// Real stats updates infrequently. First they take long time for big buffers, second
548 /// they are visible for fast-repeat keyboards even for mid documents.
549 int const default_stats_rate = 5000;
550 /// Detection of new selection, so we can react fast
551 bool already_in_selection_ = false;
552 /// Maximum size of "short" selection for which we can update with faster timer_rate
553 int const max_sel_chars = 5000;
557 QSet<Buffer const *> GuiView::GuiViewPrivate::busyBuffers;
560 GuiView::GuiView(int id)
561 : d(*new GuiViewPrivate(this)), id_(id), closing_(false), busy_(0),
562 command_execute_(false), minibuffer_focus_(false), word_count_enabled_(true),
563 char_count_enabled_(true), char_nb_count_enabled_(false),
564 toolbarsMovable_(true), devel_mode_(false)
566 connect(this, SIGNAL(bufferViewChanged()),
567 this, SLOT(onBufferViewChanged()));
569 // GuiToolbars *must* be initialised before the menu bar.
570 setIconSize(QSize(d.normalIconSize, d.normalIconSize)); // at least on Mac the default is 32 otherwise, which is huge
573 // set ourself as the current view. This is needed for the menu bar
574 // filling, at least for the static special menu item on Mac. Otherwise
575 // they are greyed out.
576 guiApp->setCurrentView(this);
578 // Fill up the menu bar.
579 guiApp->menus().fillMenuBar(menuBar(), this, true);
581 setCentralWidget(d.stack_widget_);
583 // Start autosave timer
584 if (lyxrc.autosave) {
585 // The connection is closed when this is destroyed.
586 d.autosave_timeout_.timeout.connect([this](){ autoSave();});
587 d.autosave_timeout_.setTimeout(lyxrc.autosave * 1000);
588 d.autosave_timeout_.start();
590 connect(&d.statusbar_timer_, SIGNAL(timeout()),
591 this, SLOT(clearMessage()));
592 connect(&d.statusbar_stats_timer_, SIGNAL(timeout()),
593 this, SLOT(showStats()));
594 d.statusbar_stats_timer_.start(d.timer_rate);
596 // We don't want to keep the window in memory if it is closed.
597 setAttribute(Qt::WA_DeleteOnClose, true);
599 #if !(defined(Q_OS_WIN) || defined(Q_CYGWIN_WIN)) && !defined(Q_OS_MAC)
600 // assign an icon to main form. We do not do it under Qt/Win or Qt/Mac,
601 // since the icon is provided in the application bundle. We use a themed
602 // version when available and use the bundled one as fallback.
603 setWindowIcon(QIcon::fromTheme("lyx", getPixmap("images/", "lyx", "svg,png")));
608 // use tabbed dock area for multiple docks
609 // (such as "source" and "messages")
610 setDockOptions(QMainWindow::ForceTabbedDocks);
613 // use document mode tabs on docks
614 setDocumentMode(true);
618 setAcceptDrops(true);
620 QFontMetrics const fm(statusBar()->fontMetrics());
621 int const iconheight = max(int(d.normalIconSize), fm.height());
622 QSize const iconsize(iconheight, iconheight);
624 // add busy indicator to statusbar
625 search_mode mode = theGuiApp()->imageSearchMode();
626 QString fn = toqstr(lyx::libFileSearch("images", "busy", "svgz", mode).absFileName());
627 PressableSvgWidget * busySVG = new PressableSvgWidget(fn);
628 statusBar()->addPermanentWidget(busySVG);
629 // make busy indicator square with 5px margins
630 busySVG->setMaximumSize(busySVG->height() - 5, busySVG->height() - 5);
633 QPixmap ps = QIcon(getPixmap("images/", "process-stop", "svgz")).pixmap(iconsize);
634 GuiClickableLabel * processStop = new GuiClickableLabel(statusBar());
635 processStop->setPixmap(ps);
636 processStop->setToolTip(qt_("Click here to stop export/output process"));
638 statusBar()->addPermanentWidget(processStop);
640 connect(&d.processing_thread_watcher_, SIGNAL(started()),
641 busySVG, SLOT(show()));
642 connect(&d.processing_thread_watcher_, SIGNAL(finished()),
643 busySVG, SLOT(hide()));
644 connect(&d.processing_thread_watcher_, SIGNAL(started()),
645 processStop, SLOT(show()));
646 connect(&d.processing_thread_watcher_, SIGNAL(finished()),
647 processStop, SLOT(hide()));
648 connect(processStop, SIGNAL(clicked()), this, SLOT(checkCancelBackground()));
650 connect(this, SIGNAL(scriptKilled()), busySVG, SLOT(hide()));
651 connect(this, SIGNAL(scriptKilled()), processStop, SLOT(hide()));
653 stat_counts_ = new GuiClickableLabel(statusBar());
654 stat_counts_->setAlignment(Qt::AlignCenter);
655 stat_counts_->setStyleSheet("padding-left: 5px; padding-right: 5px;");
656 stat_counts_->hide();
657 statusBar()->addPermanentWidget(stat_counts_);
659 connect(stat_counts_, SIGNAL(clicked()), this, SLOT(statsPressed()));
661 zoom_slider_ = new QSlider(Qt::Horizontal, statusBar());
662 #if (QT_VERSION >= QT_VERSION_CHECK(5, 11, 0))
663 zoom_slider_->setFixedWidth(fm.horizontalAdvance('x') * 15);
665 zoom_slider_->setFixedWidth(fm.width('x') * 15);
667 // Make the defaultZoom center
668 zoom_slider_->setRange(10, (lyxrc.defaultZoom * 2) - 10);
669 // Initialize proper zoom value
671 zoom_ratio_ = settings.value("zoom_ratio", 1.0).toDouble();
672 // Actual zoom value: default zoom + fractional offset
673 int zoom = (int)(lyxrc.defaultZoom * zoom_ratio_);
674 zoom = min(max(zoom, zoom_min_), zoom_max_);
675 zoom_slider_->setValue(zoom);
676 zoom_slider_->setToolTip(qt_("Workarea zoom level. Drag, use Ctrl-+/- or Shift-Mousewheel to adjust."));
678 // Buttons to change zoom stepwise
679 #if (QT_VERSION >= QT_VERSION_CHECK(5, 11, 0))
680 QSize s(fm.horizontalAdvance('+'), fm.height());
682 QSize s(fm.width('+'), fm.height());
684 zoom_in_ = new GuiClickableLabel(statusBar());
685 zoom_in_->setText("+");
686 zoom_in_->setFixedSize(s);
687 zoom_in_->setAlignment(Qt::AlignCenter);
688 zoom_out_ = new GuiClickableLabel(statusBar());
689 zoom_out_->setText(QString(QChar(0x2212)));
690 zoom_out_->setFixedSize(s);
691 zoom_out_->setAlignment(Qt::AlignCenter);
694 zoom_widget_ = new QWidget(statusBar());
695 zoom_widget_->setAttribute(Qt::WA_MacSmallSize);
696 zoom_widget_->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Preferred);
697 zoom_widget_->setLayout(new QHBoxLayout());
698 zoom_widget_->layout()->setSpacing(5);
699 zoom_widget_->layout()->setContentsMargins(0,0,0,0);
700 zoom_widget_->layout()->addWidget(zoom_out_);
701 zoom_widget_->layout()->addWidget(zoom_slider_);
702 zoom_widget_->layout()->addWidget(zoom_in_);
703 statusBar()->addPermanentWidget(zoom_widget_);
704 zoom_out_->setEnabled(currentBufferView()
705 && zoom_slider_->value() > zoom_slider_->minimum());
706 zoom_slider_->setEnabled(currentBufferView());
707 zoom_in_->setEnabled(currentBufferView()
708 && zoom_slider_->value() < zoom_slider_->maximum());
710 connect(zoom_slider_, SIGNAL(sliderMoved(int)), this, SLOT(zoomSliderMoved(int)));
711 connect(zoom_slider_, SIGNAL(valueChanged(int)), this, SLOT(zoomValueChanged(int)));
712 connect(this, SIGNAL(currentZoomChanged(int)), zoom_slider_, SLOT(setValue(int)));
713 connect(zoom_in_, SIGNAL(clicked()), this, SLOT(zoomInPressed()));
714 connect(zoom_out_, SIGNAL(clicked()), this, SLOT(zoomOutPressed()));
716 // QPalette palette = statusBar()->palette();
718 zoom_value_ = new GuiClickableLabel(statusBar());
719 connect(zoom_value_, SIGNAL(pressed()), this, SLOT(showZoomContextMenu()));
720 // zoom_value_->setPalette(palette);
721 zoom_value_->setForegroundRole(statusBar()->foregroundRole());
722 zoom_value_->setFixedHeight(fm.height());
723 #if (QT_VERSION >= QT_VERSION_CHECK(5, 11, 0))
724 zoom_value_->setMinimumWidth(fm.horizontalAdvance("444\%"));
726 zoom_value_->setMinimumWidth(fm.width("444\%"));
728 zoom_value_->setAlignment(Qt::AlignCenter);
729 zoom_value_->setText(toqstr(bformat(_("[[ZOOM]]%1$d%"), zoom)));
730 statusBar()->addPermanentWidget(zoom_value_);
731 zoom_value_->setEnabled(currentBufferView());
733 statusBar()->setContextMenuPolicy(Qt::CustomContextMenu);
734 connect(statusBar(), SIGNAL(customContextMenuRequested(QPoint)),
735 this, SLOT(showStatusBarContextMenu()));
737 // enable pinch to zoom
738 grabGesture(Qt::PinchGesture);
740 QPixmap shellescape = QIcon(getPixmap("images/", "emblem-shellescape", "svgz,png")).pixmap(iconsize);
741 shell_escape_ = new QLabel(statusBar());
742 shell_escape_->setPixmap(shellescape);
743 shell_escape_->setAlignment(Qt::AlignCenter);
744 shell_escape_->setContextMenuPolicy(Qt::CustomContextMenu);
745 shell_escape_->setToolTip(qt_("WARNING: LaTeX is allowed to execute "
746 "external commands for this document. "
747 "Right click to change."));
748 SEMenu * menu = new SEMenu(this);
749 connect(shell_escape_, SIGNAL(customContextMenuRequested(QPoint)),
750 menu, SLOT(showMenu(QPoint)));
751 shell_escape_->hide();
752 statusBar()->addPermanentWidget(shell_escape_);
754 QPixmap readonly = QIcon(getPixmap("images/", "emblem-readonly", "svgz,png")).pixmap(iconsize);
755 read_only_ = new QLabel(statusBar());
756 read_only_->setPixmap(readonly);
757 read_only_->setAlignment(Qt::AlignCenter);
759 statusBar()->addPermanentWidget(read_only_);
761 version_control_ = new QLabel(statusBar());
762 version_control_->setAlignment(Qt::AlignCenter);
763 version_control_->setFrameStyle(QFrame::StyledPanel);
764 version_control_->hide();
765 statusBar()->addPermanentWidget(version_control_);
767 statusBar()->setSizeGripEnabled(true);
770 connect(&d.autosave_watcher_, SIGNAL(finished()), this,
771 SLOT(autoSaveThreadFinished()));
773 connect(&d.processing_thread_watcher_, SIGNAL(started()), this,
774 SLOT(processingThreadStarted()));
775 connect(&d.processing_thread_watcher_, SIGNAL(finished()), this,
776 SLOT(processingThreadFinished()));
778 connect(this, SIGNAL(triggerShowDialog(QString const &, QString const &, Inset *)),
779 SLOT(doShowDialog(QString const &, QString const &, Inset *)));
781 // Forbid too small unresizable window because it can happen
782 // with some window manager under X11.
783 setMinimumSize(300, 200);
785 if (lyxrc.allow_geometry_session) {
786 // Now take care of session management.
791 // no session handling, default to a sane size.
792 setGeometry(50, 50, 690, 510);
795 // clear session data if any.
796 settings.remove("views");
806 void GuiView::disableShellEscape()
808 BufferView * bv = documentBufferView();
811 theSession().shellescapeFiles().remove(bv->buffer().absFileName());
812 bv->buffer().params().shell_escape = false;
813 bv->processUpdateFlags(Update::Force);
817 void GuiView::checkCancelBackground()
819 docstring const ttl = _("Cancel Export?");
820 docstring const msg = _("Do you want to cancel the background export process?");
822 Alert::prompt(ttl, msg, 1, 1,
823 _("&Cancel export"), _("Co&ntinue"));
829 void GuiView::statsPressed()
832 dispatch(FuncRequest(LFUN_STATISTICS), dr);
835 void GuiView::zoomSliderMoved(int value)
838 dispatch(FuncRequest(LFUN_BUFFER_ZOOM, convert<string>(value)), dr);
839 scheduleRedrawWorkAreas();
840 zoom_value_->setText(toqstr(bformat(_("[[ZOOM]]%1$d%"), value)));
841 zoom_in_->setEnabled(currentBufferView()
842 && value < zoom_slider_->maximum());
843 zoom_out_->setEnabled(currentBufferView()
844 && value > zoom_slider_->minimum());
848 void GuiView::zoomValueChanged(int value)
850 if (value != lyxrc.currentZoom)
851 zoomSliderMoved(value);
855 void GuiView::zoomInPressed()
858 dispatch(FuncRequest(LFUN_BUFFER_ZOOM_IN), dr);
859 scheduleRedrawWorkAreas();
863 void GuiView::zoomOutPressed()
866 dispatch(FuncRequest(LFUN_BUFFER_ZOOM_OUT), dr);
867 scheduleRedrawWorkAreas();
871 void GuiView::showZoomContextMenu()
873 QMenu * menu = guiApp->menus().menu(toqstr("context-zoom"), * this);
876 menu->exec(QCursor::pos());
880 void GuiView::showStatusBarContextMenu()
882 QMenu * menu = guiApp->menus().menu(toqstr("context-statusbar"), * this);
885 menu->exec(QCursor::pos());
889 void GuiView::scheduleRedrawWorkAreas()
891 for (int i = 0; i < d.tabWorkAreaCount(); i++) {
892 TabWorkArea* ta = d.tabWorkArea(i);
893 for (int u = 0; u < ta->count(); u++) {
894 ta->workArea(u)->scheduleRedraw(true);
900 QVector<GuiWorkArea*> GuiView::GuiViewPrivate::guiWorkAreas()
902 QVector<GuiWorkArea*> areas;
903 for (int i = 0; i < tabWorkAreaCount(); i++) {
904 TabWorkArea* ta = tabWorkArea(i);
905 for (int u = 0; u < ta->count(); u++) {
906 areas << ta->workArea(u);
912 static void handleExportStatus(GuiView * view, Buffer::ExportStatus status,
913 string const & format)
915 docstring const fmt = translateIfPossible(theFormats().prettyName(format));
918 case Buffer::ExportSuccess:
919 msg = bformat(_("Successful export to format: %1$s"), fmt);
921 case Buffer::ExportCancel:
922 msg = _("Document export cancelled.");
924 case Buffer::ExportError:
925 case Buffer::ExportNoPathToFormat:
926 case Buffer::ExportTexPathHasSpaces:
927 case Buffer::ExportConverterError:
928 msg = bformat(_("Error while exporting format: %1$s"), fmt);
930 case Buffer::PreviewSuccess:
931 msg = bformat(_("Successful preview of format: %1$s"), fmt);
933 case Buffer::PreviewError:
934 msg = bformat(_("Error while previewing format: %1$s"), fmt);
936 case Buffer::ExportKilled:
937 msg = bformat(_("Conversion cancelled while previewing format: %1$s"), fmt);
944 void GuiView::processingThreadStarted()
949 void GuiView::processingThreadFinished()
951 QFutureWatcher<Buffer::ExportStatus> const * watcher =
952 static_cast<QFutureWatcher<Buffer::ExportStatus> const *>(sender());
954 Buffer::ExportStatus const status = watcher->result();
955 handleExportStatus(this, status, d.processing_format);
958 BufferView const * const bv = currentBufferView();
959 if (bv && !bv->buffer().errorList("Export").empty()) {
964 bool const error = (status != Buffer::ExportSuccess &&
965 status != Buffer::PreviewSuccess &&
966 status != Buffer::ExportCancel);
968 ErrorList & el = bv->buffer().errorList(d.last_export_format);
969 // at this point, we do not know if buffer-view or
970 // master-buffer-view was called. If there was an export error,
971 // and the current buffer's error log is empty, we guess that
972 // it must be master-buffer-view that was called so we set
974 errors(d.last_export_format, el.empty());
979 void GuiView::autoSaveThreadFinished()
981 QFutureWatcher<docstring> const * watcher =
982 static_cast<QFutureWatcher<docstring> const *>(sender());
983 message(watcher->result());
988 void GuiView::saveLayout() const
991 settings.setValue("zoom_ratio", zoom_ratio_);
992 settings.setValue("devel_mode", devel_mode_);
993 settings.beginGroup("views");
994 settings.beginGroup(QString::number(id_));
995 if (guiApp->platformName() == "xcb") {
996 settings.setValue("pos", pos());
997 settings.setValue("size", size());
999 settings.setValue("geometry", saveGeometry());
1000 settings.setValue("layout", saveState(0));
1001 settings.setValue("icon_size", toqstr(d.iconSize(iconSize())));
1002 settings.setValue("zoom_value_visible", zoom_value_->isVisible());
1003 settings.setValue("zoom_slider_visible", zoom_widget_->isVisible());
1004 settings.setValue("word_count_enabled", word_count_enabled_);
1005 settings.setValue("char_count_enabled", char_count_enabled_);
1006 settings.setValue("char_nb_count_enabled", char_nb_count_enabled_);
1010 void GuiView::saveUISettings() const
1014 // Save the toolbar private states
1015 for (auto const & tb_p : d.toolbars_)
1016 tb_p.second->saveSession(settings);
1017 // Now take care of all other dialogs
1018 for (auto const & dlg_p : d.dialogs_)
1019 dlg_p.second->saveSession(settings);
1023 void GuiView::setCurrentZoom(const int v)
1025 Q_EMIT currentZoomChanged(v);
1026 lyxrc.currentZoom = v;
1027 zoom_value_->setText(toqstr(bformat(_("[[ZOOM]]%1$d%"), v)));
1028 zoom_in_->setEnabled(currentBufferView() && v < zoom_slider_->maximum());
1029 zoom_out_->setEnabled(currentBufferView() && v > zoom_slider_->minimum());
1033 bool GuiView::restoreLayout()
1036 zoom_ratio_ = settings.value("zoom_ratio", 1.0).toDouble();
1037 // Actual zoom value: default zoom + fractional offset
1038 int zoom = (int)(lyxrc.defaultZoom * zoom_ratio_);
1039 zoom = min(max(zoom, zoom_min_), zoom_max_);
1040 setCurrentZoom(zoom);
1041 devel_mode_ = settings.value("devel_mode", devel_mode_).toBool();
1042 settings.beginGroup("views");
1043 settings.beginGroup(QString::number(id_));
1044 QString const icon_key = "icon_size";
1045 if (!settings.contains(icon_key))
1048 //code below is skipped when when ~/.config/LyX is (re)created
1049 setIconSize(d.iconSize(settings.value(icon_key).toString()));
1051 zoom_value_->setVisible(settings.value("zoom_value_visible", true).toBool());
1053 bool const show_zoom_slider = settings.value("zoom_slider_visible", true).toBool();
1054 zoom_widget_->setVisible(show_zoom_slider);
1056 word_count_enabled_ = settings.value("word_count_enabled", true).toBool();
1057 char_count_enabled_ = settings.value("char_count_enabled", true).toBool();
1058 char_nb_count_enabled_ = settings.value("char_nb_count_enabled", true).toBool();
1059 stat_counts_->setVisible(word_count_enabled_ || char_count_enabled_ || char_nb_count_enabled_);
1061 if (guiApp->platformName() == "xcb") {
1062 QPoint pos = settings.value("pos", QPoint(50, 50)).toPoint();
1063 QSize size = settings.value("size", QSize(690, 510)).toSize();
1067 // Work-around for bug #6034: the window ends up in an undetermined
1068 // state when trying to restore a maximized window when it is
1069 // already maximized.
1070 if (!(windowState() & Qt::WindowMaximized))
1071 if (!restoreGeometry(settings.value("geometry").toByteArray()))
1072 setGeometry(50, 50, 690, 510);
1075 // Make sure layout is correctly oriented.
1076 setLayoutDirection(qApp->layoutDirection());
1078 // Allow the toc and view-source dock widget to be restored if needed.
1080 if ((dialog = findOrBuild("toc", true)))
1081 // see bug 5082. At least setup title and enabled state.
1082 // Visibility will be adjusted by restoreState below.
1083 dialog->prepareView();
1084 if ((dialog = findOrBuild("view-source", true)))
1085 dialog->prepareView();
1086 if ((dialog = findOrBuild("progress", true)))
1087 dialog->prepareView();
1089 if (!restoreState(settings.value("layout").toByteArray(), 0))
1092 // init the toolbars that have not been restored
1093 for (auto const & tb_p : guiApp->toolbars()) {
1094 GuiToolbar * tb = toolbar(tb_p.name);
1095 if (tb && !tb->isRestored())
1096 initToolbar(tb_p.name);
1099 // update lock (all) toolbars positions
1100 updateLockToolbars();
1107 GuiToolbar * GuiView::toolbar(string const & name)
1109 ToolbarMap::iterator it = d.toolbars_.find(name);
1110 if (it != d.toolbars_.end())
1113 LYXERR(Debug::GUI, "Toolbar::display: no toolbar named " << name);
1118 void GuiView::updateLockToolbars()
1120 toolbarsMovable_ = false;
1121 for (ToolbarInfo const & info : guiApp->toolbars()) {
1122 GuiToolbar * tb = toolbar(info.name);
1123 if (tb && tb->isMovable())
1124 toolbarsMovable_ = true;
1126 // set unified mac toolbars only when not movable as recommended:
1127 // https://doc.qt.io/qt-5/qmainwindow.html#unifiedTitleAndToolBarOnMac-prop
1128 setUnifiedTitleAndToolBarOnMac(!toolbarsMovable_);
1132 void GuiView::constructToolbars()
1134 for (auto const & tb_p : d.toolbars_)
1136 d.toolbars_.clear();
1138 // I don't like doing this here, but the standard toolbar
1139 // destroys this object when it's destroyed itself (vfr)
1140 d.layout_ = new LayoutBox(*this);
1141 d.stack_widget_->addWidget(d.layout_);
1142 d.layout_->move(0,0);
1144 // extracts the toolbars from the backend
1145 for (ToolbarInfo const & inf : guiApp->toolbars())
1146 d.toolbars_[inf.name] = new GuiToolbar(inf, *this);
1148 DynamicMenuButton::resetIconCache();
1152 void GuiView::initToolbars()
1154 // extracts the toolbars from the backend
1155 for (ToolbarInfo const & inf : guiApp->toolbars())
1156 initToolbar(inf.name);
1160 void GuiView::initToolbar(string const & name)
1162 GuiToolbar * tb = toolbar(name);
1165 int const visibility = guiApp->toolbars().defaultVisibility(name);
1166 bool newline = !(visibility & Toolbars::SAMEROW);
1167 tb->setVisible(false);
1168 tb->setVisibility(visibility);
1170 if (visibility & Toolbars::TOP) {
1172 addToolBarBreak(Qt::TopToolBarArea);
1173 addToolBar(Qt::TopToolBarArea, tb);
1176 if (visibility & Toolbars::BOTTOM) {
1178 addToolBarBreak(Qt::BottomToolBarArea);
1179 addToolBar(Qt::BottomToolBarArea, tb);
1182 if (visibility & Toolbars::LEFT) {
1184 addToolBarBreak(Qt::LeftToolBarArea);
1185 addToolBar(Qt::LeftToolBarArea, tb);
1188 if (visibility & Toolbars::RIGHT) {
1190 addToolBarBreak(Qt::RightToolBarArea);
1191 addToolBar(Qt::RightToolBarArea, tb);
1194 if (visibility & Toolbars::ON)
1195 tb->setVisible(true);
1197 tb->setMovable(true);
1201 TocModels & GuiView::tocModels()
1203 return d.toc_models_;
1207 void GuiView::setFocus()
1209 LYXERR(Debug::DEBUG, "GuiView::setFocus()" << this);
1210 QMainWindow::setFocus();
1214 bool GuiView::hasFocus() const
1216 if (currentWorkArea())
1217 return currentWorkArea()->hasFocus();
1218 if (currentMainWorkArea())
1219 return currentMainWorkArea()->hasFocus();
1220 return d.bg_widget_->hasFocus();
1224 void GuiView::focusInEvent(QFocusEvent * e)
1226 LYXERR(Debug::DEBUG, "GuiView::focusInEvent()" << this);
1227 QMainWindow::focusInEvent(e);
1228 // Make sure guiApp points to the correct view.
1229 guiApp->setCurrentView(this);
1230 if (currentWorkArea())
1231 currentWorkArea()->setFocus();
1232 else if (currentMainWorkArea())
1233 currentMainWorkArea()->setFocus();
1235 d.bg_widget_->setFocus();
1239 void GuiView::showEvent(QShowEvent * e)
1241 LYXERR(Debug::GUI, "Passed Geometry "
1242 << size().height() << "x" << size().width()
1243 << "+" << pos().x() << "+" << pos().y());
1245 if (d.splitter_->count() == 0)
1246 // No work area, switch to the background widget.
1250 QMainWindow::showEvent(e);
1254 bool GuiView::closeScheduled()
1261 bool GuiView::prepareAllBuffersForLogout()
1263 Buffer * first = theBufferList().first();
1267 // First, iterate over all buffers and ask the users if unsaved
1268 // changes should be saved.
1269 // We cannot use a for loop as the buffer list cycles.
1272 if (!saveBufferIfNeeded(*b, false))
1274 b = theBufferList().next(b);
1275 } while (b != first);
1277 // Next, save session state
1278 // When a view/window was closed before without quitting LyX, there
1279 // are already entries in the lastOpened list.
1280 theSession().lastOpened().clear();
1287 /** Destroy only all tabbed WorkAreas. Destruction of other WorkAreas
1288 ** is responsibility of the container (e.g., dialog)
1290 void GuiView::closeEvent(QCloseEvent * close_event)
1292 LYXERR(Debug::DEBUG, "GuiView::closeEvent()");
1294 // FIXME Bug #12828 bites here. If there is some other View open, then
1295 // we really should only refuse to close if one of the Buffers open here
1296 // is being processed.
1297 if (!GuiViewPrivate::busyBuffers.isEmpty()) {
1298 Alert::warning(_("Exit LyX"),
1299 _("LyX could not be closed because documents are being processed by LyX."));
1300 close_event->setAccepted(false);
1304 // If the user pressed the x (so we didn't call closeView
1305 // programmatically), we want to clear all existing entries.
1307 theSession().lastOpened().clear();
1312 // it can happen that this event arrives without selecting the view,
1313 // e.g. when clicking the close button on a background window.
1315 Q_EMIT closing(id_);
1316 if (!closeWorkAreaAll()) {
1318 close_event->ignore();
1322 // Make sure that nothing will use this to be closed View.
1323 guiApp->unregisterView(this);
1325 if (isFullScreen()) {
1326 // Switch off fullscreen before closing.
1331 // Make sure the timer time out will not trigger a statusbar update.
1332 d.statusbar_timer_.stop();
1333 d.statusbar_stats_timer_.stop();
1335 // Saving fullscreen requires additional tweaks in the toolbar code.
1336 // It wouldn't also work under linux natively.
1337 if (lyxrc.allow_geometry_session) {
1342 close_event->accept();
1346 void GuiView::dragEnterEvent(QDragEnterEvent * event)
1348 if (event->mimeData()->hasUrls())
1350 /// \todo Ask lyx-devel is this is enough:
1351 /// if (event->mimeData()->hasFormat("text/plain"))
1352 /// event->acceptProposedAction();
1356 void GuiView::dropEvent(QDropEvent * event)
1358 QList<QUrl> files = event->mimeData()->urls();
1359 if (files.isEmpty())
1362 LYXERR(Debug::GUI, "GuiView::dropEvent: got URLs!");
1363 for (int i = 0; i != files.size(); ++i) {
1364 string const file = os::internal_path(fromqstr(
1365 files.at(i).toLocalFile()));
1369 string const ext = support::getExtension(file);
1370 vector<const Format *> found_formats;
1372 // Find all formats that have the correct extension.
1373 for (const Format * fmt : theConverters().importableFormats())
1374 if (fmt->hasExtension(ext))
1375 found_formats.push_back(fmt);
1378 if (!found_formats.empty()) {
1379 if (found_formats.size() > 1) {
1380 //FIXME: show a dialog to choose the correct importable format
1381 LYXERR(Debug::FILES,
1382 "Multiple importable formats found, selecting first");
1384 string const arg = found_formats[0]->name() + " " + file;
1385 cmd = FuncRequest(LFUN_BUFFER_IMPORT, arg);
1388 //FIXME: do we have to explicitly check whether it's a lyx file?
1389 LYXERR(Debug::FILES,
1390 "No formats found, trying to open it as a lyx file");
1391 cmd = FuncRequest(LFUN_FILE_OPEN, file);
1393 // add the functions to the queue
1394 guiApp->addToFuncRequestQueue(cmd);
1397 // now process the collected functions. We perform the events
1398 // asynchronously. This prevents potential problems in case the
1399 // BufferView is closed within an event.
1400 guiApp->processFuncRequestQueueAsync();
1404 void GuiView::message(docstring const & str)
1406 if (ForkedProcess::iAmAChild())
1409 // call is moved to GUI-thread by GuiProgress
1410 d.progress_->appendMessage(toqstr(str));
1414 void GuiView::clearMessageText()
1416 message(docstring());
1420 void GuiView::updateStatusBarMessage(QString const & str)
1422 statusBar()->showMessage(str);
1423 d.statusbar_timer_.stop();
1424 d.statusbar_timer_.start(3000);
1428 void GuiView::clearMessage()
1430 // FIXME: This code was introduced in r19643 to fix bug #4123. However,
1431 // the hasFocus function mostly returns false, even if the focus is on
1432 // a workarea in this view.
1436 d.statusbar_timer_.stop();
1439 void GuiView::showStats()
1441 if (!statsEnabled())
1444 d.time_to_update -= d.timer_rate;
1446 BufferView * bv = currentBufferView();
1447 Buffer * buf = bv ? &bv->buffer() : nullptr;
1449 stat_counts_->hide();
1453 Cursor const & cur = bv->cursor();
1455 // we start new selection and need faster update
1456 if (!d.already_in_selection_ && cur.selection())
1457 d.time_to_update = 0;
1459 if (d.time_to_update > 0)
1462 DocIterator from, to;
1463 if (cur.selection()) {
1464 from = cur.selectionBegin();
1465 to = cur.selectionEnd();
1466 d.already_in_selection_ = true;
1468 from = doc_iterator_begin(buf);
1469 to = doc_iterator_end(buf);
1470 d.already_in_selection_ = false;
1473 buf->updateStatistics(from, to);
1476 if (word_count_enabled_) {
1477 int const words = buf->wordCount();
1479 stats << toqstr(bformat(_("%1$d Word"), words));
1481 stats << toqstr(bformat(_("%1$d Words"), words));
1483 int const chars_with_blanks = buf->charCount(true);
1484 if (char_count_enabled_) {
1485 if (chars_with_blanks == 1)
1486 stats << toqstr(bformat(_("%1$d Character"), chars_with_blanks));
1488 stats << toqstr(bformat(_("%1$d Characters"), chars_with_blanks));
1490 if (char_nb_count_enabled_) {
1491 int const chars = buf->charCount(false);
1493 stats << toqstr(bformat(_("%1$d Character (no Blanks)"), chars));
1495 stats << toqstr(bformat(_("%1$d Characters (no Blanks)"), chars));
1497 stat_counts_->setText(stats.join(qt_(", [[stats separator]]")));
1498 stat_counts_->show();
1500 d.time_to_update = d.default_stats_rate;
1501 // fast updates for small selections
1502 if (chars_with_blanks < d.max_sel_chars && cur.selection())
1503 d.time_to_update = d.timer_rate;
1507 void GuiView::updateWindowTitle(GuiWorkArea * wa)
1509 if (wa != d.current_work_area_
1510 || wa->bufferView().buffer().isInternal())
1512 Buffer const & buf = wa->bufferView().buffer();
1513 // Set the windows title
1514 docstring title = buf.fileName().displayName(130) + from_ascii("[*]");
1515 if (buf.notifiesExternalModification()) {
1516 title = bformat(_("%1$s (modified externally)"), title);
1517 // If the external modification status has changed, then maybe the status of
1518 // buffer-save has changed too.
1521 title += from_ascii(" - LyX");
1522 setWindowTitle(toqstr(title));
1523 // Sets the path for the window: this is used by OSX to
1524 // allow a context click on the title bar showing a menu
1525 // with the path up to the file
1526 setWindowFilePath(toqstr(buf.absFileName()));
1527 // Tell Qt whether the current document is changed
1528 setWindowModified(!buf.isClean());
1530 if (buf.params().shell_escape)
1531 shell_escape_->show();
1533 shell_escape_->hide();
1535 if (buf.hasReadonlyFlag())
1540 if (buf.lyxvc().inUse()) {
1541 version_control_->show();
1542 version_control_->setText(toqstr(buf.lyxvc().vcstatus()));
1544 version_control_->hide();
1548 void GuiView::on_currentWorkAreaChanged(GuiWorkArea * wa)
1550 if (d.current_work_area_)
1551 // disconnect the current work area from all slots
1552 QObject::disconnect(d.current_work_area_, nullptr, this, nullptr);
1554 disconnectBufferView();
1555 connectBufferView(wa->bufferView());
1556 connectBuffer(wa->bufferView().buffer());
1557 d.current_work_area_ = wa;
1558 QObject::connect(wa, SIGNAL(titleChanged(GuiWorkArea *)),
1559 this, SLOT(updateWindowTitle(GuiWorkArea *)));
1560 QObject::connect(wa, SIGNAL(busy(bool)),
1561 this, SLOT(setBusy(bool)));
1562 // connection of a signal to a signal
1563 QObject::connect(wa, SIGNAL(bufferViewChanged()),
1564 this, SIGNAL(bufferViewChanged()));
1565 Q_EMIT updateWindowTitle(wa);
1566 Q_EMIT bufferViewChanged();
1570 void GuiView::onBufferViewChanged()
1573 // Buffer-dependent dialogs must be updated. This is done here because
1574 // some dialogs require buffer()->text.
1576 zoom_slider_->setEnabled(currentBufferView());
1577 zoom_value_->setEnabled(currentBufferView());
1578 zoom_in_->setEnabled(currentBufferView()
1579 && zoom_slider_->value() < zoom_slider_->maximum());
1580 zoom_out_->setEnabled(currentBufferView()
1581 && zoom_slider_->value() > zoom_slider_->minimum());
1585 void GuiView::on_lastWorkAreaRemoved()
1588 // We already are in a close event. Nothing more to do.
1591 if (d.splitter_->count() > 1)
1592 // We have a splitter so don't close anything.
1595 // Reset and updates the dialogs.
1596 Q_EMIT bufferViewChanged();
1601 if (lyxrc.open_buffers_in_tabs)
1602 // Nothing more to do, the window should stay open.
1605 if (guiApp->viewIds().size() > 1) {
1611 // On Mac we also close the last window because the application stay
1612 // resident in memory. On other platforms we don't close the last
1613 // window because this would quit the application.
1619 void GuiView::updateStatusBar()
1621 // let the user see the explicit message
1622 if (d.statusbar_timer_.isActive())
1629 void GuiView::showMessage()
1633 QString msg = toqstr(theGuiApp()->viewStatusMessage());
1634 if (msg.isEmpty()) {
1635 BufferView const * bv = currentBufferView();
1637 msg = toqstr(bv->cursor().currentState(devel_mode_));
1639 msg = qt_("Welcome to LyX!");
1641 statusBar()->showMessage(msg);
1645 bool GuiView::statsEnabled() const
1647 return word_count_enabled_ || char_count_enabled_ || char_nb_count_enabled_;
1651 bool GuiView::event(QEvent * e)
1655 // Useful debug code:
1656 //case QEvent::ActivationChange:
1657 //case QEvent::WindowDeactivate:
1658 //case QEvent::Paint:
1659 //case QEvent::Enter:
1660 //case QEvent::Leave:
1661 //case QEvent::HoverEnter:
1662 //case QEvent::HoverLeave:
1663 //case QEvent::HoverMove:
1664 //case QEvent::StatusTip:
1665 //case QEvent::DragEnter:
1666 //case QEvent::DragLeave:
1667 //case QEvent::Drop:
1670 case QEvent::WindowStateChange: {
1671 QWindowStateChangeEvent * ev = (QWindowStateChangeEvent*)e;
1672 bool ofstate = (ev->oldState() & Qt::WindowFullScreen);
1673 bool result = QMainWindow::event(e);
1674 bool nfstate = (windowState() & Qt::WindowFullScreen);
1675 if (!ofstate && nfstate) {
1676 LYXERR(Debug::DEBUG, "GuiView: WindowStateChange(): full-screen " << nfstate);
1677 // switch to full-screen state
1678 if (lyxrc.full_screen_statusbar)
1679 statusBar()->hide();
1680 if (lyxrc.full_screen_menubar)
1682 if (lyxrc.full_screen_toolbars) {
1683 for (auto const & tb_p : d.toolbars_)
1684 if (tb_p.second->isVisibilityOn() && tb_p.second->isVisible())
1685 tb_p.second->hide();
1687 for (int i = 0; i != d.splitter_->count(); ++i)
1688 d.tabWorkArea(i)->setFullScreen(true);
1689 #if QT_VERSION > 0x050903
1690 //Qt's 5.9.4 ba44cdae38406c safe area measures won't allow us to go negative in margins
1691 setAttribute(Qt::WA_ContentsMarginsRespectsSafeArea, false);
1693 setContentsMargins(-2, -2, -2, -2);
1695 hideDialogs("prefs", nullptr);
1696 } else if (ofstate && !nfstate) {
1697 LYXERR(Debug::DEBUG, "GuiView: WindowStateChange(): full-screen " << nfstate);
1698 // switch back from full-screen state
1699 if (lyxrc.full_screen_statusbar && !statusBar()->isVisible())
1700 statusBar()->show();
1701 if (lyxrc.full_screen_menubar && !menuBar()->isVisible())
1703 if (lyxrc.full_screen_toolbars) {
1704 for (auto const & tb_p : d.toolbars_)
1705 if (tb_p.second->isVisibilityOn() && !tb_p.second->isVisible())
1706 tb_p.second->show();
1709 for (int i = 0; i != d.splitter_->count(); ++i)
1710 d.tabWorkArea(i)->setFullScreen(false);
1711 #if QT_VERSION > 0x050903
1712 setAttribute(Qt::WA_ContentsMarginsRespectsSafeArea, true);
1714 setContentsMargins(0, 0, 0, 0);
1719 case QEvent::WindowActivate: {
1720 GuiView * old_view = guiApp->currentView();
1721 if (this == old_view) {
1723 return QMainWindow::event(e);
1725 if (old_view && old_view->currentBufferView()) {
1726 // save current selection to the selection buffer to allow
1727 // middle-button paste in this window.
1728 cap::saveSelection(old_view->currentBufferView()->cursor());
1730 guiApp->setCurrentView(this);
1731 if (d.current_work_area_)
1732 on_currentWorkAreaChanged(d.current_work_area_);
1736 return QMainWindow::event(e);
1739 case QEvent::ShortcutOverride: {
1741 if (isFullScreen() && menuBar()->isHidden()) {
1742 QKeyEvent * ke = static_cast<QKeyEvent*>(e);
1743 // FIXME: we should also try to detect special LyX shortcut such as
1744 // Alt-P and Alt-M. Right now there is a hack in
1745 // GuiWorkArea::processKeySym() that hides again the menubar for
1747 if (ke->modifiers() & Qt::AltModifier && ke->key() != Qt::Key_Alt) {
1749 return QMainWindow::event(e);
1752 return QMainWindow::event(e);
1755 case QEvent::Gesture: {
1756 QGestureEvent *ge = static_cast<QGestureEvent*>(e);
1757 QGesture *gp = ge->gesture(Qt::PinchGesture);
1759 QPinchGesture *pinch = static_cast<QPinchGesture *>(gp);
1760 QPinchGesture::ChangeFlags changeFlags = pinch->changeFlags();
1761 qreal totalScaleFactor = pinch->totalScaleFactor();
1762 LYXERR(Debug::GUI, "totalScaleFactor: " << totalScaleFactor);
1763 if (pinch->state() == Qt::GestureStarted) {
1764 initialZoom_ = lyxrc.currentZoom;
1765 LYXERR(Debug::GUI, "initialZoom_: " << initialZoom_);
1767 if (changeFlags & QPinchGesture::ScaleFactorChanged) {
1768 qreal factor = initialZoom_ * totalScaleFactor;
1769 LYXERR(Debug::GUI, "scaleFactor: " << factor);
1770 zoomValueChanged(factor);
1773 return QMainWindow::event(e);
1776 // dark/light mode runtime switch support, OS-dependent.
1778 // Limit to Q_OS_MAC as this unnecessarily would also
1779 // trigger on Linux with grave performance issues
1781 case QEvent::ApplicationPaletteChange: {
1782 // We need to update metrics here to avoid a crash (#12786)
1783 theBufferList().changed(true);
1785 return QMainWindow::event(e);
1789 case QEvent::StyleChange: {
1790 // We need to update metrics here to avoid a crash (#12786)
1791 theBufferList().changed(true);
1792 return QMainWindow::event(e);
1796 return QMainWindow::event(e);
1800 void GuiView::resetWindowTitle()
1802 setWindowTitle(qt_("LyX"));
1805 bool GuiView::focusNextPrevChild(bool /*next*/)
1812 bool GuiView::busy() const
1818 void GuiView::setBusy(bool busy)
1820 bool const busy_before = busy_ > 0;
1821 busy ? ++busy_ : --busy_;
1822 if ((busy_ > 0) == busy_before)
1823 // busy state didn't change
1827 QApplication::setOverrideCursor(Qt::WaitCursor);
1830 QApplication::restoreOverrideCursor();
1835 void GuiView::resetCommandExecute()
1837 command_execute_ = false;
1842 double GuiView::pixelRatio() const
1844 return qt_scale_factor * devicePixelRatio();
1848 GuiWorkArea * GuiView::workArea(int index)
1850 if (TabWorkArea * twa = d.currentTabWorkArea())
1851 if (index < twa->count())
1852 return twa->workArea(index);
1857 GuiWorkArea * GuiView::workArea(Buffer & buffer)
1859 if (currentWorkArea()
1860 && ¤tWorkArea()->bufferView().buffer() == &buffer)
1861 return currentWorkArea();
1862 if (TabWorkArea * twa = d.currentTabWorkArea())
1863 return twa->workArea(buffer);
1868 GuiWorkArea * GuiView::addWorkArea(Buffer & buffer)
1870 // Automatically create a TabWorkArea if there are none yet.
1871 TabWorkArea * tab_widget = d.splitter_->count()
1872 ? d.currentTabWorkArea() : addTabWorkArea();
1873 return tab_widget->addWorkArea(buffer, *this);
1877 TabWorkArea * GuiView::addTabWorkArea()
1879 TabWorkArea * twa = new TabWorkArea;
1880 QObject::connect(twa, SIGNAL(currentWorkAreaChanged(GuiWorkArea *)),
1881 this, SLOT(on_currentWorkAreaChanged(GuiWorkArea *)));
1882 QObject::connect(twa, SIGNAL(lastWorkAreaRemoved()),
1883 this, SLOT(on_lastWorkAreaRemoved()));
1885 d.splitter_->insertWidget(d.splitter_->indexOf(d.currentTabWorkArea()) + 1,
1887 d.stack_widget_->setCurrentWidget(d.splitter_);
1892 GuiWorkArea const * GuiView::currentWorkArea() const
1894 return d.current_work_area_;
1898 GuiWorkArea * GuiView::currentWorkArea()
1900 return d.current_work_area_;
1904 GuiWorkArea const * GuiView::currentMainWorkArea() const
1906 if (!d.currentTabWorkArea())
1908 return d.currentTabWorkArea()->currentWorkArea();
1912 GuiWorkArea * GuiView::currentMainWorkArea()
1914 if (!d.currentTabWorkArea())
1916 return d.currentTabWorkArea()->currentWorkArea();
1920 void GuiView::setCurrentWorkArea(GuiWorkArea * wa)
1922 LYXERR(Debug::DEBUG, "Setting current wa: " << wa << endl);
1924 d.current_work_area_ = nullptr;
1926 Q_EMIT bufferViewChanged();
1930 // FIXME: I've no clue why this is here and why it accesses
1931 // theGuiApp()->currentView, which might be 0 (bug 6464).
1932 // See also 27525 (vfr).
1933 if (theGuiApp()->currentView() == this
1934 && theGuiApp()->currentView()->currentWorkArea() == wa)
1937 if (currentBufferView())
1938 cap::saveSelection(currentBufferView()->cursor());
1940 theGuiApp()->setCurrentView(this);
1941 d.current_work_area_ = wa;
1943 // We need to reset this now, because it will need to be
1944 // right if the tabWorkArea gets reset in the for loop. We
1945 // will change it back if we aren't in that case.
1946 GuiWorkArea * const old_cmwa = d.current_main_work_area_;
1947 d.current_main_work_area_ = wa;
1949 for (int i = 0; i != d.splitter_->count(); ++i) {
1950 if (d.tabWorkArea(i)->setCurrentWorkArea(wa)) {
1951 LYXERR(Debug::DEBUG, "Current wa: " << currentWorkArea()
1952 << ", Current main wa: " << currentMainWorkArea());
1957 d.current_main_work_area_ = old_cmwa;
1959 LYXERR(Debug::DEBUG, "This is not a tabbed wa");
1960 on_currentWorkAreaChanged(wa);
1961 BufferView & bv = wa->bufferView();
1962 bv.cursor().fixIfBroken();
1964 wa->setUpdatesEnabled(true);
1965 LYXERR(Debug::DEBUG, "Current wa: " << currentWorkArea() << ", Current main wa: " << currentMainWorkArea());
1969 void GuiView::removeWorkArea(GuiWorkArea * wa)
1971 LASSERT(wa, return);
1972 if (wa == d.current_work_area_) {
1974 disconnectBufferView();
1975 d.current_work_area_ = nullptr;
1976 d.current_main_work_area_ = nullptr;
1979 bool found_twa = false;
1980 for (int i = 0; i != d.splitter_->count(); ++i) {
1981 TabWorkArea * twa = d.tabWorkArea(i);
1982 if (twa->removeWorkArea(wa)) {
1983 // Found in this tab group, and deleted the GuiWorkArea.
1985 if (twa->count() != 0) {
1986 if (d.current_work_area_ == nullptr)
1987 // This means that we are closing the current GuiWorkArea, so
1988 // switch to the next GuiWorkArea in the found TabWorkArea.
1989 setCurrentWorkArea(twa->currentWorkArea());
1991 // No more WorkAreas in this tab group, so delete it.
1998 // It is not a tabbed work area (i.e., the search work area), so it
1999 // should be deleted by other means.
2000 LASSERT(found_twa, return);
2002 if (d.current_work_area_ == nullptr) {
2003 if (d.splitter_->count() != 0) {
2004 TabWorkArea * twa = d.currentTabWorkArea();
2005 setCurrentWorkArea(twa->currentWorkArea());
2007 // No more work areas, switch to the background widget.
2008 setCurrentWorkArea(nullptr);
2014 bool GuiView::hasVisibleWorkArea(GuiWorkArea * wa) const
2016 for (int i = 0; i < d.splitter_->count(); ++i)
2017 if (d.tabWorkArea(i)->currentWorkArea() == wa)
2020 FindAndReplace * fr = static_cast<FindAndReplace*>(find("findreplaceadv", false));
2021 return fr->isVisible() && fr->hasWorkArea(wa);
2025 LayoutBox * GuiView::getLayoutDialog() const
2031 void GuiView::updateLayoutList()
2034 d.layout_->updateContents(false);
2038 void GuiView::updateToolbars()
2040 if (d.current_work_area_) {
2042 if (d.current_work_area_->bufferView().cursor().inMathed()
2043 && !d.current_work_area_->bufferView().cursor().inRegexped())
2044 context |= Toolbars::MATH;
2045 if (lyx::getStatus(FuncRequest(LFUN_LAYOUT_TABULAR)).enabled())
2046 context |= Toolbars::TABLE;
2047 if (currentBufferView()->buffer().areChangesPresent()
2048 || (lyx::getStatus(FuncRequest(LFUN_CHANGES_TRACK)).enabled()
2049 && lyx::getStatus(FuncRequest(LFUN_CHANGES_TRACK)).onOff(true))
2050 || (lyx::getStatus(FuncRequest(LFUN_CHANGES_OUTPUT)).enabled()
2051 && lyx::getStatus(FuncRequest(LFUN_CHANGES_OUTPUT)).onOff(true)))
2052 context |= Toolbars::REVIEW;
2053 if (lyx::getStatus(FuncRequest(LFUN_IN_MATHMACROTEMPLATE)).enabled())
2054 context |= Toolbars::MATHMACROTEMPLATE;
2055 if (lyx::getStatus(FuncRequest(LFUN_IN_IPA)).enabled())
2056 context |= Toolbars::IPA;
2057 if (command_execute_)
2058 context |= Toolbars::MINIBUFFER;
2059 if (minibuffer_focus_) {
2060 context |= Toolbars::MINIBUFFER_FOCUS;
2061 minibuffer_focus_ = false;
2064 for (auto const & tb_p : d.toolbars_)
2065 tb_p.second->update(context);
2067 for (auto const & tb_p : d.toolbars_)
2068 tb_p.second->update();
2072 void GuiView::refillToolbars()
2074 DynamicMenuButton::resetIconCache();
2075 for (auto const & tb_p : d.toolbars_)
2076 tb_p.second->refill();
2080 void GuiView::setBuffer(Buffer * newBuffer, bool switch_to)
2082 LYXERR(Debug::DEBUG, "Setting buffer: " << newBuffer << endl);
2083 LASSERT(newBuffer, return);
2085 GuiWorkArea * wa = workArea(*newBuffer);
2086 if (wa == nullptr) {
2088 newBuffer->masterBuffer()->updateBuffer();
2090 wa = addWorkArea(*newBuffer);
2091 // scroll to the position when the BufferView was last closed
2092 if (lyxrc.use_lastfilepos) {
2093 LastFilePosSection::FilePos filepos =
2094 theSession().lastFilePos().load(newBuffer->fileName());
2095 wa->bufferView().moveToPosition(filepos.pit, filepos.pos, 0, 0);
2098 //Disconnect the old buffer...there's no new one.
2101 connectBuffer(*newBuffer);
2102 connectBufferView(wa->bufferView());
2104 setCurrentWorkArea(wa);
2108 void GuiView::connectBuffer(Buffer & buf)
2110 buf.setGuiDelegate(this);
2114 void GuiView::disconnectBuffer()
2116 if (d.current_work_area_)
2117 d.current_work_area_->bufferView().buffer().setGuiDelegate(nullptr);
2121 void GuiView::connectBufferView(BufferView & bv)
2123 bv.setGuiDelegate(this);
2127 void GuiView::disconnectBufferView()
2129 if (d.current_work_area_)
2130 d.current_work_area_->bufferView().setGuiDelegate(nullptr);
2134 void GuiView::errors(string const & error_type, bool from_master)
2136 BufferView const * const bv = currentBufferView();
2140 ErrorList const & el = from_master ?
2141 bv->buffer().masterBuffer()->errorList(error_type) :
2142 bv->buffer().errorList(error_type);
2147 string err = error_type;
2149 err = "from_master|" + error_type;
2150 showDialog("errorlist", err);
2154 void GuiView::updateTocItem(string const & type, DocIterator const & dit)
2156 d.toc_models_.updateItem(toqstr(type), dit);
2160 void GuiView::structureChanged()
2162 // This is called from the Buffer, which has no way to ensure that cursors
2163 // in BufferView remain valid.
2164 if (documentBufferView())
2165 documentBufferView()->cursor().sanitize();
2166 // FIXME: This is slightly expensive, though less than the tocBackend update
2167 // (#9880). This also resets the view in the Toc Widget (#6675).
2168 d.toc_models_.reset(documentBufferView());
2169 // Navigator needs more than a simple update in this case. It needs to be
2171 updateDialog("toc", "");
2175 void GuiView::updateDialog(string const & name, string const & sdata)
2177 if (!isDialogVisible(name))
2180 map<string, DialogPtr>::const_iterator it = d.dialogs_.find(name);
2181 if (it == d.dialogs_.end())
2184 Dialog * const dialog = it->second.get();
2185 if (dialog->isVisibleView())
2186 dialog->initialiseParams(sdata);
2190 BufferView * GuiView::documentBufferView()
2192 return currentMainWorkArea()
2193 ? ¤tMainWorkArea()->bufferView()
2198 BufferView const * GuiView::documentBufferView() const
2200 return currentMainWorkArea()
2201 ? ¤tMainWorkArea()->bufferView()
2206 BufferView * GuiView::currentBufferView()
2208 return d.current_work_area_ ? &d.current_work_area_->bufferView() : nullptr;
2212 BufferView const * GuiView::currentBufferView() const
2214 return d.current_work_area_ ? &d.current_work_area_->bufferView() : nullptr;
2218 docstring GuiView::GuiViewPrivate::autosaveAndDestroy(
2219 Buffer const * orig, Buffer * clone)
2221 bool const success = clone->autoSave();
2223 busyBuffers.remove(orig);
2225 ? _("Automatic save done.")
2226 : _("Automatic save failed!");
2230 void GuiView::autoSave()
2232 LYXERR(Debug::INFO, "Running autoSave()");
2234 Buffer * buffer = documentBufferView()
2235 ? &documentBufferView()->buffer() : nullptr;
2237 resetAutosaveTimers();
2241 GuiViewPrivate::busyBuffers.insert(buffer);
2242 QFuture<docstring> f = QtConcurrent::run(GuiViewPrivate::autosaveAndDestroy,
2243 buffer, buffer->cloneBufferOnly());
2244 d.autosave_watcher_.setFuture(f);
2245 resetAutosaveTimers();
2249 void GuiView::resetAutosaveTimers()
2252 d.autosave_timeout_.restart();
2258 double zoomRatio(FuncRequest const & cmd, double const zr)
2260 if (cmd.argument().empty()) {
2261 if (cmd.action() == LFUN_BUFFER_ZOOM)
2263 else if (cmd.action() == LFUN_BUFFER_ZOOM_IN)
2265 else // cmd.action() == LFUN_BUFFER_ZOOM_OUT
2268 if (cmd.action() == LFUN_BUFFER_ZOOM)
2269 return convert<int>(cmd.argument()) / double(lyxrc.defaultZoom);
2270 else if (cmd.action() == LFUN_BUFFER_ZOOM_IN)
2271 return zr + convert<int>(cmd.argument()) / 100.0;
2272 else // cmd.action() == LFUN_BUFFER_ZOOM_OUT
2273 return zr - convert<int>(cmd.argument()) / 100.0;
2280 bool GuiView::getStatus(FuncRequest const & cmd, FuncStatus & flag)
2283 Buffer * buf = currentBufferView()
2284 ? ¤tBufferView()->buffer() : nullptr;
2285 Buffer * doc_buffer = documentBufferView()
2286 ? &(documentBufferView()->buffer()) : nullptr;
2289 /* In LyX/Mac, when a dialog is open, the menus of the
2290 application can still be accessed without giving focus to
2291 the main window. In this case, we want to disable the menu
2292 entries that are buffer-related.
2293 This code must not be used on Linux and Windows, since it
2294 would disable buffer-related entries when hovering over the
2295 menu (see bug #9574).
2297 if (cmd.origin() == FuncRequest::MENU && !hasFocus()) {
2303 // Check whether we need a buffer
2304 if (!lyxaction.funcHasFlag(cmd.action(), LyXAction::NoBuffer) && !buf) {
2305 // no, exit directly
2306 flag.message(from_utf8(N_("Command not allowed with"
2307 "out any document open")));
2308 flag.setEnabled(false);
2312 if (cmd.origin() == FuncRequest::TOC) {
2313 GuiToc * toc = static_cast<GuiToc*>(findOrBuild("toc", false));
2314 if (!toc || !toc->getStatus(documentBufferView()->cursor(), cmd, flag))
2315 flag.setEnabled(false);
2319 switch(cmd.action()) {
2320 case LFUN_BUFFER_IMPORT:
2323 case LFUN_MASTER_BUFFER_EXPORT:
2325 && (doc_buffer->parent() != nullptr
2326 || doc_buffer->hasChildren())
2327 && !d.processing_thread_watcher_.isRunning()
2328 // this launches a dialog, which would be in the wrong Buffer
2329 && !(::lyx::operator==(cmd.argument(), "custom"));
2332 case LFUN_MASTER_BUFFER_UPDATE:
2333 case LFUN_MASTER_BUFFER_VIEW:
2335 && (doc_buffer->parent() != nullptr
2336 || doc_buffer->hasChildren())
2337 && !d.processing_thread_watcher_.isRunning();
2340 case LFUN_BUFFER_UPDATE:
2341 case LFUN_BUFFER_VIEW: {
2342 if (!doc_buffer || d.processing_thread_watcher_.isRunning()) {
2346 string format = to_utf8(cmd.argument());
2347 if (cmd.argument().empty())
2348 format = doc_buffer->params().getDefaultOutputFormat();
2349 enable = doc_buffer->params().isExportable(format, true);
2353 case LFUN_BUFFER_RELOAD:
2354 enable = doc_buffer && !doc_buffer->isUnnamed()
2355 && doc_buffer->fileName().exists()
2356 && (!doc_buffer->isClean() || doc_buffer->notifiesExternalModification());
2359 case LFUN_BUFFER_RESET_EXPORT:
2360 enable = doc_buffer != nullptr;
2363 case LFUN_BUFFER_CHILD_OPEN:
2364 enable = doc_buffer != nullptr;
2367 case LFUN_MASTER_BUFFER_FORALL: {
2368 if (doc_buffer == nullptr) {
2369 flag.message(from_utf8(N_("Command not allowed without a buffer open")));
2373 FuncRequest const cmdToPass = lyxaction.lookupFunc(cmd.getLongArg(0));
2374 if (cmdToPass.action() == LFUN_UNKNOWN_ACTION) {
2375 flag.message(from_utf8(N_("Invalid argument of master-buffer-forall")));
2380 for (Buffer * buf : doc_buffer->allRelatives()) {
2381 GuiWorkArea * wa = workArea(*buf);
2384 if (wa->bufferView().getStatus(cmdToPass, flag)) {
2385 enable = flag.enabled();
2392 case LFUN_BUFFER_WRITE:
2393 enable = doc_buffer && (doc_buffer->isUnnamed()
2394 || (!doc_buffer->isClean()
2395 || cmd.argument() == "force"));
2398 //FIXME: This LFUN should be moved to GuiApplication.
2399 case LFUN_BUFFER_WRITE_ALL: {
2400 // We enable the command only if there are some modified buffers
2401 Buffer * first = theBufferList().first();
2406 // We cannot use a for loop as the buffer list is a cycle.
2408 if (!b->isClean()) {
2412 b = theBufferList().next(b);
2413 } while (b != first);
2417 case LFUN_BUFFER_EXTERNAL_MODIFICATION_CLEAR:
2418 enable = doc_buffer && doc_buffer->notifiesExternalModification();
2421 case LFUN_BUFFER_EXPORT: {
2422 if (!doc_buffer || d.processing_thread_watcher_.isRunning()) {
2426 return doc_buffer->getStatus(cmd, flag);
2429 case LFUN_BUFFER_EXPORT_AS:
2430 if (!doc_buffer || d.processing_thread_watcher_.isRunning()) {
2435 case LFUN_BUFFER_WRITE_AS:
2436 case LFUN_BUFFER_WRITE_AS_TEMPLATE:
2437 enable = doc_buffer != nullptr;
2440 case LFUN_EXPORT_CANCEL:
2441 enable = d.processing_thread_watcher_.isRunning();
2444 case LFUN_BUFFER_CLOSE:
2445 case LFUN_VIEW_CLOSE:
2446 enable = doc_buffer != nullptr;
2449 case LFUN_BUFFER_CLOSE_ALL:
2450 enable = theBufferList().last() != theBufferList().first();
2453 case LFUN_BUFFER_CHKTEX: {
2454 // hide if we have no checktex command
2455 if (lyxrc.chktex_command.empty()) {
2456 flag.setUnknown(true);
2460 if (!doc_buffer || !doc_buffer->params().isLatex()
2461 || d.processing_thread_watcher_.isRunning()) {
2462 // grey out, don't hide
2470 case LFUN_CHANGES_TRACK: {
2475 return doc_buffer->getStatus(cmd, flag);
2478 case LFUN_VIEW_SPLIT:
2479 if (cmd.getArg(0) == "vertical")
2480 enable = doc_buffer && (d.splitter_->count() == 1 ||
2481 d.splitter_->orientation() == Qt::Vertical);
2483 enable = doc_buffer && (d.splitter_->count() == 1 ||
2484 d.splitter_->orientation() == Qt::Horizontal);
2487 case LFUN_TAB_GROUP_NEXT:
2488 case LFUN_TAB_GROUP_PREVIOUS:
2489 enable = (d.splitter_->count() > 1);
2492 case LFUN_TAB_GROUP_CLOSE:
2493 enable = d.tabWorkAreaCount() > 1;
2496 case LFUN_DEVEL_MODE_TOGGLE:
2497 flag.setOnOff(devel_mode_);
2500 case LFUN_TOOLBAR_SET: {
2501 string const name = cmd.getArg(0);
2502 string const state = cmd.getArg(1);
2503 if (name.empty() || state.empty()) {
2505 docstring const msg =
2506 _("Function toolbar-set requires two arguments!");
2510 if (state != "on" && state != "off" && state != "auto") {
2512 docstring const msg =
2513 bformat(_("Invalid argument \"%1$s\" to function toolbar-set!"),
2518 if (GuiToolbar * t = toolbar(name)) {
2519 bool const autovis = t->visibility() & Toolbars::AUTO;
2521 flag.setOnOff(t->isVisible() && !autovis);
2522 else if (state == "off")
2523 flag.setOnOff(!t->isVisible() && !autovis);
2524 else if (state == "auto")
2525 flag.setOnOff(autovis);
2528 docstring const msg =
2529 bformat(_("Unknown toolbar \"%1$s\""), from_utf8(name));
2535 case LFUN_TOOLBAR_TOGGLE: {
2536 string const name = cmd.getArg(0);
2537 if (GuiToolbar * t = toolbar(name))
2538 flag.setOnOff(t->isVisible());
2541 docstring const msg =
2542 bformat(_("Unknown toolbar \"%1$s\""), from_utf8(name));
2548 case LFUN_TOOLBAR_MOVABLE: {
2549 string const name = cmd.getArg(0);
2550 // use negation since locked == !movable
2552 // toolbar name * locks all toolbars
2553 flag.setOnOff(!toolbarsMovable_);
2554 else if (GuiToolbar * t = toolbar(name))
2555 flag.setOnOff(!(t->isMovable()));
2558 docstring const msg =
2559 bformat(_("Unknown toolbar \"%1$s\""), from_utf8(name));
2565 case LFUN_ICON_SIZE:
2566 flag.setOnOff(d.iconSize(cmd.argument()) == iconSize());
2569 case LFUN_DROP_LAYOUTS_CHOICE:
2570 enable = buf != nullptr;
2573 case LFUN_UI_TOGGLE:
2574 if (cmd.argument() == "zoomlevel") {
2575 flag.setOnOff(zoom_value_ ? zoom_value_->isVisible() : false);
2576 } else if (cmd.argument() == "zoomslider") {
2577 flag.setOnOff(zoom_widget_ ? zoom_widget_->isVisible() : false);
2578 } else if (cmd.argument() == "statistics-w") {
2579 flag.setOnOff(word_count_enabled_);
2580 } else if (cmd.argument() == "statistics-cb") {
2581 flag.setOnOff(char_count_enabled_);
2582 } else if (cmd.argument() == "statistics-c") {
2583 flag.setOnOff(char_nb_count_enabled_);
2585 flag.setOnOff(isFullScreen());
2588 case LFUN_DIALOG_DISCONNECT_INSET:
2591 case LFUN_DIALOG_HIDE:
2592 // FIXME: should we check if the dialog is shown?
2595 case LFUN_DIALOG_TOGGLE:
2596 flag.setOnOff(isDialogVisible(cmd.getArg(0)));
2599 case LFUN_DIALOG_SHOW: {
2600 string const name = cmd.getArg(0);
2602 enable = name == "aboutlyx"
2603 || name == "file" //FIXME: should be removed.
2604 || name == "lyxfiles"
2606 || name == "texinfo"
2607 || name == "progress"
2608 || name == "compare";
2609 else if (name == "character" || name == "symbols"
2610 || name == "mathdelimiter" || name == "mathmatrix") {
2611 if (!buf || buf->isReadonly())
2614 Cursor const & cur = currentBufferView()->cursor();
2615 enable = !(cur.inTexted() && cur.paragraph().isPassThru());
2618 else if (name == "latexlog")
2619 enable = FileName(doc_buffer->logName()).isReadableFile();
2620 else if (name == "spellchecker")
2621 enable = theSpellChecker()
2622 && !doc_buffer->text().empty();
2623 else if (name == "vclog")
2624 enable = doc_buffer->lyxvc().inUse();
2628 case LFUN_DIALOG_UPDATE: {
2629 string const name = cmd.getArg(0);
2631 enable = name == "prefs";
2635 case LFUN_COMMAND_EXECUTE:
2637 case LFUN_MENU_OPEN:
2638 // Nothing to check.
2641 case LFUN_COMPLETION_INLINE:
2642 if (!d.current_work_area_
2643 || !d.current_work_area_->completer().inlinePossible(
2644 currentBufferView()->cursor()))
2648 case LFUN_COMPLETION_POPUP:
2649 if (!d.current_work_area_
2650 || !d.current_work_area_->completer().popupPossible(
2651 currentBufferView()->cursor()))
2656 if (!d.current_work_area_
2657 || !d.current_work_area_->completer().inlinePossible(
2658 currentBufferView()->cursor()))
2662 case LFUN_COMPLETION_ACCEPT:
2663 if (!d.current_work_area_
2664 || (!d.current_work_area_->completer().popupVisible()
2665 && !d.current_work_area_->completer().inlineVisible()
2666 && !d.current_work_area_->completer().completionAvailable()))
2670 case LFUN_COMPLETION_CANCEL:
2671 if (!d.current_work_area_
2672 || (!d.current_work_area_->completer().popupVisible()
2673 && !d.current_work_area_->completer().inlineVisible()))
2677 case LFUN_BUFFER_ZOOM_OUT:
2678 case LFUN_BUFFER_ZOOM_IN:
2679 case LFUN_BUFFER_ZOOM: {
2680 int const zoom = (int)(lyxrc.defaultZoom * zoomRatio(cmd, zoom_ratio_));
2681 if (zoom < zoom_min_) {
2682 docstring const msg =
2683 bformat(_("Zoom level cannot be less than %1$d%."), zoom_min_);
2686 } else if (zoom > zoom_max_) {
2687 docstring const msg =
2688 bformat(_("Zoom level cannot be more than %1$d%."), zoom_max_);
2692 enable = doc_buffer;
2697 case LFUN_BUFFER_MOVE_NEXT:
2698 case LFUN_BUFFER_MOVE_PREVIOUS:
2699 // we do not cycle when moving
2700 case LFUN_BUFFER_NEXT:
2701 case LFUN_BUFFER_PREVIOUS:
2702 // because we cycle, it doesn't matter whether on first or last
2703 enable = (d.currentTabWorkArea()->count() > 1);
2705 case LFUN_BUFFER_SWITCH:
2706 // toggle on the current buffer, but do not toggle off
2707 // the other ones (is that a good idea?)
2709 && to_utf8(cmd.argument()) == doc_buffer->absFileName())
2710 flag.setOnOff(true);
2713 case LFUN_VC_REGISTER:
2714 enable = doc_buffer && !doc_buffer->lyxvc().inUse();
2716 case LFUN_VC_RENAME:
2717 enable = doc_buffer && doc_buffer->lyxvc().renameEnabled();
2720 enable = doc_buffer && doc_buffer->lyxvc().copyEnabled();
2722 case LFUN_VC_CHECK_IN:
2723 enable = doc_buffer && doc_buffer->lyxvc().checkInEnabled();
2725 case LFUN_VC_CHECK_OUT:
2726 enable = doc_buffer && doc_buffer->lyxvc().checkOutEnabled();
2728 case LFUN_VC_LOCKING_TOGGLE:
2729 enable = doc_buffer && !doc_buffer->hasReadonlyFlag()
2730 && doc_buffer->lyxvc().lockingToggleEnabled();
2731 flag.setOnOff(enable && doc_buffer->lyxvc().locking());
2733 case LFUN_VC_REVERT:
2734 enable = doc_buffer && doc_buffer->lyxvc().inUse()
2735 && !doc_buffer->hasReadonlyFlag();
2737 case LFUN_VC_UNDO_LAST:
2738 enable = doc_buffer && doc_buffer->lyxvc().undoLastEnabled();
2740 case LFUN_VC_REPO_UPDATE:
2741 enable = doc_buffer && doc_buffer->lyxvc().repoUpdateEnabled();
2743 case LFUN_VC_COMMAND: {
2744 if (cmd.argument().empty())
2746 if (!doc_buffer && contains(cmd.getArg(0), 'D'))
2750 case LFUN_VC_COMPARE:
2751 enable = doc_buffer && doc_buffer->lyxvc().prepareFileRevisionEnabled();
2754 case LFUN_SERVER_GOTO_FILE_ROW:
2755 case LFUN_LYX_ACTIVATE:
2756 case LFUN_WINDOW_RAISE:
2758 case LFUN_FORWARD_SEARCH:
2759 enable = !(lyxrc.forward_search_dvi.empty() && lyxrc.forward_search_pdf.empty()) &&
2760 doc_buffer && doc_buffer->isSyncTeXenabled();
2763 case LFUN_FILE_INSERT_PLAINTEXT:
2764 case LFUN_FILE_INSERT_PLAINTEXT_PARA:
2765 enable = documentBufferView() && documentBufferView()->cursor().inTexted();
2768 case LFUN_SPELLING_CONTINUOUSLY:
2769 flag.setOnOff(lyxrc.spellcheck_continuously);
2772 case LFUN_CITATION_OPEN:
2781 flag.setEnabled(false);
2787 static FileName selectTemplateFile()
2789 FileDialog dlg(qt_("Select template file"));
2790 dlg.setButton1(qt_("D&ocuments"), toqstr(lyxrc.document_path));
2791 dlg.setButton2(qt_("&Templates"), toqstr(lyxrc.template_path));
2793 FileDialog::Result result = dlg.open(toqstr(lyxrc.template_path),
2794 QStringList(qt_("LyX Documents (*.lyx)")));
2796 if (result.first == FileDialog::Later)
2798 if (result.second.isEmpty())
2800 return FileName(fromqstr(result.second));
2804 Buffer * GuiView::loadDocument(FileName const & filename, bool tolastfiles)
2808 Buffer * newBuffer = nullptr;
2810 newBuffer = checkAndLoadLyXFile(filename);
2811 } catch (ExceptionMessage const &) {
2818 message(_("Document not loaded."));
2822 setBuffer(newBuffer);
2823 newBuffer->errors("Parse");
2826 theSession().lastFiles().add(filename);
2827 theSession().writeFile();
2834 void GuiView::openDocuments(string const & fname, int origin)
2836 string initpath = lyxrc.document_path;
2838 if (documentBufferView()) {
2839 string const trypath = documentBufferView()->buffer().filePath();
2840 // If directory is writeable, use this as default.
2841 if (FileName(trypath).isDirWritable())
2847 if (fname.empty()) {
2848 FileDialog dlg(qt_("Select documents to open"));
2849 dlg.setButton1(qt_("D&ocuments"), toqstr(lyxrc.document_path));
2850 dlg.setButton2(qt_("&Examples"), toqstr(lyxrc.example_path));
2852 QStringList const filter({
2853 qt_("LyX Documents (*.lyx)"),
2854 qt_("LyX Document Backups (*.lyx~)"),
2855 qt_("All Files") + " " + wildcardAllFiles()
2857 FileDialog::Results results =
2858 dlg.openMulti(toqstr(initpath), filter);
2860 if (results.first == FileDialog::Later)
2863 files = results.second;
2865 // check selected filename
2866 if (files.isEmpty()) {
2867 message(_("Canceled."));
2871 files << toqstr(fname);
2873 // iterate over all selected files
2874 for (auto const & file : files) {
2875 string filename = fromqstr(file);
2877 // get absolute path of file and add ".lyx" to the filename if
2879 FileName const fullname =
2880 fileSearch(string(), filename, "lyx", support::may_not_exist);
2881 if (!fullname.empty())
2882 filename = fullname.absFileName();
2884 if (!fullname.onlyPath().isDirectory()) {
2885 Alert::warning(_("Invalid filename"),
2886 bformat(_("The directory in the given path\n%1$s\ndoes not exist."),
2887 from_utf8(fullname.absFileName())));
2891 // if the file doesn't exist and isn't already open (bug 6645),
2892 // let the user create one
2893 if (!fullname.exists() && !theBufferList().exists(fullname) &&
2894 !LyXVC::file_not_found_hook(fullname)) {
2896 if (origin == FuncRequest::MENU) {
2897 docstring const & msg =
2900 "does not exist. Create empty file?"),
2901 from_utf8(filename));
2902 int ret = Alert::prompt(_("File does not exist"),
2909 Buffer * const b = newFile(filename, string(), true);
2915 docstring const disp_fn = makeDisplayPath(filename);
2916 message(bformat(_("Opening document %1$s..."), disp_fn));
2919 Buffer * buf = loadDocument(fullname);
2921 str2 = bformat(_("Document %1$s opened."), disp_fn);
2922 if (buf->lyxvc().inUse())
2923 str2 += " " + from_utf8(buf->lyxvc().versionString()) +
2924 " " + _("Version control detected.");
2926 str2 = bformat(_("Could not open document %1$s"), disp_fn);
2932 // FIXME: clean that
2933 static bool import(GuiView * lv, FileName const & filename,
2934 string const & format, ErrorList & errorList)
2936 FileName const lyxfile(support::changeExtension(filename.absFileName(), ".lyx"));
2938 string loader_format;
2939 vector<string> loaders = theConverters().loaders();
2940 if (find(loaders.begin(), loaders.end(), format) == loaders.end()) {
2941 for (string const & loader : loaders) {
2942 if (!theConverters().isReachable(format, loader))
2945 string const tofile =
2946 support::changeExtension(filename.absFileName(),
2947 theFormats().extension(loader));
2948 if (theConverters().convert(nullptr, filename, FileName(tofile),
2949 filename, format, loader, errorList) != Converters::SUCCESS)
2951 loader_format = loader;
2954 if (loader_format.empty()) {
2955 frontend::Alert::error(_("Couldn't import file"),
2956 bformat(_("No information for importing the format %1$s."),
2957 translateIfPossible(theFormats().prettyName(format))));
2961 loader_format = format;
2963 if (loader_format == "lyx") {
2964 Buffer * buf = lv->loadDocument(lyxfile);
2968 Buffer * const b = newFile(lyxfile.absFileName(), string(), true);
2972 bool as_paragraphs = loader_format == "textparagraph";
2973 string filename2 = (loader_format == format) ? filename.absFileName()
2974 : support::changeExtension(filename.absFileName(),
2975 theFormats().extension(loader_format));
2976 lv->currentBufferView()->insertPlaintextFile(FileName(filename2),
2978 guiApp->setCurrentView(lv);
2979 lyx::dispatch(FuncRequest(LFUN_MARK_OFF));
2986 void GuiView::importDocument(string const & argument)
2989 string filename = split(argument, format, ' ');
2991 LYXERR(Debug::INFO, format << " file: " << filename);
2993 // need user interaction
2994 if (filename.empty()) {
2995 string initpath = lyxrc.document_path;
2996 if (documentBufferView()) {
2997 string const trypath = documentBufferView()->buffer().filePath();
2998 // If directory is writeable, use this as default.
2999 if (FileName(trypath).isDirWritable())
3003 docstring const text = bformat(_("Select %1$s file to import"),
3004 translateIfPossible(theFormats().prettyName(format)));
3006 FileDialog dlg(toqstr(text));
3007 dlg.setButton1(qt_("D&ocuments"), toqstr(lyxrc.document_path));
3008 dlg.setButton2(qt_("&Examples"), toqstr(lyxrc.example_path));
3010 docstring filter = translateIfPossible(theFormats().prettyName(format));
3013 filter += from_utf8(theFormats().extensions(format));
3016 FileDialog::Result result =
3017 dlg.open(toqstr(initpath), fileFilters(toqstr(filter)));
3019 if (result.first == FileDialog::Later)
3022 filename = fromqstr(result.second);
3024 // check selected filename
3025 if (filename.empty())
3026 message(_("Canceled."));
3029 if (filename.empty())
3032 // get absolute path of file
3033 FileName const fullname(support::makeAbsPath(filename));
3035 // Can happen if the user entered a path into the dialog
3037 if (fullname.onlyFileName().empty()) {
3038 docstring msg = bformat(_("The file name '%1$s' is invalid!\n"
3039 "Aborting import."),
3040 from_utf8(fullname.absFileName()));
3041 frontend::Alert::error(_("File name error"), msg);
3042 message(_("Canceled."));
3047 FileName const lyxfile(support::changeExtension(fullname.absFileName(), ".lyx"));
3049 // Check if the document already is open
3050 Buffer * buf = theBufferList().getBuffer(lyxfile);
3053 if (!closeBuffer()) {
3054 message(_("Canceled."));
3059 docstring const displaypath = makeDisplayPath(lyxfile.absFileName(), 30);
3061 // if the file exists already, and we didn't do
3062 // -i lyx thefile.lyx, warn
3063 if (lyxfile.exists() && fullname != lyxfile) {
3065 docstring text = bformat(_("The document %1$s already exists.\n\n"
3066 "Do you want to overwrite that document?"), displaypath);
3067 int const ret = Alert::prompt(_("Overwrite document?"),
3068 text, 0, 1, _("&Overwrite"), _("&Cancel"));
3071 message(_("Canceled."));
3076 message(bformat(_("Importing %1$s..."), displaypath));
3077 ErrorList errorList;
3078 if (import(this, fullname, format, errorList))
3079 message(_("imported."));
3081 message(_("file not imported!"));
3083 // FIXME (Abdel 12/08/06): Is there a need to display the error list here?
3087 void GuiView::newDocument(string const & filename, string templatefile,
3090 FileName initpath(lyxrc.document_path);
3091 if (documentBufferView()) {
3092 FileName const trypath(documentBufferView()->buffer().filePath());
3093 // If directory is writeable, use this as default.
3094 if (trypath.isDirWritable())
3098 if (from_template) {
3099 if (templatefile.empty())
3100 templatefile = selectTemplateFile().absFileName();
3101 if (templatefile.empty())
3106 if (filename.empty())
3107 b = newUnnamedFile(initpath, to_utf8(_("newfile")), templatefile);
3109 b = newFile(filename, templatefile, true);
3114 // If no new document could be created, it is unsure
3115 // whether there is a valid BufferView.
3116 if (currentBufferView())
3117 // Ensure the cursor is correctly positioned on screen.
3118 currentBufferView()->showCursor();
3122 bool GuiView::insertLyXFile(docstring const & fname, bool ignorelang)
3124 BufferView * bv = documentBufferView();
3129 FileName filename(to_utf8(fname));
3130 if (filename.empty()) {
3131 // Launch a file browser
3133 string initpath = lyxrc.document_path;
3134 string const trypath = bv->buffer().filePath();
3135 // If directory is writeable, use this as default.
3136 if (FileName(trypath).isDirWritable())
3140 FileDialog dlg(qt_("Select LyX document to insert"));
3141 dlg.setButton1(qt_("D&ocuments"), toqstr(lyxrc.document_path));
3142 dlg.setButton2(qt_("&Examples"), toqstr(lyxrc.example_path));
3144 FileDialog::Result result = dlg.open(toqstr(initpath),
3145 QStringList(qt_("LyX Documents (*.lyx)")));
3147 if (result.first == FileDialog::Later)
3151 filename.set(fromqstr(result.second));
3153 // check selected filename
3154 if (filename.empty()) {
3155 // emit message signal.
3156 message(_("Canceled."));
3161 bv->insertLyXFile(filename, ignorelang);
3162 bv->buffer().errors("Parse");
3167 string const GuiView::getTemplatesPath(Buffer & b)
3169 // We start off with the user's templates path
3170 string result = addPath(package().user_support().absFileName(), "templates");
3171 // Check for the document language
3172 string const langcode = b.params().language->code();
3173 string const shortcode = langcode.substr(0, 2);
3174 if (!langcode.empty() && shortcode != "en") {
3175 string subpath = addPath(result, shortcode);
3176 string subpath_long = addPath(result, langcode);
3177 // If we have a subdirectory for the language already,
3179 FileName sp = FileName(subpath);
3180 if (sp.isDirectory())
3182 else if (FileName(subpath_long).isDirectory())
3183 result = subpath_long;
3185 // Ask whether we should create such a subdirectory
3186 docstring const text =
3187 bformat(_("It is suggested to save the template in a subdirectory\n"
3188 "appropriate to the document language (%1$s).\n"
3189 "This subdirectory does not exists yet.\n"
3190 "Do you want to create it?"),
3191 _(b.params().language->display()));
3192 if (Alert::prompt(_("Create Language Directory?"),
3193 text, 0, 1, _("&Yes, Create"), _("&No, Save Template in Parent Directory")) == 0) {
3194 // If the user agreed, we try to create it and report if this failed.
3195 if (!sp.createDirectory(0777))
3196 Alert::error(_("Subdirectory creation failed!"),
3197 _("Could not create subdirectory.\n"
3198 "The template will be saved in the parent directory."));
3204 // Do we have a layout category?
3205 string const cat = b.params().baseClass() ?
3206 b.params().baseClass()->category()
3209 string subpath = addPath(result, cat);
3210 // If we have a subdirectory for the category already,
3212 FileName sp = FileName(subpath);
3213 if (sp.isDirectory())
3216 // Ask whether we should create such a subdirectory
3217 docstring const text =
3218 bformat(_("It is suggested to save the template in a subdirectory\n"
3219 "appropriate to the layout category (%1$s).\n"
3220 "This subdirectory does not exists yet.\n"
3221 "Do you want to create it?"),
3223 if (Alert::prompt(_("Create Category Directory?"),
3224 text, 0, 1, _("&Yes, Create"), _("&No, Save Template in Parent Directory")) == 0) {
3225 // If the user agreed, we try to create it and report if this failed.
3226 if (!sp.createDirectory(0777))
3227 Alert::error(_("Subdirectory creation failed!"),
3228 _("Could not create subdirectory.\n"
3229 "The template will be saved in the parent directory."));
3239 bool GuiView::renameBuffer(Buffer & b, docstring const & newname, RenameKind kind)
3241 FileName fname = b.fileName();
3242 FileName const oldname = fname;
3243 bool const as_template = (kind == LV_WRITE_AS_TEMPLATE);
3245 if (!newname.empty()) {
3248 fname = support::makeAbsPath(to_utf8(newname), getTemplatesPath(b));
3250 fname = support::makeAbsPath(to_utf8(newname),
3251 oldname.onlyPath().absFileName());
3253 // Switch to this Buffer.
3256 // No argument? Ask user through dialog.
3258 QString const title = as_template ? qt_("Choose a filename to save template as")
3259 : qt_("Choose a filename to save document as");
3260 FileDialog dlg(title);
3261 dlg.setButton1(qt_("D&ocuments"), toqstr(lyxrc.document_path));
3262 dlg.setButton2(qt_("&Templates"), toqstr(lyxrc.template_path));
3264 fname.ensureExtension(".lyx");
3266 string const path = as_template ?
3268 : fname.onlyPath().absFileName();
3269 FileDialog::Result result =
3270 dlg.save(toqstr(path),
3271 QStringList(qt_("LyX Documents (*.lyx)")),
3272 toqstr(fname.onlyFileName()));
3274 if (result.first == FileDialog::Later)
3277 fname.set(fromqstr(result.second));
3282 fname.ensureExtension(".lyx");
3285 // fname is now the new Buffer location.
3287 // if there is already a Buffer open with this name, we do not want
3288 // to have another one. (the second test makes sure we're not just
3289 // trying to overwrite ourselves, which is fine.)
3290 if (theBufferList().exists(fname) && fname != oldname
3291 && theBufferList().getBuffer(fname) != &b) {
3292 docstring const text =
3293 bformat(_("The file\n%1$s\nis already open in your current session.\n"
3294 "Please close it before attempting to overwrite it.\n"
3295 "Do you want to choose a new filename?"),
3296 from_utf8(fname.absFileName()));
3297 int const ret = Alert::prompt(_("Chosen File Already Open"),
3298 text, 0, 1, _("&Rename"), _("&Cancel"));
3300 case 0: return renameBuffer(b, docstring(), kind);
3301 case 1: return false;
3306 bool const existsLocal = fname.exists();
3307 bool const existsInVC = LyXVC::fileInVC(fname);
3308 if (existsLocal || existsInVC) {
3309 docstring const file = makeDisplayPath(fname.absFileName(), 30);
3310 if (kind != LV_WRITE_AS && existsInVC) {
3311 // renaming to a name that is already in VC
3313 docstring text = bformat(_("The document %1$s "
3314 "is already registered.\n\n"
3315 "Do you want to choose a new name?"),
3317 docstring const title = (kind == LV_VC_RENAME) ?
3318 _("Rename document?") : _("Copy document?");
3319 docstring const button = (kind == LV_VC_RENAME) ?
3320 _("&Rename") : _("&Copy");
3321 int const ret = Alert::prompt(title, text, 0, 1,
3322 button, _("&Cancel"));
3324 case 0: return renameBuffer(b, docstring(), kind);
3325 case 1: return false;
3330 docstring text = bformat(_("The document %1$s "
3331 "already exists.\n\n"
3332 "Do you want to overwrite that document?"),
3334 int const ret = Alert::prompt(_("Overwrite document?"),
3335 text, 0, 2, _("&Overwrite"),
3336 _("&Rename"), _("&Cancel"));
3339 case 1: return renameBuffer(b, docstring(), kind);
3340 case 2: return false;
3346 case LV_VC_RENAME: {
3347 string msg = b.lyxvc().rename(fname);
3350 message(from_utf8(msg));
3354 string msg = b.lyxvc().copy(fname);
3357 message(from_utf8(msg));
3361 case LV_WRITE_AS_TEMPLATE:
3364 // LyXVC created the file already in case of LV_VC_RENAME or
3365 // LV_VC_COPY, but call saveBuffer() nevertheless to get
3366 // relative paths of included stuff right if we moved e.g. from
3367 // /a/b.lyx to /a/c/b.lyx.
3369 bool const saved = saveBuffer(b, fname);
3376 bool GuiView::exportBufferAs(Buffer & b, docstring const & iformat)
3378 FileName fname = b.fileName();
3380 FileDialog dlg(qt_("Choose a filename to export the document as"));
3381 dlg.setButton1(qt_("D&ocuments"), toqstr(lyxrc.document_path));
3384 QString const anyformat = qt_("Guess from extension (*.*)");
3387 vector<Format const *> export_formats;
3388 for (Format const & f : theFormats())
3389 if (f.documentFormat())
3390 export_formats.push_back(&f);
3391 sort(export_formats.begin(), export_formats.end(), Format::formatSorter);
3392 map<QString, string> fmap;
3395 for (Format const * f : export_formats) {
3396 docstring const loc_prettyname = translateIfPossible(f->prettyname());
3397 QString const loc_filter = toqstr(bformat(from_ascii("%1$s (*.%2$s)"),
3399 from_ascii(f->extension())));
3400 types << loc_filter;
3401 fmap[loc_filter] = f->name();
3402 if (from_ascii(f->name()) == iformat) {
3403 filter = loc_filter;
3404 ext = f->extension();
3407 string ofname = fname.onlyFileName();
3409 ofname = support::changeExtension(ofname, ext);
3410 FileDialog::Result result =
3411 dlg.save(toqstr(fname.onlyPath().absFileName()),
3415 if (result.first != FileDialog::Chosen)
3419 fname.set(fromqstr(result.second));
3420 if (filter == anyformat)
3421 fmt_name = theFormats().getFormatFromExtension(fname.extension());
3423 fmt_name = fmap[filter];
3424 LYXERR(Debug::FILES, "filter=" << fromqstr(filter)
3425 << ", fmt_name=" << fmt_name << ", fname=" << fname.absFileName());
3427 if (fmt_name.empty() || fname.empty())
3430 fname.ensureExtension(theFormats().extension(fmt_name));
3432 // fname is now the new Buffer location.
3433 if (fname.exists()) {
3434 docstring const file = makeDisplayPath(fname.absFileName(), 30);
3435 docstring text = bformat(_("The document %1$s already "
3436 "exists.\n\nDo you want to "
3437 "overwrite that document?"),
3439 int const ret = Alert::prompt(_("Overwrite document?"),
3440 text, 0, 2, _("&Overwrite"), _("&Rename"), _("&Cancel"));
3443 case 1: return exportBufferAs(b, from_ascii(fmt_name));
3444 case 2: return false;
3448 FuncRequest cmd(LFUN_BUFFER_EXPORT, fmt_name + " " + fname.absFileName());
3451 return dr.dispatched();
3455 bool GuiView::isBufferBusy(Buffer const * b)
3457 return GuiViewPrivate::busyBuffers.contains(b);
3461 bool GuiView::saveBuffer(Buffer & b)
3463 return saveBuffer(b, FileName());
3467 bool GuiView::saveBuffer(Buffer & b, FileName const & fn)
3469 if (workArea(b) && workArea(b)->inDialogMode())
3472 if (fn.empty() && b.isUnnamed())
3473 return renameBuffer(b, docstring());
3475 bool const success = (fn.empty() ? b.save() : b.saveAs(fn));
3477 theSession().lastFiles().add(b.fileName());
3478 theSession().writeFile();
3482 // Switch to this Buffer.
3485 // FIXME: we don't tell the user *WHY* the save failed !!
3486 docstring const file = makeDisplayPath(b.absFileName(), 30);
3487 docstring text = bformat(_("The document %1$s could not be saved.\n\n"
3488 "Do you want to rename the document and "
3489 "try again?"), file);
3490 int const ret = Alert::prompt(_("Rename and save?"),
3491 text, 0, 2, _("&Rename"), _("&Retry"), _("&Cancel"));
3494 if (!renameBuffer(b, docstring()))
3503 return saveBuffer(b, fn);
3507 bool GuiView::hideWorkArea(GuiWorkArea * wa)
3509 return closeWorkArea(wa, false);
3513 // We only want to close the buffer if it is not visible in other workareas
3514 // of the same view, nor in other views, and if this is not a child
3515 bool GuiView::closeWorkArea(GuiWorkArea * wa)
3517 Buffer & buf = wa->bufferView().buffer();
3519 bool last_wa = d.countWorkAreasOf(buf) == 1
3520 && !inOtherView(buf) && !buf.parent();
3522 bool close_buffer = last_wa;
3525 if (lyxrc.close_buffer_with_last_view == "yes")
3527 else if (lyxrc.close_buffer_with_last_view == "no")
3528 close_buffer = false;
3531 if (buf.isUnnamed())
3532 file = from_utf8(buf.fileName().onlyFileName());
3534 file = buf.fileName().displayName(30);
3535 docstring const text = bformat(
3536 _("Last view on document %1$s is being closed.\n"
3537 "Would you like to close or hide the document?\n"
3539 "Hidden documents can be displayed back through\n"
3540 "the menu: View->Hidden->...\n"
3542 "To remove this question, set your preference in:\n"
3543 " Tools->Preferences->Look&Feel->UserInterface\n"
3545 int ret = Alert::prompt(_("Close or hide document?"),
3546 text, 0, 2, _("&Close"), _("&Hide"), _("&Cancel"));
3549 close_buffer = (ret == 0);
3553 return closeWorkArea(wa, close_buffer);
3557 bool GuiView::closeBuffer()
3559 GuiWorkArea * wa = currentMainWorkArea();
3560 // coverity complained about this
3561 // it seems unnecessary, but perhaps is worth the check
3562 LASSERT(wa, return false);
3564 setCurrentWorkArea(wa);
3565 Buffer & buf = wa->bufferView().buffer();
3566 return closeWorkArea(wa, !buf.parent());
3570 void GuiView::writeSession() const {
3571 GuiWorkArea const * active_wa = currentMainWorkArea();
3572 for (int i = 0; i < d.splitter_->count(); ++i) {
3573 TabWorkArea * twa = d.tabWorkArea(i);
3574 for (int j = 0; j < twa->count(); ++j) {
3575 GuiWorkArea * wa = twa->workArea(j);
3576 Buffer & buf = wa->bufferView().buffer();
3577 theSession().lastOpened().add(buf.fileName(), wa == active_wa);
3583 bool GuiView::closeBufferAll()
3586 for (auto & buf : theBufferList()) {
3587 if (!saveBufferIfNeeded(*buf, false)) {
3588 // Closing has been cancelled, so abort.
3593 // Close the workareas in all other views
3594 QList<int> const ids = guiApp->viewIds();
3595 for (int i = 0; i != ids.size(); ++i) {
3596 if (id_ != ids[i] && !guiApp->view(ids[i]).closeWorkAreaAll())
3600 // Close our own workareas
3601 if (!closeWorkAreaAll())
3608 bool GuiView::closeWorkAreaAll()
3610 setCurrentWorkArea(currentMainWorkArea());
3612 // We might be in a situation that there is still a tabWorkArea, but
3613 // there are no tabs anymore. This can happen when we get here after a
3614 // TabWorkArea::lastWorkAreaRemoved() signal. Therefore we count how
3615 // many TabWorkArea's have no documents anymore.
3618 // We have to call count() each time, because it can happen that
3619 // more than one splitter will disappear in one iteration (bug 5998).
3620 while (d.splitter_->count() > empty_twa) {
3621 TabWorkArea * twa = d.tabWorkArea(empty_twa);
3623 if (twa->count() == 0)
3626 setCurrentWorkArea(twa->currentWorkArea());
3627 if (!closeTabWorkArea(twa))
3635 bool GuiView::closeWorkArea(GuiWorkArea * wa, bool close_buffer)
3640 Buffer & buf = wa->bufferView().buffer();
3642 if (GuiViewPrivate::busyBuffers.contains(&buf)) {
3643 Alert::warning(_("Close document"),
3644 _("Document could not be closed because it is being processed by LyX."));
3649 return closeBuffer(buf);
3651 if (!inMultiTabs(wa))
3652 if (!saveBufferIfNeeded(buf, true))
3660 bool GuiView::closeBuffer(Buffer & buf)
3662 bool success = true;
3663 for (Buffer * child_buf : buf.getChildren()) {
3664 if (theBufferList().isOthersChild(&buf, child_buf)) {
3665 child_buf->setParent(nullptr);
3669 // FIXME: should we look in other tabworkareas?
3670 // ANSWER: I don't think so. I've tested, and if the child is
3671 // open in some other window, it closes without a problem.
3672 GuiWorkArea * child_wa = workArea(*child_buf);
3675 // If we are in a close_event all children will be closed in some time,
3676 // so no need to do it here. This will ensure that the children end up
3677 // in the session file in the correct order. If we close the master
3678 // buffer, we can close or release the child buffers here too.
3680 success = closeWorkArea(child_wa, true);
3684 // In this case the child buffer is open but hidden.
3685 // Even in this case, children can be dirty (e.g.,
3686 // after a label change in the master, see #11405).
3687 // Therefore, check this
3688 if (closing_ && (child_buf->isClean() || child_buf->paragraphs().empty())) {
3689 // If we are in a close_event all children will be closed in some time,
3690 // so no need to do it here. This will ensure that the children end up
3691 // in the session file in the correct order. If we close the master
3692 // buffer, we can close or release the child buffers here too.
3695 // Save dirty buffers also if closing_!
3696 if (saveBufferIfNeeded(*child_buf, false)) {
3697 child_buf->removeAutosaveFile();
3698 theBufferList().release(child_buf);
3700 // Saving of dirty children has been cancelled.
3701 // Cancel the whole process.
3708 // goto bookmark to update bookmark pit.
3709 // FIXME: we should update only the bookmarks related to this buffer!
3710 // FIXME: this is done also in LFUN_WINDOW_CLOSE!
3711 LYXERR(Debug::DEBUG, "GuiView::closeBuffer()");
3712 for (unsigned int i = 1; i < theSession().bookmarks().size(); ++i)
3713 guiApp->gotoBookmark(i, false, false);
3715 if (saveBufferIfNeeded(buf, false)) {
3716 buf.removeAutosaveFile();
3717 theBufferList().release(&buf);
3721 // open all children again to avoid a crash because of dangling
3722 // pointers (bug 6603)
3728 bool GuiView::closeTabWorkArea(TabWorkArea * twa)
3730 while (twa == d.currentTabWorkArea()) {
3731 twa->setCurrentIndex(twa->count() - 1);
3733 GuiWorkArea * wa = twa->currentWorkArea();
3734 Buffer & b = wa->bufferView().buffer();
3736 // We only want to close the buffer if the same buffer is not visible
3737 // in another view, and if this is not a child and if we are closing
3738 // a view (not a tabgroup).
3739 bool const close_buffer =
3740 !inOtherView(b) && !b.parent() && closing_;
3742 if (!closeWorkArea(wa, close_buffer))
3749 bool GuiView::saveBufferIfNeeded(Buffer & buf, bool hiding)
3751 if (buf.isClean() || buf.paragraphs().empty())
3754 // Switch to this Buffer.
3760 if (buf.isUnnamed()) {
3761 file = from_utf8(buf.fileName().onlyFileName());
3764 FileName filename = buf.fileName();
3766 file = filename.displayName(30);
3767 exists = filename.exists();
3770 // Bring this window to top before asking questions.
3775 if (hiding && buf.isUnnamed()) {
3776 docstring const text = bformat(_("The document %1$s has not been "
3777 "saved yet.\n\nDo you want to save "
3778 "the document?"), file);
3779 ret = Alert::prompt(_("Save new document?"),
3780 text, 0, 1, _("&Save"), _("&Cancel"));
3784 docstring const text = exists ?
3785 bformat(_("The document %1$s has unsaved changes."
3786 "\n\nDo you want to save the document or "
3787 "discard the changes?"), file) :
3788 bformat(_("The document %1$s has not been saved yet."
3789 "\n\nDo you want to save the document or "
3790 "discard it entirely?"), file);
3791 docstring const title = exists ?
3792 _("Save changed document?") : _("Save document?");
3793 ret = Alert::prompt(title, text, 0, 2,
3794 _("&Save"), _("&Discard"), _("&Cancel"));
3799 if (!saveBuffer(buf))
3803 // If we crash after this we could have no autosave file
3804 // but I guess this is really improbable (Jug).
3805 // Sometimes improbable things happen:
3806 // - see bug http://www.lyx.org/trac/ticket/6587 (ps)
3807 // buf.removeAutosaveFile();
3809 // revert all changes
3820 bool GuiView::inMultiTabs(GuiWorkArea * wa)
3822 Buffer & buf = wa->bufferView().buffer();
3824 for (int i = 0; i != d.splitter_->count(); ++i) {
3825 GuiWorkArea * wa_ = d.tabWorkArea(i)->workArea(buf);
3826 if (wa_ && wa_ != wa)
3829 return inOtherView(buf);
3833 bool GuiView::inOtherView(Buffer & buf)
3835 QList<int> const ids = guiApp->viewIds();
3837 for (int i = 0; i != ids.size(); ++i) {
3841 if (guiApp->view(ids[i]).workArea(buf))
3848 void GuiView::gotoNextOrPreviousBuffer(NextOrPrevious np, bool const move)
3850 if (!documentBufferView())
3853 if (TabWorkArea * twa = d.currentTabWorkArea()) {
3854 Buffer * const curbuf = &documentBufferView()->buffer();
3855 int nwa = twa->count();
3856 for (int i = 0; i < nwa; ++i) {
3857 if (&workArea(i)->bufferView().buffer() == curbuf) {
3860 next_index = (i == nwa - 1 ? 0 : i + 1);
3862 next_index = (i == 0 ? nwa - 1 : i - 1);
3864 twa->moveTab(i, next_index);
3866 setBuffer(&workArea(next_index)->bufferView().buffer());
3874 void GuiView::gotoNextTabWorkArea(NextOrPrevious np)
3876 int count = d.splitter_->count();
3877 for (int i = 0; i < count; ++i) {
3878 if (d.tabWorkArea(i) == d.currentTabWorkArea()) {
3881 new_index = (i == count - 1 ? 0 : i + 1);
3883 new_index = (i == 0 ? count - 1 : i - 1);
3884 setCurrentWorkArea(d.tabWorkArea(new_index)->currentWorkArea());
3891 /// make sure the document is saved
3892 static bool ensureBufferClean(Buffer * buffer)
3894 LASSERT(buffer, return false);
3895 if (buffer->isClean() && !buffer->isUnnamed())
3898 docstring const file = buffer->fileName().displayName(30);
3901 if (!buffer->isUnnamed()) {
3902 text = bformat(_("The document %1$s has unsaved "
3903 "changes.\n\nDo you want to save "
3904 "the document?"), file);
3905 title = _("Save changed document?");
3908 text = bformat(_("The document %1$s has not been "
3909 "saved yet.\n\nDo you want to save "
3910 "the document?"), file);
3911 title = _("Save new document?");
3913 int const ret = Alert::prompt(title, text, 0, 1, _("&Save"), _("&Cancel"));
3916 dispatch(FuncRequest(LFUN_BUFFER_WRITE));
3918 return buffer->isClean() && !buffer->isUnnamed();
3922 bool GuiView::reloadBuffer(Buffer & buf)
3924 currentBufferView()->cursor().reset();
3925 Buffer::ReadStatus status = buf.reload();
3926 return status == Buffer::ReadSuccess;
3930 void GuiView::checkExternallyModifiedBuffers()
3932 for (Buffer * buf : theBufferList()) {
3933 if (buf->fileName().exists() && buf->isChecksumModified()) {
3934 docstring text = bformat(_("Document \n%1$s\n has been externally modified."
3935 " Reload now? Any local changes will be lost."),
3936 from_utf8(buf->absFileName()));
3937 int const ret = Alert::prompt(_("Reload externally changed document?"),
3938 text, 0, 1, _("&Reload"), _("&Cancel"));
3946 void GuiView::dispatchVC(FuncRequest const & cmd, DispatchResult & dr)
3948 Buffer * buffer = documentBufferView()
3949 ? &(documentBufferView()->buffer()) : nullptr;
3951 switch (cmd.action()) {
3952 case LFUN_VC_REGISTER:
3953 if (!buffer || !ensureBufferClean(buffer))
3955 if (!buffer->lyxvc().inUse()) {
3956 if (buffer->lyxvc().registrer()) {
3957 reloadBuffer(*buffer);
3958 dr.clearMessageUpdate();
3963 case LFUN_VC_RENAME:
3964 case LFUN_VC_COPY: {
3965 if (!buffer || !ensureBufferClean(buffer))
3967 if (buffer->lyxvc().inUse() && !buffer->hasReadonlyFlag()) {
3968 if (buffer->lyxvc().isCheckInWithConfirmation()) {
3969 // Some changes are not yet committed.
3970 // We test here and not in getStatus(), since
3971 // this test is expensive.
3973 LyXVC::CommandResult ret =
3974 buffer->lyxvc().checkIn(log);
3976 if (ret == LyXVC::ErrorCommand ||
3977 ret == LyXVC::VCSuccess)
3978 reloadBuffer(*buffer);
3979 if (buffer->lyxvc().isCheckInWithConfirmation()) {
3980 frontend::Alert::error(
3981 _("Revision control error."),
3982 _("Document could not be checked in."));
3986 RenameKind const kind = (cmd.action() == LFUN_VC_RENAME) ?
3987 LV_VC_RENAME : LV_VC_COPY;
3988 renameBuffer(*buffer, cmd.argument(), kind);
3993 case LFUN_VC_CHECK_IN:
3994 if (!buffer || !ensureBufferClean(buffer))
3996 if (buffer->lyxvc().inUse() && !buffer->hasReadonlyFlag()) {
3998 LyXVC::CommandResult ret = buffer->lyxvc().checkIn(log);
4000 // Only skip reloading if the checkin was cancelled or
4001 // an error occurred before the real checkin VCS command
4002 // was executed, since the VCS might have changed the
4003 // file even if it could not checkin successfully.
4004 if (ret == LyXVC::ErrorCommand || ret == LyXVC::VCSuccess)
4005 reloadBuffer(*buffer);
4009 case LFUN_VC_CHECK_OUT:
4010 if (!buffer || !ensureBufferClean(buffer))
4012 if (buffer->lyxvc().inUse()) {
4013 dr.setMessage(buffer->lyxvc().checkOut());
4014 reloadBuffer(*buffer);
4018 case LFUN_VC_LOCKING_TOGGLE:
4019 if (!buffer || !ensureBufferClean(buffer) || buffer->hasReadonlyFlag())
4021 if (buffer->lyxvc().inUse()) {
4022 string res = buffer->lyxvc().lockingToggle();
4024 frontend::Alert::error(_("Revision control error."),
4025 _("Error when setting the locking property."));
4028 reloadBuffer(*buffer);
4033 case LFUN_VC_REVERT:
4036 if (buffer->lyxvc().revert()) {
4037 reloadBuffer(*buffer);
4038 dr.clearMessageUpdate();
4042 case LFUN_VC_UNDO_LAST:
4045 buffer->lyxvc().undoLast();
4046 reloadBuffer(*buffer);
4047 dr.clearMessageUpdate();
4050 case LFUN_VC_REPO_UPDATE:
4053 if (ensureBufferClean(buffer)) {
4054 dr.setMessage(buffer->lyxvc().repoUpdate());
4055 checkExternallyModifiedBuffers();
4059 case LFUN_VC_COMMAND: {
4060 string flag = cmd.getArg(0);
4061 if (buffer && contains(flag, 'R') && !ensureBufferClean(buffer))
4064 if (contains(flag, 'M')) {
4065 if (!Alert::askForText(message, _("LyX VC: Log Message")))
4068 string path = cmd.getArg(1);
4069 if (contains(path, "$$p") && buffer)
4070 path = subst(path, "$$p", buffer->filePath());
4071 LYXERR(Debug::LYXVC, "Directory: " << path);
4073 if (!pp.isReadableDirectory()) {
4074 lyxerr << _("Directory is not accessible.") << endl;
4077 support::PathChanger p(pp);
4079 string command = cmd.getArg(2);
4080 if (command.empty())
4083 command = subst(command, "$$i", buffer->absFileName());
4084 command = subst(command, "$$p", buffer->filePath());
4086 command = subst(command, "$$m", to_utf8(message));
4087 LYXERR(Debug::LYXVC, "Command: " << command);
4089 one.startscript(Systemcall::Wait, command);
4093 if (contains(flag, 'I'))
4094 buffer->markDirty();
4095 if (contains(flag, 'R'))
4096 reloadBuffer(*buffer);
4101 case LFUN_VC_COMPARE: {
4102 if (cmd.argument().empty()) {
4103 lyx::dispatch(FuncRequest(LFUN_DIALOG_SHOW, "comparehistory"));
4109 string rev1 = cmd.getArg(0);
4113 if (!buffer->lyxvc().prepareFileRevision(rev1, f1))
4116 if (isStrInt(rev1) && convert<int>(rev1) <= 0) {
4117 f2 = buffer->absFileName();
4119 string rev2 = cmd.getArg(1);
4123 if (!buffer->lyxvc().prepareFileRevision(rev2, f2))
4127 LYXERR(Debug::LYXVC, "Launching comparison for fetched revisions:\n" <<
4128 f1 << "\n" << f2 << "\n" );
4129 string par = "compare run " + quoteName(f1) + " " + quoteName(f2);
4130 lyx::dispatch(FuncRequest(LFUN_DIALOG_SHOW, par));
4140 void GuiView::openChildDocument(string const & fname)
4142 LASSERT(documentBufferView(), return);
4143 Buffer & buffer = documentBufferView()->buffer();
4144 FileName const filename = support::makeAbsPath(fname, buffer.filePath());
4145 documentBufferView()->saveBookmark(false);
4146 Buffer * child = nullptr;
4147 if (theBufferList().exists(filename)) {
4148 child = theBufferList().getBuffer(filename);
4151 message(bformat(_("Opening child document %1$s..."),
4152 makeDisplayPath(filename.absFileName())));
4153 child = loadDocument(filename, false);
4155 // Set the parent name of the child document.
4156 // This makes insertion of citations and references in the child work,
4157 // when the target is in the parent or another child document.
4159 child->setParent(&buffer);
4163 bool GuiView::goToFileRow(string const & argument)
4167 size_t i = argument.find_last_of(' ');
4168 if (i != string::npos) {
4169 file_name = os::internal_path(FileName(trim(argument.substr(0, i))).realPath());
4170 istringstream is(argument.substr(i + 1));
4175 if (i == string::npos) {
4176 LYXERR0("Wrong argument: " << argument);
4179 Buffer * buf = nullptr;
4180 string const realtmp = package().temp_dir().realPath();
4181 // We have to use os::path_prefix_is() here, instead of
4182 // simply prefixIs(), because the file name comes from
4183 // an external application and may need case adjustment.
4184 if (os::path_prefix_is(file_name, realtmp, os::CASE_ADJUSTED)) {
4185 buf = theBufferList().getBufferFromTmp(file_name, true);
4186 LYXERR(Debug::FILES, "goToFileRow: buffer lookup for " << file_name
4187 << (buf ? " success" : " failed"));
4189 // Must replace extension of the file to be .lyx
4190 // and get full path
4191 FileName const s = fileSearch(string(),
4192 support::changeExtension(file_name, ".lyx"), "lyx");
4193 // Either change buffer or load the file
4194 if (theBufferList().exists(s))
4195 buf = theBufferList().getBuffer(s);
4196 else if (s.exists()) {
4197 buf = loadDocument(s);
4202 _("File does not exist: %1$s"),
4203 makeDisplayPath(file_name)));
4209 _("No buffer for file: %1$s."),
4210 makeDisplayPath(file_name))
4215 bool success = documentBufferView()->setCursorFromRow(row);
4217 LYXERR(Debug::OUTFILE,
4218 "setCursorFromRow: invalid position for row " << row);
4219 frontend::Alert::error(_("Inverse Search Failed"),
4220 _("Invalid position requested by inverse search.\n"
4221 "You may need to update the viewed document."));
4228 Buffer::ExportStatus GuiView::GuiViewPrivate::runAndDestroy(const T& func,
4229 Buffer const * orig, Buffer * clone, string const & format)
4231 Buffer::ExportStatus const status = func(format);
4233 // the cloning operation will have produced a clone of the entire set of
4234 // documents, starting from the master. so we must delete those.
4235 Buffer * mbuf = const_cast<Buffer *>(clone->masterBuffer());
4237 if (orig->needToRemoveBiblioTemps())
4238 orig->removeBiblioTempFiles();
4239 busyBuffers.remove(orig);
4244 Buffer::ExportStatus GuiView::GuiViewPrivate::compileAndDestroy(
4245 Buffer const * orig, Buffer * clone, string const & format)
4247 Buffer::ExportStatus (Buffer::* mem_func)(std::string const &, bool) const =
4249 return runAndDestroy(lyx::bind(mem_func, clone, _1, true), orig, clone, format);
4253 Buffer::ExportStatus GuiView::GuiViewPrivate::exportAndDestroy(
4254 Buffer const * orig, Buffer * clone, string const & format)
4256 Buffer::ExportStatus (Buffer::* mem_func)(std::string const &, bool) const =
4258 return runAndDestroy(lyx::bind(mem_func, clone, _1, false), orig, clone, format);
4262 Buffer::ExportStatus GuiView::GuiViewPrivate::previewAndDestroy(
4263 Buffer const * orig, Buffer * clone, string const & format)
4265 Buffer::ExportStatus (Buffer::* mem_func)(std::string const &) const =
4267 return runAndDestroy(lyx::bind(mem_func, clone, _1), orig, clone, format);
4271 bool GuiView::GuiViewPrivate::asyncBufferProcessing(string const & argument,
4272 Buffer const * used_buffer,
4273 docstring const & msg,
4274 Buffer::ExportStatus (*asyncFunc)(Buffer const *, Buffer *, string const &),
4275 Buffer::ExportStatus (Buffer::*syncFunc)(string const &, bool) const,
4276 Buffer::ExportStatus (Buffer::*previewFunc)(string const &) const,
4277 bool allow_async, bool use_tmpdir)
4282 string format = argument;
4284 format = used_buffer->params().getDefaultOutputFormat();
4285 processing_format = format;
4287 progress_->clearMessages();
4290 #if EXPORT_in_THREAD
4292 GuiViewPrivate::busyBuffers.insert(used_buffer);
4293 Buffer * cloned_buffer = used_buffer->cloneWithChildren();
4294 if (!cloned_buffer) {
4295 Alert::error(_("Export Error"),
4296 _("Error cloning the Buffer."));
4299 QFuture<Buffer::ExportStatus> f = QtConcurrent::run(
4304 setPreviewFuture(f);
4305 last_export_format = used_buffer->params().bufferFormat();
4308 // We are asynchronous, so we don't know here anything about the success
4311 Buffer::ExportStatus status;
4313 status = (used_buffer->*syncFunc)(format, use_tmpdir);
4314 } else if (previewFunc) {
4315 status = (used_buffer->*previewFunc)(format);
4318 handleExportStatus(gv_, status, format);
4320 return (status == Buffer::ExportSuccess
4321 || status == Buffer::PreviewSuccess);
4325 Buffer::ExportStatus status;
4327 status = (used_buffer->*syncFunc)(format, true);
4328 } else if (previewFunc) {
4329 status = (used_buffer->*previewFunc)(format);
4332 handleExportStatus(gv_, status, format);
4334 return (status == Buffer::ExportSuccess
4335 || status == Buffer::PreviewSuccess);
4339 void GuiView::dispatchToBufferView(FuncRequest const & cmd, DispatchResult & dr)
4341 BufferView * bv = currentBufferView();
4342 LASSERT(bv, return);
4344 // Let the current BufferView dispatch its own actions.
4345 bv->dispatch(cmd, dr);
4346 if (dr.dispatched()) {
4347 if (cmd.action() == LFUN_REDO || cmd.action() == LFUN_UNDO)
4348 updateDialog("document", "");
4352 // Try with the document BufferView dispatch if any.
4353 BufferView * doc_bv = documentBufferView();
4354 if (doc_bv && doc_bv != bv) {
4355 doc_bv->dispatch(cmd, dr);
4356 if (dr.dispatched()) {
4357 if (cmd.action() == LFUN_REDO || cmd.action() == LFUN_UNDO)
4358 updateDialog("document", "");
4363 // Then let the current Cursor dispatch its own actions.
4364 bv->cursor().dispatch(cmd);
4366 // update completion. We do it here and not in
4367 // processKeySym to avoid another redraw just for a
4368 // changed inline completion
4369 if (cmd.origin() == FuncRequest::KEYBOARD) {
4370 if (cmd.action() == LFUN_SELF_INSERT
4371 || (cmd.action() == LFUN_ERT_INSERT && bv->cursor().inMathed()))
4372 updateCompletion(bv->cursor(), true, true);
4373 else if (cmd.action() == LFUN_CHAR_DELETE_BACKWARD)
4374 updateCompletion(bv->cursor(), false, true);
4376 updateCompletion(bv->cursor(), false, false);
4379 dr = bv->cursor().result();
4383 void GuiView::dispatch(FuncRequest const & cmd, DispatchResult & dr)
4385 BufferView * bv = currentBufferView();
4386 // By default we won't need any update.
4387 dr.screenUpdate(Update::None);
4388 // assume cmd will be dispatched
4389 dr.dispatched(true);
4391 Buffer * doc_buffer = documentBufferView()
4392 ? &(documentBufferView()->buffer()) : nullptr;
4394 if (cmd.origin() == FuncRequest::TOC) {
4395 GuiToc * toc = static_cast<GuiToc*>(findOrBuild("toc", false));
4396 toc->doDispatch(bv->cursor(), cmd, dr);
4400 string const argument = to_utf8(cmd.argument());
4402 switch(cmd.action()) {
4403 case LFUN_BUFFER_CHILD_OPEN:
4404 openChildDocument(to_utf8(cmd.argument()));
4407 case LFUN_BUFFER_IMPORT:
4408 importDocument(to_utf8(cmd.argument()));
4411 case LFUN_MASTER_BUFFER_EXPORT:
4413 doc_buffer = const_cast<Buffer *>(doc_buffer->masterBuffer());
4415 case LFUN_BUFFER_EXPORT: {
4418 // GCC only sees strfwd.h when building merged
4419 if (::lyx::operator==(cmd.argument(), "custom")) {
4420 // LFUN_MASTER_BUFFER_EXPORT is not enabled for this case,
4421 // so the following test should not be needed.
4422 // In principle, we could try to switch to such a view...
4423 // if (cmd.action() == LFUN_BUFFER_EXPORT)
4424 dispatch(FuncRequest(LFUN_DIALOG_SHOW, "sendto"), dr);
4428 string const dest = cmd.getArg(1);
4429 FileName target_dir;
4430 if (!dest.empty() && FileName::isAbsolute(dest))
4431 target_dir = FileName(support::onlyPath(dest));
4433 target_dir = doc_buffer->fileName().onlyPath();
4435 string const format = (argument.empty() || argument == "default") ?
4436 doc_buffer->params().getDefaultOutputFormat() : argument;
4438 if ((dest.empty() && doc_buffer->isUnnamed())
4439 || !target_dir.isDirWritable()) {
4440 exportBufferAs(*doc_buffer, from_utf8(format));
4443 /* TODO/Review: Is it a problem to also export the children?
4444 See the update_unincluded flag */
4445 d.asyncBufferProcessing(format,
4448 &GuiViewPrivate::exportAndDestroy,
4450 nullptr, cmd.allowAsync());
4451 // TODO Inform user about success
4455 case LFUN_BUFFER_EXPORT_AS: {
4456 LASSERT(doc_buffer, break);
4457 docstring f = cmd.argument();
4459 f = from_ascii(doc_buffer->params().getDefaultOutputFormat());
4460 exportBufferAs(*doc_buffer, f);
4464 case LFUN_BUFFER_UPDATE: {
4465 d.asyncBufferProcessing(argument,
4468 &GuiViewPrivate::compileAndDestroy,
4470 nullptr, cmd.allowAsync(), true);
4473 case LFUN_BUFFER_VIEW: {
4474 d.asyncBufferProcessing(argument,
4476 _("Previewing ..."),
4477 &GuiViewPrivate::previewAndDestroy,
4479 &Buffer::preview, cmd.allowAsync());
4482 case LFUN_MASTER_BUFFER_UPDATE: {
4483 d.asyncBufferProcessing(argument,
4484 (doc_buffer ? doc_buffer->masterBuffer() : nullptr),
4486 &GuiViewPrivate::compileAndDestroy,
4488 nullptr, cmd.allowAsync(), true);
4491 case LFUN_MASTER_BUFFER_VIEW: {
4492 d.asyncBufferProcessing(argument,
4493 (doc_buffer ? doc_buffer->masterBuffer() : nullptr),
4495 &GuiViewPrivate::previewAndDestroy,
4496 nullptr, &Buffer::preview, cmd.allowAsync());
4499 case LFUN_EXPORT_CANCEL: {
4503 case LFUN_BUFFER_SWITCH: {
4504 string const file_name = to_utf8(cmd.argument());
4505 if (!FileName::isAbsolute(file_name)) {
4507 dr.setMessage(_("Absolute filename expected."));
4511 Buffer * buffer = theBufferList().getBuffer(FileName(file_name));
4514 dr.setMessage(_("Document not loaded"));
4518 // Do we open or switch to the buffer in this view ?
4519 if (workArea(*buffer)
4520 || lyxrc.open_buffers_in_tabs || !documentBufferView()) {
4525 // Look for the buffer in other views
4526 QList<int> const ids = guiApp->viewIds();
4528 for (; i != ids.size(); ++i) {
4529 GuiView & gv = guiApp->view(ids[i]);
4530 if (gv.workArea(*buffer)) {
4532 gv.activateWindow();
4534 gv.setBuffer(buffer);
4539 // If necessary, open a new window as a last resort
4540 if (i == ids.size()) {
4541 lyx::dispatch(FuncRequest(LFUN_WINDOW_NEW));
4547 case LFUN_BUFFER_NEXT:
4548 gotoNextOrPreviousBuffer(NEXT, false);
4551 case LFUN_BUFFER_MOVE_NEXT:
4552 gotoNextOrPreviousBuffer(NEXT, true);
4555 case LFUN_BUFFER_PREVIOUS:
4556 gotoNextOrPreviousBuffer(PREV, false);
4559 case LFUN_BUFFER_MOVE_PREVIOUS:
4560 gotoNextOrPreviousBuffer(PREV, true);
4563 case LFUN_BUFFER_CHKTEX:
4564 LASSERT(doc_buffer, break);
4565 doc_buffer->runChktex();
4568 case LFUN_CHANGES_TRACK: {
4569 // the actual dispatch is done in Buffer
4570 dispatchToBufferView(cmd, dr);
4571 // but we inform the GUI (document settings) if this is toggled
4572 LASSERT(doc_buffer, break);
4573 Q_EMIT changeTrackingToggled(doc_buffer->params().track_changes);
4577 case LFUN_COMMAND_EXECUTE: {
4578 command_execute_ = true;
4579 minibuffer_focus_ = true;
4582 case LFUN_DROP_LAYOUTS_CHOICE:
4583 d.layout_->showPopup();
4586 case LFUN_MENU_OPEN:
4587 if (QMenu * menu = guiApp->menus().menu(toqstr(cmd.argument()), *this))
4588 menu->exec(QCursor::pos());
4591 case LFUN_FILE_INSERT: {
4592 bool const ignore_lang = cmd.getArg(1) == "ignorelang";
4593 if (insertLyXFile(from_utf8(cmd.getArg(0)), ignore_lang)) {
4594 dr.forceBufferUpdate();
4595 dr.screenUpdate(Update::Force);
4600 case LFUN_FILE_INSERT_PLAINTEXT:
4601 case LFUN_FILE_INSERT_PLAINTEXT_PARA: {
4602 string const fname = to_utf8(cmd.argument());
4603 if (!fname.empty() && !FileName::isAbsolute(fname)) {
4604 dr.setMessage(_("Absolute filename expected."));
4608 FileName filename(fname);
4609 if (fname.empty()) {
4610 FileDialog dlg(qt_("Select file to insert"));
4612 FileDialog::Result result = dlg.open(toqstr(bv->buffer().filePath()),
4613 QStringList(qt_("All Files")+ " " + wildcardAllFiles()));
4615 if (result.first == FileDialog::Later || result.second.isEmpty()) {
4616 dr.setMessage(_("Canceled."));
4620 filename.set(fromqstr(result.second));
4624 FuncRequest const new_cmd(cmd, filename.absoluteFilePath());
4625 bv->dispatch(new_cmd, dr);
4630 case LFUN_BUFFER_RELOAD: {
4631 LASSERT(doc_buffer, break);
4634 bool drop = (cmd.argument() == "dump");
4637 if (!drop && !doc_buffer->isClean()) {
4638 docstring const file =
4639 makeDisplayPath(doc_buffer->absFileName(), 20);
4640 if (doc_buffer->notifiesExternalModification()) {
4641 docstring text = _("The current version will be lost. "
4642 "Are you sure you want to load the version on disk "
4643 "of the document %1$s?");
4644 ret = Alert::prompt(_("Reload saved document?"),
4645 bformat(text, file), 1, 1,
4646 _("&Reload"), _("&Cancel"));
4648 docstring text = _("Any changes will be lost. "
4649 "Are you sure you want to revert to the saved version "
4650 "of the document %1$s?");
4651 ret = Alert::prompt(_("Revert to saved document?"),
4652 bformat(text, file), 1, 1,
4653 _("&Revert"), _("&Cancel"));
4658 doc_buffer->markClean();
4659 reloadBuffer(*doc_buffer);
4660 dr.forceBufferUpdate();
4665 case LFUN_BUFFER_RESET_EXPORT:
4666 LASSERT(doc_buffer, break);
4667 doc_buffer->requireFreshStart(true);
4668 dr.setMessage(_("Buffer export reset."));
4671 case LFUN_BUFFER_WRITE:
4672 LASSERT(doc_buffer, break);
4673 saveBuffer(*doc_buffer);
4676 case LFUN_BUFFER_WRITE_AS:
4677 LASSERT(doc_buffer, break);
4678 renameBuffer(*doc_buffer, cmd.argument());
4681 case LFUN_BUFFER_WRITE_AS_TEMPLATE:
4682 LASSERT(doc_buffer, break);
4683 renameBuffer(*doc_buffer, cmd.argument(),
4684 LV_WRITE_AS_TEMPLATE);
4687 case LFUN_BUFFER_WRITE_ALL: {
4688 Buffer * first = theBufferList().first();
4691 message(_("Saving all documents..."));
4692 // We cannot use a for loop as the buffer list cycles.
4695 if (!b->isClean()) {
4697 LYXERR(Debug::ACTION, "Saved " << b->absFileName());
4699 b = theBufferList().next(b);
4700 } while (b != first);
4701 dr.setMessage(_("All documents saved."));
4705 case LFUN_MASTER_BUFFER_FORALL: {
4709 FuncRequest funcToRun = lyxaction.lookupFunc(cmd.getLongArg(0));
4710 funcToRun.allowAsync(false);
4712 for (Buffer const * buf : doc_buffer->allRelatives()) {
4713 // Switch to other buffer view and resend cmd
4714 lyx::dispatch(FuncRequest(
4715 LFUN_BUFFER_SWITCH, buf->absFileName()));
4716 lyx::dispatch(funcToRun);
4719 lyx::dispatch(FuncRequest(
4720 LFUN_BUFFER_SWITCH, doc_buffer->absFileName()));
4724 case LFUN_BUFFER_EXTERNAL_MODIFICATION_CLEAR:
4725 LASSERT(doc_buffer, break);
4726 doc_buffer->clearExternalModification();
4729 case LFUN_BUFFER_CLOSE:
4733 case LFUN_BUFFER_CLOSE_ALL:
4737 case LFUN_DEVEL_MODE_TOGGLE:
4738 devel_mode_ = !devel_mode_;
4740 dr.setMessage(_("Developer mode is now enabled."));
4742 dr.setMessage(_("Developer mode is now disabled."));
4745 case LFUN_TOOLBAR_SET: {
4746 string const name = cmd.getArg(0);
4747 string const state = cmd.getArg(1);
4748 if (GuiToolbar * t = toolbar(name))
4753 case LFUN_TOOLBAR_TOGGLE: {
4754 string const name = cmd.getArg(0);
4755 if (GuiToolbar * t = toolbar(name))
4760 case LFUN_TOOLBAR_MOVABLE: {
4761 string const name = cmd.getArg(0);
4763 // toggle (all) toolbars movablility
4764 toolbarsMovable_ = !toolbarsMovable_;
4765 for (ToolbarInfo const & ti : guiApp->toolbars()) {
4766 GuiToolbar * tb = toolbar(ti.name);
4767 if (tb && tb->isMovable() != toolbarsMovable_)
4768 // toggle toolbar movablity if it does not fit lock
4769 // (all) toolbars positions state silent = true, since
4770 // status bar notifications are slow
4773 if (toolbarsMovable_)
4774 dr.setMessage(_("Toolbars unlocked."));
4776 dr.setMessage(_("Toolbars locked."));
4777 } else if (GuiToolbar * tb = toolbar(name))
4778 // toggle current toolbar movablity
4780 // update lock (all) toolbars positions
4781 updateLockToolbars();
4785 case LFUN_ICON_SIZE: {
4786 QSize size = d.iconSize(cmd.argument());
4788 dr.setMessage(bformat(_("Icon size set to %1$dx%2$d."),
4789 size.width(), size.height()));
4793 case LFUN_DIALOG_UPDATE: {
4794 string const name = to_utf8(cmd.argument());
4795 if (name == "prefs" || name == "document")
4796 updateDialog(name, string());
4797 else if (name == "paragraph")
4798 lyx::dispatch(FuncRequest(LFUN_PARAGRAPH_UPDATE));
4799 else if (currentBufferView()) {
4800 Inset * inset = currentBufferView()->editedInset(name);
4801 // Can only update a dialog connected to an existing inset
4803 // FIXME: get rid of this indirection; GuiView ask the inset
4804 // if he is kind enough to update itself...
4805 FuncRequest fr(LFUN_INSET_DIALOG_UPDATE, cmd.argument());
4806 //FIXME: pass DispatchResult here?
4807 inset->dispatch(currentBufferView()->cursor(), fr);
4813 case LFUN_DIALOG_TOGGLE: {
4814 FuncCode const func_code = isDialogVisible(cmd.getArg(0))
4815 ? LFUN_DIALOG_HIDE : LFUN_DIALOG_SHOW;
4816 dispatch(FuncRequest(func_code, cmd.argument()), dr);
4820 case LFUN_DIALOG_DISCONNECT_INSET:
4821 disconnectDialog(to_utf8(cmd.argument()));
4824 case LFUN_DIALOG_HIDE: {
4825 guiApp->hideDialogs(to_utf8(cmd.argument()), nullptr);
4829 case LFUN_DIALOG_SHOW: {
4830 string const name = cmd.getArg(0);
4831 string sdata = trim(to_utf8(cmd.argument()).substr(name.size()));
4833 if (name == "latexlog") {
4834 // getStatus checks that
4835 LASSERT(doc_buffer, break);
4836 Buffer::LogType type;
4837 string const logfile = doc_buffer->logName(&type);
4839 case Buffer::latexlog:
4842 case Buffer::buildlog:
4843 sdata = "literate ";
4846 sdata += Lexer::quoteString(logfile);
4847 showDialog("log", sdata);
4848 } else if (name == "vclog") {
4849 // getStatus checks that
4850 LASSERT(doc_buffer, break);
4851 string const sdata2 = "vc " +
4852 Lexer::quoteString(doc_buffer->lyxvc().getLogFile());
4853 showDialog("log", sdata2);
4854 } else if (name == "symbols") {
4855 sdata = bv->cursor().getEncoding()->name();
4857 showDialog("symbols", sdata);
4858 } else if (name == "findreplace") {
4859 sdata = to_utf8(bv->cursor().selectionAsString(false));
4860 showDialog(name, sdata);
4862 } else if (name == "prefs" && isFullScreen()) {
4863 lfunUiToggle("fullscreen");
4864 showDialog("prefs", sdata);
4866 showDialog(name, sdata);
4871 dr.setMessage(cmd.argument());
4874 case LFUN_UI_TOGGLE: {
4875 string arg = cmd.getArg(0);
4876 if (!lfunUiToggle(arg)) {
4877 docstring const msg = "ui-toggle " + _("%1$s unknown command!");
4878 dr.setMessage(bformat(msg, from_utf8(arg)));
4880 // Make sure the keyboard focus stays in the work area.
4885 case LFUN_VIEW_SPLIT: {
4886 LASSERT(doc_buffer, break);
4887 string const orientation = cmd.getArg(0);
4888 d.splitter_->setOrientation(orientation == "vertical"
4889 ? Qt::Vertical : Qt::Horizontal);
4890 TabWorkArea * twa = addTabWorkArea();
4891 GuiWorkArea * wa = twa->addWorkArea(*doc_buffer, *this);
4893 wa->bufferView().copySettingsFrom(*bv);
4894 dr.screenUpdate(Update::ForceAll);
4895 setCurrentWorkArea(wa);
4899 case LFUN_TAB_GROUP_NEXT:
4900 gotoNextTabWorkArea(NEXT);
4903 case LFUN_TAB_GROUP_PREVIOUS:
4904 gotoNextTabWorkArea(PREV);
4907 case LFUN_TAB_GROUP_CLOSE:
4908 if (TabWorkArea * twa = d.currentTabWorkArea()) {
4909 closeTabWorkArea(twa);
4910 d.current_work_area_ = nullptr;
4911 twa = d.currentTabWorkArea();
4912 // Switch to the next GuiWorkArea in the found TabWorkArea.
4914 // Make sure the work area is up to date.
4915 setCurrentWorkArea(twa->currentWorkArea());
4917 setCurrentWorkArea(nullptr);
4922 case LFUN_VIEW_CLOSE:
4923 if (TabWorkArea * twa = d.currentTabWorkArea()) {
4924 closeWorkArea(twa->currentWorkArea());
4925 d.current_work_area_ = nullptr;
4926 twa = d.currentTabWorkArea();
4927 // Switch to the next GuiWorkArea in the found TabWorkArea.
4929 // Make sure the work area is up to date.
4930 setCurrentWorkArea(twa->currentWorkArea());
4932 setCurrentWorkArea(nullptr);
4937 case LFUN_COMPLETION_INLINE:
4938 if (d.current_work_area_)
4939 d.current_work_area_->completer().showInline();
4942 case LFUN_COMPLETION_POPUP:
4943 if (d.current_work_area_)
4944 d.current_work_area_->completer().showPopup();
4949 if (d.current_work_area_)
4950 d.current_work_area_->completer().tab();
4953 case LFUN_COMPLETION_CANCEL:
4954 if (d.current_work_area_) {
4955 if (d.current_work_area_->completer().popupVisible())
4956 d.current_work_area_->completer().hidePopup();
4958 d.current_work_area_->completer().hideInline();
4962 case LFUN_COMPLETION_ACCEPT:
4963 if (d.current_work_area_)
4964 d.current_work_area_->completer().activate();
4967 case LFUN_BUFFER_ZOOM_IN:
4968 case LFUN_BUFFER_ZOOM_OUT:
4969 case LFUN_BUFFER_ZOOM: {
4970 zoom_ratio_ = zoomRatio(cmd, zoom_ratio_);
4972 // Actual zoom value: default zoom + fractional extra value
4973 int zoom = (int)(lyxrc.defaultZoom * zoom_ratio_);
4974 zoom = min(max(zoom, zoom_min_), zoom_max_);
4976 setCurrentZoom(zoom);
4978 dr.setMessage(bformat(_("Zoom level is now %1$d% (default value: %2$d%)"),
4979 lyxrc.currentZoom, lyxrc.defaultZoom));
4981 guiApp->fontLoader().update();
4982 // Regenerate instant previews
4983 if (lyxrc.preview != LyXRC::PREVIEW_OFF
4984 && doc_buffer && doc_buffer->loader())
4985 doc_buffer->loader()->refreshPreviews();
4986 dr.screenUpdate(Update::ForceAll | Update::FitCursor);
4990 case LFUN_VC_REGISTER:
4991 case LFUN_VC_RENAME:
4993 case LFUN_VC_CHECK_IN:
4994 case LFUN_VC_CHECK_OUT:
4995 case LFUN_VC_REPO_UPDATE:
4996 case LFUN_VC_LOCKING_TOGGLE:
4997 case LFUN_VC_REVERT:
4998 case LFUN_VC_UNDO_LAST:
4999 case LFUN_VC_COMMAND:
5000 case LFUN_VC_COMPARE:
5001 dispatchVC(cmd, dr);
5004 case LFUN_SERVER_GOTO_FILE_ROW:
5005 if(goToFileRow(to_utf8(cmd.argument())))
5006 dr.screenUpdate(Update::Force | Update::FitCursor);
5009 case LFUN_LYX_ACTIVATE:
5013 case LFUN_WINDOW_RAISE:
5019 case LFUN_FORWARD_SEARCH: {
5020 // it seems safe to assume we have a document buffer, since
5021 // getStatus wants one.
5022 LASSERT(doc_buffer, break);
5023 Buffer const * doc_master = doc_buffer->masterBuffer();
5024 FileName const path(doc_master->temppath());
5025 string const texname = doc_master->isChild(doc_buffer)
5026 ? DocFileName(changeExtension(
5027 doc_buffer->absFileName(),
5028 "tex")).mangledFileName()
5029 : doc_buffer->latexName();
5030 string const fulltexname =
5031 support::makeAbsPath(texname, doc_master->temppath()).absFileName();
5032 string const mastername =
5033 removeExtension(doc_master->latexName());
5034 FileName const dviname(addName(path.absFileName(),
5035 addExtension(mastername, "dvi")));
5036 FileName const pdfname(addName(path.absFileName(),
5037 addExtension(mastername, "pdf")));
5038 bool const have_dvi = dviname.exists();
5039 bool const have_pdf = pdfname.exists();
5040 if (!have_dvi && !have_pdf) {
5041 dr.setMessage(_("Please, preview the document first."));
5044 bool const goto_dvi = have_dvi && !lyxrc.forward_search_dvi.empty();
5045 bool const goto_pdf = have_pdf && !lyxrc.forward_search_pdf.empty();
5046 string outname = dviname.onlyFileName();
5047 string command = lyxrc.forward_search_dvi;
5048 if ((!goto_dvi || goto_pdf) &&
5049 pdfname.lastModified() > dviname.lastModified()) {
5050 outname = pdfname.onlyFileName();
5051 command = lyxrc.forward_search_pdf;
5054 DocIterator cur = bv->cursor();
5055 int row = doc_buffer->texrow().rowFromDocIterator(cur).first;
5056 LYXERR(Debug::ACTION, "Forward search: row:" << row
5058 if (row == -1 || command.empty()) {
5059 dr.setMessage(_("Couldn't proceed."));
5062 string texrow = convert<string>(row);
5064 command = subst(command, "$$n", texrow);
5065 command = subst(command, "$$f", fulltexname);
5066 command = subst(command, "$$t", texname);
5067 command = subst(command, "$$o", outname);
5069 volatile PathChanger p(path);
5071 one.startscript(Systemcall::DontWait, command);
5075 case LFUN_SPELLING_CONTINUOUSLY:
5076 lyxrc.spellcheck_continuously = !lyxrc.spellcheck_continuously;
5077 dr.screenUpdate(Update::Force);
5080 case LFUN_CITATION_OPEN: {
5081 LASSERT(doc_buffer, break);
5082 frontend::showTarget(argument, *doc_buffer);
5087 // The LFUN must be for one of BufferView, Buffer or Cursor;
5089 dispatchToBufferView(cmd, dr);
5093 // Need to update bv because many LFUNs here might have destroyed it
5094 bv = currentBufferView();
5096 // Clear non-empty selections
5097 // (e.g. from a "char-forward-select" followed by "char-backward-select")
5099 Cursor & cur = bv->cursor();
5100 if ((cur.selection() && cur.selBegin() == cur.selEnd())) {
5101 cur.clearSelection();
5107 bool GuiView::lfunUiToggle(string const & ui_component)
5109 if (ui_component == "scrollbar") {
5110 // hide() is of no help
5111 if (d.current_work_area_->verticalScrollBarPolicy() ==
5112 Qt::ScrollBarAlwaysOff)
5114 d.current_work_area_->setVerticalScrollBarPolicy(
5115 Qt::ScrollBarAsNeeded);
5117 d.current_work_area_->setVerticalScrollBarPolicy(
5118 Qt::ScrollBarAlwaysOff);
5119 } else if (ui_component == "statusbar") {
5120 statusBar()->setVisible(!statusBar()->isVisible());
5121 } else if (ui_component == "menubar") {
5122 menuBar()->setVisible(!menuBar()->isVisible());
5123 } else if (ui_component == "zoomlevel") {
5124 zoom_value_->setVisible(!zoom_value_->isVisible());
5125 } else if (ui_component == "zoomslider") {
5126 zoom_widget_->setVisible(!zoom_widget_->isVisible());
5127 } else if (ui_component == "statistics-w") {
5128 word_count_enabled_ = !word_count_enabled_;
5131 } else if (ui_component == "statistics-cb") {
5132 char_count_enabled_ = !char_count_enabled_;
5135 } else if (ui_component == "statistics-c") {
5136 char_nb_count_enabled_ = !char_nb_count_enabled_;
5139 } else if (ui_component == "frame") {
5140 int const l = contentsMargins().left();
5142 //are the frames in default state?
5143 d.current_work_area_->setFrameStyle(QFrame::NoFrame);
5145 #if QT_VERSION > 0x050903
5146 setAttribute(Qt::WA_ContentsMarginsRespectsSafeArea, false);
5148 setContentsMargins(-2, -2, -2, -2);
5150 #if QT_VERSION > 0x050903
5151 setAttribute(Qt::WA_ContentsMarginsRespectsSafeArea, true);
5153 setContentsMargins(0, 0, 0, 0);
5156 if (ui_component == "fullscreen") {
5160 stat_counts_->setVisible(statsEnabled());
5165 void GuiView::cancelExport()
5167 Systemcall::killscript();
5168 // stop busy signal immediately so that in the subsequent
5169 // "Export canceled" prompt the status bar icons are accurate.
5170 Q_EMIT scriptKilled();
5174 void GuiView::toggleFullScreen()
5176 setWindowState(windowState() ^ Qt::WindowFullScreen);
5180 Buffer const * GuiView::updateInset(Inset const * inset)
5185 Buffer const * inset_buffer = &(inset->buffer());
5187 for (int i = 0; i != d.splitter_->count(); ++i) {
5188 GuiWorkArea * wa = d.tabWorkArea(i)->currentWorkArea();
5191 Buffer const * buffer = &(wa->bufferView().buffer());
5192 if (inset_buffer == buffer)
5193 wa->scheduleRedraw(true);
5195 return inset_buffer;
5199 void GuiView::restartCaret()
5201 /* When we move around, or type, it's nice to be able to see
5202 * the caret immediately after the keypress.
5204 if (d.current_work_area_)
5205 d.current_work_area_->startBlinkingCaret();
5207 // Take this occasion to update the other GUI elements.
5213 void GuiView::updateCompletion(Cursor & cur, bool start, bool keep)
5215 if (d.current_work_area_)
5216 d.current_work_area_->completer().updateVisibility(cur, start, keep);
5221 // This list should be kept in sync with the list of insets in
5222 // src/insets/Inset.cpp. I.e., if a dialog goes with an inset, the
5223 // dialog should have the same name as the inset.
5224 // Changes should be also recorded in LFUN_DIALOG_SHOW doxygen
5225 // docs in LyXAction.cpp.
5227 char const * const dialognames[] = {
5229 "aboutlyx", "bibitem", "bibtex", "box", "branch", "changes", "character",
5230 "citation", "compare", "comparehistory", "counter", "document", "errorlist", "ert",
5231 "external", "file", "findreplace", "findreplaceadv", "float", "graphics",
5232 "href", "include", "index", "index_print", "info", "listings", "label", "line",
5233 "log", "lyxfiles", "mathdelimiter", "mathmatrix", "mathspace", "nomenclature",
5234 "nomencl_print", "note", "paragraph", "phantom", "prefs", "ref",
5235 "sendto", "space", "spellchecker", "symbols", "tabular", "tabularcreate",
5236 "thesaurus", "texinfo", "toc", "view-source", "vspace", "wrap", "progress"};
5238 char const * const * const end_dialognames =
5239 dialognames + (sizeof(dialognames) / sizeof(char *));
5243 cmpCStr(char const * name) : name_(name) {}
5244 bool operator()(char const * other) {
5245 return strcmp(other, name_) == 0;
5252 bool isValidName(string const & name)
5254 return find_if(dialognames, end_dialognames,
5255 cmpCStr(name.c_str())) != end_dialognames;
5261 void GuiView::resetDialogs()
5263 // Make sure that no LFUN uses any GuiView.
5264 guiApp->setCurrentView(nullptr);
5268 constructToolbars();
5269 guiApp->menus().fillMenuBar(menuBar(), this, false);
5270 d.layout_->updateContents(true);
5271 // Now update controls with current buffer.
5272 guiApp->setCurrentView(this);
5278 void GuiView::flatGroupBoxes(const QObject * widget, bool flag)
5280 for (QObject * child: widget->children()) {
5281 if (child->inherits("QGroupBox")) {
5282 QGroupBox * box = (QGroupBox*) child;
5285 flatGroupBoxes(child, flag);
5291 Dialog * GuiView::find(string const & name, bool hide_it) const
5293 if (!isValidName(name))
5296 map<string, DialogPtr>::iterator it = d.dialogs_.find(name);
5298 if (it != d.dialogs_.end()) {
5300 it->second->hideView();
5301 return it->second.get();
5307 Dialog * GuiView::findOrBuild(string const & name, bool hide_it)
5309 Dialog * dialog = find(name, hide_it);
5310 if (dialog != nullptr)
5313 dialog = build(name);
5316 d.dialogs_[name].reset(dialog);
5317 // Force a uniform style for group boxes
5318 // On Mac non-flat works better, on Linux flat is standard
5319 flatGroupBoxes(dialog->asQWidget(), guiApp->platformName() != "cocoa");
5320 if (lyxrc.allow_geometry_session)
5321 dialog->restoreSession();
5329 void GuiView::showDialog(string const & name, string const & sdata,
5332 triggerShowDialog(toqstr(name), toqstr(sdata), inset);
5336 void GuiView::doShowDialog(QString const & qname, QString const & qdata,
5342 const string name = fromqstr(qname);
5343 const string sdata = fromqstr(qdata);
5347 Dialog * dialog = findOrBuild(name, false);
5349 bool const visible = dialog->isVisibleView();
5350 dialog->showData(sdata);
5351 if (currentBufferView())
5352 currentBufferView()->editInset(name, inset);
5353 // We only set the focus to the new dialog if it was not yet
5354 // visible in order not to change the existing previous behaviour
5356 // activateWindow is needed for floating dockviews
5357 dialog->asQWidget()->raise();
5358 dialog->asQWidget()->activateWindow();
5359 if (dialog->wantInitialFocus())
5360 dialog->asQWidget()->setFocus();
5364 catch (ExceptionMessage const &) {
5372 bool GuiView::isDialogVisible(string const & name) const
5374 map<string, DialogPtr>::const_iterator it = d.dialogs_.find(name);
5375 if (it == d.dialogs_.end())
5377 return it->second.get()->isVisibleView() && !it->second.get()->isClosing();
5381 void GuiView::hideDialog(string const & name, Inset * inset)
5383 map<string, DialogPtr>::const_iterator it = d.dialogs_.find(name);
5384 if (it == d.dialogs_.end())
5388 if (!currentBufferView())
5390 if (inset != currentBufferView()->editedInset(name))
5394 Dialog * const dialog = it->second.get();
5395 if (dialog->isVisibleView())
5397 if (currentBufferView())
5398 currentBufferView()->editInset(name, nullptr);
5402 void GuiView::disconnectDialog(string const & name)
5404 if (!isValidName(name))
5406 if (currentBufferView())
5407 currentBufferView()->editInset(name, nullptr);
5411 void GuiView::hideAll() const
5413 for(auto const & dlg_p : d.dialogs_)
5414 dlg_p.second->hideView();
5418 void GuiView::updateDialogs()
5420 for(auto const & dlg_p : d.dialogs_) {
5421 Dialog * dialog = dlg_p.second.get();
5423 if (dialog->needBufferOpen() && !documentBufferView())
5424 hideDialog(fromqstr(dialog->name()), nullptr);
5425 else if (dialog->isVisibleView())
5426 dialog->checkStatus();
5434 Dialog * GuiView::build(string const & name)
5436 return createDialog(*this, name);
5440 SEMenu::SEMenu(QWidget * parent)
5442 QAction * action = addAction(qt_("Disable Shell Escape"));
5443 connect(action, SIGNAL(triggered()),
5444 parent, SLOT(disableShellEscape()));
5448 void PressableSvgWidget::mousePressEvent(QMouseEvent * event)
5450 if (event->button() == Qt::LeftButton) {
5455 } // namespace frontend
5458 #include "moc_GuiView.cpp"