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 static QSet<Buffer const *> busyBuffers;
532 unsigned int smallIconSize;
533 unsigned int normalIconSize;
534 unsigned int bigIconSize;
535 unsigned int hugeIconSize;
536 unsigned int giantIconSize;
538 /// flag against a race condition due to multiclicks, see bug #1119
541 // Timers for statistic updates in buffer
542 /// Current time left to the nearest info update
543 int time_to_update = 1000;
544 ///Basic step for timer in ms. Basically reaction time for short selections
545 int const timer_rate = 500;
546 /// Real stats updates infrequently. First they take long time for big buffers, second
547 /// they are visible for fast-repeat keyboards even for mid documents.
548 int const default_stats_rate = 5000;
549 /// Detection of new selection, so we can react fast
550 bool already_in_selection_ = false;
551 /// Maximum size of "short" selection for which we can update with faster timer_rate
552 int const max_sel_chars = 5000;
556 QSet<Buffer const *> GuiView::GuiViewPrivate::busyBuffers;
559 GuiView::GuiView(int id)
560 : d(*new GuiViewPrivate(this)), id_(id), closing_(false), busy_(0),
561 command_execute_(false), minibuffer_focus_(false), word_count_enabled_(true),
562 char_count_enabled_(true), char_nb_count_enabled_(false),
563 toolbarsMovable_(true), devel_mode_(false)
565 connect(this, SIGNAL(bufferViewChanged()),
566 this, SLOT(onBufferViewChanged()));
568 // GuiToolbars *must* be initialised before the menu bar.
569 setIconSize(QSize(d.normalIconSize, d.normalIconSize)); // at least on Mac the default is 32 otherwise, which is huge
572 // set ourself as the current view. This is needed for the menu bar
573 // filling, at least for the static special menu item on Mac. Otherwise
574 // they are greyed out.
575 guiApp->setCurrentView(this);
577 // Fill up the menu bar.
578 guiApp->menus().fillMenuBar(menuBar(), this, true);
580 setCentralWidget(d.stack_widget_);
582 // Start autosave timer
583 if (lyxrc.autosave) {
584 // The connection is closed when this is destroyed.
585 d.autosave_timeout_.timeout.connect([this](){ autoSave();});
586 d.autosave_timeout_.setTimeout(lyxrc.autosave * 1000);
587 d.autosave_timeout_.start();
589 connect(&d.statusbar_timer_, SIGNAL(timeout()),
590 this, SLOT(clearMessage()));
591 connect(&d.statusbar_stats_timer_, SIGNAL(timeout()),
592 this, SLOT(showStats()));
593 d.statusbar_stats_timer_.start(d.timer_rate);
595 // We don't want to keep the window in memory if it is closed.
596 setAttribute(Qt::WA_DeleteOnClose, true);
598 #if !(defined(Q_OS_WIN) || defined(Q_CYGWIN_WIN)) && !defined(Q_OS_MAC)
599 // assign an icon to main form. We do not do it under Qt/Win or Qt/Mac,
600 // since the icon is provided in the application bundle. We use a themed
601 // version when available and use the bundled one as fallback.
602 setWindowIcon(QIcon::fromTheme("lyx", getPixmap("images/", "lyx", "svg,png")));
607 // use tabbed dock area for multiple docks
608 // (such as "source" and "messages")
609 setDockOptions(QMainWindow::ForceTabbedDocks);
612 // use document mode tabs on docks
613 setDocumentMode(true);
617 setAcceptDrops(true);
619 QFontMetrics const fm(statusBar()->fontMetrics());
620 int const iconheight = max(int(d.normalIconSize), fm.height());
621 QSize const iconsize(iconheight, iconheight);
623 // add busy indicator to statusbar
624 search_mode mode = theGuiApp()->imageSearchMode();
625 QString fn = toqstr(lyx::libFileSearch("images", "busy", "svgz", mode).absFileName());
626 PressableSvgWidget * busySVG = new PressableSvgWidget(fn);
627 statusBar()->addPermanentWidget(busySVG);
628 // make busy indicator square with 5px margins
629 busySVG->setMaximumSize(busySVG->height() - 5, busySVG->height() - 5);
632 QPixmap ps = QIcon(getPixmap("images/", "process-stop", "svgz")).pixmap(iconsize);
633 GuiClickableLabel * processStop = new GuiClickableLabel(statusBar());
634 processStop->setPixmap(ps);
635 processStop->setToolTip(qt_("Click here to stop export/output process"));
637 statusBar()->addPermanentWidget(processStop);
639 connect(&d.processing_thread_watcher_, SIGNAL(started()),
640 busySVG, SLOT(show()));
641 connect(&d.processing_thread_watcher_, SIGNAL(finished()),
642 busySVG, SLOT(hide()));
643 connect(&d.processing_thread_watcher_, SIGNAL(started()),
644 processStop, SLOT(show()));
645 connect(&d.processing_thread_watcher_, SIGNAL(finished()),
646 processStop, SLOT(hide()));
647 connect(processStop, SIGNAL(clicked()), this, SLOT(checkCancelBackground()));
649 connect(this, SIGNAL(scriptKilled()), busySVG, SLOT(hide()));
650 connect(this, SIGNAL(scriptKilled()), processStop, SLOT(hide()));
652 stat_counts_ = new GuiClickableLabel(statusBar());
653 stat_counts_->setAlignment(Qt::AlignCenter);
654 stat_counts_->setFrameStyle(QFrame::StyledPanel);
655 stat_counts_->hide();
656 statusBar()->addPermanentWidget(stat_counts_);
658 connect(stat_counts_, SIGNAL(clicked()), this, SLOT(statsPressed()));
660 zoom_slider_ = new QSlider(Qt::Horizontal, statusBar());
661 #if (QT_VERSION >= QT_VERSION_CHECK(5, 11, 0))
662 zoom_slider_->setFixedWidth(fm.horizontalAdvance('x') * 15);
664 zoom_slider_->setFixedWidth(fm.width('x') * 15);
666 // Make the defaultZoom center
667 zoom_slider_->setRange(10, (lyxrc.defaultZoom * 2) - 10);
668 // Initialize proper zoom value
670 zoom_ratio_ = settings.value("zoom_ratio", 1.0).toDouble();
671 // Actual zoom value: default zoom + fractional offset
672 int zoom = (int)(lyxrc.defaultZoom * zoom_ratio_);
673 zoom = min(max(zoom, zoom_min_), zoom_max_);
674 zoom_slider_->setValue(zoom);
675 zoom_slider_->setToolTip(qt_("Workarea zoom level. Drag, use Ctrl-+/- or Shift-Mousewheel to adjust."));
677 // Buttons to change zoom stepwise
678 #if (QT_VERSION >= QT_VERSION_CHECK(5, 11, 0))
679 QSize s(fm.horizontalAdvance('+'), fm.height());
681 QSize s(fm.width('+'), fm.height());
683 zoom_in_ = new GuiClickableLabel(statusBar());
684 zoom_in_->setText("+");
685 zoom_in_->setFixedSize(s);
686 zoom_in_->setAlignment(Qt::AlignCenter);
687 zoom_out_ = new GuiClickableLabel(statusBar());
688 zoom_out_->setText(QString(QChar(0x2212)));
689 zoom_out_->setFixedSize(s);
690 zoom_out_->setAlignment(Qt::AlignCenter);
693 zoom_widget_ = new QWidget(statusBar());
694 zoom_widget_->setAttribute(Qt::WA_MacSmallSize);
695 zoom_widget_->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Preferred);
696 zoom_widget_->setLayout(new QHBoxLayout());
697 zoom_widget_->layout()->setSpacing(5);
698 zoom_widget_->layout()->setContentsMargins(0,0,0,0);
699 zoom_widget_->layout()->addWidget(zoom_out_);
700 zoom_widget_->layout()->addWidget(zoom_slider_);
701 zoom_widget_->layout()->addWidget(zoom_in_);
702 statusBar()->addPermanentWidget(zoom_widget_);
703 zoom_out_->setEnabled(currentBufferView()
704 && zoom_slider_->value() > zoom_slider_->minimum());
705 zoom_slider_->setEnabled(currentBufferView());
706 zoom_in_->setEnabled(currentBufferView()
707 && zoom_slider_->value() < zoom_slider_->maximum());
709 connect(zoom_slider_, SIGNAL(sliderMoved(int)), this, SLOT(zoomSliderMoved(int)));
710 connect(zoom_slider_, SIGNAL(valueChanged(int)), this, SLOT(zoomValueChanged(int)));
711 connect(this, SIGNAL(currentZoomChanged(int)), zoom_slider_, SLOT(setValue(int)));
712 connect(zoom_in_, SIGNAL(clicked()), this, SLOT(zoomInPressed()));
713 connect(zoom_out_, SIGNAL(clicked()), this, SLOT(zoomOutPressed()));
715 // QPalette palette = statusBar()->palette();
717 zoom_value_ = new GuiClickableLabel(statusBar());
718 connect(zoom_value_, SIGNAL(pressed()), this, SLOT(showZoomContextMenu()));
719 // zoom_value_->setPalette(palette);
720 zoom_value_->setForegroundRole(statusBar()->foregroundRole());
721 zoom_value_->setFixedHeight(fm.height());
722 #if (QT_VERSION >= QT_VERSION_CHECK(5, 11, 0))
723 zoom_value_->setMinimumWidth(fm.horizontalAdvance("444\%"));
725 zoom_value_->setMinimumWidth(fm.width("444\%"));
727 zoom_value_->setAlignment(Qt::AlignCenter);
728 zoom_value_->setText(toqstr(bformat(_("[[ZOOM]]%1$d%"), zoom)));
729 statusBar()->addPermanentWidget(zoom_value_);
730 zoom_value_->setEnabled(currentBufferView());
732 statusBar()->setContextMenuPolicy(Qt::CustomContextMenu);
733 connect(statusBar(), SIGNAL(customContextMenuRequested(QPoint)),
734 this, SLOT(showStatusBarContextMenu()));
736 // enable pinch to zoom
737 grabGesture(Qt::PinchGesture);
739 QPixmap shellescape = QIcon(getPixmap("images/", "emblem-shellescape", "svgz,png")).pixmap(iconsize);
740 shell_escape_ = new QLabel(statusBar());
741 shell_escape_->setPixmap(shellescape);
742 shell_escape_->setAlignment(Qt::AlignCenter);
743 shell_escape_->setContextMenuPolicy(Qt::CustomContextMenu);
744 shell_escape_->setToolTip(qt_("WARNING: LaTeX is allowed to execute "
745 "external commands for this document. "
746 "Right click to change."));
747 SEMenu * menu = new SEMenu(this);
748 connect(shell_escape_, SIGNAL(customContextMenuRequested(QPoint)),
749 menu, SLOT(showMenu(QPoint)));
750 shell_escape_->hide();
751 statusBar()->addPermanentWidget(shell_escape_);
753 QPixmap readonly = QIcon(getPixmap("images/", "emblem-readonly", "svgz,png")).pixmap(iconsize);
754 read_only_ = new QLabel(statusBar());
755 read_only_->setPixmap(readonly);
756 read_only_->setAlignment(Qt::AlignCenter);
758 statusBar()->addPermanentWidget(read_only_);
760 version_control_ = new QLabel(statusBar());
761 version_control_->setAlignment(Qt::AlignCenter);
762 version_control_->setFrameStyle(QFrame::StyledPanel);
763 version_control_->hide();
764 statusBar()->addPermanentWidget(version_control_);
766 statusBar()->setSizeGripEnabled(true);
769 connect(&d.autosave_watcher_, SIGNAL(finished()), this,
770 SLOT(autoSaveThreadFinished()));
772 connect(&d.processing_thread_watcher_, SIGNAL(started()), this,
773 SLOT(processingThreadStarted()));
774 connect(&d.processing_thread_watcher_, SIGNAL(finished()), this,
775 SLOT(processingThreadFinished()));
777 connect(this, SIGNAL(triggerShowDialog(QString const &, QString const &, Inset *)),
778 SLOT(doShowDialog(QString const &, QString const &, Inset *)));
780 // Forbid too small unresizable window because it can happen
781 // with some window manager under X11.
782 setMinimumSize(300, 200);
784 if (lyxrc.allow_geometry_session) {
785 // Now take care of session management.
790 // no session handling, default to a sane size.
791 setGeometry(50, 50, 690, 510);
794 // clear session data if any.
795 settings.remove("views");
805 void GuiView::disableShellEscape()
807 BufferView * bv = documentBufferView();
810 theSession().shellescapeFiles().remove(bv->buffer().absFileName());
811 bv->buffer().params().shell_escape = false;
812 bv->processUpdateFlags(Update::Force);
816 void GuiView::checkCancelBackground()
818 docstring const ttl = _("Cancel Export?");
819 docstring const msg = _("Do you want to cancel the background export process?");
821 Alert::prompt(ttl, msg, 1, 1,
822 _("&Cancel export"), _("Co&ntinue"));
828 void GuiView::statsPressed()
831 dispatch(FuncRequest(LFUN_STATISTICS), dr);
834 void GuiView::zoomSliderMoved(int value)
837 dispatch(FuncRequest(LFUN_BUFFER_ZOOM, convert<string>(value)), dr);
838 scheduleRedrawWorkAreas();
839 zoom_value_->setText(toqstr(bformat(_("[[ZOOM]]%1$d%"), value)));
840 zoom_in_->setEnabled(currentBufferView()
841 && value < zoom_slider_->maximum());
842 zoom_out_->setEnabled(currentBufferView()
843 && value > zoom_slider_->minimum());
847 void GuiView::zoomValueChanged(int value)
849 if (value != lyxrc.currentZoom)
850 zoomSliderMoved(value);
854 void GuiView::zoomInPressed()
857 dispatch(FuncRequest(LFUN_BUFFER_ZOOM_IN), dr);
858 scheduleRedrawWorkAreas();
862 void GuiView::zoomOutPressed()
865 dispatch(FuncRequest(LFUN_BUFFER_ZOOM_OUT), dr);
866 scheduleRedrawWorkAreas();
870 void GuiView::showZoomContextMenu()
872 QMenu * menu = guiApp->menus().menu(toqstr("context-zoom"), * this);
875 menu->exec(QCursor::pos());
879 void GuiView::showStatusBarContextMenu()
881 QMenu * menu = guiApp->menus().menu(toqstr("context-statusbar"), * this);
884 menu->exec(QCursor::pos());
888 void GuiView::scheduleRedrawWorkAreas()
890 for (int i = 0; i < d.tabWorkAreaCount(); i++) {
891 TabWorkArea* ta = d.tabWorkArea(i);
892 for (int u = 0; u < ta->count(); u++) {
893 ta->workArea(u)->scheduleRedraw(true);
899 QVector<GuiWorkArea*> GuiView::GuiViewPrivate::guiWorkAreas()
901 QVector<GuiWorkArea*> areas;
902 for (int i = 0; i < tabWorkAreaCount(); i++) {
903 TabWorkArea* ta = tabWorkArea(i);
904 for (int u = 0; u < ta->count(); u++) {
905 areas << ta->workArea(u);
911 static void handleExportStatus(GuiView * view, Buffer::ExportStatus status,
912 string const & format)
914 docstring const fmt = translateIfPossible(theFormats().prettyName(format));
917 case Buffer::ExportSuccess:
918 msg = bformat(_("Successful export to format: %1$s"), fmt);
920 case Buffer::ExportCancel:
921 msg = _("Document export cancelled.");
923 case Buffer::ExportError:
924 case Buffer::ExportNoPathToFormat:
925 case Buffer::ExportTexPathHasSpaces:
926 case Buffer::ExportConverterError:
927 msg = bformat(_("Error while exporting format: %1$s"), fmt);
929 case Buffer::PreviewSuccess:
930 msg = bformat(_("Successful preview of format: %1$s"), fmt);
932 case Buffer::PreviewError:
933 msg = bformat(_("Error while previewing format: %1$s"), fmt);
935 case Buffer::ExportKilled:
936 msg = bformat(_("Conversion cancelled while previewing format: %1$s"), fmt);
943 void GuiView::processingThreadStarted()
948 void GuiView::processingThreadFinished()
950 QFutureWatcher<Buffer::ExportStatus> const * watcher =
951 static_cast<QFutureWatcher<Buffer::ExportStatus> const *>(sender());
953 Buffer::ExportStatus const status = watcher->result();
954 handleExportStatus(this, status, d.processing_format);
957 BufferView const * const bv = currentBufferView();
958 if (bv && !bv->buffer().errorList("Export").empty()) {
963 bool const error = (status != Buffer::ExportSuccess &&
964 status != Buffer::PreviewSuccess &&
965 status != Buffer::ExportCancel);
967 ErrorList & el = bv->buffer().errorList(d.last_export_format);
968 // at this point, we do not know if buffer-view or
969 // master-buffer-view was called. If there was an export error,
970 // and the current buffer's error log is empty, we guess that
971 // it must be master-buffer-view that was called so we set
973 errors(d.last_export_format, el.empty());
978 void GuiView::autoSaveThreadFinished()
980 QFutureWatcher<docstring> const * watcher =
981 static_cast<QFutureWatcher<docstring> const *>(sender());
982 message(watcher->result());
987 void GuiView::saveLayout() const
990 settings.setValue("zoom_ratio", zoom_ratio_);
991 settings.setValue("devel_mode", devel_mode_);
992 settings.beginGroup("views");
993 settings.beginGroup(QString::number(id_));
994 if (guiApp->platformName() == "xcb") {
995 settings.setValue("pos", pos());
996 settings.setValue("size", size());
998 settings.setValue("geometry", saveGeometry());
999 settings.setValue("layout", saveState(0));
1000 settings.setValue("icon_size", toqstr(d.iconSize(iconSize())));
1001 settings.setValue("zoom_value_visible", zoom_value_->isVisible());
1002 settings.setValue("zoom_slider_visible", zoom_widget_->isVisible());
1003 settings.setValue("word_count_enabled", word_count_enabled_);
1004 settings.setValue("char_count_enabled", char_count_enabled_);
1005 settings.setValue("char_nb_count_enabled", char_nb_count_enabled_);
1009 void GuiView::saveUISettings() const
1013 // Save the toolbar private states
1014 for (auto const & tb_p : d.toolbars_)
1015 tb_p.second->saveSession(settings);
1016 // Now take care of all other dialogs
1017 for (auto const & dlg_p : d.dialogs_)
1018 dlg_p.second->saveSession(settings);
1022 void GuiView::setCurrentZoom(const int v)
1024 Q_EMIT currentZoomChanged(v);
1025 lyxrc.currentZoom = v;
1026 zoom_value_->setText(toqstr(bformat(_("[[ZOOM]]%1$d%"), v)));
1027 zoom_in_->setEnabled(currentBufferView() && v < zoom_slider_->maximum());
1028 zoom_out_->setEnabled(currentBufferView() && v > zoom_slider_->minimum());
1032 bool GuiView::restoreLayout()
1035 zoom_ratio_ = settings.value("zoom_ratio", 1.0).toDouble();
1036 // Actual zoom value: default zoom + fractional offset
1037 int zoom = (int)(lyxrc.defaultZoom * zoom_ratio_);
1038 zoom = min(max(zoom, zoom_min_), zoom_max_);
1039 setCurrentZoom(zoom);
1040 devel_mode_ = settings.value("devel_mode", devel_mode_).toBool();
1041 settings.beginGroup("views");
1042 settings.beginGroup(QString::number(id_));
1043 QString const icon_key = "icon_size";
1044 if (!settings.contains(icon_key))
1047 //code below is skipped when when ~/.config/LyX is (re)created
1048 setIconSize(d.iconSize(settings.value(icon_key).toString()));
1050 zoom_value_->setVisible(settings.value("zoom_value_visible", true).toBool());
1052 bool const show_zoom_slider = settings.value("zoom_slider_visible", true).toBool();
1053 zoom_widget_->setVisible(show_zoom_slider);
1055 word_count_enabled_ = settings.value("word_count_enabled", true).toBool();
1056 char_count_enabled_ = settings.value("char_count_enabled", true).toBool();
1057 char_nb_count_enabled_ = settings.value("char_nb_count_enabled", true).toBool();
1058 stat_counts_->setVisible(word_count_enabled_ || char_count_enabled_ || char_nb_count_enabled_);
1060 if (guiApp->platformName() == "xcb") {
1061 QPoint pos = settings.value("pos", QPoint(50, 50)).toPoint();
1062 QSize size = settings.value("size", QSize(690, 510)).toSize();
1066 // Work-around for bug #6034: the window ends up in an undetermined
1067 // state when trying to restore a maximized window when it is
1068 // already maximized.
1069 if (!(windowState() & Qt::WindowMaximized))
1070 if (!restoreGeometry(settings.value("geometry").toByteArray()))
1071 setGeometry(50, 50, 690, 510);
1074 // Make sure layout is correctly oriented.
1075 setLayoutDirection(qApp->layoutDirection());
1077 // Allow the toc and view-source dock widget to be restored if needed.
1079 if ((dialog = findOrBuild("toc", true)))
1080 // see bug 5082. At least setup title and enabled state.
1081 // Visibility will be adjusted by restoreState below.
1082 dialog->prepareView();
1083 if ((dialog = findOrBuild("view-source", true)))
1084 dialog->prepareView();
1085 if ((dialog = findOrBuild("progress", true)))
1086 dialog->prepareView();
1088 if (!restoreState(settings.value("layout").toByteArray(), 0))
1091 // init the toolbars that have not been restored
1092 for (auto const & tb_p : guiApp->toolbars()) {
1093 GuiToolbar * tb = toolbar(tb_p.name);
1094 if (tb && !tb->isRestored())
1095 initToolbar(tb_p.name);
1098 // update lock (all) toolbars positions
1099 updateLockToolbars();
1106 GuiToolbar * GuiView::toolbar(string const & name)
1108 ToolbarMap::iterator it = d.toolbars_.find(name);
1109 if (it != d.toolbars_.end())
1112 LYXERR(Debug::GUI, "Toolbar::display: no toolbar named " << name);
1117 void GuiView::updateLockToolbars()
1119 toolbarsMovable_ = false;
1120 for (ToolbarInfo const & info : guiApp->toolbars()) {
1121 GuiToolbar * tb = toolbar(info.name);
1122 if (tb && tb->isMovable())
1123 toolbarsMovable_ = true;
1125 // set unified mac toolbars only when not movable as recommended:
1126 // https://doc.qt.io/qt-5/qmainwindow.html#unifiedTitleAndToolBarOnMac-prop
1127 setUnifiedTitleAndToolBarOnMac(!toolbarsMovable_);
1131 void GuiView::constructToolbars()
1133 for (auto const & tb_p : d.toolbars_)
1135 d.toolbars_.clear();
1137 // I don't like doing this here, but the standard toolbar
1138 // destroys this object when it's destroyed itself (vfr)
1139 d.layout_ = new LayoutBox(*this);
1140 d.stack_widget_->addWidget(d.layout_);
1141 d.layout_->move(0,0);
1143 // extracts the toolbars from the backend
1144 for (ToolbarInfo const & inf : guiApp->toolbars())
1145 d.toolbars_[inf.name] = new GuiToolbar(inf, *this);
1147 DynamicMenuButton::resetIconCache();
1151 void GuiView::initToolbars()
1153 // extracts the toolbars from the backend
1154 for (ToolbarInfo const & inf : guiApp->toolbars())
1155 initToolbar(inf.name);
1159 void GuiView::initToolbar(string const & name)
1161 GuiToolbar * tb = toolbar(name);
1164 int const visibility = guiApp->toolbars().defaultVisibility(name);
1165 bool newline = !(visibility & Toolbars::SAMEROW);
1166 tb->setVisible(false);
1167 tb->setVisibility(visibility);
1169 if (visibility & Toolbars::TOP) {
1171 addToolBarBreak(Qt::TopToolBarArea);
1172 addToolBar(Qt::TopToolBarArea, tb);
1175 if (visibility & Toolbars::BOTTOM) {
1177 addToolBarBreak(Qt::BottomToolBarArea);
1178 addToolBar(Qt::BottomToolBarArea, tb);
1181 if (visibility & Toolbars::LEFT) {
1183 addToolBarBreak(Qt::LeftToolBarArea);
1184 addToolBar(Qt::LeftToolBarArea, tb);
1187 if (visibility & Toolbars::RIGHT) {
1189 addToolBarBreak(Qt::RightToolBarArea);
1190 addToolBar(Qt::RightToolBarArea, tb);
1193 if (visibility & Toolbars::ON)
1194 tb->setVisible(true);
1196 tb->setMovable(true);
1200 TocModels & GuiView::tocModels()
1202 return d.toc_models_;
1206 void GuiView::setFocus()
1208 LYXERR(Debug::DEBUG, "GuiView::setFocus()" << this);
1209 QMainWindow::setFocus();
1213 bool GuiView::hasFocus() const
1215 if (currentWorkArea())
1216 return currentWorkArea()->hasFocus();
1217 if (currentMainWorkArea())
1218 return currentMainWorkArea()->hasFocus();
1219 return d.bg_widget_->hasFocus();
1223 void GuiView::focusInEvent(QFocusEvent * e)
1225 LYXERR(Debug::DEBUG, "GuiView::focusInEvent()" << this);
1226 QMainWindow::focusInEvent(e);
1227 // Make sure guiApp points to the correct view.
1228 guiApp->setCurrentView(this);
1229 if (currentWorkArea())
1230 currentWorkArea()->setFocus();
1231 else if (currentMainWorkArea())
1232 currentMainWorkArea()->setFocus();
1234 d.bg_widget_->setFocus();
1238 void GuiView::showEvent(QShowEvent * e)
1240 LYXERR(Debug::GUI, "Passed Geometry "
1241 << size().height() << "x" << size().width()
1242 << "+" << pos().x() << "+" << pos().y());
1244 if (d.splitter_->count() == 0)
1245 // No work area, switch to the background widget.
1249 QMainWindow::showEvent(e);
1253 bool GuiView::closeScheduled()
1260 bool GuiView::prepareAllBuffersForLogout()
1262 Buffer * first = theBufferList().first();
1266 // First, iterate over all buffers and ask the users if unsaved
1267 // changes should be saved.
1268 // We cannot use a for loop as the buffer list cycles.
1271 if (!saveBufferIfNeeded(*b, false))
1273 b = theBufferList().next(b);
1274 } while (b != first);
1276 // Next, save session state
1277 // When a view/window was closed before without quitting LyX, there
1278 // are already entries in the lastOpened list.
1279 theSession().lastOpened().clear();
1286 /** Destroy only all tabbed WorkAreas. Destruction of other WorkAreas
1287 ** is responsibility of the container (e.g., dialog)
1289 void GuiView::closeEvent(QCloseEvent * close_event)
1291 LYXERR(Debug::DEBUG, "GuiView::closeEvent()");
1293 // FIXME Bug #12828 bites here. If there is some other View open, then
1294 // we really should only refuse to close if one of the Buffers open here
1295 // is being processed.
1296 if (!GuiViewPrivate::busyBuffers.isEmpty()) {
1297 Alert::warning(_("Exit LyX"),
1298 _("LyX could not be closed because documents are being processed by LyX."));
1299 close_event->setAccepted(false);
1303 // If the user pressed the x (so we didn't call closeView
1304 // programmatically), we want to clear all existing entries.
1306 theSession().lastOpened().clear();
1311 // it can happen that this event arrives without selecting the view,
1312 // e.g. when clicking the close button on a background window.
1314 if (!closeWorkAreaAll()) {
1316 close_event->ignore();
1320 // Make sure that nothing will use this to be closed View.
1321 guiApp->unregisterView(this);
1323 if (isFullScreen()) {
1324 // Switch off fullscreen before closing.
1329 // Make sure the timer time out will not trigger a statusbar update.
1330 d.statusbar_timer_.stop();
1331 d.statusbar_stats_timer_.stop();
1333 // Saving fullscreen requires additional tweaks in the toolbar code.
1334 // It wouldn't also work under linux natively.
1335 if (lyxrc.allow_geometry_session) {
1340 close_event->accept();
1344 void GuiView::dragEnterEvent(QDragEnterEvent * event)
1346 if (event->mimeData()->hasUrls())
1348 /// \todo Ask lyx-devel is this is enough:
1349 /// if (event->mimeData()->hasFormat("text/plain"))
1350 /// event->acceptProposedAction();
1354 void GuiView::dropEvent(QDropEvent * event)
1356 QList<QUrl> files = event->mimeData()->urls();
1357 if (files.isEmpty())
1360 LYXERR(Debug::GUI, "GuiView::dropEvent: got URLs!");
1361 for (int i = 0; i != files.size(); ++i) {
1362 string const file = os::internal_path(fromqstr(
1363 files.at(i).toLocalFile()));
1367 string const ext = support::getExtension(file);
1368 vector<const Format *> found_formats;
1370 // Find all formats that have the correct extension.
1371 for (const Format * fmt : theConverters().importableFormats())
1372 if (fmt->hasExtension(ext))
1373 found_formats.push_back(fmt);
1376 if (!found_formats.empty()) {
1377 if (found_formats.size() > 1) {
1378 //FIXME: show a dialog to choose the correct importable format
1379 LYXERR(Debug::FILES,
1380 "Multiple importable formats found, selecting first");
1382 string const arg = found_formats[0]->name() + " " + file;
1383 cmd = FuncRequest(LFUN_BUFFER_IMPORT, arg);
1386 //FIXME: do we have to explicitly check whether it's a lyx file?
1387 LYXERR(Debug::FILES,
1388 "No formats found, trying to open it as a lyx file");
1389 cmd = FuncRequest(LFUN_FILE_OPEN, file);
1391 // add the functions to the queue
1392 guiApp->addToFuncRequestQueue(cmd);
1395 // now process the collected functions. We perform the events
1396 // asynchronously. This prevents potential problems in case the
1397 // BufferView is closed within an event.
1398 guiApp->processFuncRequestQueueAsync();
1402 void GuiView::message(docstring const & str)
1404 if (ForkedProcess::iAmAChild())
1407 // call is moved to GUI-thread by GuiProgress
1408 d.progress_->appendMessage(toqstr(str));
1412 void GuiView::clearMessageText()
1414 message(docstring());
1418 void GuiView::updateStatusBarMessage(QString const & str)
1420 statusBar()->showMessage(str);
1421 d.statusbar_timer_.stop();
1422 d.statusbar_timer_.start(3000);
1426 void GuiView::clearMessage()
1428 // FIXME: This code was introduced in r19643 to fix bug #4123. However,
1429 // the hasFocus function mostly returns false, even if the focus is on
1430 // a workarea in this view.
1434 d.statusbar_timer_.stop();
1437 void GuiView::showStats()
1439 if (!statsEnabled())
1442 d.time_to_update -= d.timer_rate;
1444 BufferView * bv = currentBufferView();
1445 Buffer * buf = bv ? &bv->buffer() : nullptr;
1447 stat_counts_->hide();
1451 Cursor const & cur = bv->cursor();
1453 // we start new selection and need faster update
1454 if (!d.already_in_selection_ && cur.selection())
1455 d.time_to_update = 0;
1457 if (d.time_to_update > 0)
1460 DocIterator from, to;
1461 if (cur.selection()) {
1462 from = cur.selectionBegin();
1463 to = cur.selectionEnd();
1464 d.already_in_selection_ = true;
1466 from = doc_iterator_begin(buf);
1467 to = doc_iterator_end(buf);
1468 d.already_in_selection_ = false;
1471 buf->updateStatistics(from, to);
1474 if (word_count_enabled_) {
1475 int const words = buf->wordCount();
1477 stats << toqstr(bformat(_("%1$d Word"), words));
1479 stats << toqstr(bformat(_("%1$d Words"), words));
1481 int const chars_with_blanks = buf->charCount(true);
1482 if (char_count_enabled_) {
1483 if (chars_with_blanks == 1)
1484 stats << toqstr(bformat(_("%1$d Character"), chars_with_blanks));
1486 stats << toqstr(bformat(_("%1$d Characters"), chars_with_blanks));
1488 if (char_nb_count_enabled_) {
1489 int const chars = buf->charCount(false);
1491 stats << toqstr(bformat(_("%1$d Character (no Blanks)"), chars));
1493 stats << toqstr(bformat(_("%1$d Characters (no Blanks)"), chars));
1495 stat_counts_->setText(stats.join(qt_(", [[stats separator]]")));
1496 stat_counts_->show();
1498 d.time_to_update = d.default_stats_rate;
1499 // fast updates for small selections
1500 if (chars_with_blanks < d.max_sel_chars && cur.selection())
1501 d.time_to_update = d.timer_rate;
1505 void GuiView::updateWindowTitle(GuiWorkArea * wa)
1507 if (wa != d.current_work_area_
1508 || wa->bufferView().buffer().isInternal())
1510 Buffer const & buf = wa->bufferView().buffer();
1511 // Set the windows title
1512 docstring title = buf.fileName().displayName(130) + from_ascii("[*]");
1513 if (buf.notifiesExternalModification()) {
1514 title = bformat(_("%1$s (modified externally)"), title);
1515 // If the external modification status has changed, then maybe the status of
1516 // buffer-save has changed too.
1519 title += from_ascii(" - LyX");
1520 setWindowTitle(toqstr(title));
1521 // Sets the path for the window: this is used by OSX to
1522 // allow a context click on the title bar showing a menu
1523 // with the path up to the file
1524 setWindowFilePath(toqstr(buf.absFileName()));
1525 // Tell Qt whether the current document is changed
1526 setWindowModified(!buf.isClean());
1528 if (buf.params().shell_escape)
1529 shell_escape_->show();
1531 shell_escape_->hide();
1533 if (buf.hasReadonlyFlag())
1538 if (buf.lyxvc().inUse()) {
1539 version_control_->show();
1540 version_control_->setText(toqstr(buf.lyxvc().vcstatus()));
1542 version_control_->hide();
1546 void GuiView::on_currentWorkAreaChanged(GuiWorkArea * wa)
1548 if (d.current_work_area_)
1549 // disconnect the current work area from all slots
1550 QObject::disconnect(d.current_work_area_, nullptr, this, nullptr);
1552 disconnectBufferView();
1553 connectBufferView(wa->bufferView());
1554 connectBuffer(wa->bufferView().buffer());
1555 d.current_work_area_ = wa;
1556 QObject::connect(wa, SIGNAL(titleChanged(GuiWorkArea *)),
1557 this, SLOT(updateWindowTitle(GuiWorkArea *)));
1558 QObject::connect(wa, SIGNAL(busy(bool)),
1559 this, SLOT(setBusy(bool)));
1560 // connection of a signal to a signal
1561 QObject::connect(wa, SIGNAL(bufferViewChanged()),
1562 this, SIGNAL(bufferViewChanged()));
1563 Q_EMIT updateWindowTitle(wa);
1564 Q_EMIT bufferViewChanged();
1568 void GuiView::onBufferViewChanged()
1571 // Buffer-dependent dialogs must be updated. This is done here because
1572 // some dialogs require buffer()->text.
1574 zoom_slider_->setEnabled(currentBufferView());
1575 zoom_value_->setEnabled(currentBufferView());
1576 zoom_in_->setEnabled(currentBufferView()
1577 && zoom_slider_->value() < zoom_slider_->maximum());
1578 zoom_out_->setEnabled(currentBufferView()
1579 && zoom_slider_->value() > zoom_slider_->minimum());
1583 void GuiView::on_lastWorkAreaRemoved()
1586 // We already are in a close event. Nothing more to do.
1589 if (d.splitter_->count() > 1)
1590 // We have a splitter so don't close anything.
1593 // Reset and updates the dialogs.
1594 Q_EMIT bufferViewChanged();
1599 if (lyxrc.open_buffers_in_tabs)
1600 // Nothing more to do, the window should stay open.
1603 if (guiApp->viewIds().size() > 1) {
1609 // On Mac we also close the last window because the application stay
1610 // resident in memory. On other platforms we don't close the last
1611 // window because this would quit the application.
1617 void GuiView::updateStatusBar()
1619 // let the user see the explicit message
1620 if (d.statusbar_timer_.isActive())
1627 void GuiView::showMessage()
1631 QString msg = toqstr(theGuiApp()->viewStatusMessage());
1632 if (msg.isEmpty()) {
1633 BufferView const * bv = currentBufferView();
1635 msg = toqstr(bv->cursor().currentState(devel_mode_));
1637 msg = qt_("Welcome to LyX!");
1639 statusBar()->showMessage(msg);
1643 bool GuiView::statsEnabled() const
1645 return word_count_enabled_ || char_count_enabled_ || char_nb_count_enabled_;
1649 bool GuiView::event(QEvent * e)
1653 // Useful debug code:
1654 //case QEvent::ActivationChange:
1655 //case QEvent::WindowDeactivate:
1656 //case QEvent::Paint:
1657 //case QEvent::Enter:
1658 //case QEvent::Leave:
1659 //case QEvent::HoverEnter:
1660 //case QEvent::HoverLeave:
1661 //case QEvent::HoverMove:
1662 //case QEvent::StatusTip:
1663 //case QEvent::DragEnter:
1664 //case QEvent::DragLeave:
1665 //case QEvent::Drop:
1668 case QEvent::WindowStateChange: {
1669 QWindowStateChangeEvent * ev = (QWindowStateChangeEvent*)e;
1670 bool ofstate = (ev->oldState() & Qt::WindowFullScreen);
1671 bool result = QMainWindow::event(e);
1672 bool nfstate = (windowState() & Qt::WindowFullScreen);
1673 if (!ofstate && nfstate) {
1674 LYXERR(Debug::DEBUG, "GuiView: WindowStateChange(): full-screen " << nfstate);
1675 // switch to full-screen state
1676 if (lyxrc.full_screen_statusbar)
1677 statusBar()->hide();
1678 if (lyxrc.full_screen_menubar)
1680 if (lyxrc.full_screen_toolbars) {
1681 for (auto const & tb_p : d.toolbars_)
1682 if (tb_p.second->isVisibilityOn() && tb_p.second->isVisible())
1683 tb_p.second->hide();
1685 for (int i = 0; i != d.splitter_->count(); ++i)
1686 d.tabWorkArea(i)->setFullScreen(true);
1687 #if QT_VERSION > 0x050903
1688 //Qt's 5.9.4 ba44cdae38406c safe area measures won't allow us to go negative in margins
1689 setAttribute(Qt::WA_ContentsMarginsRespectsSafeArea, false);
1691 setContentsMargins(-2, -2, -2, -2);
1693 hideDialogs("prefs", nullptr);
1694 } else if (ofstate && !nfstate) {
1695 LYXERR(Debug::DEBUG, "GuiView: WindowStateChange(): full-screen " << nfstate);
1696 // switch back from full-screen state
1697 if (lyxrc.full_screen_statusbar && !statusBar()->isVisible())
1698 statusBar()->show();
1699 if (lyxrc.full_screen_menubar && !menuBar()->isVisible())
1701 if (lyxrc.full_screen_toolbars) {
1702 for (auto const & tb_p : d.toolbars_)
1703 if (tb_p.second->isVisibilityOn() && !tb_p.second->isVisible())
1704 tb_p.second->show();
1707 for (int i = 0; i != d.splitter_->count(); ++i)
1708 d.tabWorkArea(i)->setFullScreen(false);
1709 #if QT_VERSION > 0x050903
1710 setAttribute(Qt::WA_ContentsMarginsRespectsSafeArea, true);
1712 setContentsMargins(0, 0, 0, 0);
1717 case QEvent::WindowActivate: {
1718 GuiView * old_view = guiApp->currentView();
1719 if (this == old_view) {
1721 return QMainWindow::event(e);
1723 if (old_view && old_view->currentBufferView()) {
1724 // save current selection to the selection buffer to allow
1725 // middle-button paste in this window.
1726 cap::saveSelection(old_view->currentBufferView()->cursor());
1728 guiApp->setCurrentView(this);
1729 if (d.current_work_area_)
1730 on_currentWorkAreaChanged(d.current_work_area_);
1734 return QMainWindow::event(e);
1737 case QEvent::ShortcutOverride: {
1739 if (isFullScreen() && menuBar()->isHidden()) {
1740 QKeyEvent * ke = static_cast<QKeyEvent*>(e);
1741 // FIXME: we should also try to detect special LyX shortcut such as
1742 // Alt-P and Alt-M. Right now there is a hack in
1743 // GuiWorkArea::processKeySym() that hides again the menubar for
1745 if (ke->modifiers() & Qt::AltModifier && ke->key() != Qt::Key_Alt) {
1747 return QMainWindow::event(e);
1750 return QMainWindow::event(e);
1753 case QEvent::Gesture: {
1754 QGestureEvent *ge = static_cast<QGestureEvent*>(e);
1755 QGesture *gp = ge->gesture(Qt::PinchGesture);
1757 QPinchGesture *pinch = static_cast<QPinchGesture *>(gp);
1758 QPinchGesture::ChangeFlags changeFlags = pinch->changeFlags();
1759 qreal totalScaleFactor = pinch->totalScaleFactor();
1760 LYXERR(Debug::GUI, "totalScaleFactor: " << totalScaleFactor);
1761 if (pinch->state() == Qt::GestureStarted) {
1762 initialZoom_ = lyxrc.currentZoom;
1763 LYXERR(Debug::GUI, "initialZoom_: " << initialZoom_);
1765 if (changeFlags & QPinchGesture::ScaleFactorChanged) {
1766 qreal factor = initialZoom_ * totalScaleFactor;
1767 LYXERR(Debug::GUI, "scaleFactor: " << factor);
1768 zoomValueChanged(factor);
1771 return QMainWindow::event(e);
1774 // dark/light mode runtime switch support, OS-dependent.
1776 // Limit to Q_OS_MAC as this unnecessarily would also
1777 // trigger on Linux with grave performance issues
1779 case QEvent::ApplicationPaletteChange: {
1780 // We need to update metrics here to avoid a crash (#12786)
1781 theBufferList().changed(true);
1783 return QMainWindow::event(e);
1787 case QEvent::StyleChange: {
1788 // We need to update metrics here to avoid a crash (#12786)
1789 theBufferList().changed(true);
1790 return QMainWindow::event(e);
1794 return QMainWindow::event(e);
1798 void GuiView::resetWindowTitle()
1800 setWindowTitle(qt_("LyX"));
1803 bool GuiView::focusNextPrevChild(bool /*next*/)
1810 bool GuiView::busy() const
1816 void GuiView::setBusy(bool busy)
1818 bool const busy_before = busy_ > 0;
1819 busy ? ++busy_ : --busy_;
1820 if ((busy_ > 0) == busy_before)
1821 // busy state didn't change
1825 QApplication::setOverrideCursor(Qt::WaitCursor);
1828 QApplication::restoreOverrideCursor();
1833 void GuiView::resetCommandExecute()
1835 command_execute_ = false;
1840 double GuiView::pixelRatio() const
1842 return qt_scale_factor * devicePixelRatio();
1846 GuiWorkArea * GuiView::workArea(int index)
1848 if (TabWorkArea * twa = d.currentTabWorkArea())
1849 if (index < twa->count())
1850 return twa->workArea(index);
1855 GuiWorkArea * GuiView::workArea(Buffer & buffer)
1857 if (currentWorkArea()
1858 && ¤tWorkArea()->bufferView().buffer() == &buffer)
1859 return currentWorkArea();
1860 if (TabWorkArea * twa = d.currentTabWorkArea())
1861 return twa->workArea(buffer);
1866 GuiWorkArea * GuiView::addWorkArea(Buffer & buffer)
1868 // Automatically create a TabWorkArea if there are none yet.
1869 TabWorkArea * tab_widget = d.splitter_->count()
1870 ? d.currentTabWorkArea() : addTabWorkArea();
1871 return tab_widget->addWorkArea(buffer, *this);
1875 TabWorkArea * GuiView::addTabWorkArea()
1877 TabWorkArea * twa = new TabWorkArea;
1878 QObject::connect(twa, SIGNAL(currentWorkAreaChanged(GuiWorkArea *)),
1879 this, SLOT(on_currentWorkAreaChanged(GuiWorkArea *)));
1880 QObject::connect(twa, SIGNAL(lastWorkAreaRemoved()),
1881 this, SLOT(on_lastWorkAreaRemoved()));
1883 d.splitter_->addWidget(twa);
1884 d.stack_widget_->setCurrentWidget(d.splitter_);
1889 GuiWorkArea const * GuiView::currentWorkArea() const
1891 return d.current_work_area_;
1895 GuiWorkArea * GuiView::currentWorkArea()
1897 return d.current_work_area_;
1901 GuiWorkArea const * GuiView::currentMainWorkArea() const
1903 if (!d.currentTabWorkArea())
1905 return d.currentTabWorkArea()->currentWorkArea();
1909 GuiWorkArea * GuiView::currentMainWorkArea()
1911 if (!d.currentTabWorkArea())
1913 return d.currentTabWorkArea()->currentWorkArea();
1917 void GuiView::setCurrentWorkArea(GuiWorkArea * wa)
1919 LYXERR(Debug::DEBUG, "Setting current wa: " << wa << endl);
1921 d.current_work_area_ = nullptr;
1923 Q_EMIT bufferViewChanged();
1927 // FIXME: I've no clue why this is here and why it accesses
1928 // theGuiApp()->currentView, which might be 0 (bug 6464).
1929 // See also 27525 (vfr).
1930 if (theGuiApp()->currentView() == this
1931 && theGuiApp()->currentView()->currentWorkArea() == wa)
1934 if (currentBufferView())
1935 cap::saveSelection(currentBufferView()->cursor());
1937 theGuiApp()->setCurrentView(this);
1938 d.current_work_area_ = wa;
1940 // We need to reset this now, because it will need to be
1941 // right if the tabWorkArea gets reset in the for loop. We
1942 // will change it back if we aren't in that case.
1943 GuiWorkArea * const old_cmwa = d.current_main_work_area_;
1944 d.current_main_work_area_ = wa;
1946 for (int i = 0; i != d.splitter_->count(); ++i) {
1947 if (d.tabWorkArea(i)->setCurrentWorkArea(wa)) {
1948 LYXERR(Debug::DEBUG, "Current wa: " << currentWorkArea()
1949 << ", Current main wa: " << currentMainWorkArea());
1954 d.current_main_work_area_ = old_cmwa;
1956 LYXERR(Debug::DEBUG, "This is not a tabbed wa");
1957 on_currentWorkAreaChanged(wa);
1958 BufferView & bv = wa->bufferView();
1959 bv.cursor().fixIfBroken();
1961 wa->setUpdatesEnabled(true);
1962 LYXERR(Debug::DEBUG, "Current wa: " << currentWorkArea() << ", Current main wa: " << currentMainWorkArea());
1966 void GuiView::removeWorkArea(GuiWorkArea * wa)
1968 LASSERT(wa, return);
1969 if (wa == d.current_work_area_) {
1971 disconnectBufferView();
1972 d.current_work_area_ = nullptr;
1973 d.current_main_work_area_ = nullptr;
1976 bool found_twa = false;
1977 for (int i = 0; i != d.splitter_->count(); ++i) {
1978 TabWorkArea * twa = d.tabWorkArea(i);
1979 if (twa->removeWorkArea(wa)) {
1980 // Found in this tab group, and deleted the GuiWorkArea.
1982 if (twa->count() != 0) {
1983 if (d.current_work_area_ == nullptr)
1984 // This means that we are closing the current GuiWorkArea, so
1985 // switch to the next GuiWorkArea in the found TabWorkArea.
1986 setCurrentWorkArea(twa->currentWorkArea());
1988 // No more WorkAreas in this tab group, so delete it.
1995 // It is not a tabbed work area (i.e., the search work area), so it
1996 // should be deleted by other means.
1997 LASSERT(found_twa, return);
1999 if (d.current_work_area_ == nullptr) {
2000 if (d.splitter_->count() != 0) {
2001 TabWorkArea * twa = d.currentTabWorkArea();
2002 setCurrentWorkArea(twa->currentWorkArea());
2004 // No more work areas, switch to the background widget.
2005 setCurrentWorkArea(nullptr);
2011 bool GuiView::hasVisibleWorkArea(GuiWorkArea * wa) const
2013 for (int i = 0; i < d.splitter_->count(); ++i)
2014 if (d.tabWorkArea(i)->currentWorkArea() == wa)
2017 FindAndReplace * fr = static_cast<FindAndReplace*>(find("findreplaceadv", false));
2018 return fr->isVisible() && fr->hasWorkArea(wa);
2022 LayoutBox * GuiView::getLayoutDialog() const
2028 void GuiView::updateLayoutList()
2031 d.layout_->updateContents(false);
2035 void GuiView::updateToolbars()
2037 if (d.current_work_area_) {
2039 if (d.current_work_area_->bufferView().cursor().inMathed()
2040 && !d.current_work_area_->bufferView().cursor().inRegexped())
2041 context |= Toolbars::MATH;
2042 if (lyx::getStatus(FuncRequest(LFUN_LAYOUT_TABULAR)).enabled())
2043 context |= Toolbars::TABLE;
2044 if (currentBufferView()->buffer().areChangesPresent()
2045 || (lyx::getStatus(FuncRequest(LFUN_CHANGES_TRACK)).enabled()
2046 && lyx::getStatus(FuncRequest(LFUN_CHANGES_TRACK)).onOff(true))
2047 || (lyx::getStatus(FuncRequest(LFUN_CHANGES_OUTPUT)).enabled()
2048 && lyx::getStatus(FuncRequest(LFUN_CHANGES_OUTPUT)).onOff(true)))
2049 context |= Toolbars::REVIEW;
2050 if (lyx::getStatus(FuncRequest(LFUN_IN_MATHMACROTEMPLATE)).enabled())
2051 context |= Toolbars::MATHMACROTEMPLATE;
2052 if (lyx::getStatus(FuncRequest(LFUN_IN_IPA)).enabled())
2053 context |= Toolbars::IPA;
2054 if (command_execute_)
2055 context |= Toolbars::MINIBUFFER;
2056 if (minibuffer_focus_) {
2057 context |= Toolbars::MINIBUFFER_FOCUS;
2058 minibuffer_focus_ = false;
2061 for (auto const & tb_p : d.toolbars_)
2062 tb_p.second->update(context);
2064 for (auto const & tb_p : d.toolbars_)
2065 tb_p.second->update();
2069 void GuiView::refillToolbars()
2071 DynamicMenuButton::resetIconCache();
2072 for (auto const & tb_p : d.toolbars_)
2073 tb_p.second->refill();
2077 void GuiView::setBuffer(Buffer * newBuffer, bool switch_to)
2079 LYXERR(Debug::DEBUG, "Setting buffer: " << newBuffer << endl);
2080 LASSERT(newBuffer, return);
2082 GuiWorkArea * wa = workArea(*newBuffer);
2083 if (wa == nullptr) {
2085 newBuffer->masterBuffer()->updateBuffer();
2087 wa = addWorkArea(*newBuffer);
2088 // scroll to the position when the BufferView was last closed
2089 if (lyxrc.use_lastfilepos) {
2090 LastFilePosSection::FilePos filepos =
2091 theSession().lastFilePos().load(newBuffer->fileName());
2092 wa->bufferView().moveToPosition(filepos.pit, filepos.pos, 0, 0);
2095 //Disconnect the old buffer...there's no new one.
2098 connectBuffer(*newBuffer);
2099 connectBufferView(wa->bufferView());
2101 setCurrentWorkArea(wa);
2105 void GuiView::connectBuffer(Buffer & buf)
2107 buf.setGuiDelegate(this);
2111 void GuiView::disconnectBuffer()
2113 if (d.current_work_area_)
2114 d.current_work_area_->bufferView().buffer().setGuiDelegate(nullptr);
2118 void GuiView::connectBufferView(BufferView & bv)
2120 bv.setGuiDelegate(this);
2124 void GuiView::disconnectBufferView()
2126 if (d.current_work_area_)
2127 d.current_work_area_->bufferView().setGuiDelegate(nullptr);
2131 void GuiView::errors(string const & error_type, bool from_master)
2133 BufferView const * const bv = currentBufferView();
2137 ErrorList const & el = from_master ?
2138 bv->buffer().masterBuffer()->errorList(error_type) :
2139 bv->buffer().errorList(error_type);
2144 string err = error_type;
2146 err = "from_master|" + error_type;
2147 showDialog("errorlist", err);
2151 void GuiView::updateTocItem(string const & type, DocIterator const & dit)
2153 d.toc_models_.updateItem(toqstr(type), dit);
2157 void GuiView::structureChanged()
2159 // This is called from the Buffer, which has no way to ensure that cursors
2160 // in BufferView remain valid.
2161 if (documentBufferView())
2162 documentBufferView()->cursor().sanitize();
2163 // FIXME: This is slightly expensive, though less than the tocBackend update
2164 // (#9880). This also resets the view in the Toc Widget (#6675).
2165 d.toc_models_.reset(documentBufferView());
2166 // Navigator needs more than a simple update in this case. It needs to be
2168 updateDialog("toc", "");
2172 void GuiView::updateDialog(string const & name, string const & sdata)
2174 if (!isDialogVisible(name))
2177 map<string, DialogPtr>::const_iterator it = d.dialogs_.find(name);
2178 if (it == d.dialogs_.end())
2181 Dialog * const dialog = it->second.get();
2182 if (dialog->isVisibleView())
2183 dialog->initialiseParams(sdata);
2187 BufferView * GuiView::documentBufferView()
2189 return currentMainWorkArea()
2190 ? ¤tMainWorkArea()->bufferView()
2195 BufferView const * GuiView::documentBufferView() const
2197 return currentMainWorkArea()
2198 ? ¤tMainWorkArea()->bufferView()
2203 BufferView * GuiView::currentBufferView()
2205 return d.current_work_area_ ? &d.current_work_area_->bufferView() : nullptr;
2209 BufferView const * GuiView::currentBufferView() const
2211 return d.current_work_area_ ? &d.current_work_area_->bufferView() : nullptr;
2215 docstring GuiView::GuiViewPrivate::autosaveAndDestroy(
2216 Buffer const * orig, Buffer * clone)
2218 bool const success = clone->autoSave();
2220 busyBuffers.remove(orig);
2222 ? _("Automatic save done.")
2223 : _("Automatic save failed!");
2227 void GuiView::autoSave()
2229 LYXERR(Debug::INFO, "Running autoSave()");
2231 Buffer * buffer = documentBufferView()
2232 ? &documentBufferView()->buffer() : nullptr;
2234 resetAutosaveTimers();
2238 GuiViewPrivate::busyBuffers.insert(buffer);
2239 QFuture<docstring> f = QtConcurrent::run(GuiViewPrivate::autosaveAndDestroy,
2240 buffer, buffer->cloneBufferOnly());
2241 d.autosave_watcher_.setFuture(f);
2242 resetAutosaveTimers();
2246 void GuiView::resetAutosaveTimers()
2249 d.autosave_timeout_.restart();
2255 double zoomRatio(FuncRequest const & cmd, double const zr)
2257 if (cmd.argument().empty()) {
2258 if (cmd.action() == LFUN_BUFFER_ZOOM)
2260 else if (cmd.action() == LFUN_BUFFER_ZOOM_IN)
2262 else // cmd.action() == LFUN_BUFFER_ZOOM_OUT
2265 if (cmd.action() == LFUN_BUFFER_ZOOM)
2266 return convert<int>(cmd.argument()) / double(lyxrc.defaultZoom);
2267 else if (cmd.action() == LFUN_BUFFER_ZOOM_IN)
2268 return zr + convert<int>(cmd.argument()) / 100.0;
2269 else // cmd.action() == LFUN_BUFFER_ZOOM_OUT
2270 return zr - convert<int>(cmd.argument()) / 100.0;
2277 bool GuiView::getStatus(FuncRequest const & cmd, FuncStatus & flag)
2280 Buffer * buf = currentBufferView()
2281 ? ¤tBufferView()->buffer() : nullptr;
2282 Buffer * doc_buffer = documentBufferView()
2283 ? &(documentBufferView()->buffer()) : nullptr;
2286 /* In LyX/Mac, when a dialog is open, the menus of the
2287 application can still be accessed without giving focus to
2288 the main window. In this case, we want to disable the menu
2289 entries that are buffer-related.
2290 This code must not be used on Linux and Windows, since it
2291 would disable buffer-related entries when hovering over the
2292 menu (see bug #9574).
2294 if (cmd.origin() == FuncRequest::MENU && !hasFocus()) {
2300 // Check whether we need a buffer
2301 if (!lyxaction.funcHasFlag(cmd.action(), LyXAction::NoBuffer) && !buf) {
2302 // no, exit directly
2303 flag.message(from_utf8(N_("Command not allowed with"
2304 "out any document open")));
2305 flag.setEnabled(false);
2309 if (cmd.origin() == FuncRequest::TOC) {
2310 GuiToc * toc = static_cast<GuiToc*>(findOrBuild("toc", false));
2311 if (!toc || !toc->getStatus(documentBufferView()->cursor(), cmd, flag))
2312 flag.setEnabled(false);
2316 switch(cmd.action()) {
2317 case LFUN_BUFFER_IMPORT:
2320 case LFUN_MASTER_BUFFER_EXPORT:
2322 && (doc_buffer->parent() != nullptr
2323 || doc_buffer->hasChildren())
2324 && !d.processing_thread_watcher_.isRunning()
2325 // this launches a dialog, which would be in the wrong Buffer
2326 && !(::lyx::operator==(cmd.argument(), "custom"));
2329 case LFUN_MASTER_BUFFER_UPDATE:
2330 case LFUN_MASTER_BUFFER_VIEW:
2332 && (doc_buffer->parent() != nullptr
2333 || doc_buffer->hasChildren())
2334 && !d.processing_thread_watcher_.isRunning();
2337 case LFUN_BUFFER_UPDATE:
2338 case LFUN_BUFFER_VIEW: {
2339 if (!doc_buffer || d.processing_thread_watcher_.isRunning()) {
2343 string format = to_utf8(cmd.argument());
2344 if (cmd.argument().empty())
2345 format = doc_buffer->params().getDefaultOutputFormat();
2346 enable = doc_buffer->params().isExportable(format, true);
2350 case LFUN_BUFFER_RELOAD:
2351 enable = doc_buffer && !doc_buffer->isUnnamed()
2352 && doc_buffer->fileName().exists()
2353 && (!doc_buffer->isClean() || doc_buffer->notifiesExternalModification());
2356 case LFUN_BUFFER_RESET_EXPORT:
2357 enable = doc_buffer != nullptr;
2360 case LFUN_BUFFER_CHILD_OPEN:
2361 enable = doc_buffer != nullptr;
2364 case LFUN_MASTER_BUFFER_FORALL: {
2365 if (doc_buffer == nullptr) {
2366 flag.message(from_utf8(N_("Command not allowed without a buffer open")));
2370 FuncRequest const cmdToPass = lyxaction.lookupFunc(cmd.getLongArg(0));
2371 if (cmdToPass.action() == LFUN_UNKNOWN_ACTION) {
2372 flag.message(from_utf8(N_("Invalid argument of master-buffer-forall")));
2377 for (Buffer * buf : doc_buffer->allRelatives()) {
2378 GuiWorkArea * wa = workArea(*buf);
2381 if (wa->bufferView().getStatus(cmdToPass, flag)) {
2382 enable = flag.enabled();
2389 case LFUN_BUFFER_WRITE:
2390 enable = doc_buffer && (doc_buffer->isUnnamed()
2391 || (!doc_buffer->isClean()
2392 || cmd.argument() == "force"));
2395 //FIXME: This LFUN should be moved to GuiApplication.
2396 case LFUN_BUFFER_WRITE_ALL: {
2397 // We enable the command only if there are some modified buffers
2398 Buffer * first = theBufferList().first();
2403 // We cannot use a for loop as the buffer list is a cycle.
2405 if (!b->isClean()) {
2409 b = theBufferList().next(b);
2410 } while (b != first);
2414 case LFUN_BUFFER_EXTERNAL_MODIFICATION_CLEAR:
2415 enable = doc_buffer && doc_buffer->notifiesExternalModification();
2418 case LFUN_BUFFER_EXPORT: {
2419 if (!doc_buffer || d.processing_thread_watcher_.isRunning()) {
2423 return doc_buffer->getStatus(cmd, flag);
2426 case LFUN_BUFFER_EXPORT_AS:
2427 if (!doc_buffer || d.processing_thread_watcher_.isRunning()) {
2432 case LFUN_BUFFER_WRITE_AS:
2433 case LFUN_BUFFER_WRITE_AS_TEMPLATE:
2434 enable = doc_buffer != nullptr;
2437 case LFUN_EXPORT_CANCEL:
2438 enable = d.processing_thread_watcher_.isRunning();
2441 case LFUN_BUFFER_CLOSE:
2442 case LFUN_VIEW_CLOSE:
2443 enable = doc_buffer != nullptr;
2446 case LFUN_BUFFER_CLOSE_ALL:
2447 enable = theBufferList().last() != theBufferList().first();
2450 case LFUN_BUFFER_CHKTEX: {
2451 // hide if we have no checktex command
2452 if (lyxrc.chktex_command.empty()) {
2453 flag.setUnknown(true);
2457 if (!doc_buffer || !doc_buffer->params().isLatex()
2458 || d.processing_thread_watcher_.isRunning()) {
2459 // grey out, don't hide
2467 case LFUN_CHANGES_TRACK: {
2472 return doc_buffer->getStatus(cmd, flag);
2475 case LFUN_VIEW_SPLIT:
2476 if (cmd.getArg(0) == "vertical")
2477 enable = doc_buffer && (d.splitter_->count() == 1 ||
2478 d.splitter_->orientation() == Qt::Vertical);
2480 enable = doc_buffer && (d.splitter_->count() == 1 ||
2481 d.splitter_->orientation() == Qt::Horizontal);
2484 case LFUN_TAB_GROUP_NEXT:
2485 case LFUN_TAB_GROUP_PREVIOUS:
2486 enable = (d.splitter_->count() > 1);
2489 case LFUN_TAB_GROUP_CLOSE:
2490 enable = d.tabWorkAreaCount() > 1;
2493 case LFUN_DEVEL_MODE_TOGGLE:
2494 flag.setOnOff(devel_mode_);
2497 case LFUN_TOOLBAR_SET: {
2498 string const name = cmd.getArg(0);
2499 string const state = cmd.getArg(1);
2500 if (name.empty() || state.empty()) {
2502 docstring const msg =
2503 _("Function toolbar-set requires two arguments!");
2507 if (state != "on" && state != "off" && state != "auto") {
2509 docstring const msg =
2510 bformat(_("Invalid argument \"%1$s\" to function toolbar-set!"),
2515 if (GuiToolbar * t = toolbar(name)) {
2516 bool const autovis = t->visibility() & Toolbars::AUTO;
2518 flag.setOnOff(t->isVisible() && !autovis);
2519 else if (state == "off")
2520 flag.setOnOff(!t->isVisible() && !autovis);
2521 else if (state == "auto")
2522 flag.setOnOff(autovis);
2525 docstring const msg =
2526 bformat(_("Unknown toolbar \"%1$s\""), from_utf8(name));
2532 case LFUN_TOOLBAR_TOGGLE: {
2533 string const name = cmd.getArg(0);
2534 if (GuiToolbar * t = toolbar(name))
2535 flag.setOnOff(t->isVisible());
2538 docstring const msg =
2539 bformat(_("Unknown toolbar \"%1$s\""), from_utf8(name));
2545 case LFUN_TOOLBAR_MOVABLE: {
2546 string const name = cmd.getArg(0);
2547 // use negation since locked == !movable
2549 // toolbar name * locks all toolbars
2550 flag.setOnOff(!toolbarsMovable_);
2551 else if (GuiToolbar * t = toolbar(name))
2552 flag.setOnOff(!(t->isMovable()));
2555 docstring const msg =
2556 bformat(_("Unknown toolbar \"%1$s\""), from_utf8(name));
2562 case LFUN_ICON_SIZE:
2563 flag.setOnOff(d.iconSize(cmd.argument()) == iconSize());
2566 case LFUN_DROP_LAYOUTS_CHOICE:
2567 enable = buf != nullptr;
2570 case LFUN_UI_TOGGLE:
2571 if (cmd.argument() == "zoomlevel") {
2572 flag.setOnOff(zoom_value_ ? zoom_value_->isVisible() : false);
2573 } else if (cmd.argument() == "zoomslider") {
2574 flag.setOnOff(zoom_widget_ ? zoom_widget_->isVisible() : false);
2575 } else if (cmd.argument() == "statistics-w") {
2576 flag.setOnOff(word_count_enabled_);
2577 } else if (cmd.argument() == "statistics-cb") {
2578 flag.setOnOff(char_count_enabled_);
2579 } else if (cmd.argument() == "statistics-c") {
2580 flag.setOnOff(char_nb_count_enabled_);
2582 flag.setOnOff(isFullScreen());
2585 case LFUN_DIALOG_DISCONNECT_INSET:
2588 case LFUN_DIALOG_HIDE:
2589 // FIXME: should we check if the dialog is shown?
2592 case LFUN_DIALOG_TOGGLE:
2593 flag.setOnOff(isDialogVisible(cmd.getArg(0)));
2596 case LFUN_DIALOG_SHOW: {
2597 string const name = cmd.getArg(0);
2599 enable = name == "aboutlyx"
2600 || name == "file" //FIXME: should be removed.
2601 || name == "lyxfiles"
2603 || name == "texinfo"
2604 || name == "progress"
2605 || name == "compare";
2606 else if (name == "character" || name == "symbols"
2607 || name == "mathdelimiter" || name == "mathmatrix") {
2608 if (!buf || buf->isReadonly())
2611 Cursor const & cur = currentBufferView()->cursor();
2612 enable = !(cur.inTexted() && cur.paragraph().isPassThru());
2615 else if (name == "latexlog")
2616 enable = FileName(doc_buffer->logName()).isReadableFile();
2617 else if (name == "spellchecker")
2618 enable = theSpellChecker()
2619 && !doc_buffer->text().empty();
2620 else if (name == "vclog")
2621 enable = doc_buffer->lyxvc().inUse();
2625 case LFUN_DIALOG_UPDATE: {
2626 string const name = cmd.getArg(0);
2628 enable = name == "prefs";
2632 case LFUN_COMMAND_EXECUTE:
2634 case LFUN_MENU_OPEN:
2635 // Nothing to check.
2638 case LFUN_COMPLETION_INLINE:
2639 if (!d.current_work_area_
2640 || !d.current_work_area_->completer().inlinePossible(
2641 currentBufferView()->cursor()))
2645 case LFUN_COMPLETION_POPUP:
2646 if (!d.current_work_area_
2647 || !d.current_work_area_->completer().popupPossible(
2648 currentBufferView()->cursor()))
2653 if (!d.current_work_area_
2654 || !d.current_work_area_->completer().inlinePossible(
2655 currentBufferView()->cursor()))
2659 case LFUN_COMPLETION_ACCEPT:
2660 if (!d.current_work_area_
2661 || (!d.current_work_area_->completer().popupVisible()
2662 && !d.current_work_area_->completer().inlineVisible()
2663 && !d.current_work_area_->completer().completionAvailable()))
2667 case LFUN_COMPLETION_CANCEL:
2668 if (!d.current_work_area_
2669 || (!d.current_work_area_->completer().popupVisible()
2670 && !d.current_work_area_->completer().inlineVisible()))
2674 case LFUN_BUFFER_ZOOM_OUT:
2675 case LFUN_BUFFER_ZOOM_IN:
2676 case LFUN_BUFFER_ZOOM: {
2677 int const zoom = (int)(lyxrc.defaultZoom * zoomRatio(cmd, zoom_ratio_));
2678 if (zoom < zoom_min_) {
2679 docstring const msg =
2680 bformat(_("Zoom level cannot be less than %1$d%."), zoom_min_);
2683 } else if (zoom > zoom_max_) {
2684 docstring const msg =
2685 bformat(_("Zoom level cannot be more than %1$d%."), zoom_max_);
2689 enable = doc_buffer;
2694 case LFUN_BUFFER_MOVE_NEXT:
2695 case LFUN_BUFFER_MOVE_PREVIOUS:
2696 // we do not cycle when moving
2697 case LFUN_BUFFER_NEXT:
2698 case LFUN_BUFFER_PREVIOUS:
2699 // because we cycle, it doesn't matter whether on first or last
2700 enable = (d.currentTabWorkArea()->count() > 1);
2702 case LFUN_BUFFER_SWITCH:
2703 // toggle on the current buffer, but do not toggle off
2704 // the other ones (is that a good idea?)
2706 && to_utf8(cmd.argument()) == doc_buffer->absFileName())
2707 flag.setOnOff(true);
2710 case LFUN_VC_REGISTER:
2711 enable = doc_buffer && !doc_buffer->lyxvc().inUse();
2713 case LFUN_VC_RENAME:
2714 enable = doc_buffer && doc_buffer->lyxvc().renameEnabled();
2717 enable = doc_buffer && doc_buffer->lyxvc().copyEnabled();
2719 case LFUN_VC_CHECK_IN:
2720 enable = doc_buffer && doc_buffer->lyxvc().checkInEnabled();
2722 case LFUN_VC_CHECK_OUT:
2723 enable = doc_buffer && doc_buffer->lyxvc().checkOutEnabled();
2725 case LFUN_VC_LOCKING_TOGGLE:
2726 enable = doc_buffer && !doc_buffer->hasReadonlyFlag()
2727 && doc_buffer->lyxvc().lockingToggleEnabled();
2728 flag.setOnOff(enable && doc_buffer->lyxvc().locking());
2730 case LFUN_VC_REVERT:
2731 enable = doc_buffer && doc_buffer->lyxvc().inUse()
2732 && !doc_buffer->hasReadonlyFlag();
2734 case LFUN_VC_UNDO_LAST:
2735 enable = doc_buffer && doc_buffer->lyxvc().undoLastEnabled();
2737 case LFUN_VC_REPO_UPDATE:
2738 enable = doc_buffer && doc_buffer->lyxvc().repoUpdateEnabled();
2740 case LFUN_VC_COMMAND: {
2741 if (cmd.argument().empty())
2743 if (!doc_buffer && contains(cmd.getArg(0), 'D'))
2747 case LFUN_VC_COMPARE:
2748 enable = doc_buffer && doc_buffer->lyxvc().prepareFileRevisionEnabled();
2751 case LFUN_SERVER_GOTO_FILE_ROW:
2752 case LFUN_LYX_ACTIVATE:
2753 case LFUN_WINDOW_RAISE:
2755 case LFUN_FORWARD_SEARCH:
2756 enable = !(lyxrc.forward_search_dvi.empty() && lyxrc.forward_search_pdf.empty()) &&
2757 doc_buffer && doc_buffer->isSyncTeXenabled();
2760 case LFUN_FILE_INSERT_PLAINTEXT:
2761 case LFUN_FILE_INSERT_PLAINTEXT_PARA:
2762 enable = documentBufferView() && documentBufferView()->cursor().inTexted();
2765 case LFUN_SPELLING_CONTINUOUSLY:
2766 flag.setOnOff(lyxrc.spellcheck_continuously);
2769 case LFUN_CITATION_OPEN:
2778 flag.setEnabled(false);
2784 static FileName selectTemplateFile()
2786 FileDialog dlg(qt_("Select template file"));
2787 dlg.setButton1(qt_("D&ocuments"), toqstr(lyxrc.document_path));
2788 dlg.setButton2(qt_("&Templates"), toqstr(lyxrc.template_path));
2790 FileDialog::Result result = dlg.open(toqstr(lyxrc.template_path),
2791 QStringList(qt_("LyX Documents (*.lyx)")));
2793 if (result.first == FileDialog::Later)
2795 if (result.second.isEmpty())
2797 return FileName(fromqstr(result.second));
2801 Buffer * GuiView::loadDocument(FileName const & filename, bool tolastfiles)
2805 Buffer * newBuffer = nullptr;
2807 newBuffer = checkAndLoadLyXFile(filename);
2808 } catch (ExceptionMessage const &) {
2815 message(_("Document not loaded."));
2819 setBuffer(newBuffer);
2820 newBuffer->errors("Parse");
2823 theSession().lastFiles().add(filename);
2824 theSession().writeFile();
2831 void GuiView::openDocuments(string const & fname, int origin)
2833 string initpath = lyxrc.document_path;
2835 if (documentBufferView()) {
2836 string const trypath = documentBufferView()->buffer().filePath();
2837 // If directory is writeable, use this as default.
2838 if (FileName(trypath).isDirWritable())
2844 if (fname.empty()) {
2845 FileDialog dlg(qt_("Select documents to open"));
2846 dlg.setButton1(qt_("D&ocuments"), toqstr(lyxrc.document_path));
2847 dlg.setButton2(qt_("&Examples"), toqstr(lyxrc.example_path));
2849 QStringList const filter({
2850 qt_("LyX Documents (*.lyx)"),
2851 qt_("LyX Document Backups (*.lyx~)"),
2852 qt_("All Files") + " " + wildcardAllFiles()
2854 FileDialog::Results results =
2855 dlg.openMulti(toqstr(initpath), filter);
2857 if (results.first == FileDialog::Later)
2860 files = results.second;
2862 // check selected filename
2863 if (files.isEmpty()) {
2864 message(_("Canceled."));
2868 files << toqstr(fname);
2870 // iterate over all selected files
2871 for (auto const & file : files) {
2872 string filename = fromqstr(file);
2874 // get absolute path of file and add ".lyx" to the filename if
2876 FileName const fullname =
2877 fileSearch(string(), filename, "lyx", support::may_not_exist);
2878 if (!fullname.empty())
2879 filename = fullname.absFileName();
2881 if (!fullname.onlyPath().isDirectory()) {
2882 Alert::warning(_("Invalid filename"),
2883 bformat(_("The directory in the given path\n%1$s\ndoes not exist."),
2884 from_utf8(fullname.absFileName())));
2888 // if the file doesn't exist and isn't already open (bug 6645),
2889 // let the user create one
2890 if (!fullname.exists() && !theBufferList().exists(fullname) &&
2891 !LyXVC::file_not_found_hook(fullname)) {
2893 if (origin == FuncRequest::MENU) {
2894 docstring const & msg =
2897 "does not exist. Create empty file?"),
2898 from_utf8(filename));
2899 int ret = Alert::prompt(_("File does not exist"),
2906 Buffer * const b = newFile(filename, string(), true);
2912 docstring const disp_fn = makeDisplayPath(filename);
2913 message(bformat(_("Opening document %1$s..."), disp_fn));
2916 Buffer * buf = loadDocument(fullname);
2918 str2 = bformat(_("Document %1$s opened."), disp_fn);
2919 if (buf->lyxvc().inUse())
2920 str2 += " " + from_utf8(buf->lyxvc().versionString()) +
2921 " " + _("Version control detected.");
2923 str2 = bformat(_("Could not open document %1$s"), disp_fn);
2929 // FIXME: clean that
2930 static bool import(GuiView * lv, FileName const & filename,
2931 string const & format, ErrorList & errorList)
2933 FileName const lyxfile(support::changeExtension(filename.absFileName(), ".lyx"));
2935 string loader_format;
2936 vector<string> loaders = theConverters().loaders();
2937 if (find(loaders.begin(), loaders.end(), format) == loaders.end()) {
2938 for (string const & loader : loaders) {
2939 if (!theConverters().isReachable(format, loader))
2942 string const tofile =
2943 support::changeExtension(filename.absFileName(),
2944 theFormats().extension(loader));
2945 if (theConverters().convert(nullptr, filename, FileName(tofile),
2946 filename, format, loader, errorList) != Converters::SUCCESS)
2948 loader_format = loader;
2951 if (loader_format.empty()) {
2952 frontend::Alert::error(_("Couldn't import file"),
2953 bformat(_("No information for importing the format %1$s."),
2954 translateIfPossible(theFormats().prettyName(format))));
2958 loader_format = format;
2960 if (loader_format == "lyx") {
2961 Buffer * buf = lv->loadDocument(lyxfile);
2965 Buffer * const b = newFile(lyxfile.absFileName(), string(), true);
2969 bool as_paragraphs = loader_format == "textparagraph";
2970 string filename2 = (loader_format == format) ? filename.absFileName()
2971 : support::changeExtension(filename.absFileName(),
2972 theFormats().extension(loader_format));
2973 lv->currentBufferView()->insertPlaintextFile(FileName(filename2),
2975 guiApp->setCurrentView(lv);
2976 lyx::dispatch(FuncRequest(LFUN_MARK_OFF));
2983 void GuiView::importDocument(string const & argument)
2986 string filename = split(argument, format, ' ');
2988 LYXERR(Debug::INFO, format << " file: " << filename);
2990 // need user interaction
2991 if (filename.empty()) {
2992 string initpath = lyxrc.document_path;
2993 if (documentBufferView()) {
2994 string const trypath = documentBufferView()->buffer().filePath();
2995 // If directory is writeable, use this as default.
2996 if (FileName(trypath).isDirWritable())
3000 docstring const text = bformat(_("Select %1$s file to import"),
3001 translateIfPossible(theFormats().prettyName(format)));
3003 FileDialog dlg(toqstr(text));
3004 dlg.setButton1(qt_("D&ocuments"), toqstr(lyxrc.document_path));
3005 dlg.setButton2(qt_("&Examples"), toqstr(lyxrc.example_path));
3007 docstring filter = translateIfPossible(theFormats().prettyName(format));
3010 filter += from_utf8(theFormats().extensions(format));
3013 FileDialog::Result result =
3014 dlg.open(toqstr(initpath), fileFilters(toqstr(filter)));
3016 if (result.first == FileDialog::Later)
3019 filename = fromqstr(result.second);
3021 // check selected filename
3022 if (filename.empty())
3023 message(_("Canceled."));
3026 if (filename.empty())
3029 // get absolute path of file
3030 FileName const fullname(support::makeAbsPath(filename));
3032 // Can happen if the user entered a path into the dialog
3034 if (fullname.onlyFileName().empty()) {
3035 docstring msg = bformat(_("The file name '%1$s' is invalid!\n"
3036 "Aborting import."),
3037 from_utf8(fullname.absFileName()));
3038 frontend::Alert::error(_("File name error"), msg);
3039 message(_("Canceled."));
3044 FileName const lyxfile(support::changeExtension(fullname.absFileName(), ".lyx"));
3046 // Check if the document already is open
3047 Buffer * buf = theBufferList().getBuffer(lyxfile);
3050 if (!closeBuffer()) {
3051 message(_("Canceled."));
3056 docstring const displaypath = makeDisplayPath(lyxfile.absFileName(), 30);
3058 // if the file exists already, and we didn't do
3059 // -i lyx thefile.lyx, warn
3060 if (lyxfile.exists() && fullname != lyxfile) {
3062 docstring text = bformat(_("The document %1$s already exists.\n\n"
3063 "Do you want to overwrite that document?"), displaypath);
3064 int const ret = Alert::prompt(_("Overwrite document?"),
3065 text, 0, 1, _("&Overwrite"), _("&Cancel"));
3068 message(_("Canceled."));
3073 message(bformat(_("Importing %1$s..."), displaypath));
3074 ErrorList errorList;
3075 if (import(this, fullname, format, errorList))
3076 message(_("imported."));
3078 message(_("file not imported!"));
3080 // FIXME (Abdel 12/08/06): Is there a need to display the error list here?
3084 void GuiView::newDocument(string const & filename, string templatefile,
3087 FileName initpath(lyxrc.document_path);
3088 if (documentBufferView()) {
3089 FileName const trypath(documentBufferView()->buffer().filePath());
3090 // If directory is writeable, use this as default.
3091 if (trypath.isDirWritable())
3095 if (from_template) {
3096 if (templatefile.empty())
3097 templatefile = selectTemplateFile().absFileName();
3098 if (templatefile.empty())
3103 if (filename.empty())
3104 b = newUnnamedFile(initpath, to_utf8(_("newfile")), templatefile);
3106 b = newFile(filename, templatefile, true);
3111 // If no new document could be created, it is unsure
3112 // whether there is a valid BufferView.
3113 if (currentBufferView())
3114 // Ensure the cursor is correctly positioned on screen.
3115 currentBufferView()->showCursor();
3119 bool GuiView::insertLyXFile(docstring const & fname, bool ignorelang)
3121 BufferView * bv = documentBufferView();
3126 FileName filename(to_utf8(fname));
3127 if (filename.empty()) {
3128 // Launch a file browser
3130 string initpath = lyxrc.document_path;
3131 string const trypath = bv->buffer().filePath();
3132 // If directory is writeable, use this as default.
3133 if (FileName(trypath).isDirWritable())
3137 FileDialog dlg(qt_("Select LyX document to insert"));
3138 dlg.setButton1(qt_("D&ocuments"), toqstr(lyxrc.document_path));
3139 dlg.setButton2(qt_("&Examples"), toqstr(lyxrc.example_path));
3141 FileDialog::Result result = dlg.open(toqstr(initpath),
3142 QStringList(qt_("LyX Documents (*.lyx)")));
3144 if (result.first == FileDialog::Later)
3148 filename.set(fromqstr(result.second));
3150 // check selected filename
3151 if (filename.empty()) {
3152 // emit message signal.
3153 message(_("Canceled."));
3158 bv->insertLyXFile(filename, ignorelang);
3159 bv->buffer().errors("Parse");
3164 string const GuiView::getTemplatesPath(Buffer & b)
3166 // We start off with the user's templates path
3167 string result = addPath(package().user_support().absFileName(), "templates");
3168 // Check for the document language
3169 string const langcode = b.params().language->code();
3170 string const shortcode = langcode.substr(0, 2);
3171 if (!langcode.empty() && shortcode != "en") {
3172 string subpath = addPath(result, shortcode);
3173 string subpath_long = addPath(result, langcode);
3174 // If we have a subdirectory for the language already,
3176 FileName sp = FileName(subpath);
3177 if (sp.isDirectory())
3179 else if (FileName(subpath_long).isDirectory())
3180 result = subpath_long;
3182 // Ask whether we should create such a subdirectory
3183 docstring const text =
3184 bformat(_("It is suggested to save the template in a subdirectory\n"
3185 "appropriate to the document language (%1$s).\n"
3186 "This subdirectory does not exists yet.\n"
3187 "Do you want to create it?"),
3188 _(b.params().language->display()));
3189 if (Alert::prompt(_("Create Language Directory?"),
3190 text, 0, 1, _("&Yes, Create"), _("&No, Save Template in Parent Directory")) == 0) {
3191 // If the user agreed, we try to create it and report if this failed.
3192 if (!sp.createDirectory(0777))
3193 Alert::error(_("Subdirectory creation failed!"),
3194 _("Could not create subdirectory.\n"
3195 "The template will be saved in the parent directory."));
3201 // Do we have a layout category?
3202 string const cat = b.params().baseClass() ?
3203 b.params().baseClass()->category()
3206 string subpath = addPath(result, cat);
3207 // If we have a subdirectory for the category already,
3209 FileName sp = FileName(subpath);
3210 if (sp.isDirectory())
3213 // Ask whether we should create such a subdirectory
3214 docstring const text =
3215 bformat(_("It is suggested to save the template in a subdirectory\n"
3216 "appropriate to the layout category (%1$s).\n"
3217 "This subdirectory does not exists yet.\n"
3218 "Do you want to create it?"),
3220 if (Alert::prompt(_("Create Category Directory?"),
3221 text, 0, 1, _("&Yes, Create"), _("&No, Save Template in Parent Directory")) == 0) {
3222 // If the user agreed, we try to create it and report if this failed.
3223 if (!sp.createDirectory(0777))
3224 Alert::error(_("Subdirectory creation failed!"),
3225 _("Could not create subdirectory.\n"
3226 "The template will be saved in the parent directory."));
3236 bool GuiView::renameBuffer(Buffer & b, docstring const & newname, RenameKind kind)
3238 FileName fname = b.fileName();
3239 FileName const oldname = fname;
3240 bool const as_template = (kind == LV_WRITE_AS_TEMPLATE);
3242 if (!newname.empty()) {
3245 fname = support::makeAbsPath(to_utf8(newname), getTemplatesPath(b));
3247 fname = support::makeAbsPath(to_utf8(newname),
3248 oldname.onlyPath().absFileName());
3250 // Switch to this Buffer.
3253 // No argument? Ask user through dialog.
3255 QString const title = as_template ? qt_("Choose a filename to save template as")
3256 : qt_("Choose a filename to save document as");
3257 FileDialog dlg(title);
3258 dlg.setButton1(qt_("D&ocuments"), toqstr(lyxrc.document_path));
3259 dlg.setButton2(qt_("&Templates"), toqstr(lyxrc.template_path));
3261 fname.ensureExtension(".lyx");
3263 string const path = as_template ?
3265 : fname.onlyPath().absFileName();
3266 FileDialog::Result result =
3267 dlg.save(toqstr(path),
3268 QStringList(qt_("LyX Documents (*.lyx)")),
3269 toqstr(fname.onlyFileName()));
3271 if (result.first == FileDialog::Later)
3274 fname.set(fromqstr(result.second));
3279 fname.ensureExtension(".lyx");
3282 // fname is now the new Buffer location.
3284 // if there is already a Buffer open with this name, we do not want
3285 // to have another one. (the second test makes sure we're not just
3286 // trying to overwrite ourselves, which is fine.)
3287 if (theBufferList().exists(fname) && fname != oldname
3288 && theBufferList().getBuffer(fname) != &b) {
3289 docstring const text =
3290 bformat(_("The file\n%1$s\nis already open in your current session.\n"
3291 "Please close it before attempting to overwrite it.\n"
3292 "Do you want to choose a new filename?"),
3293 from_utf8(fname.absFileName()));
3294 int const ret = Alert::prompt(_("Chosen File Already Open"),
3295 text, 0, 1, _("&Rename"), _("&Cancel"));
3297 case 0: return renameBuffer(b, docstring(), kind);
3298 case 1: return false;
3303 bool const existsLocal = fname.exists();
3304 bool const existsInVC = LyXVC::fileInVC(fname);
3305 if (existsLocal || existsInVC) {
3306 docstring const file = makeDisplayPath(fname.absFileName(), 30);
3307 if (kind != LV_WRITE_AS && existsInVC) {
3308 // renaming to a name that is already in VC
3310 docstring text = bformat(_("The document %1$s "
3311 "is already registered.\n\n"
3312 "Do you want to choose a new name?"),
3314 docstring const title = (kind == LV_VC_RENAME) ?
3315 _("Rename document?") : _("Copy document?");
3316 docstring const button = (kind == LV_VC_RENAME) ?
3317 _("&Rename") : _("&Copy");
3318 int const ret = Alert::prompt(title, text, 0, 1,
3319 button, _("&Cancel"));
3321 case 0: return renameBuffer(b, docstring(), kind);
3322 case 1: return false;
3327 docstring text = bformat(_("The document %1$s "
3328 "already exists.\n\n"
3329 "Do you want to overwrite that document?"),
3331 int const ret = Alert::prompt(_("Overwrite document?"),
3332 text, 0, 2, _("&Overwrite"),
3333 _("&Rename"), _("&Cancel"));
3336 case 1: return renameBuffer(b, docstring(), kind);
3337 case 2: return false;
3343 case LV_VC_RENAME: {
3344 string msg = b.lyxvc().rename(fname);
3347 message(from_utf8(msg));
3351 string msg = b.lyxvc().copy(fname);
3354 message(from_utf8(msg));
3358 case LV_WRITE_AS_TEMPLATE:
3361 // LyXVC created the file already in case of LV_VC_RENAME or
3362 // LV_VC_COPY, but call saveBuffer() nevertheless to get
3363 // relative paths of included stuff right if we moved e.g. from
3364 // /a/b.lyx to /a/c/b.lyx.
3366 bool const saved = saveBuffer(b, fname);
3373 bool GuiView::exportBufferAs(Buffer & b, docstring const & iformat)
3375 FileName fname = b.fileName();
3377 FileDialog dlg(qt_("Choose a filename to export the document as"));
3378 dlg.setButton1(qt_("D&ocuments"), toqstr(lyxrc.document_path));
3381 QString const anyformat = qt_("Guess from extension (*.*)");
3384 vector<Format const *> export_formats;
3385 for (Format const & f : theFormats())
3386 if (f.documentFormat())
3387 export_formats.push_back(&f);
3388 sort(export_formats.begin(), export_formats.end(), Format::formatSorter);
3389 map<QString, string> fmap;
3392 for (Format const * f : export_formats) {
3393 docstring const loc_prettyname = translateIfPossible(f->prettyname());
3394 QString const loc_filter = toqstr(bformat(from_ascii("%1$s (*.%2$s)"),
3396 from_ascii(f->extension())));
3397 types << loc_filter;
3398 fmap[loc_filter] = f->name();
3399 if (from_ascii(f->name()) == iformat) {
3400 filter = loc_filter;
3401 ext = f->extension();
3404 string ofname = fname.onlyFileName();
3406 ofname = support::changeExtension(ofname, ext);
3407 FileDialog::Result result =
3408 dlg.save(toqstr(fname.onlyPath().absFileName()),
3412 if (result.first != FileDialog::Chosen)
3416 fname.set(fromqstr(result.second));
3417 if (filter == anyformat)
3418 fmt_name = theFormats().getFormatFromExtension(fname.extension());
3420 fmt_name = fmap[filter];
3421 LYXERR(Debug::FILES, "filter=" << fromqstr(filter)
3422 << ", fmt_name=" << fmt_name << ", fname=" << fname.absFileName());
3424 if (fmt_name.empty() || fname.empty())
3427 fname.ensureExtension(theFormats().extension(fmt_name));
3429 // fname is now the new Buffer location.
3430 if (fname.exists()) {
3431 docstring const file = makeDisplayPath(fname.absFileName(), 30);
3432 docstring text = bformat(_("The document %1$s already "
3433 "exists.\n\nDo you want to "
3434 "overwrite that document?"),
3436 int const ret = Alert::prompt(_("Overwrite document?"),
3437 text, 0, 2, _("&Overwrite"), _("&Rename"), _("&Cancel"));
3440 case 1: return exportBufferAs(b, from_ascii(fmt_name));
3441 case 2: return false;
3445 FuncRequest cmd(LFUN_BUFFER_EXPORT, fmt_name + " " + fname.absFileName());
3448 return dr.dispatched();
3452 bool GuiView::saveBuffer(Buffer & b)
3454 return saveBuffer(b, FileName());
3458 bool GuiView::saveBuffer(Buffer & b, FileName const & fn)
3460 if (workArea(b) && workArea(b)->inDialogMode())
3463 if (fn.empty() && b.isUnnamed())
3464 return renameBuffer(b, docstring());
3466 bool const success = (fn.empty() ? b.save() : b.saveAs(fn));
3468 theSession().lastFiles().add(b.fileName());
3469 theSession().writeFile();
3473 // Switch to this Buffer.
3476 // FIXME: we don't tell the user *WHY* the save failed !!
3477 docstring const file = makeDisplayPath(b.absFileName(), 30);
3478 docstring text = bformat(_("The document %1$s could not be saved.\n\n"
3479 "Do you want to rename the document and "
3480 "try again?"), file);
3481 int const ret = Alert::prompt(_("Rename and save?"),
3482 text, 0, 2, _("&Rename"), _("&Retry"), _("&Cancel"));
3485 if (!renameBuffer(b, docstring()))
3494 return saveBuffer(b, fn);
3498 bool GuiView::hideWorkArea(GuiWorkArea * wa)
3500 return closeWorkArea(wa, false);
3504 // We only want to close the buffer if it is not visible in other workareas
3505 // of the same view, nor in other views, and if this is not a child
3506 bool GuiView::closeWorkArea(GuiWorkArea * wa)
3508 Buffer & buf = wa->bufferView().buffer();
3510 bool last_wa = d.countWorkAreasOf(buf) == 1
3511 && !inOtherView(buf) && !buf.parent();
3513 bool close_buffer = last_wa;
3516 if (lyxrc.close_buffer_with_last_view == "yes")
3518 else if (lyxrc.close_buffer_with_last_view == "no")
3519 close_buffer = false;
3522 if (buf.isUnnamed())
3523 file = from_utf8(buf.fileName().onlyFileName());
3525 file = buf.fileName().displayName(30);
3526 docstring const text = bformat(
3527 _("Last view on document %1$s is being closed.\n"
3528 "Would you like to close or hide the document?\n"
3530 "Hidden documents can be displayed back through\n"
3531 "the menu: View->Hidden->...\n"
3533 "To remove this question, set your preference in:\n"
3534 " Tools->Preferences->Look&Feel->UserInterface\n"
3536 int ret = Alert::prompt(_("Close or hide document?"),
3537 text, 0, 2, _("&Close"), _("&Hide"), _("&Cancel"));
3540 close_buffer = (ret == 0);
3544 return closeWorkArea(wa, close_buffer);
3548 bool GuiView::closeBuffer()
3550 GuiWorkArea * wa = currentMainWorkArea();
3551 // coverity complained about this
3552 // it seems unnecessary, but perhaps is worth the check
3553 LASSERT(wa, return false);
3555 setCurrentWorkArea(wa);
3556 Buffer & buf = wa->bufferView().buffer();
3557 return closeWorkArea(wa, !buf.parent());
3561 void GuiView::writeSession() const {
3562 GuiWorkArea const * active_wa = currentMainWorkArea();
3563 for (int i = 0; i < d.splitter_->count(); ++i) {
3564 TabWorkArea * twa = d.tabWorkArea(i);
3565 for (int j = 0; j < twa->count(); ++j) {
3566 GuiWorkArea * wa = twa->workArea(j);
3567 Buffer & buf = wa->bufferView().buffer();
3568 theSession().lastOpened().add(buf.fileName(), wa == active_wa);
3574 bool GuiView::closeBufferAll()
3577 for (auto & buf : theBufferList()) {
3578 if (!saveBufferIfNeeded(*buf, false)) {
3579 // Closing has been cancelled, so abort.
3584 // Close the workareas in all other views
3585 QList<int> const ids = guiApp->viewIds();
3586 for (int i = 0; i != ids.size(); ++i) {
3587 if (id_ != ids[i] && !guiApp->view(ids[i]).closeWorkAreaAll())
3591 // Close our own workareas
3592 if (!closeWorkAreaAll())
3599 bool GuiView::closeWorkAreaAll()
3601 setCurrentWorkArea(currentMainWorkArea());
3603 // We might be in a situation that there is still a tabWorkArea, but
3604 // there are no tabs anymore. This can happen when we get here after a
3605 // TabWorkArea::lastWorkAreaRemoved() signal. Therefore we count how
3606 // many TabWorkArea's have no documents anymore.
3609 // We have to call count() each time, because it can happen that
3610 // more than one splitter will disappear in one iteration (bug 5998).
3611 while (d.splitter_->count() > empty_twa) {
3612 TabWorkArea * twa = d.tabWorkArea(empty_twa);
3614 if (twa->count() == 0)
3617 setCurrentWorkArea(twa->currentWorkArea());
3618 if (!closeTabWorkArea(twa))
3626 bool GuiView::closeWorkArea(GuiWorkArea * wa, bool close_buffer)
3631 Buffer & buf = wa->bufferView().buffer();
3633 if (GuiViewPrivate::busyBuffers.contains(&buf)) {
3634 Alert::warning(_("Close document"),
3635 _("Document could not be closed because it is being processed by LyX."));
3640 return closeBuffer(buf);
3642 if (!inMultiTabs(wa))
3643 if (!saveBufferIfNeeded(buf, true))
3651 bool GuiView::closeBuffer(Buffer & buf)
3653 bool success = true;
3654 for (Buffer * child_buf : buf.getChildren()) {
3655 if (theBufferList().isOthersChild(&buf, child_buf)) {
3656 child_buf->setParent(nullptr);
3660 // FIXME: should we look in other tabworkareas?
3661 // ANSWER: I don't think so. I've tested, and if the child is
3662 // open in some other window, it closes without a problem.
3663 GuiWorkArea * child_wa = workArea(*child_buf);
3666 // If we are in a close_event all children will be closed in some time,
3667 // so no need to do it here. This will ensure that the children end up
3668 // in the session file in the correct order. If we close the master
3669 // buffer, we can close or release the child buffers here too.
3671 success = closeWorkArea(child_wa, true);
3675 // In this case the child buffer is open but hidden.
3676 // Even in this case, children can be dirty (e.g.,
3677 // after a label change in the master, see #11405).
3678 // Therefore, check this
3679 if (closing_ && (child_buf->isClean() || child_buf->paragraphs().empty())) {
3680 // If we are in a close_event all children will be closed in some time,
3681 // so no need to do it here. This will ensure that the children end up
3682 // in the session file in the correct order. If we close the master
3683 // buffer, we can close or release the child buffers here too.
3686 // Save dirty buffers also if closing_!
3687 if (saveBufferIfNeeded(*child_buf, false)) {
3688 child_buf->removeAutosaveFile();
3689 theBufferList().release(child_buf);
3691 // Saving of dirty children has been cancelled.
3692 // Cancel the whole process.
3699 // goto bookmark to update bookmark pit.
3700 // FIXME: we should update only the bookmarks related to this buffer!
3701 // FIXME: this is done also in LFUN_WINDOW_CLOSE!
3702 LYXERR(Debug::DEBUG, "GuiView::closeBuffer()");
3703 for (unsigned int i = 1; i < theSession().bookmarks().size(); ++i)
3704 guiApp->gotoBookmark(i, false, false);
3706 if (saveBufferIfNeeded(buf, false)) {
3707 buf.removeAutosaveFile();
3708 theBufferList().release(&buf);
3712 // open all children again to avoid a crash because of dangling
3713 // pointers (bug 6603)
3719 bool GuiView::closeTabWorkArea(TabWorkArea * twa)
3721 while (twa == d.currentTabWorkArea()) {
3722 twa->setCurrentIndex(twa->count() - 1);
3724 GuiWorkArea * wa = twa->currentWorkArea();
3725 Buffer & b = wa->bufferView().buffer();
3727 // We only want to close the buffer if the same buffer is not visible
3728 // in another view, and if this is not a child and if we are closing
3729 // a view (not a tabgroup).
3730 bool const close_buffer =
3731 !inOtherView(b) && !b.parent() && closing_;
3733 if (!closeWorkArea(wa, close_buffer))
3740 bool GuiView::saveBufferIfNeeded(Buffer & buf, bool hiding)
3742 if (buf.isClean() || buf.paragraphs().empty())
3745 // Switch to this Buffer.
3751 if (buf.isUnnamed()) {
3752 file = from_utf8(buf.fileName().onlyFileName());
3755 FileName filename = buf.fileName();
3757 file = filename.displayName(30);
3758 exists = filename.exists();
3761 // Bring this window to top before asking questions.
3766 if (hiding && buf.isUnnamed()) {
3767 docstring const text = bformat(_("The document %1$s has not been "
3768 "saved yet.\n\nDo you want to save "
3769 "the document?"), file);
3770 ret = Alert::prompt(_("Save new document?"),
3771 text, 0, 1, _("&Save"), _("&Cancel"));
3775 docstring const text = exists ?
3776 bformat(_("The document %1$s has unsaved changes."
3777 "\n\nDo you want to save the document or "
3778 "discard the changes?"), file) :
3779 bformat(_("The document %1$s has not been saved yet."
3780 "\n\nDo you want to save the document or "
3781 "discard it entirely?"), file);
3782 docstring const title = exists ?
3783 _("Save changed document?") : _("Save document?");
3784 ret = Alert::prompt(title, text, 0, 2,
3785 _("&Save"), _("&Discard"), _("&Cancel"));
3790 if (!saveBuffer(buf))
3794 // If we crash after this we could have no autosave file
3795 // but I guess this is really improbable (Jug).
3796 // Sometimes improbable things happen:
3797 // - see bug http://www.lyx.org/trac/ticket/6587 (ps)
3798 // buf.removeAutosaveFile();
3800 // revert all changes
3811 bool GuiView::inMultiTabs(GuiWorkArea * wa)
3813 Buffer & buf = wa->bufferView().buffer();
3815 for (int i = 0; i != d.splitter_->count(); ++i) {
3816 GuiWorkArea * wa_ = d.tabWorkArea(i)->workArea(buf);
3817 if (wa_ && wa_ != wa)
3820 return inOtherView(buf);
3824 bool GuiView::inOtherView(Buffer & buf)
3826 QList<int> const ids = guiApp->viewIds();
3828 for (int i = 0; i != ids.size(); ++i) {
3832 if (guiApp->view(ids[i]).workArea(buf))
3839 void GuiView::gotoNextOrPreviousBuffer(NextOrPrevious np, bool const move)
3841 if (!documentBufferView())
3844 if (TabWorkArea * twa = d.currentTabWorkArea()) {
3845 Buffer * const curbuf = &documentBufferView()->buffer();
3846 int nwa = twa->count();
3847 for (int i = 0; i < nwa; ++i) {
3848 if (&workArea(i)->bufferView().buffer() == curbuf) {
3851 next_index = (i == nwa - 1 ? 0 : i + 1);
3853 next_index = (i == 0 ? nwa - 1 : i - 1);
3855 twa->moveTab(i, next_index);
3857 setBuffer(&workArea(next_index)->bufferView().buffer());
3865 void GuiView::gotoNextTabWorkArea(NextOrPrevious np)
3867 int count = d.splitter_->count();
3868 for (int i = 0; i < count; ++i) {
3869 if (d.tabWorkArea(i) == d.currentTabWorkArea()) {
3872 new_index = (i == count - 1 ? 0 : i + 1);
3874 new_index = (i == 0 ? count - 1 : i - 1);
3875 setCurrentWorkArea(d.tabWorkArea(new_index)->currentWorkArea());
3882 /// make sure the document is saved
3883 static bool ensureBufferClean(Buffer * buffer)
3885 LASSERT(buffer, return false);
3886 if (buffer->isClean() && !buffer->isUnnamed())
3889 docstring const file = buffer->fileName().displayName(30);
3892 if (!buffer->isUnnamed()) {
3893 text = bformat(_("The document %1$s has unsaved "
3894 "changes.\n\nDo you want to save "
3895 "the document?"), file);
3896 title = _("Save changed document?");
3899 text = bformat(_("The document %1$s has not been "
3900 "saved yet.\n\nDo you want to save "
3901 "the document?"), file);
3902 title = _("Save new document?");
3904 int const ret = Alert::prompt(title, text, 0, 1, _("&Save"), _("&Cancel"));
3907 dispatch(FuncRequest(LFUN_BUFFER_WRITE));
3909 return buffer->isClean() && !buffer->isUnnamed();
3913 bool GuiView::reloadBuffer(Buffer & buf)
3915 currentBufferView()->cursor().reset();
3916 Buffer::ReadStatus status = buf.reload();
3917 return status == Buffer::ReadSuccess;
3921 void GuiView::checkExternallyModifiedBuffers()
3923 for (Buffer * buf : theBufferList()) {
3924 if (buf->fileName().exists() && buf->isChecksumModified()) {
3925 docstring text = bformat(_("Document \n%1$s\n has been externally modified."
3926 " Reload now? Any local changes will be lost."),
3927 from_utf8(buf->absFileName()));
3928 int const ret = Alert::prompt(_("Reload externally changed document?"),
3929 text, 0, 1, _("&Reload"), _("&Cancel"));
3937 void GuiView::dispatchVC(FuncRequest const & cmd, DispatchResult & dr)
3939 Buffer * buffer = documentBufferView()
3940 ? &(documentBufferView()->buffer()) : nullptr;
3942 switch (cmd.action()) {
3943 case LFUN_VC_REGISTER:
3944 if (!buffer || !ensureBufferClean(buffer))
3946 if (!buffer->lyxvc().inUse()) {
3947 if (buffer->lyxvc().registrer()) {
3948 reloadBuffer(*buffer);
3949 dr.clearMessageUpdate();
3954 case LFUN_VC_RENAME:
3955 case LFUN_VC_COPY: {
3956 if (!buffer || !ensureBufferClean(buffer))
3958 if (buffer->lyxvc().inUse() && !buffer->hasReadonlyFlag()) {
3959 if (buffer->lyxvc().isCheckInWithConfirmation()) {
3960 // Some changes are not yet committed.
3961 // We test here and not in getStatus(), since
3962 // this test is expensive.
3964 LyXVC::CommandResult ret =
3965 buffer->lyxvc().checkIn(log);
3967 if (ret == LyXVC::ErrorCommand ||
3968 ret == LyXVC::VCSuccess)
3969 reloadBuffer(*buffer);
3970 if (buffer->lyxvc().isCheckInWithConfirmation()) {
3971 frontend::Alert::error(
3972 _("Revision control error."),
3973 _("Document could not be checked in."));
3977 RenameKind const kind = (cmd.action() == LFUN_VC_RENAME) ?
3978 LV_VC_RENAME : LV_VC_COPY;
3979 renameBuffer(*buffer, cmd.argument(), kind);
3984 case LFUN_VC_CHECK_IN:
3985 if (!buffer || !ensureBufferClean(buffer))
3987 if (buffer->lyxvc().inUse() && !buffer->hasReadonlyFlag()) {
3989 LyXVC::CommandResult ret = buffer->lyxvc().checkIn(log);
3991 // Only skip reloading if the checkin was cancelled or
3992 // an error occurred before the real checkin VCS command
3993 // was executed, since the VCS might have changed the
3994 // file even if it could not checkin successfully.
3995 if (ret == LyXVC::ErrorCommand || ret == LyXVC::VCSuccess)
3996 reloadBuffer(*buffer);
4000 case LFUN_VC_CHECK_OUT:
4001 if (!buffer || !ensureBufferClean(buffer))
4003 if (buffer->lyxvc().inUse()) {
4004 dr.setMessage(buffer->lyxvc().checkOut());
4005 reloadBuffer(*buffer);
4009 case LFUN_VC_LOCKING_TOGGLE:
4010 if (!buffer || !ensureBufferClean(buffer) || buffer->hasReadonlyFlag())
4012 if (buffer->lyxvc().inUse()) {
4013 string res = buffer->lyxvc().lockingToggle();
4015 frontend::Alert::error(_("Revision control error."),
4016 _("Error when setting the locking property."));
4019 reloadBuffer(*buffer);
4024 case LFUN_VC_REVERT:
4027 if (buffer->lyxvc().revert()) {
4028 reloadBuffer(*buffer);
4029 dr.clearMessageUpdate();
4033 case LFUN_VC_UNDO_LAST:
4036 buffer->lyxvc().undoLast();
4037 reloadBuffer(*buffer);
4038 dr.clearMessageUpdate();
4041 case LFUN_VC_REPO_UPDATE:
4044 if (ensureBufferClean(buffer)) {
4045 dr.setMessage(buffer->lyxvc().repoUpdate());
4046 checkExternallyModifiedBuffers();
4050 case LFUN_VC_COMMAND: {
4051 string flag = cmd.getArg(0);
4052 if (buffer && contains(flag, 'R') && !ensureBufferClean(buffer))
4055 if (contains(flag, 'M')) {
4056 if (!Alert::askForText(message, _("LyX VC: Log Message")))
4059 string path = cmd.getArg(1);
4060 if (contains(path, "$$p") && buffer)
4061 path = subst(path, "$$p", buffer->filePath());
4062 LYXERR(Debug::LYXVC, "Directory: " << path);
4064 if (!pp.isReadableDirectory()) {
4065 lyxerr << _("Directory is not accessible.") << endl;
4068 support::PathChanger p(pp);
4070 string command = cmd.getArg(2);
4071 if (command.empty())
4074 command = subst(command, "$$i", buffer->absFileName());
4075 command = subst(command, "$$p", buffer->filePath());
4077 command = subst(command, "$$m", to_utf8(message));
4078 LYXERR(Debug::LYXVC, "Command: " << command);
4080 one.startscript(Systemcall::Wait, command);
4084 if (contains(flag, 'I'))
4085 buffer->markDirty();
4086 if (contains(flag, 'R'))
4087 reloadBuffer(*buffer);
4092 case LFUN_VC_COMPARE: {
4093 if (cmd.argument().empty()) {
4094 lyx::dispatch(FuncRequest(LFUN_DIALOG_SHOW, "comparehistory"));
4100 string rev1 = cmd.getArg(0);
4104 if (!buffer->lyxvc().prepareFileRevision(rev1, f1))
4107 if (isStrInt(rev1) && convert<int>(rev1) <= 0) {
4108 f2 = buffer->absFileName();
4110 string rev2 = cmd.getArg(1);
4114 if (!buffer->lyxvc().prepareFileRevision(rev2, f2))
4118 LYXERR(Debug::LYXVC, "Launching comparison for fetched revisions:\n" <<
4119 f1 << "\n" << f2 << "\n" );
4120 string par = "compare run " + quoteName(f1) + " " + quoteName(f2);
4121 lyx::dispatch(FuncRequest(LFUN_DIALOG_SHOW, par));
4131 void GuiView::openChildDocument(string const & fname)
4133 LASSERT(documentBufferView(), return);
4134 Buffer & buffer = documentBufferView()->buffer();
4135 FileName const filename = support::makeAbsPath(fname, buffer.filePath());
4136 documentBufferView()->saveBookmark(false);
4137 Buffer * child = nullptr;
4138 if (theBufferList().exists(filename)) {
4139 child = theBufferList().getBuffer(filename);
4142 message(bformat(_("Opening child document %1$s..."),
4143 makeDisplayPath(filename.absFileName())));
4144 child = loadDocument(filename, false);
4146 // Set the parent name of the child document.
4147 // This makes insertion of citations and references in the child work,
4148 // when the target is in the parent or another child document.
4150 child->setParent(&buffer);
4154 bool GuiView::goToFileRow(string const & argument)
4158 size_t i = argument.find_last_of(' ');
4159 if (i != string::npos) {
4160 file_name = os::internal_path(FileName(trim(argument.substr(0, i))).realPath());
4161 istringstream is(argument.substr(i + 1));
4166 if (i == string::npos) {
4167 LYXERR0("Wrong argument: " << argument);
4170 Buffer * buf = nullptr;
4171 string const realtmp = package().temp_dir().realPath();
4172 // We have to use os::path_prefix_is() here, instead of
4173 // simply prefixIs(), because the file name comes from
4174 // an external application and may need case adjustment.
4175 if (os::path_prefix_is(file_name, realtmp, os::CASE_ADJUSTED)) {
4176 buf = theBufferList().getBufferFromTmp(file_name, true);
4177 LYXERR(Debug::FILES, "goToFileRow: buffer lookup for " << file_name
4178 << (buf ? " success" : " failed"));
4180 // Must replace extension of the file to be .lyx
4181 // and get full path
4182 FileName const s = fileSearch(string(),
4183 support::changeExtension(file_name, ".lyx"), "lyx");
4184 // Either change buffer or load the file
4185 if (theBufferList().exists(s))
4186 buf = theBufferList().getBuffer(s);
4187 else if (s.exists()) {
4188 buf = loadDocument(s);
4193 _("File does not exist: %1$s"),
4194 makeDisplayPath(file_name)));
4200 _("No buffer for file: %1$s."),
4201 makeDisplayPath(file_name))
4206 bool success = documentBufferView()->setCursorFromRow(row);
4208 LYXERR(Debug::OUTFILE,
4209 "setCursorFromRow: invalid position for row " << row);
4210 frontend::Alert::error(_("Inverse Search Failed"),
4211 _("Invalid position requested by inverse search.\n"
4212 "You may need to update the viewed document."));
4219 Buffer::ExportStatus GuiView::GuiViewPrivate::runAndDestroy(const T& func,
4220 Buffer const * orig, Buffer * clone, string const & format)
4222 Buffer::ExportStatus const status = func(format);
4224 // the cloning operation will have produced a clone of the entire set of
4225 // documents, starting from the master. so we must delete those.
4226 Buffer * mbuf = const_cast<Buffer *>(clone->masterBuffer());
4228 busyBuffers.remove(orig);
4233 Buffer::ExportStatus GuiView::GuiViewPrivate::compileAndDestroy(
4234 Buffer const * orig, Buffer * clone, string const & format)
4236 Buffer::ExportStatus (Buffer::* mem_func)(std::string const &, bool) const =
4238 return runAndDestroy(lyx::bind(mem_func, clone, _1, true), orig, clone, format);
4242 Buffer::ExportStatus GuiView::GuiViewPrivate::exportAndDestroy(
4243 Buffer const * orig, Buffer * clone, string const & format)
4245 Buffer::ExportStatus (Buffer::* mem_func)(std::string const &, bool) const =
4247 return runAndDestroy(lyx::bind(mem_func, clone, _1, false), orig, clone, format);
4251 Buffer::ExportStatus GuiView::GuiViewPrivate::previewAndDestroy(
4252 Buffer const * orig, Buffer * clone, string const & format)
4254 Buffer::ExportStatus (Buffer::* mem_func)(std::string const &) const =
4256 return runAndDestroy(lyx::bind(mem_func, clone, _1), orig, clone, format);
4260 bool GuiView::GuiViewPrivate::asyncBufferProcessing(string const & argument,
4261 Buffer const * used_buffer,
4262 docstring const & msg,
4263 Buffer::ExportStatus (*asyncFunc)(Buffer const *, Buffer *, string const &),
4264 Buffer::ExportStatus (Buffer::*syncFunc)(string const &, bool) const,
4265 Buffer::ExportStatus (Buffer::*previewFunc)(string const &) const,
4266 bool allow_async, bool use_tmpdir)
4271 string format = argument;
4273 format = used_buffer->params().getDefaultOutputFormat();
4274 processing_format = format;
4276 progress_->clearMessages();
4279 #if EXPORT_in_THREAD
4281 GuiViewPrivate::busyBuffers.insert(used_buffer);
4282 Buffer * cloned_buffer = used_buffer->cloneWithChildren();
4283 if (!cloned_buffer) {
4284 Alert::error(_("Export Error"),
4285 _("Error cloning the Buffer."));
4288 QFuture<Buffer::ExportStatus> f = QtConcurrent::run(
4293 setPreviewFuture(f);
4294 last_export_format = used_buffer->params().bufferFormat();
4297 // We are asynchronous, so we don't know here anything about the success
4300 Buffer::ExportStatus status;
4302 status = (used_buffer->*syncFunc)(format, use_tmpdir);
4303 } else if (previewFunc) {
4304 status = (used_buffer->*previewFunc)(format);
4307 handleExportStatus(gv_, status, format);
4309 return (status == Buffer::ExportSuccess
4310 || status == Buffer::PreviewSuccess);
4314 Buffer::ExportStatus status;
4316 status = (used_buffer->*syncFunc)(format, true);
4317 } else if (previewFunc) {
4318 status = (used_buffer->*previewFunc)(format);
4321 handleExportStatus(gv_, status, format);
4323 return (status == Buffer::ExportSuccess
4324 || status == Buffer::PreviewSuccess);
4328 void GuiView::dispatchToBufferView(FuncRequest const & cmd, DispatchResult & dr)
4330 BufferView * bv = currentBufferView();
4331 LASSERT(bv, return);
4333 // Let the current BufferView dispatch its own actions.
4334 bv->dispatch(cmd, dr);
4335 if (dr.dispatched()) {
4336 if (cmd.action() == LFUN_REDO || cmd.action() == LFUN_UNDO)
4337 updateDialog("document", "");
4341 // Try with the document BufferView dispatch if any.
4342 BufferView * doc_bv = documentBufferView();
4343 if (doc_bv && doc_bv != bv) {
4344 doc_bv->dispatch(cmd, dr);
4345 if (dr.dispatched()) {
4346 if (cmd.action() == LFUN_REDO || cmd.action() == LFUN_UNDO)
4347 updateDialog("document", "");
4352 // Then let the current Cursor dispatch its own actions.
4353 bv->cursor().dispatch(cmd);
4355 // update completion. We do it here and not in
4356 // processKeySym to avoid another redraw just for a
4357 // changed inline completion
4358 if (cmd.origin() == FuncRequest::KEYBOARD) {
4359 if (cmd.action() == LFUN_SELF_INSERT
4360 || (cmd.action() == LFUN_ERT_INSERT && bv->cursor().inMathed()))
4361 updateCompletion(bv->cursor(), true, true);
4362 else if (cmd.action() == LFUN_CHAR_DELETE_BACKWARD)
4363 updateCompletion(bv->cursor(), false, true);
4365 updateCompletion(bv->cursor(), false, false);
4368 dr = bv->cursor().result();
4372 void GuiView::dispatch(FuncRequest const & cmd, DispatchResult & dr)
4374 BufferView * bv = currentBufferView();
4375 // By default we won't need any update.
4376 dr.screenUpdate(Update::None);
4377 // assume cmd will be dispatched
4378 dr.dispatched(true);
4380 Buffer * doc_buffer = documentBufferView()
4381 ? &(documentBufferView()->buffer()) : nullptr;
4383 if (cmd.origin() == FuncRequest::TOC) {
4384 GuiToc * toc = static_cast<GuiToc*>(findOrBuild("toc", false));
4385 toc->doDispatch(bv->cursor(), cmd, dr);
4389 string const argument = to_utf8(cmd.argument());
4391 switch(cmd.action()) {
4392 case LFUN_BUFFER_CHILD_OPEN:
4393 openChildDocument(to_utf8(cmd.argument()));
4396 case LFUN_BUFFER_IMPORT:
4397 importDocument(to_utf8(cmd.argument()));
4400 case LFUN_MASTER_BUFFER_EXPORT:
4402 doc_buffer = const_cast<Buffer *>(doc_buffer->masterBuffer());
4404 case LFUN_BUFFER_EXPORT: {
4407 // GCC only sees strfwd.h when building merged
4408 if (::lyx::operator==(cmd.argument(), "custom")) {
4409 // LFUN_MASTER_BUFFER_EXPORT is not enabled for this case,
4410 // so the following test should not be needed.
4411 // In principle, we could try to switch to such a view...
4412 // if (cmd.action() == LFUN_BUFFER_EXPORT)
4413 dispatch(FuncRequest(LFUN_DIALOG_SHOW, "sendto"), dr);
4417 string const dest = cmd.getArg(1);
4418 FileName target_dir;
4419 if (!dest.empty() && FileName::isAbsolute(dest))
4420 target_dir = FileName(support::onlyPath(dest));
4422 target_dir = doc_buffer->fileName().onlyPath();
4424 string const format = (argument.empty() || argument == "default") ?
4425 doc_buffer->params().getDefaultOutputFormat() : argument;
4427 if ((dest.empty() && doc_buffer->isUnnamed())
4428 || !target_dir.isDirWritable()) {
4429 exportBufferAs(*doc_buffer, from_utf8(format));
4432 /* TODO/Review: Is it a problem to also export the children?
4433 See the update_unincluded flag */
4434 d.asyncBufferProcessing(format,
4437 &GuiViewPrivate::exportAndDestroy,
4439 nullptr, cmd.allowAsync());
4440 // TODO Inform user about success
4444 case LFUN_BUFFER_EXPORT_AS: {
4445 LASSERT(doc_buffer, break);
4446 docstring f = cmd.argument();
4448 f = from_ascii(doc_buffer->params().getDefaultOutputFormat());
4449 exportBufferAs(*doc_buffer, f);
4453 case LFUN_BUFFER_UPDATE: {
4454 d.asyncBufferProcessing(argument,
4457 &GuiViewPrivate::compileAndDestroy,
4459 nullptr, cmd.allowAsync(), true);
4462 case LFUN_BUFFER_VIEW: {
4463 d.asyncBufferProcessing(argument,
4465 _("Previewing ..."),
4466 &GuiViewPrivate::previewAndDestroy,
4468 &Buffer::preview, cmd.allowAsync());
4471 case LFUN_MASTER_BUFFER_UPDATE: {
4472 d.asyncBufferProcessing(argument,
4473 (doc_buffer ? doc_buffer->masterBuffer() : nullptr),
4475 &GuiViewPrivate::compileAndDestroy,
4477 nullptr, cmd.allowAsync(), true);
4480 case LFUN_MASTER_BUFFER_VIEW: {
4481 d.asyncBufferProcessing(argument,
4482 (doc_buffer ? doc_buffer->masterBuffer() : nullptr),
4484 &GuiViewPrivate::previewAndDestroy,
4485 nullptr, &Buffer::preview, cmd.allowAsync());
4488 case LFUN_EXPORT_CANCEL: {
4492 case LFUN_BUFFER_SWITCH: {
4493 string const file_name = to_utf8(cmd.argument());
4494 if (!FileName::isAbsolute(file_name)) {
4496 dr.setMessage(_("Absolute filename expected."));
4500 Buffer * buffer = theBufferList().getBuffer(FileName(file_name));
4503 dr.setMessage(_("Document not loaded"));
4507 // Do we open or switch to the buffer in this view ?
4508 if (workArea(*buffer)
4509 || lyxrc.open_buffers_in_tabs || !documentBufferView()) {
4514 // Look for the buffer in other views
4515 QList<int> const ids = guiApp->viewIds();
4517 for (; i != ids.size(); ++i) {
4518 GuiView & gv = guiApp->view(ids[i]);
4519 if (gv.workArea(*buffer)) {
4521 gv.activateWindow();
4523 gv.setBuffer(buffer);
4528 // If necessary, open a new window as a last resort
4529 if (i == ids.size()) {
4530 lyx::dispatch(FuncRequest(LFUN_WINDOW_NEW));
4536 case LFUN_BUFFER_NEXT:
4537 gotoNextOrPreviousBuffer(NEXT, false);
4540 case LFUN_BUFFER_MOVE_NEXT:
4541 gotoNextOrPreviousBuffer(NEXT, true);
4544 case LFUN_BUFFER_PREVIOUS:
4545 gotoNextOrPreviousBuffer(PREV, false);
4548 case LFUN_BUFFER_MOVE_PREVIOUS:
4549 gotoNextOrPreviousBuffer(PREV, true);
4552 case LFUN_BUFFER_CHKTEX:
4553 LASSERT(doc_buffer, break);
4554 doc_buffer->runChktex();
4557 case LFUN_CHANGES_TRACK: {
4558 // the actual dispatch is done in Buffer
4559 dispatchToBufferView(cmd, dr);
4560 // but we inform the GUI (document settings) if this is toggled
4561 LASSERT(doc_buffer, break);
4562 Q_EMIT changeTrackingToggled(doc_buffer->params().track_changes);
4566 case LFUN_COMMAND_EXECUTE: {
4567 command_execute_ = true;
4568 minibuffer_focus_ = true;
4571 case LFUN_DROP_LAYOUTS_CHOICE:
4572 d.layout_->showPopup();
4575 case LFUN_MENU_OPEN:
4576 if (QMenu * menu = guiApp->menus().menu(toqstr(cmd.argument()), *this))
4577 menu->exec(QCursor::pos());
4580 case LFUN_FILE_INSERT: {
4581 bool const ignore_lang = cmd.getArg(1) == "ignorelang";
4582 if (insertLyXFile(from_utf8(cmd.getArg(0)), ignore_lang)) {
4583 dr.forceBufferUpdate();
4584 dr.screenUpdate(Update::Force);
4589 case LFUN_FILE_INSERT_PLAINTEXT:
4590 case LFUN_FILE_INSERT_PLAINTEXT_PARA: {
4591 string const fname = to_utf8(cmd.argument());
4592 if (!fname.empty() && !FileName::isAbsolute(fname)) {
4593 dr.setMessage(_("Absolute filename expected."));
4597 FileName filename(fname);
4598 if (fname.empty()) {
4599 FileDialog dlg(qt_("Select file to insert"));
4601 FileDialog::Result result = dlg.open(toqstr(bv->buffer().filePath()),
4602 QStringList(qt_("All Files")+ " " + wildcardAllFiles()));
4604 if (result.first == FileDialog::Later || result.second.isEmpty()) {
4605 dr.setMessage(_("Canceled."));
4609 filename.set(fromqstr(result.second));
4613 FuncRequest const new_cmd(cmd, filename.absoluteFilePath());
4614 bv->dispatch(new_cmd, dr);
4619 case LFUN_BUFFER_RELOAD: {
4620 LASSERT(doc_buffer, break);
4623 bool drop = (cmd.argument() == "dump");
4626 if (!drop && !doc_buffer->isClean()) {
4627 docstring const file =
4628 makeDisplayPath(doc_buffer->absFileName(), 20);
4629 if (doc_buffer->notifiesExternalModification()) {
4630 docstring text = _("The current version will be lost. "
4631 "Are you sure you want to load the version on disk "
4632 "of the document %1$s?");
4633 ret = Alert::prompt(_("Reload saved document?"),
4634 bformat(text, file), 1, 1,
4635 _("&Reload"), _("&Cancel"));
4637 docstring text = _("Any changes will be lost. "
4638 "Are you sure you want to revert to the saved version "
4639 "of the document %1$s?");
4640 ret = Alert::prompt(_("Revert to saved document?"),
4641 bformat(text, file), 1, 1,
4642 _("&Revert"), _("&Cancel"));
4647 doc_buffer->markClean();
4648 reloadBuffer(*doc_buffer);
4649 dr.forceBufferUpdate();
4654 case LFUN_BUFFER_RESET_EXPORT:
4655 LASSERT(doc_buffer, break);
4656 doc_buffer->requireFreshStart(true);
4657 dr.setMessage(_("Buffer export reset."));
4660 case LFUN_BUFFER_WRITE:
4661 LASSERT(doc_buffer, break);
4662 saveBuffer(*doc_buffer);
4665 case LFUN_BUFFER_WRITE_AS:
4666 LASSERT(doc_buffer, break);
4667 renameBuffer(*doc_buffer, cmd.argument());
4670 case LFUN_BUFFER_WRITE_AS_TEMPLATE:
4671 LASSERT(doc_buffer, break);
4672 renameBuffer(*doc_buffer, cmd.argument(),
4673 LV_WRITE_AS_TEMPLATE);
4676 case LFUN_BUFFER_WRITE_ALL: {
4677 Buffer * first = theBufferList().first();
4680 message(_("Saving all documents..."));
4681 // We cannot use a for loop as the buffer list cycles.
4684 if (!b->isClean()) {
4686 LYXERR(Debug::ACTION, "Saved " << b->absFileName());
4688 b = theBufferList().next(b);
4689 } while (b != first);
4690 dr.setMessage(_("All documents saved."));
4694 case LFUN_MASTER_BUFFER_FORALL: {
4698 FuncRequest funcToRun = lyxaction.lookupFunc(cmd.getLongArg(0));
4699 funcToRun.allowAsync(false);
4701 for (Buffer const * buf : doc_buffer->allRelatives()) {
4702 // Switch to other buffer view and resend cmd
4703 lyx::dispatch(FuncRequest(
4704 LFUN_BUFFER_SWITCH, buf->absFileName()));
4705 lyx::dispatch(funcToRun);
4708 lyx::dispatch(FuncRequest(
4709 LFUN_BUFFER_SWITCH, doc_buffer->absFileName()));
4713 case LFUN_BUFFER_EXTERNAL_MODIFICATION_CLEAR:
4714 LASSERT(doc_buffer, break);
4715 doc_buffer->clearExternalModification();
4718 case LFUN_BUFFER_CLOSE:
4722 case LFUN_BUFFER_CLOSE_ALL:
4726 case LFUN_DEVEL_MODE_TOGGLE:
4727 devel_mode_ = !devel_mode_;
4729 dr.setMessage(_("Developer mode is now enabled."));
4731 dr.setMessage(_("Developer mode is now disabled."));
4734 case LFUN_TOOLBAR_SET: {
4735 string const name = cmd.getArg(0);
4736 string const state = cmd.getArg(1);
4737 if (GuiToolbar * t = toolbar(name))
4742 case LFUN_TOOLBAR_TOGGLE: {
4743 string const name = cmd.getArg(0);
4744 if (GuiToolbar * t = toolbar(name))
4749 case LFUN_TOOLBAR_MOVABLE: {
4750 string const name = cmd.getArg(0);
4752 // toggle (all) toolbars movablility
4753 toolbarsMovable_ = !toolbarsMovable_;
4754 for (ToolbarInfo const & ti : guiApp->toolbars()) {
4755 GuiToolbar * tb = toolbar(ti.name);
4756 if (tb && tb->isMovable() != toolbarsMovable_)
4757 // toggle toolbar movablity if it does not fit lock
4758 // (all) toolbars positions state silent = true, since
4759 // status bar notifications are slow
4762 if (toolbarsMovable_)
4763 dr.setMessage(_("Toolbars unlocked."));
4765 dr.setMessage(_("Toolbars locked."));
4766 } else if (GuiToolbar * tb = toolbar(name))
4767 // toggle current toolbar movablity
4769 // update lock (all) toolbars positions
4770 updateLockToolbars();
4774 case LFUN_ICON_SIZE: {
4775 QSize size = d.iconSize(cmd.argument());
4777 dr.setMessage(bformat(_("Icon size set to %1$dx%2$d."),
4778 size.width(), size.height()));
4782 case LFUN_DIALOG_UPDATE: {
4783 string const name = to_utf8(cmd.argument());
4784 if (name == "prefs" || name == "document")
4785 updateDialog(name, string());
4786 else if (name == "paragraph")
4787 lyx::dispatch(FuncRequest(LFUN_PARAGRAPH_UPDATE));
4788 else if (currentBufferView()) {
4789 Inset * inset = currentBufferView()->editedInset(name);
4790 // Can only update a dialog connected to an existing inset
4792 // FIXME: get rid of this indirection; GuiView ask the inset
4793 // if he is kind enough to update itself...
4794 FuncRequest fr(LFUN_INSET_DIALOG_UPDATE, cmd.argument());
4795 //FIXME: pass DispatchResult here?
4796 inset->dispatch(currentBufferView()->cursor(), fr);
4802 case LFUN_DIALOG_TOGGLE: {
4803 FuncCode const func_code = isDialogVisible(cmd.getArg(0))
4804 ? LFUN_DIALOG_HIDE : LFUN_DIALOG_SHOW;
4805 dispatch(FuncRequest(func_code, cmd.argument()), dr);
4809 case LFUN_DIALOG_DISCONNECT_INSET:
4810 disconnectDialog(to_utf8(cmd.argument()));
4813 case LFUN_DIALOG_HIDE: {
4814 guiApp->hideDialogs(to_utf8(cmd.argument()), nullptr);
4818 case LFUN_DIALOG_SHOW: {
4819 string const name = cmd.getArg(0);
4820 string sdata = trim(to_utf8(cmd.argument()).substr(name.size()));
4822 if (name == "latexlog") {
4823 // getStatus checks that
4824 LASSERT(doc_buffer, break);
4825 Buffer::LogType type;
4826 string const logfile = doc_buffer->logName(&type);
4828 case Buffer::latexlog:
4831 case Buffer::buildlog:
4832 sdata = "literate ";
4835 sdata += Lexer::quoteString(logfile);
4836 showDialog("log", sdata);
4837 } else if (name == "vclog") {
4838 // getStatus checks that
4839 LASSERT(doc_buffer, break);
4840 string const sdata2 = "vc " +
4841 Lexer::quoteString(doc_buffer->lyxvc().getLogFile());
4842 showDialog("log", sdata2);
4843 } else if (name == "symbols") {
4844 sdata = bv->cursor().getEncoding()->name();
4846 showDialog("symbols", sdata);
4847 } else if (name == "findreplace") {
4848 sdata = to_utf8(bv->cursor().selectionAsString(false));
4849 showDialog(name, sdata);
4851 } else if (name == "prefs" && isFullScreen()) {
4852 lfunUiToggle("fullscreen");
4853 showDialog("prefs", sdata);
4855 showDialog(name, sdata);
4860 dr.setMessage(cmd.argument());
4863 case LFUN_UI_TOGGLE: {
4864 string arg = cmd.getArg(0);
4865 if (!lfunUiToggle(arg)) {
4866 docstring const msg = "ui-toggle " + _("%1$s unknown command!");
4867 dr.setMessage(bformat(msg, from_utf8(arg)));
4869 // Make sure the keyboard focus stays in the work area.
4874 case LFUN_VIEW_SPLIT: {
4875 LASSERT(doc_buffer, break);
4876 string const orientation = cmd.getArg(0);
4877 d.splitter_->setOrientation(orientation == "vertical"
4878 ? Qt::Vertical : Qt::Horizontal);
4879 TabWorkArea * twa = addTabWorkArea();
4880 GuiWorkArea * wa = twa->addWorkArea(*doc_buffer, *this);
4882 wa->bufferView().setCursor(bv->cursor());
4883 dr.screenUpdate(Update::ForceAll | Update::FitCursor);
4884 setCurrentWorkArea(wa);
4888 case LFUN_TAB_GROUP_NEXT:
4889 gotoNextTabWorkArea(NEXT);
4892 case LFUN_TAB_GROUP_PREVIOUS:
4893 gotoNextTabWorkArea(PREV);
4896 case LFUN_TAB_GROUP_CLOSE:
4897 if (TabWorkArea * twa = d.currentTabWorkArea()) {
4898 closeTabWorkArea(twa);
4899 d.current_work_area_ = nullptr;
4900 twa = d.currentTabWorkArea();
4901 // Switch to the next GuiWorkArea in the found TabWorkArea.
4903 // Make sure the work area is up to date.
4904 setCurrentWorkArea(twa->currentWorkArea());
4906 setCurrentWorkArea(nullptr);
4911 case LFUN_VIEW_CLOSE:
4912 if (TabWorkArea * twa = d.currentTabWorkArea()) {
4913 closeWorkArea(twa->currentWorkArea());
4914 d.current_work_area_ = nullptr;
4915 twa = d.currentTabWorkArea();
4916 // Switch to the next GuiWorkArea in the found TabWorkArea.
4918 // Make sure the work area is up to date.
4919 setCurrentWorkArea(twa->currentWorkArea());
4921 setCurrentWorkArea(nullptr);
4926 case LFUN_COMPLETION_INLINE:
4927 if (d.current_work_area_)
4928 d.current_work_area_->completer().showInline();
4931 case LFUN_COMPLETION_POPUP:
4932 if (d.current_work_area_)
4933 d.current_work_area_->completer().showPopup();
4938 if (d.current_work_area_)
4939 d.current_work_area_->completer().tab();
4942 case LFUN_COMPLETION_CANCEL:
4943 if (d.current_work_area_) {
4944 if (d.current_work_area_->completer().popupVisible())
4945 d.current_work_area_->completer().hidePopup();
4947 d.current_work_area_->completer().hideInline();
4951 case LFUN_COMPLETION_ACCEPT:
4952 if (d.current_work_area_)
4953 d.current_work_area_->completer().activate();
4956 case LFUN_BUFFER_ZOOM_IN:
4957 case LFUN_BUFFER_ZOOM_OUT:
4958 case LFUN_BUFFER_ZOOM: {
4959 zoom_ratio_ = zoomRatio(cmd, zoom_ratio_);
4961 // Actual zoom value: default zoom + fractional extra value
4962 int zoom = (int)(lyxrc.defaultZoom * zoom_ratio_);
4963 zoom = min(max(zoom, zoom_min_), zoom_max_);
4965 setCurrentZoom(zoom);
4967 dr.setMessage(bformat(_("Zoom level is now %1$d% (default value: %2$d%)"),
4968 lyxrc.currentZoom, lyxrc.defaultZoom));
4970 guiApp->fontLoader().update();
4971 // Regenerate instant previews
4972 if (lyxrc.preview != LyXRC::PREVIEW_OFF
4973 && doc_buffer && doc_buffer->loader())
4974 doc_buffer->loader()->refreshPreviews();
4975 dr.screenUpdate(Update::ForceAll | Update::FitCursor);
4979 case LFUN_VC_REGISTER:
4980 case LFUN_VC_RENAME:
4982 case LFUN_VC_CHECK_IN:
4983 case LFUN_VC_CHECK_OUT:
4984 case LFUN_VC_REPO_UPDATE:
4985 case LFUN_VC_LOCKING_TOGGLE:
4986 case LFUN_VC_REVERT:
4987 case LFUN_VC_UNDO_LAST:
4988 case LFUN_VC_COMMAND:
4989 case LFUN_VC_COMPARE:
4990 dispatchVC(cmd, dr);
4993 case LFUN_SERVER_GOTO_FILE_ROW:
4994 if(goToFileRow(to_utf8(cmd.argument())))
4995 dr.screenUpdate(Update::Force | Update::FitCursor);
4998 case LFUN_LYX_ACTIVATE:
5002 case LFUN_WINDOW_RAISE:
5008 case LFUN_FORWARD_SEARCH: {
5009 // it seems safe to assume we have a document buffer, since
5010 // getStatus wants one.
5011 LASSERT(doc_buffer, break);
5012 Buffer const * doc_master = doc_buffer->masterBuffer();
5013 FileName const path(doc_master->temppath());
5014 string const texname = doc_master->isChild(doc_buffer)
5015 ? DocFileName(changeExtension(
5016 doc_buffer->absFileName(),
5017 "tex")).mangledFileName()
5018 : doc_buffer->latexName();
5019 string const fulltexname =
5020 support::makeAbsPath(texname, doc_master->temppath()).absFileName();
5021 string const mastername =
5022 removeExtension(doc_master->latexName());
5023 FileName const dviname(addName(path.absFileName(),
5024 addExtension(mastername, "dvi")));
5025 FileName const pdfname(addName(path.absFileName(),
5026 addExtension(mastername, "pdf")));
5027 bool const have_dvi = dviname.exists();
5028 bool const have_pdf = pdfname.exists();
5029 if (!have_dvi && !have_pdf) {
5030 dr.setMessage(_("Please, preview the document first."));
5033 bool const goto_dvi = have_dvi && !lyxrc.forward_search_dvi.empty();
5034 bool const goto_pdf = have_pdf && !lyxrc.forward_search_pdf.empty();
5035 string outname = dviname.onlyFileName();
5036 string command = lyxrc.forward_search_dvi;
5037 if ((!goto_dvi || goto_pdf) &&
5038 pdfname.lastModified() > dviname.lastModified()) {
5039 outname = pdfname.onlyFileName();
5040 command = lyxrc.forward_search_pdf;
5043 DocIterator cur = bv->cursor();
5044 int row = doc_buffer->texrow().rowFromDocIterator(cur).first;
5045 LYXERR(Debug::ACTION, "Forward search: row:" << row
5047 if (row == -1 || command.empty()) {
5048 dr.setMessage(_("Couldn't proceed."));
5051 string texrow = convert<string>(row);
5053 command = subst(command, "$$n", texrow);
5054 command = subst(command, "$$f", fulltexname);
5055 command = subst(command, "$$t", texname);
5056 command = subst(command, "$$o", outname);
5058 volatile PathChanger p(path);
5060 one.startscript(Systemcall::DontWait, command);
5064 case LFUN_SPELLING_CONTINUOUSLY:
5065 lyxrc.spellcheck_continuously = !lyxrc.spellcheck_continuously;
5066 dr.screenUpdate(Update::Force);
5069 case LFUN_CITATION_OPEN: {
5070 LASSERT(doc_buffer, break);
5071 frontend::showTarget(argument, *doc_buffer);
5076 // The LFUN must be for one of BufferView, Buffer or Cursor;
5078 dispatchToBufferView(cmd, dr);
5082 // Need to update bv because many LFUNs here might have destroyed it
5083 bv = currentBufferView();
5085 // Clear non-empty selections
5086 // (e.g. from a "char-forward-select" followed by "char-backward-select")
5088 Cursor & cur = bv->cursor();
5089 if ((cur.selection() && cur.selBegin() == cur.selEnd())) {
5090 cur.clearSelection();
5096 bool GuiView::lfunUiToggle(string const & ui_component)
5098 if (ui_component == "scrollbar") {
5099 // hide() is of no help
5100 if (d.current_work_area_->verticalScrollBarPolicy() ==
5101 Qt::ScrollBarAlwaysOff)
5103 d.current_work_area_->setVerticalScrollBarPolicy(
5104 Qt::ScrollBarAsNeeded);
5106 d.current_work_area_->setVerticalScrollBarPolicy(
5107 Qt::ScrollBarAlwaysOff);
5108 } else if (ui_component == "statusbar") {
5109 statusBar()->setVisible(!statusBar()->isVisible());
5110 } else if (ui_component == "menubar") {
5111 menuBar()->setVisible(!menuBar()->isVisible());
5112 } else if (ui_component == "zoomlevel") {
5113 zoom_value_->setVisible(!zoom_value_->isVisible());
5114 } else if (ui_component == "zoomslider") {
5115 zoom_widget_->setVisible(!zoom_widget_->isVisible());
5116 } else if (ui_component == "statistics-w") {
5117 word_count_enabled_ = !word_count_enabled_;
5120 } else if (ui_component == "statistics-cb") {
5121 char_count_enabled_ = !char_count_enabled_;
5124 } else if (ui_component == "statistics-c") {
5125 char_nb_count_enabled_ = !char_nb_count_enabled_;
5128 } else if (ui_component == "frame") {
5129 int const l = contentsMargins().left();
5131 //are the frames in default state?
5132 d.current_work_area_->setFrameStyle(QFrame::NoFrame);
5134 #if QT_VERSION > 0x050903
5135 setAttribute(Qt::WA_ContentsMarginsRespectsSafeArea, false);
5137 setContentsMargins(-2, -2, -2, -2);
5139 #if QT_VERSION > 0x050903
5140 setAttribute(Qt::WA_ContentsMarginsRespectsSafeArea, true);
5142 setContentsMargins(0, 0, 0, 0);
5145 if (ui_component == "fullscreen") {
5149 stat_counts_->setVisible(statsEnabled());
5154 void GuiView::cancelExport()
5156 Systemcall::killscript();
5157 // stop busy signal immediately so that in the subsequent
5158 // "Export canceled" prompt the status bar icons are accurate.
5159 Q_EMIT scriptKilled();
5163 void GuiView::toggleFullScreen()
5165 setWindowState(windowState() ^ Qt::WindowFullScreen);
5169 Buffer const * GuiView::updateInset(Inset const * inset)
5174 Buffer const * inset_buffer = &(inset->buffer());
5176 for (int i = 0; i != d.splitter_->count(); ++i) {
5177 GuiWorkArea * wa = d.tabWorkArea(i)->currentWorkArea();
5180 Buffer const * buffer = &(wa->bufferView().buffer());
5181 if (inset_buffer == buffer)
5182 wa->scheduleRedraw(true);
5184 return inset_buffer;
5188 void GuiView::restartCaret()
5190 /* When we move around, or type, it's nice to be able to see
5191 * the caret immediately after the keypress.
5193 if (d.current_work_area_)
5194 d.current_work_area_->startBlinkingCaret();
5196 // Take this occasion to update the other GUI elements.
5202 void GuiView::updateCompletion(Cursor & cur, bool start, bool keep)
5204 if (d.current_work_area_)
5205 d.current_work_area_->completer().updateVisibility(cur, start, keep);
5210 // This list should be kept in sync with the list of insets in
5211 // src/insets/Inset.cpp. I.e., if a dialog goes with an inset, the
5212 // dialog should have the same name as the inset.
5213 // Changes should be also recorded in LFUN_DIALOG_SHOW doxygen
5214 // docs in LyXAction.cpp.
5216 char const * const dialognames[] = {
5218 "aboutlyx", "bibitem", "bibtex", "box", "branch", "changes", "character",
5219 "citation", "compare", "comparehistory", "counter", "document", "errorlist", "ert",
5220 "external", "file", "findreplace", "findreplaceadv", "float", "graphics",
5221 "href", "include", "index", "index_print", "info", "listings", "label", "line",
5222 "log", "lyxfiles", "mathdelimiter", "mathmatrix", "mathspace", "nomenclature",
5223 "nomencl_print", "note", "paragraph", "phantom", "prefs", "ref",
5224 "sendto", "space", "spellchecker", "symbols", "tabular", "tabularcreate",
5225 "thesaurus", "texinfo", "toc", "view-source", "vspace", "wrap", "progress"};
5227 char const * const * const end_dialognames =
5228 dialognames + (sizeof(dialognames) / sizeof(char *));
5232 cmpCStr(char const * name) : name_(name) {}
5233 bool operator()(char const * other) {
5234 return strcmp(other, name_) == 0;
5241 bool isValidName(string const & name)
5243 return find_if(dialognames, end_dialognames,
5244 cmpCStr(name.c_str())) != end_dialognames;
5250 void GuiView::resetDialogs()
5252 // Make sure that no LFUN uses any GuiView.
5253 guiApp->setCurrentView(nullptr);
5257 constructToolbars();
5258 guiApp->menus().fillMenuBar(menuBar(), this, false);
5259 d.layout_->updateContents(true);
5260 // Now update controls with current buffer.
5261 guiApp->setCurrentView(this);
5267 void GuiView::flatGroupBoxes(const QObject * widget, bool flag)
5269 for (QObject * child: widget->children()) {
5270 if (child->inherits("QGroupBox")) {
5271 QGroupBox * box = (QGroupBox*) child;
5274 flatGroupBoxes(child, flag);
5280 Dialog * GuiView::find(string const & name, bool hide_it) const
5282 if (!isValidName(name))
5285 map<string, DialogPtr>::iterator it = d.dialogs_.find(name);
5287 if (it != d.dialogs_.end()) {
5289 it->second->hideView();
5290 return it->second.get();
5296 Dialog * GuiView::findOrBuild(string const & name, bool hide_it)
5298 Dialog * dialog = find(name, hide_it);
5299 if (dialog != nullptr)
5302 dialog = build(name);
5305 d.dialogs_[name].reset(dialog);
5306 // Force a uniform style for group boxes
5307 // On Mac non-flat works better, on Linux flat is standard
5308 flatGroupBoxes(dialog->asQWidget(), guiApp->platformName() != "cocoa");
5309 if (lyxrc.allow_geometry_session)
5310 dialog->restoreSession();
5318 void GuiView::showDialog(string const & name, string const & sdata,
5321 triggerShowDialog(toqstr(name), toqstr(sdata), inset);
5325 void GuiView::doShowDialog(QString const & qname, QString const & qdata,
5331 const string name = fromqstr(qname);
5332 const string sdata = fromqstr(qdata);
5336 Dialog * dialog = findOrBuild(name, false);
5338 bool const visible = dialog->isVisibleView();
5339 dialog->showData(sdata);
5340 if (currentBufferView())
5341 currentBufferView()->editInset(name, inset);
5342 // We only set the focus to the new dialog if it was not yet
5343 // visible in order not to change the existing previous behaviour
5345 // activateWindow is needed for floating dockviews
5346 dialog->asQWidget()->raise();
5347 dialog->asQWidget()->activateWindow();
5348 if (dialog->wantInitialFocus())
5349 dialog->asQWidget()->setFocus();
5353 catch (ExceptionMessage const &) {
5361 bool GuiView::isDialogVisible(string const & name) const
5363 map<string, DialogPtr>::const_iterator it = d.dialogs_.find(name);
5364 if (it == d.dialogs_.end())
5366 return it->second.get()->isVisibleView() && !it->second.get()->isClosing();
5370 void GuiView::hideDialog(string const & name, Inset * inset)
5372 map<string, DialogPtr>::const_iterator it = d.dialogs_.find(name);
5373 if (it == d.dialogs_.end())
5377 if (!currentBufferView())
5379 if (inset != currentBufferView()->editedInset(name))
5383 Dialog * const dialog = it->second.get();
5384 if (dialog->isVisibleView())
5386 if (currentBufferView())
5387 currentBufferView()->editInset(name, nullptr);
5391 void GuiView::disconnectDialog(string const & name)
5393 if (!isValidName(name))
5395 if (currentBufferView())
5396 currentBufferView()->editInset(name, nullptr);
5400 void GuiView::hideAll() const
5402 for(auto const & dlg_p : d.dialogs_)
5403 dlg_p.second->hideView();
5407 void GuiView::updateDialogs()
5409 for(auto const & dlg_p : d.dialogs_) {
5410 Dialog * dialog = dlg_p.second.get();
5412 if (dialog->needBufferOpen() && !documentBufferView())
5413 hideDialog(fromqstr(dialog->name()), nullptr);
5414 else if (dialog->isVisibleView())
5415 dialog->checkStatus();
5423 Dialog * GuiView::build(string const & name)
5425 return createDialog(*this, name);
5429 SEMenu::SEMenu(QWidget * parent)
5431 QAction * action = addAction(qt_("Disable Shell Escape"));
5432 connect(action, SIGNAL(triggered()),
5433 parent, SLOT(disableShellEscape()));
5437 void PressableSvgWidget::mousePressEvent(QMouseEvent * event)
5439 if (event->button() == Qt::LeftButton) {
5444 } // namespace frontend
5447 #include "moc_GuiView.cpp"