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_->insertWidget(d.splitter_->indexOf(d.currentTabWorkArea()) + 1,
1885 d.stack_widget_->setCurrentWidget(d.splitter_);
1890 GuiWorkArea const * GuiView::currentWorkArea() const
1892 return d.current_work_area_;
1896 GuiWorkArea * GuiView::currentWorkArea()
1898 return d.current_work_area_;
1902 GuiWorkArea const * GuiView::currentMainWorkArea() const
1904 if (!d.currentTabWorkArea())
1906 return d.currentTabWorkArea()->currentWorkArea();
1910 GuiWorkArea * GuiView::currentMainWorkArea()
1912 if (!d.currentTabWorkArea())
1914 return d.currentTabWorkArea()->currentWorkArea();
1918 void GuiView::setCurrentWorkArea(GuiWorkArea * wa)
1920 LYXERR(Debug::DEBUG, "Setting current wa: " << wa << endl);
1922 d.current_work_area_ = nullptr;
1924 Q_EMIT bufferViewChanged();
1928 // FIXME: I've no clue why this is here and why it accesses
1929 // theGuiApp()->currentView, which might be 0 (bug 6464).
1930 // See also 27525 (vfr).
1931 if (theGuiApp()->currentView() == this
1932 && theGuiApp()->currentView()->currentWorkArea() == wa)
1935 if (currentBufferView())
1936 cap::saveSelection(currentBufferView()->cursor());
1938 theGuiApp()->setCurrentView(this);
1939 d.current_work_area_ = wa;
1941 // We need to reset this now, because it will need to be
1942 // right if the tabWorkArea gets reset in the for loop. We
1943 // will change it back if we aren't in that case.
1944 GuiWorkArea * const old_cmwa = d.current_main_work_area_;
1945 d.current_main_work_area_ = wa;
1947 for (int i = 0; i != d.splitter_->count(); ++i) {
1948 if (d.tabWorkArea(i)->setCurrentWorkArea(wa)) {
1949 LYXERR(Debug::DEBUG, "Current wa: " << currentWorkArea()
1950 << ", Current main wa: " << currentMainWorkArea());
1955 d.current_main_work_area_ = old_cmwa;
1957 LYXERR(Debug::DEBUG, "This is not a tabbed wa");
1958 on_currentWorkAreaChanged(wa);
1959 BufferView & bv = wa->bufferView();
1960 bv.cursor().fixIfBroken();
1962 wa->setUpdatesEnabled(true);
1963 LYXERR(Debug::DEBUG, "Current wa: " << currentWorkArea() << ", Current main wa: " << currentMainWorkArea());
1967 void GuiView::removeWorkArea(GuiWorkArea * wa)
1969 LASSERT(wa, return);
1970 if (wa == d.current_work_area_) {
1972 disconnectBufferView();
1973 d.current_work_area_ = nullptr;
1974 d.current_main_work_area_ = nullptr;
1977 bool found_twa = false;
1978 for (int i = 0; i != d.splitter_->count(); ++i) {
1979 TabWorkArea * twa = d.tabWorkArea(i);
1980 if (twa->removeWorkArea(wa)) {
1981 // Found in this tab group, and deleted the GuiWorkArea.
1983 if (twa->count() != 0) {
1984 if (d.current_work_area_ == nullptr)
1985 // This means that we are closing the current GuiWorkArea, so
1986 // switch to the next GuiWorkArea in the found TabWorkArea.
1987 setCurrentWorkArea(twa->currentWorkArea());
1989 // No more WorkAreas in this tab group, so delete it.
1996 // It is not a tabbed work area (i.e., the search work area), so it
1997 // should be deleted by other means.
1998 LASSERT(found_twa, return);
2000 if (d.current_work_area_ == nullptr) {
2001 if (d.splitter_->count() != 0) {
2002 TabWorkArea * twa = d.currentTabWorkArea();
2003 setCurrentWorkArea(twa->currentWorkArea());
2005 // No more work areas, switch to the background widget.
2006 setCurrentWorkArea(nullptr);
2012 bool GuiView::hasVisibleWorkArea(GuiWorkArea * wa) const
2014 for (int i = 0; i < d.splitter_->count(); ++i)
2015 if (d.tabWorkArea(i)->currentWorkArea() == wa)
2018 FindAndReplace * fr = static_cast<FindAndReplace*>(find("findreplaceadv", false));
2019 return fr->isVisible() && fr->hasWorkArea(wa);
2023 LayoutBox * GuiView::getLayoutDialog() const
2029 void GuiView::updateLayoutList()
2032 d.layout_->updateContents(false);
2036 void GuiView::updateToolbars()
2038 if (d.current_work_area_) {
2040 if (d.current_work_area_->bufferView().cursor().inMathed()
2041 && !d.current_work_area_->bufferView().cursor().inRegexped())
2042 context |= Toolbars::MATH;
2043 if (lyx::getStatus(FuncRequest(LFUN_LAYOUT_TABULAR)).enabled())
2044 context |= Toolbars::TABLE;
2045 if (currentBufferView()->buffer().areChangesPresent()
2046 || (lyx::getStatus(FuncRequest(LFUN_CHANGES_TRACK)).enabled()
2047 && lyx::getStatus(FuncRequest(LFUN_CHANGES_TRACK)).onOff(true))
2048 || (lyx::getStatus(FuncRequest(LFUN_CHANGES_OUTPUT)).enabled()
2049 && lyx::getStatus(FuncRequest(LFUN_CHANGES_OUTPUT)).onOff(true)))
2050 context |= Toolbars::REVIEW;
2051 if (lyx::getStatus(FuncRequest(LFUN_IN_MATHMACROTEMPLATE)).enabled())
2052 context |= Toolbars::MATHMACROTEMPLATE;
2053 if (lyx::getStatus(FuncRequest(LFUN_IN_IPA)).enabled())
2054 context |= Toolbars::IPA;
2055 if (command_execute_)
2056 context |= Toolbars::MINIBUFFER;
2057 if (minibuffer_focus_) {
2058 context |= Toolbars::MINIBUFFER_FOCUS;
2059 minibuffer_focus_ = false;
2062 for (auto const & tb_p : d.toolbars_)
2063 tb_p.second->update(context);
2065 for (auto const & tb_p : d.toolbars_)
2066 tb_p.second->update();
2070 void GuiView::refillToolbars()
2072 DynamicMenuButton::resetIconCache();
2073 for (auto const & tb_p : d.toolbars_)
2074 tb_p.second->refill();
2078 void GuiView::setBuffer(Buffer * newBuffer, bool switch_to)
2080 LYXERR(Debug::DEBUG, "Setting buffer: " << newBuffer << endl);
2081 LASSERT(newBuffer, return);
2083 GuiWorkArea * wa = workArea(*newBuffer);
2084 if (wa == nullptr) {
2086 newBuffer->masterBuffer()->updateBuffer();
2088 wa = addWorkArea(*newBuffer);
2089 // scroll to the position when the BufferView was last closed
2090 if (lyxrc.use_lastfilepos) {
2091 LastFilePosSection::FilePos filepos =
2092 theSession().lastFilePos().load(newBuffer->fileName());
2093 wa->bufferView().moveToPosition(filepos.pit, filepos.pos, 0, 0);
2096 //Disconnect the old buffer...there's no new one.
2099 connectBuffer(*newBuffer);
2100 connectBufferView(wa->bufferView());
2102 setCurrentWorkArea(wa);
2106 void GuiView::connectBuffer(Buffer & buf)
2108 buf.setGuiDelegate(this);
2112 void GuiView::disconnectBuffer()
2114 if (d.current_work_area_)
2115 d.current_work_area_->bufferView().buffer().setGuiDelegate(nullptr);
2119 void GuiView::connectBufferView(BufferView & bv)
2121 bv.setGuiDelegate(this);
2125 void GuiView::disconnectBufferView()
2127 if (d.current_work_area_)
2128 d.current_work_area_->bufferView().setGuiDelegate(nullptr);
2132 void GuiView::errors(string const & error_type, bool from_master)
2134 BufferView const * const bv = currentBufferView();
2138 ErrorList const & el = from_master ?
2139 bv->buffer().masterBuffer()->errorList(error_type) :
2140 bv->buffer().errorList(error_type);
2145 string err = error_type;
2147 err = "from_master|" + error_type;
2148 showDialog("errorlist", err);
2152 void GuiView::updateTocItem(string const & type, DocIterator const & dit)
2154 d.toc_models_.updateItem(toqstr(type), dit);
2158 void GuiView::structureChanged()
2160 // This is called from the Buffer, which has no way to ensure that cursors
2161 // in BufferView remain valid.
2162 if (documentBufferView())
2163 documentBufferView()->cursor().sanitize();
2164 // FIXME: This is slightly expensive, though less than the tocBackend update
2165 // (#9880). This also resets the view in the Toc Widget (#6675).
2166 d.toc_models_.reset(documentBufferView());
2167 // Navigator needs more than a simple update in this case. It needs to be
2169 updateDialog("toc", "");
2173 void GuiView::updateDialog(string const & name, string const & sdata)
2175 if (!isDialogVisible(name))
2178 map<string, DialogPtr>::const_iterator it = d.dialogs_.find(name);
2179 if (it == d.dialogs_.end())
2182 Dialog * const dialog = it->second.get();
2183 if (dialog->isVisibleView())
2184 dialog->initialiseParams(sdata);
2188 BufferView * GuiView::documentBufferView()
2190 return currentMainWorkArea()
2191 ? ¤tMainWorkArea()->bufferView()
2196 BufferView const * GuiView::documentBufferView() const
2198 return currentMainWorkArea()
2199 ? ¤tMainWorkArea()->bufferView()
2204 BufferView * GuiView::currentBufferView()
2206 return d.current_work_area_ ? &d.current_work_area_->bufferView() : nullptr;
2210 BufferView const * GuiView::currentBufferView() const
2212 return d.current_work_area_ ? &d.current_work_area_->bufferView() : nullptr;
2216 docstring GuiView::GuiViewPrivate::autosaveAndDestroy(
2217 Buffer const * orig, Buffer * clone)
2219 bool const success = clone->autoSave();
2221 busyBuffers.remove(orig);
2223 ? _("Automatic save done.")
2224 : _("Automatic save failed!");
2228 void GuiView::autoSave()
2230 LYXERR(Debug::INFO, "Running autoSave()");
2232 Buffer * buffer = documentBufferView()
2233 ? &documentBufferView()->buffer() : nullptr;
2235 resetAutosaveTimers();
2239 GuiViewPrivate::busyBuffers.insert(buffer);
2240 QFuture<docstring> f = QtConcurrent::run(GuiViewPrivate::autosaveAndDestroy,
2241 buffer, buffer->cloneBufferOnly());
2242 d.autosave_watcher_.setFuture(f);
2243 resetAutosaveTimers();
2247 void GuiView::resetAutosaveTimers()
2250 d.autosave_timeout_.restart();
2256 double zoomRatio(FuncRequest const & cmd, double const zr)
2258 if (cmd.argument().empty()) {
2259 if (cmd.action() == LFUN_BUFFER_ZOOM)
2261 else if (cmd.action() == LFUN_BUFFER_ZOOM_IN)
2263 else // cmd.action() == LFUN_BUFFER_ZOOM_OUT
2266 if (cmd.action() == LFUN_BUFFER_ZOOM)
2267 return convert<int>(cmd.argument()) / double(lyxrc.defaultZoom);
2268 else if (cmd.action() == LFUN_BUFFER_ZOOM_IN)
2269 return zr + convert<int>(cmd.argument()) / 100.0;
2270 else // cmd.action() == LFUN_BUFFER_ZOOM_OUT
2271 return zr - convert<int>(cmd.argument()) / 100.0;
2278 bool GuiView::getStatus(FuncRequest const & cmd, FuncStatus & flag)
2281 Buffer * buf = currentBufferView()
2282 ? ¤tBufferView()->buffer() : nullptr;
2283 Buffer * doc_buffer = documentBufferView()
2284 ? &(documentBufferView()->buffer()) : nullptr;
2287 /* In LyX/Mac, when a dialog is open, the menus of the
2288 application can still be accessed without giving focus to
2289 the main window. In this case, we want to disable the menu
2290 entries that are buffer-related.
2291 This code must not be used on Linux and Windows, since it
2292 would disable buffer-related entries when hovering over the
2293 menu (see bug #9574).
2295 if (cmd.origin() == FuncRequest::MENU && !hasFocus()) {
2301 // Check whether we need a buffer
2302 if (!lyxaction.funcHasFlag(cmd.action(), LyXAction::NoBuffer) && !buf) {
2303 // no, exit directly
2304 flag.message(from_utf8(N_("Command not allowed with"
2305 "out any document open")));
2306 flag.setEnabled(false);
2310 if (cmd.origin() == FuncRequest::TOC) {
2311 GuiToc * toc = static_cast<GuiToc*>(findOrBuild("toc", false));
2312 if (!toc || !toc->getStatus(documentBufferView()->cursor(), cmd, flag))
2313 flag.setEnabled(false);
2317 switch(cmd.action()) {
2318 case LFUN_BUFFER_IMPORT:
2321 case LFUN_MASTER_BUFFER_EXPORT:
2323 && (doc_buffer->parent() != nullptr
2324 || doc_buffer->hasChildren())
2325 && !d.processing_thread_watcher_.isRunning()
2326 // this launches a dialog, which would be in the wrong Buffer
2327 && !(::lyx::operator==(cmd.argument(), "custom"));
2330 case LFUN_MASTER_BUFFER_UPDATE:
2331 case LFUN_MASTER_BUFFER_VIEW:
2333 && (doc_buffer->parent() != nullptr
2334 || doc_buffer->hasChildren())
2335 && !d.processing_thread_watcher_.isRunning();
2338 case LFUN_BUFFER_UPDATE:
2339 case LFUN_BUFFER_VIEW: {
2340 if (!doc_buffer || d.processing_thread_watcher_.isRunning()) {
2344 string format = to_utf8(cmd.argument());
2345 if (cmd.argument().empty())
2346 format = doc_buffer->params().getDefaultOutputFormat();
2347 enable = doc_buffer->params().isExportable(format, true);
2351 case LFUN_BUFFER_RELOAD:
2352 enable = doc_buffer && !doc_buffer->isUnnamed()
2353 && doc_buffer->fileName().exists()
2354 && (!doc_buffer->isClean() || doc_buffer->notifiesExternalModification());
2357 case LFUN_BUFFER_RESET_EXPORT:
2358 enable = doc_buffer != nullptr;
2361 case LFUN_BUFFER_CHILD_OPEN:
2362 enable = doc_buffer != nullptr;
2365 case LFUN_MASTER_BUFFER_FORALL: {
2366 if (doc_buffer == nullptr) {
2367 flag.message(from_utf8(N_("Command not allowed without a buffer open")));
2371 FuncRequest const cmdToPass = lyxaction.lookupFunc(cmd.getLongArg(0));
2372 if (cmdToPass.action() == LFUN_UNKNOWN_ACTION) {
2373 flag.message(from_utf8(N_("Invalid argument of master-buffer-forall")));
2378 for (Buffer * buf : doc_buffer->allRelatives()) {
2379 GuiWorkArea * wa = workArea(*buf);
2382 if (wa->bufferView().getStatus(cmdToPass, flag)) {
2383 enable = flag.enabled();
2390 case LFUN_BUFFER_WRITE:
2391 enable = doc_buffer && (doc_buffer->isUnnamed()
2392 || (!doc_buffer->isClean()
2393 || cmd.argument() == "force"));
2396 //FIXME: This LFUN should be moved to GuiApplication.
2397 case LFUN_BUFFER_WRITE_ALL: {
2398 // We enable the command only if there are some modified buffers
2399 Buffer * first = theBufferList().first();
2404 // We cannot use a for loop as the buffer list is a cycle.
2406 if (!b->isClean()) {
2410 b = theBufferList().next(b);
2411 } while (b != first);
2415 case LFUN_BUFFER_EXTERNAL_MODIFICATION_CLEAR:
2416 enable = doc_buffer && doc_buffer->notifiesExternalModification();
2419 case LFUN_BUFFER_EXPORT: {
2420 if (!doc_buffer || d.processing_thread_watcher_.isRunning()) {
2424 return doc_buffer->getStatus(cmd, flag);
2427 case LFUN_BUFFER_EXPORT_AS:
2428 if (!doc_buffer || d.processing_thread_watcher_.isRunning()) {
2433 case LFUN_BUFFER_WRITE_AS:
2434 case LFUN_BUFFER_WRITE_AS_TEMPLATE:
2435 enable = doc_buffer != nullptr;
2438 case LFUN_EXPORT_CANCEL:
2439 enable = d.processing_thread_watcher_.isRunning();
2442 case LFUN_BUFFER_CLOSE:
2443 case LFUN_VIEW_CLOSE:
2444 enable = doc_buffer != nullptr;
2447 case LFUN_BUFFER_CLOSE_ALL:
2448 enable = theBufferList().last() != theBufferList().first();
2451 case LFUN_BUFFER_CHKTEX: {
2452 // hide if we have no checktex command
2453 if (lyxrc.chktex_command.empty()) {
2454 flag.setUnknown(true);
2458 if (!doc_buffer || !doc_buffer->params().isLatex()
2459 || d.processing_thread_watcher_.isRunning()) {
2460 // grey out, don't hide
2468 case LFUN_CHANGES_TRACK: {
2473 return doc_buffer->getStatus(cmd, flag);
2476 case LFUN_VIEW_SPLIT:
2477 if (cmd.getArg(0) == "vertical")
2478 enable = doc_buffer && (d.splitter_->count() == 1 ||
2479 d.splitter_->orientation() == Qt::Vertical);
2481 enable = doc_buffer && (d.splitter_->count() == 1 ||
2482 d.splitter_->orientation() == Qt::Horizontal);
2485 case LFUN_TAB_GROUP_NEXT:
2486 case LFUN_TAB_GROUP_PREVIOUS:
2487 enable = (d.splitter_->count() > 1);
2490 case LFUN_TAB_GROUP_CLOSE:
2491 enable = d.tabWorkAreaCount() > 1;
2494 case LFUN_DEVEL_MODE_TOGGLE:
2495 flag.setOnOff(devel_mode_);
2498 case LFUN_TOOLBAR_SET: {
2499 string const name = cmd.getArg(0);
2500 string const state = cmd.getArg(1);
2501 if (name.empty() || state.empty()) {
2503 docstring const msg =
2504 _("Function toolbar-set requires two arguments!");
2508 if (state != "on" && state != "off" && state != "auto") {
2510 docstring const msg =
2511 bformat(_("Invalid argument \"%1$s\" to function toolbar-set!"),
2516 if (GuiToolbar * t = toolbar(name)) {
2517 bool const autovis = t->visibility() & Toolbars::AUTO;
2519 flag.setOnOff(t->isVisible() && !autovis);
2520 else if (state == "off")
2521 flag.setOnOff(!t->isVisible() && !autovis);
2522 else if (state == "auto")
2523 flag.setOnOff(autovis);
2526 docstring const msg =
2527 bformat(_("Unknown toolbar \"%1$s\""), from_utf8(name));
2533 case LFUN_TOOLBAR_TOGGLE: {
2534 string const name = cmd.getArg(0);
2535 if (GuiToolbar * t = toolbar(name))
2536 flag.setOnOff(t->isVisible());
2539 docstring const msg =
2540 bformat(_("Unknown toolbar \"%1$s\""), from_utf8(name));
2546 case LFUN_TOOLBAR_MOVABLE: {
2547 string const name = cmd.getArg(0);
2548 // use negation since locked == !movable
2550 // toolbar name * locks all toolbars
2551 flag.setOnOff(!toolbarsMovable_);
2552 else if (GuiToolbar * t = toolbar(name))
2553 flag.setOnOff(!(t->isMovable()));
2556 docstring const msg =
2557 bformat(_("Unknown toolbar \"%1$s\""), from_utf8(name));
2563 case LFUN_ICON_SIZE:
2564 flag.setOnOff(d.iconSize(cmd.argument()) == iconSize());
2567 case LFUN_DROP_LAYOUTS_CHOICE:
2568 enable = buf != nullptr;
2571 case LFUN_UI_TOGGLE:
2572 if (cmd.argument() == "zoomlevel") {
2573 flag.setOnOff(zoom_value_ ? zoom_value_->isVisible() : false);
2574 } else if (cmd.argument() == "zoomslider") {
2575 flag.setOnOff(zoom_widget_ ? zoom_widget_->isVisible() : false);
2576 } else if (cmd.argument() == "statistics-w") {
2577 flag.setOnOff(word_count_enabled_);
2578 } else if (cmd.argument() == "statistics-cb") {
2579 flag.setOnOff(char_count_enabled_);
2580 } else if (cmd.argument() == "statistics-c") {
2581 flag.setOnOff(char_nb_count_enabled_);
2583 flag.setOnOff(isFullScreen());
2586 case LFUN_DIALOG_DISCONNECT_INSET:
2589 case LFUN_DIALOG_HIDE:
2590 // FIXME: should we check if the dialog is shown?
2593 case LFUN_DIALOG_TOGGLE:
2594 flag.setOnOff(isDialogVisible(cmd.getArg(0)));
2597 case LFUN_DIALOG_SHOW: {
2598 string const name = cmd.getArg(0);
2600 enable = name == "aboutlyx"
2601 || name == "file" //FIXME: should be removed.
2602 || name == "lyxfiles"
2604 || name == "texinfo"
2605 || name == "progress"
2606 || name == "compare";
2607 else if (name == "character" || name == "symbols"
2608 || name == "mathdelimiter" || name == "mathmatrix") {
2609 if (!buf || buf->isReadonly())
2612 Cursor const & cur = currentBufferView()->cursor();
2613 enable = !(cur.inTexted() && cur.paragraph().isPassThru());
2616 else if (name == "latexlog")
2617 enable = FileName(doc_buffer->logName()).isReadableFile();
2618 else if (name == "spellchecker")
2619 enable = theSpellChecker()
2620 && !doc_buffer->text().empty();
2621 else if (name == "vclog")
2622 enable = doc_buffer->lyxvc().inUse();
2626 case LFUN_DIALOG_UPDATE: {
2627 string const name = cmd.getArg(0);
2629 enable = name == "prefs";
2633 case LFUN_COMMAND_EXECUTE:
2635 case LFUN_MENU_OPEN:
2636 // Nothing to check.
2639 case LFUN_COMPLETION_INLINE:
2640 if (!d.current_work_area_
2641 || !d.current_work_area_->completer().inlinePossible(
2642 currentBufferView()->cursor()))
2646 case LFUN_COMPLETION_POPUP:
2647 if (!d.current_work_area_
2648 || !d.current_work_area_->completer().popupPossible(
2649 currentBufferView()->cursor()))
2654 if (!d.current_work_area_
2655 || !d.current_work_area_->completer().inlinePossible(
2656 currentBufferView()->cursor()))
2660 case LFUN_COMPLETION_ACCEPT:
2661 if (!d.current_work_area_
2662 || (!d.current_work_area_->completer().popupVisible()
2663 && !d.current_work_area_->completer().inlineVisible()
2664 && !d.current_work_area_->completer().completionAvailable()))
2668 case LFUN_COMPLETION_CANCEL:
2669 if (!d.current_work_area_
2670 || (!d.current_work_area_->completer().popupVisible()
2671 && !d.current_work_area_->completer().inlineVisible()))
2675 case LFUN_BUFFER_ZOOM_OUT:
2676 case LFUN_BUFFER_ZOOM_IN:
2677 case LFUN_BUFFER_ZOOM: {
2678 int const zoom = (int)(lyxrc.defaultZoom * zoomRatio(cmd, zoom_ratio_));
2679 if (zoom < zoom_min_) {
2680 docstring const msg =
2681 bformat(_("Zoom level cannot be less than %1$d%."), zoom_min_);
2684 } else if (zoom > zoom_max_) {
2685 docstring const msg =
2686 bformat(_("Zoom level cannot be more than %1$d%."), zoom_max_);
2690 enable = doc_buffer;
2695 case LFUN_BUFFER_MOVE_NEXT:
2696 case LFUN_BUFFER_MOVE_PREVIOUS:
2697 // we do not cycle when moving
2698 case LFUN_BUFFER_NEXT:
2699 case LFUN_BUFFER_PREVIOUS:
2700 // because we cycle, it doesn't matter whether on first or last
2701 enable = (d.currentTabWorkArea()->count() > 1);
2703 case LFUN_BUFFER_SWITCH:
2704 // toggle on the current buffer, but do not toggle off
2705 // the other ones (is that a good idea?)
2707 && to_utf8(cmd.argument()) == doc_buffer->absFileName())
2708 flag.setOnOff(true);
2711 case LFUN_VC_REGISTER:
2712 enable = doc_buffer && !doc_buffer->lyxvc().inUse();
2714 case LFUN_VC_RENAME:
2715 enable = doc_buffer && doc_buffer->lyxvc().renameEnabled();
2718 enable = doc_buffer && doc_buffer->lyxvc().copyEnabled();
2720 case LFUN_VC_CHECK_IN:
2721 enable = doc_buffer && doc_buffer->lyxvc().checkInEnabled();
2723 case LFUN_VC_CHECK_OUT:
2724 enable = doc_buffer && doc_buffer->lyxvc().checkOutEnabled();
2726 case LFUN_VC_LOCKING_TOGGLE:
2727 enable = doc_buffer && !doc_buffer->hasReadonlyFlag()
2728 && doc_buffer->lyxvc().lockingToggleEnabled();
2729 flag.setOnOff(enable && doc_buffer->lyxvc().locking());
2731 case LFUN_VC_REVERT:
2732 enable = doc_buffer && doc_buffer->lyxvc().inUse()
2733 && !doc_buffer->hasReadonlyFlag();
2735 case LFUN_VC_UNDO_LAST:
2736 enable = doc_buffer && doc_buffer->lyxvc().undoLastEnabled();
2738 case LFUN_VC_REPO_UPDATE:
2739 enable = doc_buffer && doc_buffer->lyxvc().repoUpdateEnabled();
2741 case LFUN_VC_COMMAND: {
2742 if (cmd.argument().empty())
2744 if (!doc_buffer && contains(cmd.getArg(0), 'D'))
2748 case LFUN_VC_COMPARE:
2749 enable = doc_buffer && doc_buffer->lyxvc().prepareFileRevisionEnabled();
2752 case LFUN_SERVER_GOTO_FILE_ROW:
2753 case LFUN_LYX_ACTIVATE:
2754 case LFUN_WINDOW_RAISE:
2756 case LFUN_FORWARD_SEARCH:
2757 enable = !(lyxrc.forward_search_dvi.empty() && lyxrc.forward_search_pdf.empty()) &&
2758 doc_buffer && doc_buffer->isSyncTeXenabled();
2761 case LFUN_FILE_INSERT_PLAINTEXT:
2762 case LFUN_FILE_INSERT_PLAINTEXT_PARA:
2763 enable = documentBufferView() && documentBufferView()->cursor().inTexted();
2766 case LFUN_SPELLING_CONTINUOUSLY:
2767 flag.setOnOff(lyxrc.spellcheck_continuously);
2770 case LFUN_CITATION_OPEN:
2779 flag.setEnabled(false);
2785 static FileName selectTemplateFile()
2787 FileDialog dlg(qt_("Select template file"));
2788 dlg.setButton1(qt_("D&ocuments"), toqstr(lyxrc.document_path));
2789 dlg.setButton2(qt_("&Templates"), toqstr(lyxrc.template_path));
2791 FileDialog::Result result = dlg.open(toqstr(lyxrc.template_path),
2792 QStringList(qt_("LyX Documents (*.lyx)")));
2794 if (result.first == FileDialog::Later)
2796 if (result.second.isEmpty())
2798 return FileName(fromqstr(result.second));
2802 Buffer * GuiView::loadDocument(FileName const & filename, bool tolastfiles)
2806 Buffer * newBuffer = nullptr;
2808 newBuffer = checkAndLoadLyXFile(filename);
2809 } catch (ExceptionMessage const &) {
2816 message(_("Document not loaded."));
2820 setBuffer(newBuffer);
2821 newBuffer->errors("Parse");
2824 theSession().lastFiles().add(filename);
2825 theSession().writeFile();
2832 void GuiView::openDocuments(string const & fname, int origin)
2834 string initpath = lyxrc.document_path;
2836 if (documentBufferView()) {
2837 string const trypath = documentBufferView()->buffer().filePath();
2838 // If directory is writeable, use this as default.
2839 if (FileName(trypath).isDirWritable())
2845 if (fname.empty()) {
2846 FileDialog dlg(qt_("Select documents to open"));
2847 dlg.setButton1(qt_("D&ocuments"), toqstr(lyxrc.document_path));
2848 dlg.setButton2(qt_("&Examples"), toqstr(lyxrc.example_path));
2850 QStringList const filter({
2851 qt_("LyX Documents (*.lyx)"),
2852 qt_("LyX Document Backups (*.lyx~)"),
2853 qt_("All Files") + " " + wildcardAllFiles()
2855 FileDialog::Results results =
2856 dlg.openMulti(toqstr(initpath), filter);
2858 if (results.first == FileDialog::Later)
2861 files = results.second;
2863 // check selected filename
2864 if (files.isEmpty()) {
2865 message(_("Canceled."));
2869 files << toqstr(fname);
2871 // iterate over all selected files
2872 for (auto const & file : files) {
2873 string filename = fromqstr(file);
2875 // get absolute path of file and add ".lyx" to the filename if
2877 FileName const fullname =
2878 fileSearch(string(), filename, "lyx", support::may_not_exist);
2879 if (!fullname.empty())
2880 filename = fullname.absFileName();
2882 if (!fullname.onlyPath().isDirectory()) {
2883 Alert::warning(_("Invalid filename"),
2884 bformat(_("The directory in the given path\n%1$s\ndoes not exist."),
2885 from_utf8(fullname.absFileName())));
2889 // if the file doesn't exist and isn't already open (bug 6645),
2890 // let the user create one
2891 if (!fullname.exists() && !theBufferList().exists(fullname) &&
2892 !LyXVC::file_not_found_hook(fullname)) {
2894 if (origin == FuncRequest::MENU) {
2895 docstring const & msg =
2898 "does not exist. Create empty file?"),
2899 from_utf8(filename));
2900 int ret = Alert::prompt(_("File does not exist"),
2907 Buffer * const b = newFile(filename, string(), true);
2913 docstring const disp_fn = makeDisplayPath(filename);
2914 message(bformat(_("Opening document %1$s..."), disp_fn));
2917 Buffer * buf = loadDocument(fullname);
2919 str2 = bformat(_("Document %1$s opened."), disp_fn);
2920 if (buf->lyxvc().inUse())
2921 str2 += " " + from_utf8(buf->lyxvc().versionString()) +
2922 " " + _("Version control detected.");
2924 str2 = bformat(_("Could not open document %1$s"), disp_fn);
2930 // FIXME: clean that
2931 static bool import(GuiView * lv, FileName const & filename,
2932 string const & format, ErrorList & errorList)
2934 FileName const lyxfile(support::changeExtension(filename.absFileName(), ".lyx"));
2936 string loader_format;
2937 vector<string> loaders = theConverters().loaders();
2938 if (find(loaders.begin(), loaders.end(), format) == loaders.end()) {
2939 for (string const & loader : loaders) {
2940 if (!theConverters().isReachable(format, loader))
2943 string const tofile =
2944 support::changeExtension(filename.absFileName(),
2945 theFormats().extension(loader));
2946 if (theConverters().convert(nullptr, filename, FileName(tofile),
2947 filename, format, loader, errorList) != Converters::SUCCESS)
2949 loader_format = loader;
2952 if (loader_format.empty()) {
2953 frontend::Alert::error(_("Couldn't import file"),
2954 bformat(_("No information for importing the format %1$s."),
2955 translateIfPossible(theFormats().prettyName(format))));
2959 loader_format = format;
2961 if (loader_format == "lyx") {
2962 Buffer * buf = lv->loadDocument(lyxfile);
2966 Buffer * const b = newFile(lyxfile.absFileName(), string(), true);
2970 bool as_paragraphs = loader_format == "textparagraph";
2971 string filename2 = (loader_format == format) ? filename.absFileName()
2972 : support::changeExtension(filename.absFileName(),
2973 theFormats().extension(loader_format));
2974 lv->currentBufferView()->insertPlaintextFile(FileName(filename2),
2976 guiApp->setCurrentView(lv);
2977 lyx::dispatch(FuncRequest(LFUN_MARK_OFF));
2984 void GuiView::importDocument(string const & argument)
2987 string filename = split(argument, format, ' ');
2989 LYXERR(Debug::INFO, format << " file: " << filename);
2991 // need user interaction
2992 if (filename.empty()) {
2993 string initpath = lyxrc.document_path;
2994 if (documentBufferView()) {
2995 string const trypath = documentBufferView()->buffer().filePath();
2996 // If directory is writeable, use this as default.
2997 if (FileName(trypath).isDirWritable())
3001 docstring const text = bformat(_("Select %1$s file to import"),
3002 translateIfPossible(theFormats().prettyName(format)));
3004 FileDialog dlg(toqstr(text));
3005 dlg.setButton1(qt_("D&ocuments"), toqstr(lyxrc.document_path));
3006 dlg.setButton2(qt_("&Examples"), toqstr(lyxrc.example_path));
3008 docstring filter = translateIfPossible(theFormats().prettyName(format));
3011 filter += from_utf8(theFormats().extensions(format));
3014 FileDialog::Result result =
3015 dlg.open(toqstr(initpath), fileFilters(toqstr(filter)));
3017 if (result.first == FileDialog::Later)
3020 filename = fromqstr(result.second);
3022 // check selected filename
3023 if (filename.empty())
3024 message(_("Canceled."));
3027 if (filename.empty())
3030 // get absolute path of file
3031 FileName const fullname(support::makeAbsPath(filename));
3033 // Can happen if the user entered a path into the dialog
3035 if (fullname.onlyFileName().empty()) {
3036 docstring msg = bformat(_("The file name '%1$s' is invalid!\n"
3037 "Aborting import."),
3038 from_utf8(fullname.absFileName()));
3039 frontend::Alert::error(_("File name error"), msg);
3040 message(_("Canceled."));
3045 FileName const lyxfile(support::changeExtension(fullname.absFileName(), ".lyx"));
3047 // Check if the document already is open
3048 Buffer * buf = theBufferList().getBuffer(lyxfile);
3051 if (!closeBuffer()) {
3052 message(_("Canceled."));
3057 docstring const displaypath = makeDisplayPath(lyxfile.absFileName(), 30);
3059 // if the file exists already, and we didn't do
3060 // -i lyx thefile.lyx, warn
3061 if (lyxfile.exists() && fullname != lyxfile) {
3063 docstring text = bformat(_("The document %1$s already exists.\n\n"
3064 "Do you want to overwrite that document?"), displaypath);
3065 int const ret = Alert::prompt(_("Overwrite document?"),
3066 text, 0, 1, _("&Overwrite"), _("&Cancel"));
3069 message(_("Canceled."));
3074 message(bformat(_("Importing %1$s..."), displaypath));
3075 ErrorList errorList;
3076 if (import(this, fullname, format, errorList))
3077 message(_("imported."));
3079 message(_("file not imported!"));
3081 // FIXME (Abdel 12/08/06): Is there a need to display the error list here?
3085 void GuiView::newDocument(string const & filename, string templatefile,
3088 FileName initpath(lyxrc.document_path);
3089 if (documentBufferView()) {
3090 FileName const trypath(documentBufferView()->buffer().filePath());
3091 // If directory is writeable, use this as default.
3092 if (trypath.isDirWritable())
3096 if (from_template) {
3097 if (templatefile.empty())
3098 templatefile = selectTemplateFile().absFileName();
3099 if (templatefile.empty())
3104 if (filename.empty())
3105 b = newUnnamedFile(initpath, to_utf8(_("newfile")), templatefile);
3107 b = newFile(filename, templatefile, true);
3112 // If no new document could be created, it is unsure
3113 // whether there is a valid BufferView.
3114 if (currentBufferView())
3115 // Ensure the cursor is correctly positioned on screen.
3116 currentBufferView()->showCursor();
3120 bool GuiView::insertLyXFile(docstring const & fname, bool ignorelang)
3122 BufferView * bv = documentBufferView();
3127 FileName filename(to_utf8(fname));
3128 if (filename.empty()) {
3129 // Launch a file browser
3131 string initpath = lyxrc.document_path;
3132 string const trypath = bv->buffer().filePath();
3133 // If directory is writeable, use this as default.
3134 if (FileName(trypath).isDirWritable())
3138 FileDialog dlg(qt_("Select LyX document to insert"));
3139 dlg.setButton1(qt_("D&ocuments"), toqstr(lyxrc.document_path));
3140 dlg.setButton2(qt_("&Examples"), toqstr(lyxrc.example_path));
3142 FileDialog::Result result = dlg.open(toqstr(initpath),
3143 QStringList(qt_("LyX Documents (*.lyx)")));
3145 if (result.first == FileDialog::Later)
3149 filename.set(fromqstr(result.second));
3151 // check selected filename
3152 if (filename.empty()) {
3153 // emit message signal.
3154 message(_("Canceled."));
3159 bv->insertLyXFile(filename, ignorelang);
3160 bv->buffer().errors("Parse");
3165 string const GuiView::getTemplatesPath(Buffer & b)
3167 // We start off with the user's templates path
3168 string result = addPath(package().user_support().absFileName(), "templates");
3169 // Check for the document language
3170 string const langcode = b.params().language->code();
3171 string const shortcode = langcode.substr(0, 2);
3172 if (!langcode.empty() && shortcode != "en") {
3173 string subpath = addPath(result, shortcode);
3174 string subpath_long = addPath(result, langcode);
3175 // If we have a subdirectory for the language already,
3177 FileName sp = FileName(subpath);
3178 if (sp.isDirectory())
3180 else if (FileName(subpath_long).isDirectory())
3181 result = subpath_long;
3183 // Ask whether we should create such a subdirectory
3184 docstring const text =
3185 bformat(_("It is suggested to save the template in a subdirectory\n"
3186 "appropriate to the document language (%1$s).\n"
3187 "This subdirectory does not exists yet.\n"
3188 "Do you want to create it?"),
3189 _(b.params().language->display()));
3190 if (Alert::prompt(_("Create Language Directory?"),
3191 text, 0, 1, _("&Yes, Create"), _("&No, Save Template in Parent Directory")) == 0) {
3192 // If the user agreed, we try to create it and report if this failed.
3193 if (!sp.createDirectory(0777))
3194 Alert::error(_("Subdirectory creation failed!"),
3195 _("Could not create subdirectory.\n"
3196 "The template will be saved in the parent directory."));
3202 // Do we have a layout category?
3203 string const cat = b.params().baseClass() ?
3204 b.params().baseClass()->category()
3207 string subpath = addPath(result, cat);
3208 // If we have a subdirectory for the category already,
3210 FileName sp = FileName(subpath);
3211 if (sp.isDirectory())
3214 // Ask whether we should create such a subdirectory
3215 docstring const text =
3216 bformat(_("It is suggested to save the template in a subdirectory\n"
3217 "appropriate to the layout category (%1$s).\n"
3218 "This subdirectory does not exists yet.\n"
3219 "Do you want to create it?"),
3221 if (Alert::prompt(_("Create Category Directory?"),
3222 text, 0, 1, _("&Yes, Create"), _("&No, Save Template in Parent Directory")) == 0) {
3223 // If the user agreed, we try to create it and report if this failed.
3224 if (!sp.createDirectory(0777))
3225 Alert::error(_("Subdirectory creation failed!"),
3226 _("Could not create subdirectory.\n"
3227 "The template will be saved in the parent directory."));
3237 bool GuiView::renameBuffer(Buffer & b, docstring const & newname, RenameKind kind)
3239 FileName fname = b.fileName();
3240 FileName const oldname = fname;
3241 bool const as_template = (kind == LV_WRITE_AS_TEMPLATE);
3243 if (!newname.empty()) {
3246 fname = support::makeAbsPath(to_utf8(newname), getTemplatesPath(b));
3248 fname = support::makeAbsPath(to_utf8(newname),
3249 oldname.onlyPath().absFileName());
3251 // Switch to this Buffer.
3254 // No argument? Ask user through dialog.
3256 QString const title = as_template ? qt_("Choose a filename to save template as")
3257 : qt_("Choose a filename to save document as");
3258 FileDialog dlg(title);
3259 dlg.setButton1(qt_("D&ocuments"), toqstr(lyxrc.document_path));
3260 dlg.setButton2(qt_("&Templates"), toqstr(lyxrc.template_path));
3262 fname.ensureExtension(".lyx");
3264 string const path = as_template ?
3266 : fname.onlyPath().absFileName();
3267 FileDialog::Result result =
3268 dlg.save(toqstr(path),
3269 QStringList(qt_("LyX Documents (*.lyx)")),
3270 toqstr(fname.onlyFileName()));
3272 if (result.first == FileDialog::Later)
3275 fname.set(fromqstr(result.second));
3280 fname.ensureExtension(".lyx");
3283 // fname is now the new Buffer location.
3285 // if there is already a Buffer open with this name, we do not want
3286 // to have another one. (the second test makes sure we're not just
3287 // trying to overwrite ourselves, which is fine.)
3288 if (theBufferList().exists(fname) && fname != oldname
3289 && theBufferList().getBuffer(fname) != &b) {
3290 docstring const text =
3291 bformat(_("The file\n%1$s\nis already open in your current session.\n"
3292 "Please close it before attempting to overwrite it.\n"
3293 "Do you want to choose a new filename?"),
3294 from_utf8(fname.absFileName()));
3295 int const ret = Alert::prompt(_("Chosen File Already Open"),
3296 text, 0, 1, _("&Rename"), _("&Cancel"));
3298 case 0: return renameBuffer(b, docstring(), kind);
3299 case 1: return false;
3304 bool const existsLocal = fname.exists();
3305 bool const existsInVC = LyXVC::fileInVC(fname);
3306 if (existsLocal || existsInVC) {
3307 docstring const file = makeDisplayPath(fname.absFileName(), 30);
3308 if (kind != LV_WRITE_AS && existsInVC) {
3309 // renaming to a name that is already in VC
3311 docstring text = bformat(_("The document %1$s "
3312 "is already registered.\n\n"
3313 "Do you want to choose a new name?"),
3315 docstring const title = (kind == LV_VC_RENAME) ?
3316 _("Rename document?") : _("Copy document?");
3317 docstring const button = (kind == LV_VC_RENAME) ?
3318 _("&Rename") : _("&Copy");
3319 int const ret = Alert::prompt(title, text, 0, 1,
3320 button, _("&Cancel"));
3322 case 0: return renameBuffer(b, docstring(), kind);
3323 case 1: return false;
3328 docstring text = bformat(_("The document %1$s "
3329 "already exists.\n\n"
3330 "Do you want to overwrite that document?"),
3332 int const ret = Alert::prompt(_("Overwrite document?"),
3333 text, 0, 2, _("&Overwrite"),
3334 _("&Rename"), _("&Cancel"));
3337 case 1: return renameBuffer(b, docstring(), kind);
3338 case 2: return false;
3344 case LV_VC_RENAME: {
3345 string msg = b.lyxvc().rename(fname);
3348 message(from_utf8(msg));
3352 string msg = b.lyxvc().copy(fname);
3355 message(from_utf8(msg));
3359 case LV_WRITE_AS_TEMPLATE:
3362 // LyXVC created the file already in case of LV_VC_RENAME or
3363 // LV_VC_COPY, but call saveBuffer() nevertheless to get
3364 // relative paths of included stuff right if we moved e.g. from
3365 // /a/b.lyx to /a/c/b.lyx.
3367 bool const saved = saveBuffer(b, fname);
3374 bool GuiView::exportBufferAs(Buffer & b, docstring const & iformat)
3376 FileName fname = b.fileName();
3378 FileDialog dlg(qt_("Choose a filename to export the document as"));
3379 dlg.setButton1(qt_("D&ocuments"), toqstr(lyxrc.document_path));
3382 QString const anyformat = qt_("Guess from extension (*.*)");
3385 vector<Format const *> export_formats;
3386 for (Format const & f : theFormats())
3387 if (f.documentFormat())
3388 export_formats.push_back(&f);
3389 sort(export_formats.begin(), export_formats.end(), Format::formatSorter);
3390 map<QString, string> fmap;
3393 for (Format const * f : export_formats) {
3394 docstring const loc_prettyname = translateIfPossible(f->prettyname());
3395 QString const loc_filter = toqstr(bformat(from_ascii("%1$s (*.%2$s)"),
3397 from_ascii(f->extension())));
3398 types << loc_filter;
3399 fmap[loc_filter] = f->name();
3400 if (from_ascii(f->name()) == iformat) {
3401 filter = loc_filter;
3402 ext = f->extension();
3405 string ofname = fname.onlyFileName();
3407 ofname = support::changeExtension(ofname, ext);
3408 FileDialog::Result result =
3409 dlg.save(toqstr(fname.onlyPath().absFileName()),
3413 if (result.first != FileDialog::Chosen)
3417 fname.set(fromqstr(result.second));
3418 if (filter == anyformat)
3419 fmt_name = theFormats().getFormatFromExtension(fname.extension());
3421 fmt_name = fmap[filter];
3422 LYXERR(Debug::FILES, "filter=" << fromqstr(filter)
3423 << ", fmt_name=" << fmt_name << ", fname=" << fname.absFileName());
3425 if (fmt_name.empty() || fname.empty())
3428 fname.ensureExtension(theFormats().extension(fmt_name));
3430 // fname is now the new Buffer location.
3431 if (fname.exists()) {
3432 docstring const file = makeDisplayPath(fname.absFileName(), 30);
3433 docstring text = bformat(_("The document %1$s already "
3434 "exists.\n\nDo you want to "
3435 "overwrite that document?"),
3437 int const ret = Alert::prompt(_("Overwrite document?"),
3438 text, 0, 2, _("&Overwrite"), _("&Rename"), _("&Cancel"));
3441 case 1: return exportBufferAs(b, from_ascii(fmt_name));
3442 case 2: return false;
3446 FuncRequest cmd(LFUN_BUFFER_EXPORT, fmt_name + " " + fname.absFileName());
3449 return dr.dispatched();
3453 bool GuiView::saveBuffer(Buffer & b)
3455 return saveBuffer(b, FileName());
3459 bool GuiView::saveBuffer(Buffer & b, FileName const & fn)
3461 if (workArea(b) && workArea(b)->inDialogMode())
3464 if (fn.empty() && b.isUnnamed())
3465 return renameBuffer(b, docstring());
3467 bool const success = (fn.empty() ? b.save() : b.saveAs(fn));
3469 theSession().lastFiles().add(b.fileName());
3470 theSession().writeFile();
3474 // Switch to this Buffer.
3477 // FIXME: we don't tell the user *WHY* the save failed !!
3478 docstring const file = makeDisplayPath(b.absFileName(), 30);
3479 docstring text = bformat(_("The document %1$s could not be saved.\n\n"
3480 "Do you want to rename the document and "
3481 "try again?"), file);
3482 int const ret = Alert::prompt(_("Rename and save?"),
3483 text, 0, 2, _("&Rename"), _("&Retry"), _("&Cancel"));
3486 if (!renameBuffer(b, docstring()))
3495 return saveBuffer(b, fn);
3499 bool GuiView::hideWorkArea(GuiWorkArea * wa)
3501 return closeWorkArea(wa, false);
3505 // We only want to close the buffer if it is not visible in other workareas
3506 // of the same view, nor in other views, and if this is not a child
3507 bool GuiView::closeWorkArea(GuiWorkArea * wa)
3509 Buffer & buf = wa->bufferView().buffer();
3511 bool last_wa = d.countWorkAreasOf(buf) == 1
3512 && !inOtherView(buf) && !buf.parent();
3514 bool close_buffer = last_wa;
3517 if (lyxrc.close_buffer_with_last_view == "yes")
3519 else if (lyxrc.close_buffer_with_last_view == "no")
3520 close_buffer = false;
3523 if (buf.isUnnamed())
3524 file = from_utf8(buf.fileName().onlyFileName());
3526 file = buf.fileName().displayName(30);
3527 docstring const text = bformat(
3528 _("Last view on document %1$s is being closed.\n"
3529 "Would you like to close or hide the document?\n"
3531 "Hidden documents can be displayed back through\n"
3532 "the menu: View->Hidden->...\n"
3534 "To remove this question, set your preference in:\n"
3535 " Tools->Preferences->Look&Feel->UserInterface\n"
3537 int ret = Alert::prompt(_("Close or hide document?"),
3538 text, 0, 2, _("&Close"), _("&Hide"), _("&Cancel"));
3541 close_buffer = (ret == 0);
3545 return closeWorkArea(wa, close_buffer);
3549 bool GuiView::closeBuffer()
3551 GuiWorkArea * wa = currentMainWorkArea();
3552 // coverity complained about this
3553 // it seems unnecessary, but perhaps is worth the check
3554 LASSERT(wa, return false);
3556 setCurrentWorkArea(wa);
3557 Buffer & buf = wa->bufferView().buffer();
3558 return closeWorkArea(wa, !buf.parent());
3562 void GuiView::writeSession() const {
3563 GuiWorkArea const * active_wa = currentMainWorkArea();
3564 for (int i = 0; i < d.splitter_->count(); ++i) {
3565 TabWorkArea * twa = d.tabWorkArea(i);
3566 for (int j = 0; j < twa->count(); ++j) {
3567 GuiWorkArea * wa = twa->workArea(j);
3568 Buffer & buf = wa->bufferView().buffer();
3569 theSession().lastOpened().add(buf.fileName(), wa == active_wa);
3575 bool GuiView::closeBufferAll()
3578 for (auto & buf : theBufferList()) {
3579 if (!saveBufferIfNeeded(*buf, false)) {
3580 // Closing has been cancelled, so abort.
3585 // Close the workareas in all other views
3586 QList<int> const ids = guiApp->viewIds();
3587 for (int i = 0; i != ids.size(); ++i) {
3588 if (id_ != ids[i] && !guiApp->view(ids[i]).closeWorkAreaAll())
3592 // Close our own workareas
3593 if (!closeWorkAreaAll())
3600 bool GuiView::closeWorkAreaAll()
3602 setCurrentWorkArea(currentMainWorkArea());
3604 // We might be in a situation that there is still a tabWorkArea, but
3605 // there are no tabs anymore. This can happen when we get here after a
3606 // TabWorkArea::lastWorkAreaRemoved() signal. Therefore we count how
3607 // many TabWorkArea's have no documents anymore.
3610 // We have to call count() each time, because it can happen that
3611 // more than one splitter will disappear in one iteration (bug 5998).
3612 while (d.splitter_->count() > empty_twa) {
3613 TabWorkArea * twa = d.tabWorkArea(empty_twa);
3615 if (twa->count() == 0)
3618 setCurrentWorkArea(twa->currentWorkArea());
3619 if (!closeTabWorkArea(twa))
3627 bool GuiView::closeWorkArea(GuiWorkArea * wa, bool close_buffer)
3632 Buffer & buf = wa->bufferView().buffer();
3634 if (GuiViewPrivate::busyBuffers.contains(&buf)) {
3635 Alert::warning(_("Close document"),
3636 _("Document could not be closed because it is being processed by LyX."));
3641 return closeBuffer(buf);
3643 if (!inMultiTabs(wa))
3644 if (!saveBufferIfNeeded(buf, true))
3652 bool GuiView::closeBuffer(Buffer & buf)
3654 bool success = true;
3655 for (Buffer * child_buf : buf.getChildren()) {
3656 if (theBufferList().isOthersChild(&buf, child_buf)) {
3657 child_buf->setParent(nullptr);
3661 // FIXME: should we look in other tabworkareas?
3662 // ANSWER: I don't think so. I've tested, and if the child is
3663 // open in some other window, it closes without a problem.
3664 GuiWorkArea * child_wa = workArea(*child_buf);
3667 // If we are in a close_event all children will be closed in some time,
3668 // so no need to do it here. This will ensure that the children end up
3669 // in the session file in the correct order. If we close the master
3670 // buffer, we can close or release the child buffers here too.
3672 success = closeWorkArea(child_wa, true);
3676 // In this case the child buffer is open but hidden.
3677 // Even in this case, children can be dirty (e.g.,
3678 // after a label change in the master, see #11405).
3679 // Therefore, check this
3680 if (closing_ && (child_buf->isClean() || child_buf->paragraphs().empty())) {
3681 // If we are in a close_event all children will be closed in some time,
3682 // so no need to do it here. This will ensure that the children end up
3683 // in the session file in the correct order. If we close the master
3684 // buffer, we can close or release the child buffers here too.
3687 // Save dirty buffers also if closing_!
3688 if (saveBufferIfNeeded(*child_buf, false)) {
3689 child_buf->removeAutosaveFile();
3690 theBufferList().release(child_buf);
3692 // Saving of dirty children has been cancelled.
3693 // Cancel the whole process.
3700 // goto bookmark to update bookmark pit.
3701 // FIXME: we should update only the bookmarks related to this buffer!
3702 // FIXME: this is done also in LFUN_WINDOW_CLOSE!
3703 LYXERR(Debug::DEBUG, "GuiView::closeBuffer()");
3704 for (unsigned int i = 1; i < theSession().bookmarks().size(); ++i)
3705 guiApp->gotoBookmark(i, false, false);
3707 if (saveBufferIfNeeded(buf, false)) {
3708 buf.removeAutosaveFile();
3709 theBufferList().release(&buf);
3713 // open all children again to avoid a crash because of dangling
3714 // pointers (bug 6603)
3720 bool GuiView::closeTabWorkArea(TabWorkArea * twa)
3722 while (twa == d.currentTabWorkArea()) {
3723 twa->setCurrentIndex(twa->count() - 1);
3725 GuiWorkArea * wa = twa->currentWorkArea();
3726 Buffer & b = wa->bufferView().buffer();
3728 // We only want to close the buffer if the same buffer is not visible
3729 // in another view, and if this is not a child and if we are closing
3730 // a view (not a tabgroup).
3731 bool const close_buffer =
3732 !inOtherView(b) && !b.parent() && closing_;
3734 if (!closeWorkArea(wa, close_buffer))
3741 bool GuiView::saveBufferIfNeeded(Buffer & buf, bool hiding)
3743 if (buf.isClean() || buf.paragraphs().empty())
3746 // Switch to this Buffer.
3752 if (buf.isUnnamed()) {
3753 file = from_utf8(buf.fileName().onlyFileName());
3756 FileName filename = buf.fileName();
3758 file = filename.displayName(30);
3759 exists = filename.exists();
3762 // Bring this window to top before asking questions.
3767 if (hiding && buf.isUnnamed()) {
3768 docstring const text = bformat(_("The document %1$s has not been "
3769 "saved yet.\n\nDo you want to save "
3770 "the document?"), file);
3771 ret = Alert::prompt(_("Save new document?"),
3772 text, 0, 1, _("&Save"), _("&Cancel"));
3776 docstring const text = exists ?
3777 bformat(_("The document %1$s has unsaved changes."
3778 "\n\nDo you want to save the document or "
3779 "discard the changes?"), file) :
3780 bformat(_("The document %1$s has not been saved yet."
3781 "\n\nDo you want to save the document or "
3782 "discard it entirely?"), file);
3783 docstring const title = exists ?
3784 _("Save changed document?") : _("Save document?");
3785 ret = Alert::prompt(title, text, 0, 2,
3786 _("&Save"), _("&Discard"), _("&Cancel"));
3791 if (!saveBuffer(buf))
3795 // If we crash after this we could have no autosave file
3796 // but I guess this is really improbable (Jug).
3797 // Sometimes improbable things happen:
3798 // - see bug http://www.lyx.org/trac/ticket/6587 (ps)
3799 // buf.removeAutosaveFile();
3801 // revert all changes
3812 bool GuiView::inMultiTabs(GuiWorkArea * wa)
3814 Buffer & buf = wa->bufferView().buffer();
3816 for (int i = 0; i != d.splitter_->count(); ++i) {
3817 GuiWorkArea * wa_ = d.tabWorkArea(i)->workArea(buf);
3818 if (wa_ && wa_ != wa)
3821 return inOtherView(buf);
3825 bool GuiView::inOtherView(Buffer & buf)
3827 QList<int> const ids = guiApp->viewIds();
3829 for (int i = 0; i != ids.size(); ++i) {
3833 if (guiApp->view(ids[i]).workArea(buf))
3840 void GuiView::gotoNextOrPreviousBuffer(NextOrPrevious np, bool const move)
3842 if (!documentBufferView())
3845 if (TabWorkArea * twa = d.currentTabWorkArea()) {
3846 Buffer * const curbuf = &documentBufferView()->buffer();
3847 int nwa = twa->count();
3848 for (int i = 0; i < nwa; ++i) {
3849 if (&workArea(i)->bufferView().buffer() == curbuf) {
3852 next_index = (i == nwa - 1 ? 0 : i + 1);
3854 next_index = (i == 0 ? nwa - 1 : i - 1);
3856 twa->moveTab(i, next_index);
3858 setBuffer(&workArea(next_index)->bufferView().buffer());
3866 void GuiView::gotoNextTabWorkArea(NextOrPrevious np)
3868 int count = d.splitter_->count();
3869 for (int i = 0; i < count; ++i) {
3870 if (d.tabWorkArea(i) == d.currentTabWorkArea()) {
3873 new_index = (i == count - 1 ? 0 : i + 1);
3875 new_index = (i == 0 ? count - 1 : i - 1);
3876 setCurrentWorkArea(d.tabWorkArea(new_index)->currentWorkArea());
3883 /// make sure the document is saved
3884 static bool ensureBufferClean(Buffer * buffer)
3886 LASSERT(buffer, return false);
3887 if (buffer->isClean() && !buffer->isUnnamed())
3890 docstring const file = buffer->fileName().displayName(30);
3893 if (!buffer->isUnnamed()) {
3894 text = bformat(_("The document %1$s has unsaved "
3895 "changes.\n\nDo you want to save "
3896 "the document?"), file);
3897 title = _("Save changed document?");
3900 text = bformat(_("The document %1$s has not been "
3901 "saved yet.\n\nDo you want to save "
3902 "the document?"), file);
3903 title = _("Save new document?");
3905 int const ret = Alert::prompt(title, text, 0, 1, _("&Save"), _("&Cancel"));
3908 dispatch(FuncRequest(LFUN_BUFFER_WRITE));
3910 return buffer->isClean() && !buffer->isUnnamed();
3914 bool GuiView::reloadBuffer(Buffer & buf)
3916 currentBufferView()->cursor().reset();
3917 Buffer::ReadStatus status = buf.reload();
3918 return status == Buffer::ReadSuccess;
3922 void GuiView::checkExternallyModifiedBuffers()
3924 for (Buffer * buf : theBufferList()) {
3925 if (buf->fileName().exists() && buf->isChecksumModified()) {
3926 docstring text = bformat(_("Document \n%1$s\n has been externally modified."
3927 " Reload now? Any local changes will be lost."),
3928 from_utf8(buf->absFileName()));
3929 int const ret = Alert::prompt(_("Reload externally changed document?"),
3930 text, 0, 1, _("&Reload"), _("&Cancel"));
3938 void GuiView::dispatchVC(FuncRequest const & cmd, DispatchResult & dr)
3940 Buffer * buffer = documentBufferView()
3941 ? &(documentBufferView()->buffer()) : nullptr;
3943 switch (cmd.action()) {
3944 case LFUN_VC_REGISTER:
3945 if (!buffer || !ensureBufferClean(buffer))
3947 if (!buffer->lyxvc().inUse()) {
3948 if (buffer->lyxvc().registrer()) {
3949 reloadBuffer(*buffer);
3950 dr.clearMessageUpdate();
3955 case LFUN_VC_RENAME:
3956 case LFUN_VC_COPY: {
3957 if (!buffer || !ensureBufferClean(buffer))
3959 if (buffer->lyxvc().inUse() && !buffer->hasReadonlyFlag()) {
3960 if (buffer->lyxvc().isCheckInWithConfirmation()) {
3961 // Some changes are not yet committed.
3962 // We test here and not in getStatus(), since
3963 // this test is expensive.
3965 LyXVC::CommandResult ret =
3966 buffer->lyxvc().checkIn(log);
3968 if (ret == LyXVC::ErrorCommand ||
3969 ret == LyXVC::VCSuccess)
3970 reloadBuffer(*buffer);
3971 if (buffer->lyxvc().isCheckInWithConfirmation()) {
3972 frontend::Alert::error(
3973 _("Revision control error."),
3974 _("Document could not be checked in."));
3978 RenameKind const kind = (cmd.action() == LFUN_VC_RENAME) ?
3979 LV_VC_RENAME : LV_VC_COPY;
3980 renameBuffer(*buffer, cmd.argument(), kind);
3985 case LFUN_VC_CHECK_IN:
3986 if (!buffer || !ensureBufferClean(buffer))
3988 if (buffer->lyxvc().inUse() && !buffer->hasReadonlyFlag()) {
3990 LyXVC::CommandResult ret = buffer->lyxvc().checkIn(log);
3992 // Only skip reloading if the checkin was cancelled or
3993 // an error occurred before the real checkin VCS command
3994 // was executed, since the VCS might have changed the
3995 // file even if it could not checkin successfully.
3996 if (ret == LyXVC::ErrorCommand || ret == LyXVC::VCSuccess)
3997 reloadBuffer(*buffer);
4001 case LFUN_VC_CHECK_OUT:
4002 if (!buffer || !ensureBufferClean(buffer))
4004 if (buffer->lyxvc().inUse()) {
4005 dr.setMessage(buffer->lyxvc().checkOut());
4006 reloadBuffer(*buffer);
4010 case LFUN_VC_LOCKING_TOGGLE:
4011 if (!buffer || !ensureBufferClean(buffer) || buffer->hasReadonlyFlag())
4013 if (buffer->lyxvc().inUse()) {
4014 string res = buffer->lyxvc().lockingToggle();
4016 frontend::Alert::error(_("Revision control error."),
4017 _("Error when setting the locking property."));
4020 reloadBuffer(*buffer);
4025 case LFUN_VC_REVERT:
4028 if (buffer->lyxvc().revert()) {
4029 reloadBuffer(*buffer);
4030 dr.clearMessageUpdate();
4034 case LFUN_VC_UNDO_LAST:
4037 buffer->lyxvc().undoLast();
4038 reloadBuffer(*buffer);
4039 dr.clearMessageUpdate();
4042 case LFUN_VC_REPO_UPDATE:
4045 if (ensureBufferClean(buffer)) {
4046 dr.setMessage(buffer->lyxvc().repoUpdate());
4047 checkExternallyModifiedBuffers();
4051 case LFUN_VC_COMMAND: {
4052 string flag = cmd.getArg(0);
4053 if (buffer && contains(flag, 'R') && !ensureBufferClean(buffer))
4056 if (contains(flag, 'M')) {
4057 if (!Alert::askForText(message, _("LyX VC: Log Message")))
4060 string path = cmd.getArg(1);
4061 if (contains(path, "$$p") && buffer)
4062 path = subst(path, "$$p", buffer->filePath());
4063 LYXERR(Debug::LYXVC, "Directory: " << path);
4065 if (!pp.isReadableDirectory()) {
4066 lyxerr << _("Directory is not accessible.") << endl;
4069 support::PathChanger p(pp);
4071 string command = cmd.getArg(2);
4072 if (command.empty())
4075 command = subst(command, "$$i", buffer->absFileName());
4076 command = subst(command, "$$p", buffer->filePath());
4078 command = subst(command, "$$m", to_utf8(message));
4079 LYXERR(Debug::LYXVC, "Command: " << command);
4081 one.startscript(Systemcall::Wait, command);
4085 if (contains(flag, 'I'))
4086 buffer->markDirty();
4087 if (contains(flag, 'R'))
4088 reloadBuffer(*buffer);
4093 case LFUN_VC_COMPARE: {
4094 if (cmd.argument().empty()) {
4095 lyx::dispatch(FuncRequest(LFUN_DIALOG_SHOW, "comparehistory"));
4101 string rev1 = cmd.getArg(0);
4105 if (!buffer->lyxvc().prepareFileRevision(rev1, f1))
4108 if (isStrInt(rev1) && convert<int>(rev1) <= 0) {
4109 f2 = buffer->absFileName();
4111 string rev2 = cmd.getArg(1);
4115 if (!buffer->lyxvc().prepareFileRevision(rev2, f2))
4119 LYXERR(Debug::LYXVC, "Launching comparison for fetched revisions:\n" <<
4120 f1 << "\n" << f2 << "\n" );
4121 string par = "compare run " + quoteName(f1) + " " + quoteName(f2);
4122 lyx::dispatch(FuncRequest(LFUN_DIALOG_SHOW, par));
4132 void GuiView::openChildDocument(string const & fname)
4134 LASSERT(documentBufferView(), return);
4135 Buffer & buffer = documentBufferView()->buffer();
4136 FileName const filename = support::makeAbsPath(fname, buffer.filePath());
4137 documentBufferView()->saveBookmark(false);
4138 Buffer * child = nullptr;
4139 if (theBufferList().exists(filename)) {
4140 child = theBufferList().getBuffer(filename);
4143 message(bformat(_("Opening child document %1$s..."),
4144 makeDisplayPath(filename.absFileName())));
4145 child = loadDocument(filename, false);
4147 // Set the parent name of the child document.
4148 // This makes insertion of citations and references in the child work,
4149 // when the target is in the parent or another child document.
4151 child->setParent(&buffer);
4155 bool GuiView::goToFileRow(string const & argument)
4159 size_t i = argument.find_last_of(' ');
4160 if (i != string::npos) {
4161 file_name = os::internal_path(FileName(trim(argument.substr(0, i))).realPath());
4162 istringstream is(argument.substr(i + 1));
4167 if (i == string::npos) {
4168 LYXERR0("Wrong argument: " << argument);
4171 Buffer * buf = nullptr;
4172 string const realtmp = package().temp_dir().realPath();
4173 // We have to use os::path_prefix_is() here, instead of
4174 // simply prefixIs(), because the file name comes from
4175 // an external application and may need case adjustment.
4176 if (os::path_prefix_is(file_name, realtmp, os::CASE_ADJUSTED)) {
4177 buf = theBufferList().getBufferFromTmp(file_name, true);
4178 LYXERR(Debug::FILES, "goToFileRow: buffer lookup for " << file_name
4179 << (buf ? " success" : " failed"));
4181 // Must replace extension of the file to be .lyx
4182 // and get full path
4183 FileName const s = fileSearch(string(),
4184 support::changeExtension(file_name, ".lyx"), "lyx");
4185 // Either change buffer or load the file
4186 if (theBufferList().exists(s))
4187 buf = theBufferList().getBuffer(s);
4188 else if (s.exists()) {
4189 buf = loadDocument(s);
4194 _("File does not exist: %1$s"),
4195 makeDisplayPath(file_name)));
4201 _("No buffer for file: %1$s."),
4202 makeDisplayPath(file_name))
4207 bool success = documentBufferView()->setCursorFromRow(row);
4209 LYXERR(Debug::OUTFILE,
4210 "setCursorFromRow: invalid position for row " << row);
4211 frontend::Alert::error(_("Inverse Search Failed"),
4212 _("Invalid position requested by inverse search.\n"
4213 "You may need to update the viewed document."));
4220 Buffer::ExportStatus GuiView::GuiViewPrivate::runAndDestroy(const T& func,
4221 Buffer const * orig, Buffer * clone, string const & format)
4223 Buffer::ExportStatus const status = func(format);
4225 // the cloning operation will have produced a clone of the entire set of
4226 // documents, starting from the master. so we must delete those.
4227 Buffer * mbuf = const_cast<Buffer *>(clone->masterBuffer());
4229 busyBuffers.remove(orig);
4234 Buffer::ExportStatus GuiView::GuiViewPrivate::compileAndDestroy(
4235 Buffer const * orig, Buffer * clone, string const & format)
4237 Buffer::ExportStatus (Buffer::* mem_func)(std::string const &, bool) const =
4239 return runAndDestroy(lyx::bind(mem_func, clone, _1, true), orig, clone, format);
4243 Buffer::ExportStatus GuiView::GuiViewPrivate::exportAndDestroy(
4244 Buffer const * orig, Buffer * clone, string const & format)
4246 Buffer::ExportStatus (Buffer::* mem_func)(std::string const &, bool) const =
4248 return runAndDestroy(lyx::bind(mem_func, clone, _1, false), orig, clone, format);
4252 Buffer::ExportStatus GuiView::GuiViewPrivate::previewAndDestroy(
4253 Buffer const * orig, Buffer * clone, string const & format)
4255 Buffer::ExportStatus (Buffer::* mem_func)(std::string const &) const =
4257 return runAndDestroy(lyx::bind(mem_func, clone, _1), orig, clone, format);
4261 bool GuiView::GuiViewPrivate::asyncBufferProcessing(string const & argument,
4262 Buffer const * used_buffer,
4263 docstring const & msg,
4264 Buffer::ExportStatus (*asyncFunc)(Buffer const *, Buffer *, string const &),
4265 Buffer::ExportStatus (Buffer::*syncFunc)(string const &, bool) const,
4266 Buffer::ExportStatus (Buffer::*previewFunc)(string const &) const,
4267 bool allow_async, bool use_tmpdir)
4272 string format = argument;
4274 format = used_buffer->params().getDefaultOutputFormat();
4275 processing_format = format;
4277 progress_->clearMessages();
4280 #if EXPORT_in_THREAD
4282 GuiViewPrivate::busyBuffers.insert(used_buffer);
4283 Buffer * cloned_buffer = used_buffer->cloneWithChildren();
4284 if (!cloned_buffer) {
4285 Alert::error(_("Export Error"),
4286 _("Error cloning the Buffer."));
4289 QFuture<Buffer::ExportStatus> f = QtConcurrent::run(
4294 setPreviewFuture(f);
4295 last_export_format = used_buffer->params().bufferFormat();
4298 // We are asynchronous, so we don't know here anything about the success
4301 Buffer::ExportStatus status;
4303 status = (used_buffer->*syncFunc)(format, use_tmpdir);
4304 } else if (previewFunc) {
4305 status = (used_buffer->*previewFunc)(format);
4308 handleExportStatus(gv_, status, format);
4310 return (status == Buffer::ExportSuccess
4311 || status == Buffer::PreviewSuccess);
4315 Buffer::ExportStatus status;
4317 status = (used_buffer->*syncFunc)(format, true);
4318 } else if (previewFunc) {
4319 status = (used_buffer->*previewFunc)(format);
4322 handleExportStatus(gv_, status, format);
4324 return (status == Buffer::ExportSuccess
4325 || status == Buffer::PreviewSuccess);
4329 void GuiView::dispatchToBufferView(FuncRequest const & cmd, DispatchResult & dr)
4331 BufferView * bv = currentBufferView();
4332 LASSERT(bv, return);
4334 // Let the current BufferView dispatch its own actions.
4335 bv->dispatch(cmd, dr);
4336 if (dr.dispatched()) {
4337 if (cmd.action() == LFUN_REDO || cmd.action() == LFUN_UNDO)
4338 updateDialog("document", "");
4342 // Try with the document BufferView dispatch if any.
4343 BufferView * doc_bv = documentBufferView();
4344 if (doc_bv && doc_bv != bv) {
4345 doc_bv->dispatch(cmd, dr);
4346 if (dr.dispatched()) {
4347 if (cmd.action() == LFUN_REDO || cmd.action() == LFUN_UNDO)
4348 updateDialog("document", "");
4353 // Then let the current Cursor dispatch its own actions.
4354 bv->cursor().dispatch(cmd);
4356 // update completion. We do it here and not in
4357 // processKeySym to avoid another redraw just for a
4358 // changed inline completion
4359 if (cmd.origin() == FuncRequest::KEYBOARD) {
4360 if (cmd.action() == LFUN_SELF_INSERT
4361 || (cmd.action() == LFUN_ERT_INSERT && bv->cursor().inMathed()))
4362 updateCompletion(bv->cursor(), true, true);
4363 else if (cmd.action() == LFUN_CHAR_DELETE_BACKWARD)
4364 updateCompletion(bv->cursor(), false, true);
4366 updateCompletion(bv->cursor(), false, false);
4369 dr = bv->cursor().result();
4373 void GuiView::dispatch(FuncRequest const & cmd, DispatchResult & dr)
4375 BufferView * bv = currentBufferView();
4376 // By default we won't need any update.
4377 dr.screenUpdate(Update::None);
4378 // assume cmd will be dispatched
4379 dr.dispatched(true);
4381 Buffer * doc_buffer = documentBufferView()
4382 ? &(documentBufferView()->buffer()) : nullptr;
4384 if (cmd.origin() == FuncRequest::TOC) {
4385 GuiToc * toc = static_cast<GuiToc*>(findOrBuild("toc", false));
4386 toc->doDispatch(bv->cursor(), cmd, dr);
4390 string const argument = to_utf8(cmd.argument());
4392 switch(cmd.action()) {
4393 case LFUN_BUFFER_CHILD_OPEN:
4394 openChildDocument(to_utf8(cmd.argument()));
4397 case LFUN_BUFFER_IMPORT:
4398 importDocument(to_utf8(cmd.argument()));
4401 case LFUN_MASTER_BUFFER_EXPORT:
4403 doc_buffer = const_cast<Buffer *>(doc_buffer->masterBuffer());
4405 case LFUN_BUFFER_EXPORT: {
4408 // GCC only sees strfwd.h when building merged
4409 if (::lyx::operator==(cmd.argument(), "custom")) {
4410 // LFUN_MASTER_BUFFER_EXPORT is not enabled for this case,
4411 // so the following test should not be needed.
4412 // In principle, we could try to switch to such a view...
4413 // if (cmd.action() == LFUN_BUFFER_EXPORT)
4414 dispatch(FuncRequest(LFUN_DIALOG_SHOW, "sendto"), dr);
4418 string const dest = cmd.getArg(1);
4419 FileName target_dir;
4420 if (!dest.empty() && FileName::isAbsolute(dest))
4421 target_dir = FileName(support::onlyPath(dest));
4423 target_dir = doc_buffer->fileName().onlyPath();
4425 string const format = (argument.empty() || argument == "default") ?
4426 doc_buffer->params().getDefaultOutputFormat() : argument;
4428 if ((dest.empty() && doc_buffer->isUnnamed())
4429 || !target_dir.isDirWritable()) {
4430 exportBufferAs(*doc_buffer, from_utf8(format));
4433 /* TODO/Review: Is it a problem to also export the children?
4434 See the update_unincluded flag */
4435 d.asyncBufferProcessing(format,
4438 &GuiViewPrivate::exportAndDestroy,
4440 nullptr, cmd.allowAsync());
4441 // TODO Inform user about success
4445 case LFUN_BUFFER_EXPORT_AS: {
4446 LASSERT(doc_buffer, break);
4447 docstring f = cmd.argument();
4449 f = from_ascii(doc_buffer->params().getDefaultOutputFormat());
4450 exportBufferAs(*doc_buffer, f);
4454 case LFUN_BUFFER_UPDATE: {
4455 d.asyncBufferProcessing(argument,
4458 &GuiViewPrivate::compileAndDestroy,
4460 nullptr, cmd.allowAsync(), true);
4463 case LFUN_BUFFER_VIEW: {
4464 d.asyncBufferProcessing(argument,
4466 _("Previewing ..."),
4467 &GuiViewPrivate::previewAndDestroy,
4469 &Buffer::preview, cmd.allowAsync());
4472 case LFUN_MASTER_BUFFER_UPDATE: {
4473 d.asyncBufferProcessing(argument,
4474 (doc_buffer ? doc_buffer->masterBuffer() : nullptr),
4476 &GuiViewPrivate::compileAndDestroy,
4478 nullptr, cmd.allowAsync(), true);
4481 case LFUN_MASTER_BUFFER_VIEW: {
4482 d.asyncBufferProcessing(argument,
4483 (doc_buffer ? doc_buffer->masterBuffer() : nullptr),
4485 &GuiViewPrivate::previewAndDestroy,
4486 nullptr, &Buffer::preview, cmd.allowAsync());
4489 case LFUN_EXPORT_CANCEL: {
4493 case LFUN_BUFFER_SWITCH: {
4494 string const file_name = to_utf8(cmd.argument());
4495 if (!FileName::isAbsolute(file_name)) {
4497 dr.setMessage(_("Absolute filename expected."));
4501 Buffer * buffer = theBufferList().getBuffer(FileName(file_name));
4504 dr.setMessage(_("Document not loaded"));
4508 // Do we open or switch to the buffer in this view ?
4509 if (workArea(*buffer)
4510 || lyxrc.open_buffers_in_tabs || !documentBufferView()) {
4515 // Look for the buffer in other views
4516 QList<int> const ids = guiApp->viewIds();
4518 for (; i != ids.size(); ++i) {
4519 GuiView & gv = guiApp->view(ids[i]);
4520 if (gv.workArea(*buffer)) {
4522 gv.activateWindow();
4524 gv.setBuffer(buffer);
4529 // If necessary, open a new window as a last resort
4530 if (i == ids.size()) {
4531 lyx::dispatch(FuncRequest(LFUN_WINDOW_NEW));
4537 case LFUN_BUFFER_NEXT:
4538 gotoNextOrPreviousBuffer(NEXT, false);
4541 case LFUN_BUFFER_MOVE_NEXT:
4542 gotoNextOrPreviousBuffer(NEXT, true);
4545 case LFUN_BUFFER_PREVIOUS:
4546 gotoNextOrPreviousBuffer(PREV, false);
4549 case LFUN_BUFFER_MOVE_PREVIOUS:
4550 gotoNextOrPreviousBuffer(PREV, true);
4553 case LFUN_BUFFER_CHKTEX:
4554 LASSERT(doc_buffer, break);
4555 doc_buffer->runChktex();
4558 case LFUN_CHANGES_TRACK: {
4559 // the actual dispatch is done in Buffer
4560 dispatchToBufferView(cmd, dr);
4561 // but we inform the GUI (document settings) if this is toggled
4562 LASSERT(doc_buffer, break);
4563 Q_EMIT changeTrackingToggled(doc_buffer->params().track_changes);
4567 case LFUN_COMMAND_EXECUTE: {
4568 command_execute_ = true;
4569 minibuffer_focus_ = true;
4572 case LFUN_DROP_LAYOUTS_CHOICE:
4573 d.layout_->showPopup();
4576 case LFUN_MENU_OPEN:
4577 if (QMenu * menu = guiApp->menus().menu(toqstr(cmd.argument()), *this))
4578 menu->exec(QCursor::pos());
4581 case LFUN_FILE_INSERT: {
4582 bool const ignore_lang = cmd.getArg(1) == "ignorelang";
4583 if (insertLyXFile(from_utf8(cmd.getArg(0)), ignore_lang)) {
4584 dr.forceBufferUpdate();
4585 dr.screenUpdate(Update::Force);
4590 case LFUN_FILE_INSERT_PLAINTEXT:
4591 case LFUN_FILE_INSERT_PLAINTEXT_PARA: {
4592 string const fname = to_utf8(cmd.argument());
4593 if (!fname.empty() && !FileName::isAbsolute(fname)) {
4594 dr.setMessage(_("Absolute filename expected."));
4598 FileName filename(fname);
4599 if (fname.empty()) {
4600 FileDialog dlg(qt_("Select file to insert"));
4602 FileDialog::Result result = dlg.open(toqstr(bv->buffer().filePath()),
4603 QStringList(qt_("All Files")+ " " + wildcardAllFiles()));
4605 if (result.first == FileDialog::Later || result.second.isEmpty()) {
4606 dr.setMessage(_("Canceled."));
4610 filename.set(fromqstr(result.second));
4614 FuncRequest const new_cmd(cmd, filename.absoluteFilePath());
4615 bv->dispatch(new_cmd, dr);
4620 case LFUN_BUFFER_RELOAD: {
4621 LASSERT(doc_buffer, break);
4624 bool drop = (cmd.argument() == "dump");
4627 if (!drop && !doc_buffer->isClean()) {
4628 docstring const file =
4629 makeDisplayPath(doc_buffer->absFileName(), 20);
4630 if (doc_buffer->notifiesExternalModification()) {
4631 docstring text = _("The current version will be lost. "
4632 "Are you sure you want to load the version on disk "
4633 "of the document %1$s?");
4634 ret = Alert::prompt(_("Reload saved document?"),
4635 bformat(text, file), 1, 1,
4636 _("&Reload"), _("&Cancel"));
4638 docstring text = _("Any changes will be lost. "
4639 "Are you sure you want to revert to the saved version "
4640 "of the document %1$s?");
4641 ret = Alert::prompt(_("Revert to saved document?"),
4642 bformat(text, file), 1, 1,
4643 _("&Revert"), _("&Cancel"));
4648 doc_buffer->markClean();
4649 reloadBuffer(*doc_buffer);
4650 dr.forceBufferUpdate();
4655 case LFUN_BUFFER_RESET_EXPORT:
4656 LASSERT(doc_buffer, break);
4657 doc_buffer->requireFreshStart(true);
4658 dr.setMessage(_("Buffer export reset."));
4661 case LFUN_BUFFER_WRITE:
4662 LASSERT(doc_buffer, break);
4663 saveBuffer(*doc_buffer);
4666 case LFUN_BUFFER_WRITE_AS:
4667 LASSERT(doc_buffer, break);
4668 renameBuffer(*doc_buffer, cmd.argument());
4671 case LFUN_BUFFER_WRITE_AS_TEMPLATE:
4672 LASSERT(doc_buffer, break);
4673 renameBuffer(*doc_buffer, cmd.argument(),
4674 LV_WRITE_AS_TEMPLATE);
4677 case LFUN_BUFFER_WRITE_ALL: {
4678 Buffer * first = theBufferList().first();
4681 message(_("Saving all documents..."));
4682 // We cannot use a for loop as the buffer list cycles.
4685 if (!b->isClean()) {
4687 LYXERR(Debug::ACTION, "Saved " << b->absFileName());
4689 b = theBufferList().next(b);
4690 } while (b != first);
4691 dr.setMessage(_("All documents saved."));
4695 case LFUN_MASTER_BUFFER_FORALL: {
4699 FuncRequest funcToRun = lyxaction.lookupFunc(cmd.getLongArg(0));
4700 funcToRun.allowAsync(false);
4702 for (Buffer const * buf : doc_buffer->allRelatives()) {
4703 // Switch to other buffer view and resend cmd
4704 lyx::dispatch(FuncRequest(
4705 LFUN_BUFFER_SWITCH, buf->absFileName()));
4706 lyx::dispatch(funcToRun);
4709 lyx::dispatch(FuncRequest(
4710 LFUN_BUFFER_SWITCH, doc_buffer->absFileName()));
4714 case LFUN_BUFFER_EXTERNAL_MODIFICATION_CLEAR:
4715 LASSERT(doc_buffer, break);
4716 doc_buffer->clearExternalModification();
4719 case LFUN_BUFFER_CLOSE:
4723 case LFUN_BUFFER_CLOSE_ALL:
4727 case LFUN_DEVEL_MODE_TOGGLE:
4728 devel_mode_ = !devel_mode_;
4730 dr.setMessage(_("Developer mode is now enabled."));
4732 dr.setMessage(_("Developer mode is now disabled."));
4735 case LFUN_TOOLBAR_SET: {
4736 string const name = cmd.getArg(0);
4737 string const state = cmd.getArg(1);
4738 if (GuiToolbar * t = toolbar(name))
4743 case LFUN_TOOLBAR_TOGGLE: {
4744 string const name = cmd.getArg(0);
4745 if (GuiToolbar * t = toolbar(name))
4750 case LFUN_TOOLBAR_MOVABLE: {
4751 string const name = cmd.getArg(0);
4753 // toggle (all) toolbars movablility
4754 toolbarsMovable_ = !toolbarsMovable_;
4755 for (ToolbarInfo const & ti : guiApp->toolbars()) {
4756 GuiToolbar * tb = toolbar(ti.name);
4757 if (tb && tb->isMovable() != toolbarsMovable_)
4758 // toggle toolbar movablity if it does not fit lock
4759 // (all) toolbars positions state silent = true, since
4760 // status bar notifications are slow
4763 if (toolbarsMovable_)
4764 dr.setMessage(_("Toolbars unlocked."));
4766 dr.setMessage(_("Toolbars locked."));
4767 } else if (GuiToolbar * tb = toolbar(name))
4768 // toggle current toolbar movablity
4770 // update lock (all) toolbars positions
4771 updateLockToolbars();
4775 case LFUN_ICON_SIZE: {
4776 QSize size = d.iconSize(cmd.argument());
4778 dr.setMessage(bformat(_("Icon size set to %1$dx%2$d."),
4779 size.width(), size.height()));
4783 case LFUN_DIALOG_UPDATE: {
4784 string const name = to_utf8(cmd.argument());
4785 if (name == "prefs" || name == "document")
4786 updateDialog(name, string());
4787 else if (name == "paragraph")
4788 lyx::dispatch(FuncRequest(LFUN_PARAGRAPH_UPDATE));
4789 else if (currentBufferView()) {
4790 Inset * inset = currentBufferView()->editedInset(name);
4791 // Can only update a dialog connected to an existing inset
4793 // FIXME: get rid of this indirection; GuiView ask the inset
4794 // if he is kind enough to update itself...
4795 FuncRequest fr(LFUN_INSET_DIALOG_UPDATE, cmd.argument());
4796 //FIXME: pass DispatchResult here?
4797 inset->dispatch(currentBufferView()->cursor(), fr);
4803 case LFUN_DIALOG_TOGGLE: {
4804 FuncCode const func_code = isDialogVisible(cmd.getArg(0))
4805 ? LFUN_DIALOG_HIDE : LFUN_DIALOG_SHOW;
4806 dispatch(FuncRequest(func_code, cmd.argument()), dr);
4810 case LFUN_DIALOG_DISCONNECT_INSET:
4811 disconnectDialog(to_utf8(cmd.argument()));
4814 case LFUN_DIALOG_HIDE: {
4815 guiApp->hideDialogs(to_utf8(cmd.argument()), nullptr);
4819 case LFUN_DIALOG_SHOW: {
4820 string const name = cmd.getArg(0);
4821 string sdata = trim(to_utf8(cmd.argument()).substr(name.size()));
4823 if (name == "latexlog") {
4824 // getStatus checks that
4825 LASSERT(doc_buffer, break);
4826 Buffer::LogType type;
4827 string const logfile = doc_buffer->logName(&type);
4829 case Buffer::latexlog:
4832 case Buffer::buildlog:
4833 sdata = "literate ";
4836 sdata += Lexer::quoteString(logfile);
4837 showDialog("log", sdata);
4838 } else if (name == "vclog") {
4839 // getStatus checks that
4840 LASSERT(doc_buffer, break);
4841 string const sdata2 = "vc " +
4842 Lexer::quoteString(doc_buffer->lyxvc().getLogFile());
4843 showDialog("log", sdata2);
4844 } else if (name == "symbols") {
4845 sdata = bv->cursor().getEncoding()->name();
4847 showDialog("symbols", sdata);
4848 } else if (name == "findreplace") {
4849 sdata = to_utf8(bv->cursor().selectionAsString(false));
4850 showDialog(name, sdata);
4852 } else if (name == "prefs" && isFullScreen()) {
4853 lfunUiToggle("fullscreen");
4854 showDialog("prefs", sdata);
4856 showDialog(name, sdata);
4861 dr.setMessage(cmd.argument());
4864 case LFUN_UI_TOGGLE: {
4865 string arg = cmd.getArg(0);
4866 if (!lfunUiToggle(arg)) {
4867 docstring const msg = "ui-toggle " + _("%1$s unknown command!");
4868 dr.setMessage(bformat(msg, from_utf8(arg)));
4870 // Make sure the keyboard focus stays in the work area.
4875 case LFUN_VIEW_SPLIT: {
4876 LASSERT(doc_buffer, break);
4877 string const orientation = cmd.getArg(0);
4878 d.splitter_->setOrientation(orientation == "vertical"
4879 ? Qt::Vertical : Qt::Horizontal);
4880 TabWorkArea * twa = addTabWorkArea();
4881 GuiWorkArea * wa = twa->addWorkArea(*doc_buffer, *this);
4883 wa->bufferView().copySettingsFrom(*bv);
4884 dr.screenUpdate(Update::ForceAll);
4885 setCurrentWorkArea(wa);
4889 case LFUN_TAB_GROUP_NEXT:
4890 gotoNextTabWorkArea(NEXT);
4893 case LFUN_TAB_GROUP_PREVIOUS:
4894 gotoNextTabWorkArea(PREV);
4897 case LFUN_TAB_GROUP_CLOSE:
4898 if (TabWorkArea * twa = d.currentTabWorkArea()) {
4899 closeTabWorkArea(twa);
4900 d.current_work_area_ = nullptr;
4901 twa = d.currentTabWorkArea();
4902 // Switch to the next GuiWorkArea in the found TabWorkArea.
4904 // Make sure the work area is up to date.
4905 setCurrentWorkArea(twa->currentWorkArea());
4907 setCurrentWorkArea(nullptr);
4912 case LFUN_VIEW_CLOSE:
4913 if (TabWorkArea * twa = d.currentTabWorkArea()) {
4914 closeWorkArea(twa->currentWorkArea());
4915 d.current_work_area_ = nullptr;
4916 twa = d.currentTabWorkArea();
4917 // Switch to the next GuiWorkArea in the found TabWorkArea.
4919 // Make sure the work area is up to date.
4920 setCurrentWorkArea(twa->currentWorkArea());
4922 setCurrentWorkArea(nullptr);
4927 case LFUN_COMPLETION_INLINE:
4928 if (d.current_work_area_)
4929 d.current_work_area_->completer().showInline();
4932 case LFUN_COMPLETION_POPUP:
4933 if (d.current_work_area_)
4934 d.current_work_area_->completer().showPopup();
4939 if (d.current_work_area_)
4940 d.current_work_area_->completer().tab();
4943 case LFUN_COMPLETION_CANCEL:
4944 if (d.current_work_area_) {
4945 if (d.current_work_area_->completer().popupVisible())
4946 d.current_work_area_->completer().hidePopup();
4948 d.current_work_area_->completer().hideInline();
4952 case LFUN_COMPLETION_ACCEPT:
4953 if (d.current_work_area_)
4954 d.current_work_area_->completer().activate();
4957 case LFUN_BUFFER_ZOOM_IN:
4958 case LFUN_BUFFER_ZOOM_OUT:
4959 case LFUN_BUFFER_ZOOM: {
4960 zoom_ratio_ = zoomRatio(cmd, zoom_ratio_);
4962 // Actual zoom value: default zoom + fractional extra value
4963 int zoom = (int)(lyxrc.defaultZoom * zoom_ratio_);
4964 zoom = min(max(zoom, zoom_min_), zoom_max_);
4966 setCurrentZoom(zoom);
4968 dr.setMessage(bformat(_("Zoom level is now %1$d% (default value: %2$d%)"),
4969 lyxrc.currentZoom, lyxrc.defaultZoom));
4971 guiApp->fontLoader().update();
4972 // Regenerate instant previews
4973 if (lyxrc.preview != LyXRC::PREVIEW_OFF
4974 && doc_buffer && doc_buffer->loader())
4975 doc_buffer->loader()->refreshPreviews();
4976 dr.screenUpdate(Update::ForceAll | Update::FitCursor);
4980 case LFUN_VC_REGISTER:
4981 case LFUN_VC_RENAME:
4983 case LFUN_VC_CHECK_IN:
4984 case LFUN_VC_CHECK_OUT:
4985 case LFUN_VC_REPO_UPDATE:
4986 case LFUN_VC_LOCKING_TOGGLE:
4987 case LFUN_VC_REVERT:
4988 case LFUN_VC_UNDO_LAST:
4989 case LFUN_VC_COMMAND:
4990 case LFUN_VC_COMPARE:
4991 dispatchVC(cmd, dr);
4994 case LFUN_SERVER_GOTO_FILE_ROW:
4995 if(goToFileRow(to_utf8(cmd.argument())))
4996 dr.screenUpdate(Update::Force | Update::FitCursor);
4999 case LFUN_LYX_ACTIVATE:
5003 case LFUN_WINDOW_RAISE:
5009 case LFUN_FORWARD_SEARCH: {
5010 // it seems safe to assume we have a document buffer, since
5011 // getStatus wants one.
5012 LASSERT(doc_buffer, break);
5013 Buffer const * doc_master = doc_buffer->masterBuffer();
5014 FileName const path(doc_master->temppath());
5015 string const texname = doc_master->isChild(doc_buffer)
5016 ? DocFileName(changeExtension(
5017 doc_buffer->absFileName(),
5018 "tex")).mangledFileName()
5019 : doc_buffer->latexName();
5020 string const fulltexname =
5021 support::makeAbsPath(texname, doc_master->temppath()).absFileName();
5022 string const mastername =
5023 removeExtension(doc_master->latexName());
5024 FileName const dviname(addName(path.absFileName(),
5025 addExtension(mastername, "dvi")));
5026 FileName const pdfname(addName(path.absFileName(),
5027 addExtension(mastername, "pdf")));
5028 bool const have_dvi = dviname.exists();
5029 bool const have_pdf = pdfname.exists();
5030 if (!have_dvi && !have_pdf) {
5031 dr.setMessage(_("Please, preview the document first."));
5034 bool const goto_dvi = have_dvi && !lyxrc.forward_search_dvi.empty();
5035 bool const goto_pdf = have_pdf && !lyxrc.forward_search_pdf.empty();
5036 string outname = dviname.onlyFileName();
5037 string command = lyxrc.forward_search_dvi;
5038 if ((!goto_dvi || goto_pdf) &&
5039 pdfname.lastModified() > dviname.lastModified()) {
5040 outname = pdfname.onlyFileName();
5041 command = lyxrc.forward_search_pdf;
5044 DocIterator cur = bv->cursor();
5045 int row = doc_buffer->texrow().rowFromDocIterator(cur).first;
5046 LYXERR(Debug::ACTION, "Forward search: row:" << row
5048 if (row == -1 || command.empty()) {
5049 dr.setMessage(_("Couldn't proceed."));
5052 string texrow = convert<string>(row);
5054 command = subst(command, "$$n", texrow);
5055 command = subst(command, "$$f", fulltexname);
5056 command = subst(command, "$$t", texname);
5057 command = subst(command, "$$o", outname);
5059 volatile PathChanger p(path);
5061 one.startscript(Systemcall::DontWait, command);
5065 case LFUN_SPELLING_CONTINUOUSLY:
5066 lyxrc.spellcheck_continuously = !lyxrc.spellcheck_continuously;
5067 dr.screenUpdate(Update::Force);
5070 case LFUN_CITATION_OPEN: {
5071 LASSERT(doc_buffer, break);
5072 frontend::showTarget(argument, *doc_buffer);
5077 // The LFUN must be for one of BufferView, Buffer or Cursor;
5079 dispatchToBufferView(cmd, dr);
5083 // Need to update bv because many LFUNs here might have destroyed it
5084 bv = currentBufferView();
5086 // Clear non-empty selections
5087 // (e.g. from a "char-forward-select" followed by "char-backward-select")
5089 Cursor & cur = bv->cursor();
5090 if ((cur.selection() && cur.selBegin() == cur.selEnd())) {
5091 cur.clearSelection();
5097 bool GuiView::lfunUiToggle(string const & ui_component)
5099 if (ui_component == "scrollbar") {
5100 // hide() is of no help
5101 if (d.current_work_area_->verticalScrollBarPolicy() ==
5102 Qt::ScrollBarAlwaysOff)
5104 d.current_work_area_->setVerticalScrollBarPolicy(
5105 Qt::ScrollBarAsNeeded);
5107 d.current_work_area_->setVerticalScrollBarPolicy(
5108 Qt::ScrollBarAlwaysOff);
5109 } else if (ui_component == "statusbar") {
5110 statusBar()->setVisible(!statusBar()->isVisible());
5111 } else if (ui_component == "menubar") {
5112 menuBar()->setVisible(!menuBar()->isVisible());
5113 } else if (ui_component == "zoomlevel") {
5114 zoom_value_->setVisible(!zoom_value_->isVisible());
5115 } else if (ui_component == "zoomslider") {
5116 zoom_widget_->setVisible(!zoom_widget_->isVisible());
5117 } else if (ui_component == "statistics-w") {
5118 word_count_enabled_ = !word_count_enabled_;
5121 } else if (ui_component == "statistics-cb") {
5122 char_count_enabled_ = !char_count_enabled_;
5125 } else if (ui_component == "statistics-c") {
5126 char_nb_count_enabled_ = !char_nb_count_enabled_;
5129 } else if (ui_component == "frame") {
5130 int const l = contentsMargins().left();
5132 //are the frames in default state?
5133 d.current_work_area_->setFrameStyle(QFrame::NoFrame);
5135 #if QT_VERSION > 0x050903
5136 setAttribute(Qt::WA_ContentsMarginsRespectsSafeArea, false);
5138 setContentsMargins(-2, -2, -2, -2);
5140 #if QT_VERSION > 0x050903
5141 setAttribute(Qt::WA_ContentsMarginsRespectsSafeArea, true);
5143 setContentsMargins(0, 0, 0, 0);
5146 if (ui_component == "fullscreen") {
5150 stat_counts_->setVisible(statsEnabled());
5155 void GuiView::cancelExport()
5157 Systemcall::killscript();
5158 // stop busy signal immediately so that in the subsequent
5159 // "Export canceled" prompt the status bar icons are accurate.
5160 Q_EMIT scriptKilled();
5164 void GuiView::toggleFullScreen()
5166 setWindowState(windowState() ^ Qt::WindowFullScreen);
5170 Buffer const * GuiView::updateInset(Inset const * inset)
5175 Buffer const * inset_buffer = &(inset->buffer());
5177 for (int i = 0; i != d.splitter_->count(); ++i) {
5178 GuiWorkArea * wa = d.tabWorkArea(i)->currentWorkArea();
5181 Buffer const * buffer = &(wa->bufferView().buffer());
5182 if (inset_buffer == buffer)
5183 wa->scheduleRedraw(true);
5185 return inset_buffer;
5189 void GuiView::restartCaret()
5191 /* When we move around, or type, it's nice to be able to see
5192 * the caret immediately after the keypress.
5194 if (d.current_work_area_)
5195 d.current_work_area_->startBlinkingCaret();
5197 // Take this occasion to update the other GUI elements.
5203 void GuiView::updateCompletion(Cursor & cur, bool start, bool keep)
5205 if (d.current_work_area_)
5206 d.current_work_area_->completer().updateVisibility(cur, start, keep);
5211 // This list should be kept in sync with the list of insets in
5212 // src/insets/Inset.cpp. I.e., if a dialog goes with an inset, the
5213 // dialog should have the same name as the inset.
5214 // Changes should be also recorded in LFUN_DIALOG_SHOW doxygen
5215 // docs in LyXAction.cpp.
5217 char const * const dialognames[] = {
5219 "aboutlyx", "bibitem", "bibtex", "box", "branch", "changes", "character",
5220 "citation", "compare", "comparehistory", "counter", "document", "errorlist", "ert",
5221 "external", "file", "findreplace", "findreplaceadv", "float", "graphics",
5222 "href", "include", "index", "index_print", "info", "listings", "label", "line",
5223 "log", "lyxfiles", "mathdelimiter", "mathmatrix", "mathspace", "nomenclature",
5224 "nomencl_print", "note", "paragraph", "phantom", "prefs", "ref",
5225 "sendto", "space", "spellchecker", "symbols", "tabular", "tabularcreate",
5226 "thesaurus", "texinfo", "toc", "view-source", "vspace", "wrap", "progress"};
5228 char const * const * const end_dialognames =
5229 dialognames + (sizeof(dialognames) / sizeof(char *));
5233 cmpCStr(char const * name) : name_(name) {}
5234 bool operator()(char const * other) {
5235 return strcmp(other, name_) == 0;
5242 bool isValidName(string const & name)
5244 return find_if(dialognames, end_dialognames,
5245 cmpCStr(name.c_str())) != end_dialognames;
5251 void GuiView::resetDialogs()
5253 // Make sure that no LFUN uses any GuiView.
5254 guiApp->setCurrentView(nullptr);
5258 constructToolbars();
5259 guiApp->menus().fillMenuBar(menuBar(), this, false);
5260 d.layout_->updateContents(true);
5261 // Now update controls with current buffer.
5262 guiApp->setCurrentView(this);
5268 void GuiView::flatGroupBoxes(const QObject * widget, bool flag)
5270 for (QObject * child: widget->children()) {
5271 if (child->inherits("QGroupBox")) {
5272 QGroupBox * box = (QGroupBox*) child;
5275 flatGroupBoxes(child, flag);
5281 Dialog * GuiView::find(string const & name, bool hide_it) const
5283 if (!isValidName(name))
5286 map<string, DialogPtr>::iterator it = d.dialogs_.find(name);
5288 if (it != d.dialogs_.end()) {
5290 it->second->hideView();
5291 return it->second.get();
5297 Dialog * GuiView::findOrBuild(string const & name, bool hide_it)
5299 Dialog * dialog = find(name, hide_it);
5300 if (dialog != nullptr)
5303 dialog = build(name);
5306 d.dialogs_[name].reset(dialog);
5307 // Force a uniform style for group boxes
5308 // On Mac non-flat works better, on Linux flat is standard
5309 flatGroupBoxes(dialog->asQWidget(), guiApp->platformName() != "cocoa");
5310 if (lyxrc.allow_geometry_session)
5311 dialog->restoreSession();
5319 void GuiView::showDialog(string const & name, string const & sdata,
5322 triggerShowDialog(toqstr(name), toqstr(sdata), inset);
5326 void GuiView::doShowDialog(QString const & qname, QString const & qdata,
5332 const string name = fromqstr(qname);
5333 const string sdata = fromqstr(qdata);
5337 Dialog * dialog = findOrBuild(name, false);
5339 bool const visible = dialog->isVisibleView();
5340 dialog->showData(sdata);
5341 if (currentBufferView())
5342 currentBufferView()->editInset(name, inset);
5343 // We only set the focus to the new dialog if it was not yet
5344 // visible in order not to change the existing previous behaviour
5346 // activateWindow is needed for floating dockviews
5347 dialog->asQWidget()->raise();
5348 dialog->asQWidget()->activateWindow();
5349 if (dialog->wantInitialFocus())
5350 dialog->asQWidget()->setFocus();
5354 catch (ExceptionMessage const &) {
5362 bool GuiView::isDialogVisible(string const & name) const
5364 map<string, DialogPtr>::const_iterator it = d.dialogs_.find(name);
5365 if (it == d.dialogs_.end())
5367 return it->second.get()->isVisibleView() && !it->second.get()->isClosing();
5371 void GuiView::hideDialog(string const & name, Inset * inset)
5373 map<string, DialogPtr>::const_iterator it = d.dialogs_.find(name);
5374 if (it == d.dialogs_.end())
5378 if (!currentBufferView())
5380 if (inset != currentBufferView()->editedInset(name))
5384 Dialog * const dialog = it->second.get();
5385 if (dialog->isVisibleView())
5387 if (currentBufferView())
5388 currentBufferView()->editInset(name, nullptr);
5392 void GuiView::disconnectDialog(string const & name)
5394 if (!isValidName(name))
5396 if (currentBufferView())
5397 currentBufferView()->editInset(name, nullptr);
5401 void GuiView::hideAll() const
5403 for(auto const & dlg_p : d.dialogs_)
5404 dlg_p.second->hideView();
5408 void GuiView::updateDialogs()
5410 for(auto const & dlg_p : d.dialogs_) {
5411 Dialog * dialog = dlg_p.second.get();
5413 if (dialog->needBufferOpen() && !documentBufferView())
5414 hideDialog(fromqstr(dialog->name()), nullptr);
5415 else if (dialog->isVisibleView())
5416 dialog->checkStatus();
5424 Dialog * GuiView::build(string const & name)
5426 return createDialog(*this, name);
5430 SEMenu::SEMenu(QWidget * parent)
5432 QAction * action = addAction(qt_("Disable Shell Escape"));
5433 connect(action, SIGNAL(triggered()),
5434 parent, SLOT(disableShellEscape()));
5438 void PressableSvgWidget::mousePressEvent(QMouseEvent * event)
5440 if (event->button() == Qt::LeftButton) {
5445 } // namespace frontend
5448 #include "moc_GuiView.cpp"