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 #if QT_VERSION >= 0x050200
1126 // set unified mac toolbars only when not movable as recommended:
1127 // https://doc.qt.io/qt-5/qmainwindow.html#unifiedTitleAndToolBarOnMac-prop
1128 setUnifiedTitleAndToolBarOnMac(!toolbarsMovable_);
1133 void GuiView::constructToolbars()
1135 for (auto const & tb_p : d.toolbars_)
1137 d.toolbars_.clear();
1139 // I don't like doing this here, but the standard toolbar
1140 // destroys this object when it's destroyed itself (vfr)
1141 d.layout_ = new LayoutBox(*this);
1142 d.stack_widget_->addWidget(d.layout_);
1143 d.layout_->move(0,0);
1145 // extracts the toolbars from the backend
1146 for (ToolbarInfo const & inf : guiApp->toolbars())
1147 d.toolbars_[inf.name] = new GuiToolbar(inf, *this);
1149 DynamicMenuButton::resetIconCache();
1153 void GuiView::initToolbars()
1155 // extracts the toolbars from the backend
1156 for (ToolbarInfo const & inf : guiApp->toolbars())
1157 initToolbar(inf.name);
1161 void GuiView::initToolbar(string const & name)
1163 GuiToolbar * tb = toolbar(name);
1166 int const visibility = guiApp->toolbars().defaultVisibility(name);
1167 bool newline = !(visibility & Toolbars::SAMEROW);
1168 tb->setVisible(false);
1169 tb->setVisibility(visibility);
1171 if (visibility & Toolbars::TOP) {
1173 addToolBarBreak(Qt::TopToolBarArea);
1174 addToolBar(Qt::TopToolBarArea, tb);
1177 if (visibility & Toolbars::BOTTOM) {
1179 addToolBarBreak(Qt::BottomToolBarArea);
1180 addToolBar(Qt::BottomToolBarArea, tb);
1183 if (visibility & Toolbars::LEFT) {
1185 addToolBarBreak(Qt::LeftToolBarArea);
1186 addToolBar(Qt::LeftToolBarArea, tb);
1189 if (visibility & Toolbars::RIGHT) {
1191 addToolBarBreak(Qt::RightToolBarArea);
1192 addToolBar(Qt::RightToolBarArea, tb);
1195 if (visibility & Toolbars::ON)
1196 tb->setVisible(true);
1198 tb->setMovable(true);
1202 TocModels & GuiView::tocModels()
1204 return d.toc_models_;
1208 void GuiView::setFocus()
1210 LYXERR(Debug::DEBUG, "GuiView::setFocus()" << this);
1211 QMainWindow::setFocus();
1215 bool GuiView::hasFocus() const
1217 if (currentWorkArea())
1218 return currentWorkArea()->hasFocus();
1219 if (currentMainWorkArea())
1220 return currentMainWorkArea()->hasFocus();
1221 return d.bg_widget_->hasFocus();
1225 void GuiView::focusInEvent(QFocusEvent * e)
1227 LYXERR(Debug::DEBUG, "GuiView::focusInEvent()" << this);
1228 QMainWindow::focusInEvent(e);
1229 // Make sure guiApp points to the correct view.
1230 guiApp->setCurrentView(this);
1231 if (currentWorkArea())
1232 currentWorkArea()->setFocus();
1233 else if (currentMainWorkArea())
1234 currentMainWorkArea()->setFocus();
1236 d.bg_widget_->setFocus();
1240 void GuiView::showEvent(QShowEvent * e)
1242 LYXERR(Debug::GUI, "Passed Geometry "
1243 << size().height() << "x" << size().width()
1244 << "+" << pos().x() << "+" << pos().y());
1246 if (d.splitter_->count() == 0)
1247 // No work area, switch to the background widget.
1251 QMainWindow::showEvent(e);
1255 bool GuiView::closeScheduled()
1262 bool GuiView::prepareAllBuffersForLogout()
1264 Buffer * first = theBufferList().first();
1268 // First, iterate over all buffers and ask the users if unsaved
1269 // changes should be saved.
1270 // We cannot use a for loop as the buffer list cycles.
1273 if (!saveBufferIfNeeded(*b, false))
1275 b = theBufferList().next(b);
1276 } while (b != first);
1278 // Next, save session state
1279 // When a view/window was closed before without quitting LyX, there
1280 // are already entries in the lastOpened list.
1281 theSession().lastOpened().clear();
1288 /** Destroy only all tabbed WorkAreas. Destruction of other WorkAreas
1289 ** is responsibility of the container (e.g., dialog)
1291 void GuiView::closeEvent(QCloseEvent * close_event)
1293 LYXERR(Debug::DEBUG, "GuiView::closeEvent()");
1295 // FIXME Bug #12828 bites here. If there is some other View open, then
1296 // we really should only refuse to close if one of the Buffers open here
1297 // is being processed.
1298 if (!GuiViewPrivate::busyBuffers.isEmpty()) {
1299 Alert::warning(_("Exit LyX"),
1300 _("LyX could not be closed because documents are being processed by LyX."));
1301 close_event->setAccepted(false);
1305 // If the user pressed the x (so we didn't call closeView
1306 // programmatically), we want to clear all existing entries.
1308 theSession().lastOpened().clear();
1313 // it can happen that this event arrives without selecting the view,
1314 // e.g. when clicking the close button on a background window.
1316 if (!closeWorkAreaAll()) {
1318 close_event->ignore();
1322 // Make sure that nothing will use this to be closed View.
1323 guiApp->unregisterView(this);
1325 if (isFullScreen()) {
1326 // Switch off fullscreen before closing.
1331 // Make sure the timer time out will not trigger a statusbar update.
1332 d.statusbar_timer_.stop();
1333 d.statusbar_stats_timer_.stop();
1335 // Saving fullscreen requires additional tweaks in the toolbar code.
1336 // It wouldn't also work under linux natively.
1337 if (lyxrc.allow_geometry_session) {
1342 close_event->accept();
1346 void GuiView::dragEnterEvent(QDragEnterEvent * event)
1348 if (event->mimeData()->hasUrls())
1350 /// \todo Ask lyx-devel is this is enough:
1351 /// if (event->mimeData()->hasFormat("text/plain"))
1352 /// event->acceptProposedAction();
1356 void GuiView::dropEvent(QDropEvent * event)
1358 QList<QUrl> files = event->mimeData()->urls();
1359 if (files.isEmpty())
1362 LYXERR(Debug::GUI, "GuiView::dropEvent: got URLs!");
1363 for (int i = 0; i != files.size(); ++i) {
1364 string const file = os::internal_path(fromqstr(
1365 files.at(i).toLocalFile()));
1369 string const ext = support::getExtension(file);
1370 vector<const Format *> found_formats;
1372 // Find all formats that have the correct extension.
1373 for (const Format * fmt : theConverters().importableFormats())
1374 if (fmt->hasExtension(ext))
1375 found_formats.push_back(fmt);
1378 if (!found_formats.empty()) {
1379 if (found_formats.size() > 1) {
1380 //FIXME: show a dialog to choose the correct importable format
1381 LYXERR(Debug::FILES,
1382 "Multiple importable formats found, selecting first");
1384 string const arg = found_formats[0]->name() + " " + file;
1385 cmd = FuncRequest(LFUN_BUFFER_IMPORT, arg);
1388 //FIXME: do we have to explicitly check whether it's a lyx file?
1389 LYXERR(Debug::FILES,
1390 "No formats found, trying to open it as a lyx file");
1391 cmd = FuncRequest(LFUN_FILE_OPEN, file);
1393 // add the functions to the queue
1394 guiApp->addToFuncRequestQueue(cmd);
1397 // now process the collected functions. We perform the events
1398 // asynchronously. This prevents potential problems in case the
1399 // BufferView is closed within an event.
1400 guiApp->processFuncRequestQueueAsync();
1404 void GuiView::message(docstring const & str)
1406 if (ForkedProcess::iAmAChild())
1409 // call is moved to GUI-thread by GuiProgress
1410 d.progress_->appendMessage(toqstr(str));
1414 void GuiView::clearMessageText()
1416 message(docstring());
1420 void GuiView::updateStatusBarMessage(QString const & str)
1422 statusBar()->showMessage(str);
1423 d.statusbar_timer_.stop();
1424 d.statusbar_timer_.start(3000);
1428 void GuiView::clearMessage()
1430 // FIXME: This code was introduced in r19643 to fix bug #4123. However,
1431 // the hasFocus function mostly returns false, even if the focus is on
1432 // a workarea in this view.
1436 d.statusbar_timer_.stop();
1439 void GuiView::showStats()
1441 if (!statsEnabled())
1444 d.time_to_update -= d.timer_rate;
1446 BufferView * bv = currentBufferView();
1447 Buffer * buf = bv ? &bv->buffer() : nullptr;
1449 stat_counts_->hide();
1453 Cursor const & cur = bv->cursor();
1455 // we start new selection and need faster update
1456 if (!d.already_in_selection_ && cur.selection())
1457 d.time_to_update = 0;
1459 if (d.time_to_update > 0)
1462 DocIterator from, to;
1463 if (cur.selection()) {
1464 from = cur.selectionBegin();
1465 to = cur.selectionEnd();
1466 d.already_in_selection_ = true;
1468 from = doc_iterator_begin(buf);
1469 to = doc_iterator_end(buf);
1470 d.already_in_selection_ = false;
1473 buf->updateStatistics(from, to);
1476 if (word_count_enabled_) {
1477 int const words = buf->wordCount();
1479 stats << toqstr(bformat(_("%1$d Word"), words));
1481 stats << toqstr(bformat(_("%1$d Words"), words));
1483 int const chars_with_blanks = buf->charCount(true);
1484 if (char_count_enabled_) {
1485 if (chars_with_blanks == 1)
1486 stats << toqstr(bformat(_("%1$d Character"), chars_with_blanks));
1488 stats << toqstr(bformat(_("%1$d Characters"), chars_with_blanks));
1490 if (char_nb_count_enabled_) {
1491 int const chars = buf->charCount(false);
1493 stats << toqstr(bformat(_("%1$d Character (no Blanks)"), chars));
1495 stats << toqstr(bformat(_("%1$d Characters (no Blanks)"), chars));
1497 stat_counts_->setText(stats.join(qt_(", [[stats separator]]")));
1498 stat_counts_->show();
1500 d.time_to_update = d.default_stats_rate;
1501 // fast updates for small selections
1502 if (chars_with_blanks < d.max_sel_chars && cur.selection())
1503 d.time_to_update = d.timer_rate;
1507 void GuiView::updateWindowTitle(GuiWorkArea * wa)
1509 if (wa != d.current_work_area_
1510 || wa->bufferView().buffer().isInternal())
1512 Buffer const & buf = wa->bufferView().buffer();
1513 // Set the windows title
1514 docstring title = buf.fileName().displayName(130) + from_ascii("[*]");
1515 if (buf.notifiesExternalModification()) {
1516 title = bformat(_("%1$s (modified externally)"), title);
1517 // If the external modification status has changed, then maybe the status of
1518 // buffer-save has changed too.
1521 title += from_ascii(" - LyX");
1522 setWindowTitle(toqstr(title));
1523 // Sets the path for the window: this is used by OSX to
1524 // allow a context click on the title bar showing a menu
1525 // with the path up to the file
1526 setWindowFilePath(toqstr(buf.absFileName()));
1527 // Tell Qt whether the current document is changed
1528 setWindowModified(!buf.isClean());
1530 if (buf.params().shell_escape)
1531 shell_escape_->show();
1533 shell_escape_->hide();
1535 if (buf.hasReadonlyFlag())
1540 if (buf.lyxvc().inUse()) {
1541 version_control_->show();
1542 version_control_->setText(toqstr(buf.lyxvc().vcstatus()));
1544 version_control_->hide();
1548 void GuiView::on_currentWorkAreaChanged(GuiWorkArea * wa)
1550 if (d.current_work_area_)
1551 // disconnect the current work area from all slots
1552 QObject::disconnect(d.current_work_area_, nullptr, this, nullptr);
1554 disconnectBufferView();
1555 connectBufferView(wa->bufferView());
1556 connectBuffer(wa->bufferView().buffer());
1557 d.current_work_area_ = wa;
1558 QObject::connect(wa, SIGNAL(titleChanged(GuiWorkArea *)),
1559 this, SLOT(updateWindowTitle(GuiWorkArea *)));
1560 QObject::connect(wa, SIGNAL(busy(bool)),
1561 this, SLOT(setBusy(bool)));
1562 // connection of a signal to a signal
1563 QObject::connect(wa, SIGNAL(bufferViewChanged()),
1564 this, SIGNAL(bufferViewChanged()));
1565 Q_EMIT updateWindowTitle(wa);
1566 Q_EMIT bufferViewChanged();
1570 void GuiView::onBufferViewChanged()
1573 // Buffer-dependent dialogs must be updated. This is done here because
1574 // some dialogs require buffer()->text.
1576 zoom_slider_->setEnabled(currentBufferView());
1577 zoom_value_->setEnabled(currentBufferView());
1578 zoom_in_->setEnabled(currentBufferView()
1579 && zoom_slider_->value() < zoom_slider_->maximum());
1580 zoom_out_->setEnabled(currentBufferView()
1581 && zoom_slider_->value() > zoom_slider_->minimum());
1585 void GuiView::on_lastWorkAreaRemoved()
1588 // We already are in a close event. Nothing more to do.
1591 if (d.splitter_->count() > 1)
1592 // We have a splitter so don't close anything.
1595 // Reset and updates the dialogs.
1596 Q_EMIT bufferViewChanged();
1601 if (lyxrc.open_buffers_in_tabs)
1602 // Nothing more to do, the window should stay open.
1605 if (guiApp->viewIds().size() > 1) {
1611 // On Mac we also close the last window because the application stay
1612 // resident in memory. On other platforms we don't close the last
1613 // window because this would quit the application.
1619 void GuiView::updateStatusBar()
1621 // let the user see the explicit message
1622 if (d.statusbar_timer_.isActive())
1629 void GuiView::showMessage()
1633 QString msg = toqstr(theGuiApp()->viewStatusMessage());
1634 if (msg.isEmpty()) {
1635 BufferView const * bv = currentBufferView();
1637 msg = toqstr(bv->cursor().currentState(devel_mode_));
1639 msg = qt_("Welcome to LyX!");
1641 statusBar()->showMessage(msg);
1645 bool GuiView::statsEnabled() const
1647 return word_count_enabled_ || char_count_enabled_ || char_nb_count_enabled_;
1651 bool GuiView::event(QEvent * e)
1655 // Useful debug code:
1656 //case QEvent::ActivationChange:
1657 //case QEvent::WindowDeactivate:
1658 //case QEvent::Paint:
1659 //case QEvent::Enter:
1660 //case QEvent::Leave:
1661 //case QEvent::HoverEnter:
1662 //case QEvent::HoverLeave:
1663 //case QEvent::HoverMove:
1664 //case QEvent::StatusTip:
1665 //case QEvent::DragEnter:
1666 //case QEvent::DragLeave:
1667 //case QEvent::Drop:
1670 case QEvent::WindowStateChange: {
1671 QWindowStateChangeEvent * ev = (QWindowStateChangeEvent*)e;
1672 bool ofstate = (ev->oldState() & Qt::WindowFullScreen);
1673 bool result = QMainWindow::event(e);
1674 bool nfstate = (windowState() & Qt::WindowFullScreen);
1675 if (!ofstate && nfstate) {
1676 LYXERR(Debug::DEBUG, "GuiView: WindowStateChange(): full-screen " << nfstate);
1677 // switch to full-screen state
1678 if (lyxrc.full_screen_statusbar)
1679 statusBar()->hide();
1680 if (lyxrc.full_screen_menubar)
1682 if (lyxrc.full_screen_toolbars) {
1683 for (auto const & tb_p : d.toolbars_)
1684 if (tb_p.second->isVisibilityOn() && tb_p.second->isVisible())
1685 tb_p.second->hide();
1687 for (int i = 0; i != d.splitter_->count(); ++i)
1688 d.tabWorkArea(i)->setFullScreen(true);
1689 #if QT_VERSION > 0x050903
1690 //Qt's 5.9.4 ba44cdae38406c safe area measures won't allow us to go negative in margins
1691 setAttribute(Qt::WA_ContentsMarginsRespectsSafeArea, false);
1693 setContentsMargins(-2, -2, -2, -2);
1695 hideDialogs("prefs", nullptr);
1696 } else if (ofstate && !nfstate) {
1697 LYXERR(Debug::DEBUG, "GuiView: WindowStateChange(): full-screen " << nfstate);
1698 // switch back from full-screen state
1699 if (lyxrc.full_screen_statusbar && !statusBar()->isVisible())
1700 statusBar()->show();
1701 if (lyxrc.full_screen_menubar && !menuBar()->isVisible())
1703 if (lyxrc.full_screen_toolbars) {
1704 for (auto const & tb_p : d.toolbars_)
1705 if (tb_p.second->isVisibilityOn() && !tb_p.second->isVisible())
1706 tb_p.second->show();
1709 for (int i = 0; i != d.splitter_->count(); ++i)
1710 d.tabWorkArea(i)->setFullScreen(false);
1711 #if QT_VERSION > 0x050903
1712 setAttribute(Qt::WA_ContentsMarginsRespectsSafeArea, true);
1714 setContentsMargins(0, 0, 0, 0);
1719 case QEvent::WindowActivate: {
1720 GuiView * old_view = guiApp->currentView();
1721 if (this == old_view) {
1723 return QMainWindow::event(e);
1725 if (old_view && old_view->currentBufferView()) {
1726 // save current selection to the selection buffer to allow
1727 // middle-button paste in this window.
1728 cap::saveSelection(old_view->currentBufferView()->cursor());
1730 guiApp->setCurrentView(this);
1731 if (d.current_work_area_)
1732 on_currentWorkAreaChanged(d.current_work_area_);
1736 return QMainWindow::event(e);
1739 case QEvent::ShortcutOverride: {
1741 if (isFullScreen() && menuBar()->isHidden()) {
1742 QKeyEvent * ke = static_cast<QKeyEvent*>(e);
1743 // FIXME: we should also try to detect special LyX shortcut such as
1744 // Alt-P and Alt-M. Right now there is a hack in
1745 // GuiWorkArea::processKeySym() that hides again the menubar for
1747 if (ke->modifiers() & Qt::AltModifier && ke->key() != Qt::Key_Alt) {
1749 return QMainWindow::event(e);
1752 return QMainWindow::event(e);
1755 case QEvent::Gesture: {
1756 QGestureEvent *ge = static_cast<QGestureEvent*>(e);
1757 QGesture *gp = ge->gesture(Qt::PinchGesture);
1759 QPinchGesture *pinch = static_cast<QPinchGesture *>(gp);
1760 QPinchGesture::ChangeFlags changeFlags = pinch->changeFlags();
1761 qreal totalScaleFactor = pinch->totalScaleFactor();
1762 LYXERR(Debug::GUI, "totalScaleFactor: " << totalScaleFactor);
1763 if (pinch->state() == Qt::GestureStarted) {
1764 initialZoom_ = lyxrc.currentZoom;
1765 LYXERR(Debug::GUI, "initialZoom_: " << initialZoom_);
1767 if (changeFlags & QPinchGesture::ScaleFactorChanged) {
1768 qreal factor = initialZoom_ * totalScaleFactor;
1769 LYXERR(Debug::GUI, "scaleFactor: " << factor);
1770 zoomValueChanged(factor);
1773 return QMainWindow::event(e);
1776 // dark/light mode runtime switch support, OS-dependent.
1778 // Limit to Q_OS_MAC as this unnecessarily would also
1779 // trigger on Linux with grave performance issues
1781 case QEvent::ApplicationPaletteChange: {
1782 // We need to update metrics here to avoid a crash (#12786)
1783 theBufferList().changed(true);
1785 return QMainWindow::event(e);
1789 case QEvent::StyleChange: {
1790 // We need to update metrics here to avoid a crash (#12786)
1791 theBufferList().changed(true);
1792 return QMainWindow::event(e);
1796 return QMainWindow::event(e);
1800 void GuiView::resetWindowTitle()
1802 setWindowTitle(qt_("LyX"));
1805 bool GuiView::focusNextPrevChild(bool /*next*/)
1812 bool GuiView::busy() const
1818 void GuiView::setBusy(bool busy)
1820 bool const busy_before = busy_ > 0;
1821 busy ? ++busy_ : --busy_;
1822 if ((busy_ > 0) == busy_before)
1823 // busy state didn't change
1827 QApplication::setOverrideCursor(Qt::WaitCursor);
1830 QApplication::restoreOverrideCursor();
1835 void GuiView::resetCommandExecute()
1837 command_execute_ = false;
1842 double GuiView::pixelRatio() const
1844 return qt_scale_factor * devicePixelRatio();
1848 GuiWorkArea * GuiView::workArea(int index)
1850 if (TabWorkArea * twa = d.currentTabWorkArea())
1851 if (index < twa->count())
1852 return twa->workArea(index);
1857 GuiWorkArea * GuiView::workArea(Buffer & buffer)
1859 if (currentWorkArea()
1860 && ¤tWorkArea()->bufferView().buffer() == &buffer)
1861 return currentWorkArea();
1862 if (TabWorkArea * twa = d.currentTabWorkArea())
1863 return twa->workArea(buffer);
1868 GuiWorkArea * GuiView::addWorkArea(Buffer & buffer)
1870 // Automatically create a TabWorkArea if there are none yet.
1871 TabWorkArea * tab_widget = d.splitter_->count()
1872 ? d.currentTabWorkArea() : addTabWorkArea();
1873 return tab_widget->addWorkArea(buffer, *this);
1877 TabWorkArea * GuiView::addTabWorkArea()
1879 TabWorkArea * twa = new TabWorkArea;
1880 QObject::connect(twa, SIGNAL(currentWorkAreaChanged(GuiWorkArea *)),
1881 this, SLOT(on_currentWorkAreaChanged(GuiWorkArea *)));
1882 QObject::connect(twa, SIGNAL(lastWorkAreaRemoved()),
1883 this, SLOT(on_lastWorkAreaRemoved()));
1885 d.splitter_->addWidget(twa);
1886 d.stack_widget_->setCurrentWidget(d.splitter_);
1891 GuiWorkArea const * GuiView::currentWorkArea() const
1893 return d.current_work_area_;
1897 GuiWorkArea * GuiView::currentWorkArea()
1899 return d.current_work_area_;
1903 GuiWorkArea const * GuiView::currentMainWorkArea() const
1905 if (!d.currentTabWorkArea())
1907 return d.currentTabWorkArea()->currentWorkArea();
1911 GuiWorkArea * GuiView::currentMainWorkArea()
1913 if (!d.currentTabWorkArea())
1915 return d.currentTabWorkArea()->currentWorkArea();
1919 void GuiView::setCurrentWorkArea(GuiWorkArea * wa)
1921 LYXERR(Debug::DEBUG, "Setting current wa: " << wa << endl);
1923 d.current_work_area_ = nullptr;
1925 Q_EMIT bufferViewChanged();
1929 // FIXME: I've no clue why this is here and why it accesses
1930 // theGuiApp()->currentView, which might be 0 (bug 6464).
1931 // See also 27525 (vfr).
1932 if (theGuiApp()->currentView() == this
1933 && theGuiApp()->currentView()->currentWorkArea() == wa)
1936 if (currentBufferView())
1937 cap::saveSelection(currentBufferView()->cursor());
1939 theGuiApp()->setCurrentView(this);
1940 d.current_work_area_ = wa;
1942 // We need to reset this now, because it will need to be
1943 // right if the tabWorkArea gets reset in the for loop. We
1944 // will change it back if we aren't in that case.
1945 GuiWorkArea * const old_cmwa = d.current_main_work_area_;
1946 d.current_main_work_area_ = wa;
1948 for (int i = 0; i != d.splitter_->count(); ++i) {
1949 if (d.tabWorkArea(i)->setCurrentWorkArea(wa)) {
1950 LYXERR(Debug::DEBUG, "Current wa: " << currentWorkArea()
1951 << ", Current main wa: " << currentMainWorkArea());
1956 d.current_main_work_area_ = old_cmwa;
1958 LYXERR(Debug::DEBUG, "This is not a tabbed wa");
1959 on_currentWorkAreaChanged(wa);
1960 BufferView & bv = wa->bufferView();
1961 bv.cursor().fixIfBroken();
1963 wa->setUpdatesEnabled(true);
1964 LYXERR(Debug::DEBUG, "Current wa: " << currentWorkArea() << ", Current main wa: " << currentMainWorkArea());
1968 void GuiView::removeWorkArea(GuiWorkArea * wa)
1970 LASSERT(wa, return);
1971 if (wa == d.current_work_area_) {
1973 disconnectBufferView();
1974 d.current_work_area_ = nullptr;
1975 d.current_main_work_area_ = nullptr;
1978 bool found_twa = false;
1979 for (int i = 0; i != d.splitter_->count(); ++i) {
1980 TabWorkArea * twa = d.tabWorkArea(i);
1981 if (twa->removeWorkArea(wa)) {
1982 // Found in this tab group, and deleted the GuiWorkArea.
1984 if (twa->count() != 0) {
1985 if (d.current_work_area_ == nullptr)
1986 // This means that we are closing the current GuiWorkArea, so
1987 // switch to the next GuiWorkArea in the found TabWorkArea.
1988 setCurrentWorkArea(twa->currentWorkArea());
1990 // No more WorkAreas in this tab group, so delete it.
1997 // It is not a tabbed work area (i.e., the search work area), so it
1998 // should be deleted by other means.
1999 LASSERT(found_twa, return);
2001 if (d.current_work_area_ == nullptr) {
2002 if (d.splitter_->count() != 0) {
2003 TabWorkArea * twa = d.currentTabWorkArea();
2004 setCurrentWorkArea(twa->currentWorkArea());
2006 // No more work areas, switch to the background widget.
2007 setCurrentWorkArea(nullptr);
2013 bool GuiView::hasVisibleWorkArea(GuiWorkArea * wa) const
2015 for (int i = 0; i < d.splitter_->count(); ++i)
2016 if (d.tabWorkArea(i)->currentWorkArea() == wa)
2019 FindAndReplace * fr = static_cast<FindAndReplace*>(find("findreplaceadv", false));
2020 return fr->isVisible() && fr->hasWorkArea(wa);
2024 LayoutBox * GuiView::getLayoutDialog() const
2030 void GuiView::updateLayoutList()
2033 d.layout_->updateContents(false);
2037 void GuiView::updateToolbars()
2039 if (d.current_work_area_) {
2041 if (d.current_work_area_->bufferView().cursor().inMathed()
2042 && !d.current_work_area_->bufferView().cursor().inRegexped())
2043 context |= Toolbars::MATH;
2044 if (lyx::getStatus(FuncRequest(LFUN_LAYOUT_TABULAR)).enabled())
2045 context |= Toolbars::TABLE;
2046 if (currentBufferView()->buffer().areChangesPresent()
2047 || (lyx::getStatus(FuncRequest(LFUN_CHANGES_TRACK)).enabled()
2048 && lyx::getStatus(FuncRequest(LFUN_CHANGES_TRACK)).onOff(true))
2049 || (lyx::getStatus(FuncRequest(LFUN_CHANGES_OUTPUT)).enabled()
2050 && lyx::getStatus(FuncRequest(LFUN_CHANGES_OUTPUT)).onOff(true)))
2051 context |= Toolbars::REVIEW;
2052 if (lyx::getStatus(FuncRequest(LFUN_IN_MATHMACROTEMPLATE)).enabled())
2053 context |= Toolbars::MATHMACROTEMPLATE;
2054 if (lyx::getStatus(FuncRequest(LFUN_IN_IPA)).enabled())
2055 context |= Toolbars::IPA;
2056 if (command_execute_)
2057 context |= Toolbars::MINIBUFFER;
2058 if (minibuffer_focus_) {
2059 context |= Toolbars::MINIBUFFER_FOCUS;
2060 minibuffer_focus_ = false;
2063 for (auto const & tb_p : d.toolbars_)
2064 tb_p.second->update(context);
2066 for (auto const & tb_p : d.toolbars_)
2067 tb_p.second->update();
2071 void GuiView::refillToolbars()
2073 DynamicMenuButton::resetIconCache();
2074 for (auto const & tb_p : d.toolbars_)
2075 tb_p.second->refill();
2079 void GuiView::setBuffer(Buffer * newBuffer, bool switch_to)
2081 LYXERR(Debug::DEBUG, "Setting buffer: " << newBuffer << endl);
2082 LASSERT(newBuffer, return);
2084 GuiWorkArea * wa = workArea(*newBuffer);
2085 if (wa == nullptr) {
2087 newBuffer->masterBuffer()->updateBuffer();
2089 wa = addWorkArea(*newBuffer);
2090 // scroll to the position when the BufferView was last closed
2091 if (lyxrc.use_lastfilepos) {
2092 LastFilePosSection::FilePos filepos =
2093 theSession().lastFilePos().load(newBuffer->fileName());
2094 wa->bufferView().moveToPosition(filepos.pit, filepos.pos, 0, 0);
2097 //Disconnect the old buffer...there's no new one.
2100 connectBuffer(*newBuffer);
2101 connectBufferView(wa->bufferView());
2103 setCurrentWorkArea(wa);
2107 void GuiView::connectBuffer(Buffer & buf)
2109 buf.setGuiDelegate(this);
2113 void GuiView::disconnectBuffer()
2115 if (d.current_work_area_)
2116 d.current_work_area_->bufferView().buffer().setGuiDelegate(nullptr);
2120 void GuiView::connectBufferView(BufferView & bv)
2122 bv.setGuiDelegate(this);
2126 void GuiView::disconnectBufferView()
2128 if (d.current_work_area_)
2129 d.current_work_area_->bufferView().setGuiDelegate(nullptr);
2133 void GuiView::errors(string const & error_type, bool from_master)
2135 BufferView const * const bv = currentBufferView();
2139 ErrorList const & el = from_master ?
2140 bv->buffer().masterBuffer()->errorList(error_type) :
2141 bv->buffer().errorList(error_type);
2146 string err = error_type;
2148 err = "from_master|" + error_type;
2149 showDialog("errorlist", err);
2153 void GuiView::updateTocItem(string const & type, DocIterator const & dit)
2155 d.toc_models_.updateItem(toqstr(type), dit);
2159 void GuiView::structureChanged()
2161 // This is called from the Buffer, which has no way to ensure that cursors
2162 // in BufferView remain valid.
2163 if (documentBufferView())
2164 documentBufferView()->cursor().sanitize();
2165 // FIXME: This is slightly expensive, though less than the tocBackend update
2166 // (#9880). This also resets the view in the Toc Widget (#6675).
2167 d.toc_models_.reset(documentBufferView());
2168 // Navigator needs more than a simple update in this case. It needs to be
2170 updateDialog("toc", "");
2174 void GuiView::updateDialog(string const & name, string const & sdata)
2176 if (!isDialogVisible(name))
2179 map<string, DialogPtr>::const_iterator it = d.dialogs_.find(name);
2180 if (it == d.dialogs_.end())
2183 Dialog * const dialog = it->second.get();
2184 if (dialog->isVisibleView())
2185 dialog->initialiseParams(sdata);
2189 BufferView * GuiView::documentBufferView()
2191 return currentMainWorkArea()
2192 ? ¤tMainWorkArea()->bufferView()
2197 BufferView const * GuiView::documentBufferView() const
2199 return currentMainWorkArea()
2200 ? ¤tMainWorkArea()->bufferView()
2205 BufferView * GuiView::currentBufferView()
2207 return d.current_work_area_ ? &d.current_work_area_->bufferView() : nullptr;
2211 BufferView const * GuiView::currentBufferView() const
2213 return d.current_work_area_ ? &d.current_work_area_->bufferView() : nullptr;
2217 docstring GuiView::GuiViewPrivate::autosaveAndDestroy(
2218 Buffer const * orig, Buffer * clone)
2220 bool const success = clone->autoSave();
2222 busyBuffers.remove(orig);
2224 ? _("Automatic save done.")
2225 : _("Automatic save failed!");
2229 void GuiView::autoSave()
2231 LYXERR(Debug::INFO, "Running autoSave()");
2233 Buffer * buffer = documentBufferView()
2234 ? &documentBufferView()->buffer() : nullptr;
2236 resetAutosaveTimers();
2240 GuiViewPrivate::busyBuffers.insert(buffer);
2241 QFuture<docstring> f = QtConcurrent::run(GuiViewPrivate::autosaveAndDestroy,
2242 buffer, buffer->cloneBufferOnly());
2243 d.autosave_watcher_.setFuture(f);
2244 resetAutosaveTimers();
2248 void GuiView::resetAutosaveTimers()
2251 d.autosave_timeout_.restart();
2257 double zoomRatio(FuncRequest const & cmd, double const zr)
2259 if (cmd.argument().empty()) {
2260 if (cmd.action() == LFUN_BUFFER_ZOOM)
2262 else if (cmd.action() == LFUN_BUFFER_ZOOM_IN)
2264 else // cmd.action() == LFUN_BUFFER_ZOOM_OUT
2267 if (cmd.action() == LFUN_BUFFER_ZOOM)
2268 return convert<int>(cmd.argument()) / double(lyxrc.defaultZoom);
2269 else if (cmd.action() == LFUN_BUFFER_ZOOM_IN)
2270 return zr + convert<int>(cmd.argument()) / 100.0;
2271 else // cmd.action() == LFUN_BUFFER_ZOOM_OUT
2272 return zr - convert<int>(cmd.argument()) / 100.0;
2279 bool GuiView::getStatus(FuncRequest const & cmd, FuncStatus & flag)
2282 Buffer * buf = currentBufferView()
2283 ? ¤tBufferView()->buffer() : nullptr;
2284 Buffer * doc_buffer = documentBufferView()
2285 ? &(documentBufferView()->buffer()) : nullptr;
2288 /* In LyX/Mac, when a dialog is open, the menus of the
2289 application can still be accessed without giving focus to
2290 the main window. In this case, we want to disable the menu
2291 entries that are buffer-related.
2292 This code must not be used on Linux and Windows, since it
2293 would disable buffer-related entries when hovering over the
2294 menu (see bug #9574).
2296 if (cmd.origin() == FuncRequest::MENU && !hasFocus()) {
2302 // Check whether we need a buffer
2303 if (!lyxaction.funcHasFlag(cmd.action(), LyXAction::NoBuffer) && !buf) {
2304 // no, exit directly
2305 flag.message(from_utf8(N_("Command not allowed with"
2306 "out any document open")));
2307 flag.setEnabled(false);
2311 if (cmd.origin() == FuncRequest::TOC) {
2312 GuiToc * toc = static_cast<GuiToc*>(findOrBuild("toc", false));
2313 if (!toc || !toc->getStatus(documentBufferView()->cursor(), cmd, flag))
2314 flag.setEnabled(false);
2318 switch(cmd.action()) {
2319 case LFUN_BUFFER_IMPORT:
2322 case LFUN_MASTER_BUFFER_EXPORT:
2324 && (doc_buffer->parent() != nullptr
2325 || doc_buffer->hasChildren())
2326 && !d.processing_thread_watcher_.isRunning()
2327 // this launches a dialog, which would be in the wrong Buffer
2328 && !(::lyx::operator==(cmd.argument(), "custom"));
2331 case LFUN_MASTER_BUFFER_UPDATE:
2332 case LFUN_MASTER_BUFFER_VIEW:
2334 && (doc_buffer->parent() != nullptr
2335 || doc_buffer->hasChildren())
2336 && !d.processing_thread_watcher_.isRunning();
2339 case LFUN_BUFFER_UPDATE:
2340 case LFUN_BUFFER_VIEW: {
2341 if (!doc_buffer || d.processing_thread_watcher_.isRunning()) {
2345 string format = to_utf8(cmd.argument());
2346 if (cmd.argument().empty())
2347 format = doc_buffer->params().getDefaultOutputFormat();
2348 enable = doc_buffer->params().isExportable(format, true);
2352 case LFUN_BUFFER_RELOAD:
2353 enable = doc_buffer && !doc_buffer->isUnnamed()
2354 && doc_buffer->fileName().exists()
2355 && (!doc_buffer->isClean() || doc_buffer->notifiesExternalModification());
2358 case LFUN_BUFFER_RESET_EXPORT:
2359 enable = doc_buffer != nullptr;
2362 case LFUN_BUFFER_CHILD_OPEN:
2363 enable = doc_buffer != nullptr;
2366 case LFUN_MASTER_BUFFER_FORALL: {
2367 if (doc_buffer == nullptr) {
2368 flag.message(from_utf8(N_("Command not allowed without a buffer open")));
2372 FuncRequest const cmdToPass = lyxaction.lookupFunc(cmd.getLongArg(0));
2373 if (cmdToPass.action() == LFUN_UNKNOWN_ACTION) {
2374 flag.message(from_utf8(N_("Invalid argument of master-buffer-forall")));
2379 for (Buffer * buf : doc_buffer->allRelatives()) {
2380 GuiWorkArea * wa = workArea(*buf);
2383 if (wa->bufferView().getStatus(cmdToPass, flag)) {
2384 enable = flag.enabled();
2391 case LFUN_BUFFER_WRITE:
2392 enable = doc_buffer && (doc_buffer->isUnnamed()
2393 || (!doc_buffer->isClean()
2394 || cmd.argument() == "force"));
2397 //FIXME: This LFUN should be moved to GuiApplication.
2398 case LFUN_BUFFER_WRITE_ALL: {
2399 // We enable the command only if there are some modified buffers
2400 Buffer * first = theBufferList().first();
2405 // We cannot use a for loop as the buffer list is a cycle.
2407 if (!b->isClean()) {
2411 b = theBufferList().next(b);
2412 } while (b != first);
2416 case LFUN_BUFFER_EXTERNAL_MODIFICATION_CLEAR:
2417 enable = doc_buffer && doc_buffer->notifiesExternalModification();
2420 case LFUN_BUFFER_EXPORT: {
2421 if (!doc_buffer || d.processing_thread_watcher_.isRunning()) {
2425 return doc_buffer->getStatus(cmd, flag);
2428 case LFUN_BUFFER_EXPORT_AS:
2429 if (!doc_buffer || d.processing_thread_watcher_.isRunning()) {
2434 case LFUN_BUFFER_WRITE_AS:
2435 case LFUN_BUFFER_WRITE_AS_TEMPLATE:
2436 enable = doc_buffer != nullptr;
2439 case LFUN_EXPORT_CANCEL:
2440 enable = d.processing_thread_watcher_.isRunning();
2443 case LFUN_BUFFER_CLOSE:
2444 case LFUN_VIEW_CLOSE:
2445 enable = doc_buffer != nullptr;
2448 case LFUN_BUFFER_CLOSE_ALL:
2449 enable = theBufferList().last() != theBufferList().first();
2452 case LFUN_BUFFER_CHKTEX: {
2453 // hide if we have no checktex command
2454 if (lyxrc.chktex_command.empty()) {
2455 flag.setUnknown(true);
2459 if (!doc_buffer || !doc_buffer->params().isLatex()
2460 || d.processing_thread_watcher_.isRunning()) {
2461 // grey out, don't hide
2469 case LFUN_CHANGES_TRACK: {
2474 return doc_buffer->getStatus(cmd, flag);
2477 case LFUN_VIEW_SPLIT:
2478 if (cmd.getArg(0) == "vertical")
2479 enable = doc_buffer && (d.splitter_->count() == 1 ||
2480 d.splitter_->orientation() == Qt::Vertical);
2482 enable = doc_buffer && (d.splitter_->count() == 1 ||
2483 d.splitter_->orientation() == Qt::Horizontal);
2486 case LFUN_TAB_GROUP_NEXT:
2487 case LFUN_TAB_GROUP_PREVIOUS:
2488 enable = (d.splitter_->count() > 1);
2491 case LFUN_TAB_GROUP_CLOSE:
2492 enable = d.tabWorkAreaCount() > 1;
2495 case LFUN_DEVEL_MODE_TOGGLE:
2496 flag.setOnOff(devel_mode_);
2499 case LFUN_TOOLBAR_SET: {
2500 string const name = cmd.getArg(0);
2501 string const state = cmd.getArg(1);
2502 if (name.empty() || state.empty()) {
2504 docstring const msg =
2505 _("Function toolbar-set requires two arguments!");
2509 if (state != "on" && state != "off" && state != "auto") {
2511 docstring const msg =
2512 bformat(_("Invalid argument \"%1$s\" to function toolbar-set!"),
2517 if (GuiToolbar * t = toolbar(name)) {
2518 bool const autovis = t->visibility() & Toolbars::AUTO;
2520 flag.setOnOff(t->isVisible() && !autovis);
2521 else if (state == "off")
2522 flag.setOnOff(!t->isVisible() && !autovis);
2523 else if (state == "auto")
2524 flag.setOnOff(autovis);
2527 docstring const msg =
2528 bformat(_("Unknown toolbar \"%1$s\""), from_utf8(name));
2534 case LFUN_TOOLBAR_TOGGLE: {
2535 string const name = cmd.getArg(0);
2536 if (GuiToolbar * t = toolbar(name))
2537 flag.setOnOff(t->isVisible());
2540 docstring const msg =
2541 bformat(_("Unknown toolbar \"%1$s\""), from_utf8(name));
2547 case LFUN_TOOLBAR_MOVABLE: {
2548 string const name = cmd.getArg(0);
2549 // use negation since locked == !movable
2551 // toolbar name * locks all toolbars
2552 flag.setOnOff(!toolbarsMovable_);
2553 else if (GuiToolbar * t = toolbar(name))
2554 flag.setOnOff(!(t->isMovable()));
2557 docstring const msg =
2558 bformat(_("Unknown toolbar \"%1$s\""), from_utf8(name));
2564 case LFUN_ICON_SIZE:
2565 flag.setOnOff(d.iconSize(cmd.argument()) == iconSize());
2568 case LFUN_DROP_LAYOUTS_CHOICE:
2569 enable = buf != nullptr;
2572 case LFUN_UI_TOGGLE:
2573 if (cmd.argument() == "zoomlevel") {
2574 flag.setOnOff(zoom_value_ ? zoom_value_->isVisible() : false);
2575 } else if (cmd.argument() == "zoomslider") {
2576 flag.setOnOff(zoom_widget_ ? zoom_widget_->isVisible() : false);
2577 } else if (cmd.argument() == "statistics-w") {
2578 flag.setOnOff(word_count_enabled_);
2579 } else if (cmd.argument() == "statistics-cb") {
2580 flag.setOnOff(char_count_enabled_);
2581 } else if (cmd.argument() == "statistics-c") {
2582 flag.setOnOff(char_nb_count_enabled_);
2584 flag.setOnOff(isFullScreen());
2587 case LFUN_DIALOG_DISCONNECT_INSET:
2590 case LFUN_DIALOG_HIDE:
2591 // FIXME: should we check if the dialog is shown?
2594 case LFUN_DIALOG_TOGGLE:
2595 flag.setOnOff(isDialogVisible(cmd.getArg(0)));
2598 case LFUN_DIALOG_SHOW: {
2599 string const name = cmd.getArg(0);
2601 enable = name == "aboutlyx"
2602 || name == "file" //FIXME: should be removed.
2603 || name == "lyxfiles"
2605 || name == "texinfo"
2606 || name == "progress"
2607 || name == "compare";
2608 else if (name == "character" || name == "symbols"
2609 || name == "mathdelimiter" || name == "mathmatrix") {
2610 if (!buf || buf->isReadonly())
2613 Cursor const & cur = currentBufferView()->cursor();
2614 enable = !(cur.inTexted() && cur.paragraph().isPassThru());
2617 else if (name == "latexlog")
2618 enable = FileName(doc_buffer->logName()).isReadableFile();
2619 else if (name == "spellchecker")
2620 enable = theSpellChecker()
2621 && !doc_buffer->text().empty();
2622 else if (name == "vclog")
2623 enable = doc_buffer->lyxvc().inUse();
2627 case LFUN_DIALOG_UPDATE: {
2628 string const name = cmd.getArg(0);
2630 enable = name == "prefs";
2634 case LFUN_COMMAND_EXECUTE:
2636 case LFUN_MENU_OPEN:
2637 // Nothing to check.
2640 case LFUN_COMPLETION_INLINE:
2641 if (!d.current_work_area_
2642 || !d.current_work_area_->completer().inlinePossible(
2643 currentBufferView()->cursor()))
2647 case LFUN_COMPLETION_POPUP:
2648 if (!d.current_work_area_
2649 || !d.current_work_area_->completer().popupPossible(
2650 currentBufferView()->cursor()))
2655 if (!d.current_work_area_
2656 || !d.current_work_area_->completer().inlinePossible(
2657 currentBufferView()->cursor()))
2661 case LFUN_COMPLETION_ACCEPT:
2662 if (!d.current_work_area_
2663 || (!d.current_work_area_->completer().popupVisible()
2664 && !d.current_work_area_->completer().inlineVisible()
2665 && !d.current_work_area_->completer().completionAvailable()))
2669 case LFUN_COMPLETION_CANCEL:
2670 if (!d.current_work_area_
2671 || (!d.current_work_area_->completer().popupVisible()
2672 && !d.current_work_area_->completer().inlineVisible()))
2676 case LFUN_BUFFER_ZOOM_OUT:
2677 case LFUN_BUFFER_ZOOM_IN:
2678 case LFUN_BUFFER_ZOOM: {
2679 int const zoom = (int)(lyxrc.defaultZoom * zoomRatio(cmd, zoom_ratio_));
2680 if (zoom < zoom_min_) {
2681 docstring const msg =
2682 bformat(_("Zoom level cannot be less than %1$d%."), zoom_min_);
2685 } else if (zoom > zoom_max_) {
2686 docstring const msg =
2687 bformat(_("Zoom level cannot be more than %1$d%."), zoom_max_);
2691 enable = doc_buffer;
2696 case LFUN_BUFFER_MOVE_NEXT:
2697 case LFUN_BUFFER_MOVE_PREVIOUS:
2698 // we do not cycle when moving
2699 case LFUN_BUFFER_NEXT:
2700 case LFUN_BUFFER_PREVIOUS:
2701 // because we cycle, it doesn't matter whether on first or last
2702 enable = (d.currentTabWorkArea()->count() > 1);
2704 case LFUN_BUFFER_SWITCH:
2705 // toggle on the current buffer, but do not toggle off
2706 // the other ones (is that a good idea?)
2708 && to_utf8(cmd.argument()) == doc_buffer->absFileName())
2709 flag.setOnOff(true);
2712 case LFUN_VC_REGISTER:
2713 enable = doc_buffer && !doc_buffer->lyxvc().inUse();
2715 case LFUN_VC_RENAME:
2716 enable = doc_buffer && doc_buffer->lyxvc().renameEnabled();
2719 enable = doc_buffer && doc_buffer->lyxvc().copyEnabled();
2721 case LFUN_VC_CHECK_IN:
2722 enable = doc_buffer && doc_buffer->lyxvc().checkInEnabled();
2724 case LFUN_VC_CHECK_OUT:
2725 enable = doc_buffer && doc_buffer->lyxvc().checkOutEnabled();
2727 case LFUN_VC_LOCKING_TOGGLE:
2728 enable = doc_buffer && !doc_buffer->hasReadonlyFlag()
2729 && doc_buffer->lyxvc().lockingToggleEnabled();
2730 flag.setOnOff(enable && doc_buffer->lyxvc().locking());
2732 case LFUN_VC_REVERT:
2733 enable = doc_buffer && doc_buffer->lyxvc().inUse()
2734 && !doc_buffer->hasReadonlyFlag();
2736 case LFUN_VC_UNDO_LAST:
2737 enable = doc_buffer && doc_buffer->lyxvc().undoLastEnabled();
2739 case LFUN_VC_REPO_UPDATE:
2740 enable = doc_buffer && doc_buffer->lyxvc().repoUpdateEnabled();
2742 case LFUN_VC_COMMAND: {
2743 if (cmd.argument().empty())
2745 if (!doc_buffer && contains(cmd.getArg(0), 'D'))
2749 case LFUN_VC_COMPARE:
2750 enable = doc_buffer && doc_buffer->lyxvc().prepareFileRevisionEnabled();
2753 case LFUN_SERVER_GOTO_FILE_ROW:
2754 case LFUN_LYX_ACTIVATE:
2755 case LFUN_WINDOW_RAISE:
2757 case LFUN_FORWARD_SEARCH:
2758 enable = !(lyxrc.forward_search_dvi.empty() && lyxrc.forward_search_pdf.empty()) &&
2759 doc_buffer && doc_buffer->isSyncTeXenabled();
2762 case LFUN_FILE_INSERT_PLAINTEXT:
2763 case LFUN_FILE_INSERT_PLAINTEXT_PARA:
2764 enable = documentBufferView() && documentBufferView()->cursor().inTexted();
2767 case LFUN_SPELLING_CONTINUOUSLY:
2768 flag.setOnOff(lyxrc.spellcheck_continuously);
2771 case LFUN_CITATION_OPEN:
2780 flag.setEnabled(false);
2786 static FileName selectTemplateFile()
2788 FileDialog dlg(qt_("Select template file"));
2789 dlg.setButton1(qt_("D&ocuments"), toqstr(lyxrc.document_path));
2790 dlg.setButton2(qt_("&Templates"), toqstr(lyxrc.template_path));
2792 FileDialog::Result result = dlg.open(toqstr(lyxrc.template_path),
2793 QStringList(qt_("LyX Documents (*.lyx)")));
2795 if (result.first == FileDialog::Later)
2797 if (result.second.isEmpty())
2799 return FileName(fromqstr(result.second));
2803 Buffer * GuiView::loadDocument(FileName const & filename, bool tolastfiles)
2807 Buffer * newBuffer = nullptr;
2809 newBuffer = checkAndLoadLyXFile(filename);
2810 } catch (ExceptionMessage const &) {
2817 message(_("Document not loaded."));
2821 setBuffer(newBuffer);
2822 newBuffer->errors("Parse");
2825 theSession().lastFiles().add(filename);
2826 theSession().writeFile();
2833 void GuiView::openDocuments(string const & fname, int origin)
2835 string initpath = lyxrc.document_path;
2837 if (documentBufferView()) {
2838 string const trypath = documentBufferView()->buffer().filePath();
2839 // If directory is writeable, use this as default.
2840 if (FileName(trypath).isDirWritable())
2846 if (fname.empty()) {
2847 FileDialog dlg(qt_("Select documents to open"));
2848 dlg.setButton1(qt_("D&ocuments"), toqstr(lyxrc.document_path));
2849 dlg.setButton2(qt_("&Examples"), toqstr(lyxrc.example_path));
2851 QStringList const filter({
2852 qt_("LyX Documents (*.lyx)"),
2853 qt_("LyX Document Backups (*.lyx~)"),
2854 qt_("All Files") + " " + wildcardAllFiles()
2856 FileDialog::Results results =
2857 dlg.openMulti(toqstr(initpath), filter);
2859 if (results.first == FileDialog::Later)
2862 files = results.second;
2864 // check selected filename
2865 if (files.isEmpty()) {
2866 message(_("Canceled."));
2870 files << toqstr(fname);
2872 // iterate over all selected files
2873 for (auto const & file : files) {
2874 string filename = fromqstr(file);
2876 // get absolute path of file and add ".lyx" to the filename if
2878 FileName const fullname =
2879 fileSearch(string(), filename, "lyx", support::may_not_exist);
2880 if (!fullname.empty())
2881 filename = fullname.absFileName();
2883 if (!fullname.onlyPath().isDirectory()) {
2884 Alert::warning(_("Invalid filename"),
2885 bformat(_("The directory in the given path\n%1$s\ndoes not exist."),
2886 from_utf8(fullname.absFileName())));
2890 // if the file doesn't exist and isn't already open (bug 6645),
2891 // let the user create one
2892 if (!fullname.exists() && !theBufferList().exists(fullname) &&
2893 !LyXVC::file_not_found_hook(fullname)) {
2895 if (origin == FuncRequest::MENU) {
2896 docstring const & msg =
2899 "does not exist. Create empty file?"),
2900 from_utf8(filename));
2901 int ret = Alert::prompt(_("File does not exist"),
2908 Buffer * const b = newFile(filename, string(), true);
2914 docstring const disp_fn = makeDisplayPath(filename);
2915 message(bformat(_("Opening document %1$s..."), disp_fn));
2918 Buffer * buf = loadDocument(fullname);
2920 str2 = bformat(_("Document %1$s opened."), disp_fn);
2921 if (buf->lyxvc().inUse())
2922 str2 += " " + from_utf8(buf->lyxvc().versionString()) +
2923 " " + _("Version control detected.");
2925 str2 = bformat(_("Could not open document %1$s"), disp_fn);
2931 // FIXME: clean that
2932 static bool import(GuiView * lv, FileName const & filename,
2933 string const & format, ErrorList & errorList)
2935 FileName const lyxfile(support::changeExtension(filename.absFileName(), ".lyx"));
2937 string loader_format;
2938 vector<string> loaders = theConverters().loaders();
2939 if (find(loaders.begin(), loaders.end(), format) == loaders.end()) {
2940 for (string const & loader : loaders) {
2941 if (!theConverters().isReachable(format, loader))
2944 string const tofile =
2945 support::changeExtension(filename.absFileName(),
2946 theFormats().extension(loader));
2947 if (theConverters().convert(nullptr, filename, FileName(tofile),
2948 filename, format, loader, errorList) != Converters::SUCCESS)
2950 loader_format = loader;
2953 if (loader_format.empty()) {
2954 frontend::Alert::error(_("Couldn't import file"),
2955 bformat(_("No information for importing the format %1$s."),
2956 translateIfPossible(theFormats().prettyName(format))));
2960 loader_format = format;
2962 if (loader_format == "lyx") {
2963 Buffer * buf = lv->loadDocument(lyxfile);
2967 Buffer * const b = newFile(lyxfile.absFileName(), string(), true);
2971 bool as_paragraphs = loader_format == "textparagraph";
2972 string filename2 = (loader_format == format) ? filename.absFileName()
2973 : support::changeExtension(filename.absFileName(),
2974 theFormats().extension(loader_format));
2975 lv->currentBufferView()->insertPlaintextFile(FileName(filename2),
2977 guiApp->setCurrentView(lv);
2978 lyx::dispatch(FuncRequest(LFUN_MARK_OFF));
2985 void GuiView::importDocument(string const & argument)
2988 string filename = split(argument, format, ' ');
2990 LYXERR(Debug::INFO, format << " file: " << filename);
2992 // need user interaction
2993 if (filename.empty()) {
2994 string initpath = lyxrc.document_path;
2995 if (documentBufferView()) {
2996 string const trypath = documentBufferView()->buffer().filePath();
2997 // If directory is writeable, use this as default.
2998 if (FileName(trypath).isDirWritable())
3002 docstring const text = bformat(_("Select %1$s file to import"),
3003 translateIfPossible(theFormats().prettyName(format)));
3005 FileDialog dlg(toqstr(text));
3006 dlg.setButton1(qt_("D&ocuments"), toqstr(lyxrc.document_path));
3007 dlg.setButton2(qt_("&Examples"), toqstr(lyxrc.example_path));
3009 docstring filter = translateIfPossible(theFormats().prettyName(format));
3012 filter += from_utf8(theFormats().extensions(format));
3015 FileDialog::Result result =
3016 dlg.open(toqstr(initpath), fileFilters(toqstr(filter)));
3018 if (result.first == FileDialog::Later)
3021 filename = fromqstr(result.second);
3023 // check selected filename
3024 if (filename.empty())
3025 message(_("Canceled."));
3028 if (filename.empty())
3031 // get absolute path of file
3032 FileName const fullname(support::makeAbsPath(filename));
3034 // Can happen if the user entered a path into the dialog
3036 if (fullname.onlyFileName().empty()) {
3037 docstring msg = bformat(_("The file name '%1$s' is invalid!\n"
3038 "Aborting import."),
3039 from_utf8(fullname.absFileName()));
3040 frontend::Alert::error(_("File name error"), msg);
3041 message(_("Canceled."));
3046 FileName const lyxfile(support::changeExtension(fullname.absFileName(), ".lyx"));
3048 // Check if the document already is open
3049 Buffer * buf = theBufferList().getBuffer(lyxfile);
3052 if (!closeBuffer()) {
3053 message(_("Canceled."));
3058 docstring const displaypath = makeDisplayPath(lyxfile.absFileName(), 30);
3060 // if the file exists already, and we didn't do
3061 // -i lyx thefile.lyx, warn
3062 if (lyxfile.exists() && fullname != lyxfile) {
3064 docstring text = bformat(_("The document %1$s already exists.\n\n"
3065 "Do you want to overwrite that document?"), displaypath);
3066 int const ret = Alert::prompt(_("Overwrite document?"),
3067 text, 0, 1, _("&Overwrite"), _("&Cancel"));
3070 message(_("Canceled."));
3075 message(bformat(_("Importing %1$s..."), displaypath));
3076 ErrorList errorList;
3077 if (import(this, fullname, format, errorList))
3078 message(_("imported."));
3080 message(_("file not imported!"));
3082 // FIXME (Abdel 12/08/06): Is there a need to display the error list here?
3086 void GuiView::newDocument(string const & filename, string templatefile,
3089 FileName initpath(lyxrc.document_path);
3090 if (documentBufferView()) {
3091 FileName const trypath(documentBufferView()->buffer().filePath());
3092 // If directory is writeable, use this as default.
3093 if (trypath.isDirWritable())
3097 if (from_template) {
3098 if (templatefile.empty())
3099 templatefile = selectTemplateFile().absFileName();
3100 if (templatefile.empty())
3105 if (filename.empty())
3106 b = newUnnamedFile(initpath, to_utf8(_("newfile")), templatefile);
3108 b = newFile(filename, templatefile, true);
3113 // If no new document could be created, it is unsure
3114 // whether there is a valid BufferView.
3115 if (currentBufferView())
3116 // Ensure the cursor is correctly positioned on screen.
3117 currentBufferView()->showCursor();
3121 bool GuiView::insertLyXFile(docstring const & fname, bool ignorelang)
3123 BufferView * bv = documentBufferView();
3128 FileName filename(to_utf8(fname));
3129 if (filename.empty()) {
3130 // Launch a file browser
3132 string initpath = lyxrc.document_path;
3133 string const trypath = bv->buffer().filePath();
3134 // If directory is writeable, use this as default.
3135 if (FileName(trypath).isDirWritable())
3139 FileDialog dlg(qt_("Select LyX document to insert"));
3140 dlg.setButton1(qt_("D&ocuments"), toqstr(lyxrc.document_path));
3141 dlg.setButton2(qt_("&Examples"), toqstr(lyxrc.example_path));
3143 FileDialog::Result result = dlg.open(toqstr(initpath),
3144 QStringList(qt_("LyX Documents (*.lyx)")));
3146 if (result.first == FileDialog::Later)
3150 filename.set(fromqstr(result.second));
3152 // check selected filename
3153 if (filename.empty()) {
3154 // emit message signal.
3155 message(_("Canceled."));
3160 bv->insertLyXFile(filename, ignorelang);
3161 bv->buffer().errors("Parse");
3166 string const GuiView::getTemplatesPath(Buffer & b)
3168 // We start off with the user's templates path
3169 string result = addPath(package().user_support().absFileName(), "templates");
3170 // Check for the document language
3171 string const langcode = b.params().language->code();
3172 string const shortcode = langcode.substr(0, 2);
3173 if (!langcode.empty() && shortcode != "en") {
3174 string subpath = addPath(result, shortcode);
3175 string subpath_long = addPath(result, langcode);
3176 // If we have a subdirectory for the language already,
3178 FileName sp = FileName(subpath);
3179 if (sp.isDirectory())
3181 else if (FileName(subpath_long).isDirectory())
3182 result = subpath_long;
3184 // Ask whether we should create such a subdirectory
3185 docstring const text =
3186 bformat(_("It is suggested to save the template in a subdirectory\n"
3187 "appropriate to the document language (%1$s).\n"
3188 "This subdirectory does not exists yet.\n"
3189 "Do you want to create it?"),
3190 _(b.params().language->display()));
3191 if (Alert::prompt(_("Create Language Directory?"),
3192 text, 0, 1, _("&Yes, Create"), _("&No, Save Template in Parent Directory")) == 0) {
3193 // If the user agreed, we try to create it and report if this failed.
3194 if (!sp.createDirectory(0777))
3195 Alert::error(_("Subdirectory creation failed!"),
3196 _("Could not create subdirectory.\n"
3197 "The template will be saved in the parent directory."));
3203 // Do we have a layout category?
3204 string const cat = b.params().baseClass() ?
3205 b.params().baseClass()->category()
3208 string subpath = addPath(result, cat);
3209 // If we have a subdirectory for the category already,
3211 FileName sp = FileName(subpath);
3212 if (sp.isDirectory())
3215 // Ask whether we should create such a subdirectory
3216 docstring const text =
3217 bformat(_("It is suggested to save the template in a subdirectory\n"
3218 "appropriate to the layout category (%1$s).\n"
3219 "This subdirectory does not exists yet.\n"
3220 "Do you want to create it?"),
3222 if (Alert::prompt(_("Create Category Directory?"),
3223 text, 0, 1, _("&Yes, Create"), _("&No, Save Template in Parent Directory")) == 0) {
3224 // If the user agreed, we try to create it and report if this failed.
3225 if (!sp.createDirectory(0777))
3226 Alert::error(_("Subdirectory creation failed!"),
3227 _("Could not create subdirectory.\n"
3228 "The template will be saved in the parent directory."));
3238 bool GuiView::renameBuffer(Buffer & b, docstring const & newname, RenameKind kind)
3240 FileName fname = b.fileName();
3241 FileName const oldname = fname;
3242 bool const as_template = (kind == LV_WRITE_AS_TEMPLATE);
3244 if (!newname.empty()) {
3247 fname = support::makeAbsPath(to_utf8(newname), getTemplatesPath(b));
3249 fname = support::makeAbsPath(to_utf8(newname),
3250 oldname.onlyPath().absFileName());
3252 // Switch to this Buffer.
3255 // No argument? Ask user through dialog.
3257 QString const title = as_template ? qt_("Choose a filename to save template as")
3258 : qt_("Choose a filename to save document as");
3259 FileDialog dlg(title);
3260 dlg.setButton1(qt_("D&ocuments"), toqstr(lyxrc.document_path));
3261 dlg.setButton2(qt_("&Templates"), toqstr(lyxrc.template_path));
3263 fname.ensureExtension(".lyx");
3265 string const path = as_template ?
3267 : fname.onlyPath().absFileName();
3268 FileDialog::Result result =
3269 dlg.save(toqstr(path),
3270 QStringList(qt_("LyX Documents (*.lyx)")),
3271 toqstr(fname.onlyFileName()));
3273 if (result.first == FileDialog::Later)
3276 fname.set(fromqstr(result.second));
3281 fname.ensureExtension(".lyx");
3284 // fname is now the new Buffer location.
3286 // if there is already a Buffer open with this name, we do not want
3287 // to have another one. (the second test makes sure we're not just
3288 // trying to overwrite ourselves, which is fine.)
3289 if (theBufferList().exists(fname) && fname != oldname
3290 && theBufferList().getBuffer(fname) != &b) {
3291 docstring const text =
3292 bformat(_("The file\n%1$s\nis already open in your current session.\n"
3293 "Please close it before attempting to overwrite it.\n"
3294 "Do you want to choose a new filename?"),
3295 from_utf8(fname.absFileName()));
3296 int const ret = Alert::prompt(_("Chosen File Already Open"),
3297 text, 0, 1, _("&Rename"), _("&Cancel"));
3299 case 0: return renameBuffer(b, docstring(), kind);
3300 case 1: return false;
3305 bool const existsLocal = fname.exists();
3306 bool const existsInVC = LyXVC::fileInVC(fname);
3307 if (existsLocal || existsInVC) {
3308 docstring const file = makeDisplayPath(fname.absFileName(), 30);
3309 if (kind != LV_WRITE_AS && existsInVC) {
3310 // renaming to a name that is already in VC
3312 docstring text = bformat(_("The document %1$s "
3313 "is already registered.\n\n"
3314 "Do you want to choose a new name?"),
3316 docstring const title = (kind == LV_VC_RENAME) ?
3317 _("Rename document?") : _("Copy document?");
3318 docstring const button = (kind == LV_VC_RENAME) ?
3319 _("&Rename") : _("&Copy");
3320 int const ret = Alert::prompt(title, text, 0, 1,
3321 button, _("&Cancel"));
3323 case 0: return renameBuffer(b, docstring(), kind);
3324 case 1: return false;
3329 docstring text = bformat(_("The document %1$s "
3330 "already exists.\n\n"
3331 "Do you want to overwrite that document?"),
3333 int const ret = Alert::prompt(_("Overwrite document?"),
3334 text, 0, 2, _("&Overwrite"),
3335 _("&Rename"), _("&Cancel"));
3338 case 1: return renameBuffer(b, docstring(), kind);
3339 case 2: return false;
3345 case LV_VC_RENAME: {
3346 string msg = b.lyxvc().rename(fname);
3349 message(from_utf8(msg));
3353 string msg = b.lyxvc().copy(fname);
3356 message(from_utf8(msg));
3360 case LV_WRITE_AS_TEMPLATE:
3363 // LyXVC created the file already in case of LV_VC_RENAME or
3364 // LV_VC_COPY, but call saveBuffer() nevertheless to get
3365 // relative paths of included stuff right if we moved e.g. from
3366 // /a/b.lyx to /a/c/b.lyx.
3368 bool const saved = saveBuffer(b, fname);
3375 bool GuiView::exportBufferAs(Buffer & b, docstring const & iformat)
3377 FileName fname = b.fileName();
3379 FileDialog dlg(qt_("Choose a filename to export the document as"));
3380 dlg.setButton1(qt_("D&ocuments"), toqstr(lyxrc.document_path));
3383 QString const anyformat = qt_("Guess from extension (*.*)");
3386 vector<Format const *> export_formats;
3387 for (Format const & f : theFormats())
3388 if (f.documentFormat())
3389 export_formats.push_back(&f);
3390 sort(export_formats.begin(), export_formats.end(), Format::formatSorter);
3391 map<QString, string> fmap;
3394 for (Format const * f : export_formats) {
3395 docstring const loc_prettyname = translateIfPossible(f->prettyname());
3396 QString const loc_filter = toqstr(bformat(from_ascii("%1$s (*.%2$s)"),
3398 from_ascii(f->extension())));
3399 types << loc_filter;
3400 fmap[loc_filter] = f->name();
3401 if (from_ascii(f->name()) == iformat) {
3402 filter = loc_filter;
3403 ext = f->extension();
3406 string ofname = fname.onlyFileName();
3408 ofname = support::changeExtension(ofname, ext);
3409 FileDialog::Result result =
3410 dlg.save(toqstr(fname.onlyPath().absFileName()),
3414 if (result.first != FileDialog::Chosen)
3418 fname.set(fromqstr(result.second));
3419 if (filter == anyformat)
3420 fmt_name = theFormats().getFormatFromExtension(fname.extension());
3422 fmt_name = fmap[filter];
3423 LYXERR(Debug::FILES, "filter=" << fromqstr(filter)
3424 << ", fmt_name=" << fmt_name << ", fname=" << fname.absFileName());
3426 if (fmt_name.empty() || fname.empty())
3429 fname.ensureExtension(theFormats().extension(fmt_name));
3431 // fname is now the new Buffer location.
3432 if (fname.exists()) {
3433 docstring const file = makeDisplayPath(fname.absFileName(), 30);
3434 docstring text = bformat(_("The document %1$s already "
3435 "exists.\n\nDo you want to "
3436 "overwrite that document?"),
3438 int const ret = Alert::prompt(_("Overwrite document?"),
3439 text, 0, 2, _("&Overwrite"), _("&Rename"), _("&Cancel"));
3442 case 1: return exportBufferAs(b, from_ascii(fmt_name));
3443 case 2: return false;
3447 FuncRequest cmd(LFUN_BUFFER_EXPORT, fmt_name + " " + fname.absFileName());
3450 return dr.dispatched();
3454 bool GuiView::saveBuffer(Buffer & b)
3456 return saveBuffer(b, FileName());
3460 bool GuiView::saveBuffer(Buffer & b, FileName const & fn)
3462 if (workArea(b) && workArea(b)->inDialogMode())
3465 if (fn.empty() && b.isUnnamed())
3466 return renameBuffer(b, docstring());
3468 bool const success = (fn.empty() ? b.save() : b.saveAs(fn));
3470 theSession().lastFiles().add(b.fileName());
3471 theSession().writeFile();
3475 // Switch to this Buffer.
3478 // FIXME: we don't tell the user *WHY* the save failed !!
3479 docstring const file = makeDisplayPath(b.absFileName(), 30);
3480 docstring text = bformat(_("The document %1$s could not be saved.\n\n"
3481 "Do you want to rename the document and "
3482 "try again?"), file);
3483 int const ret = Alert::prompt(_("Rename and save?"),
3484 text, 0, 2, _("&Rename"), _("&Retry"), _("&Cancel"));
3487 if (!renameBuffer(b, docstring()))
3496 return saveBuffer(b, fn);
3500 bool GuiView::hideWorkArea(GuiWorkArea * wa)
3502 return closeWorkArea(wa, false);
3506 // We only want to close the buffer if it is not visible in other workareas
3507 // of the same view, nor in other views, and if this is not a child
3508 bool GuiView::closeWorkArea(GuiWorkArea * wa)
3510 Buffer & buf = wa->bufferView().buffer();
3512 bool last_wa = d.countWorkAreasOf(buf) == 1
3513 && !inOtherView(buf) && !buf.parent();
3515 bool close_buffer = last_wa;
3518 if (lyxrc.close_buffer_with_last_view == "yes")
3520 else if (lyxrc.close_buffer_with_last_view == "no")
3521 close_buffer = false;
3524 if (buf.isUnnamed())
3525 file = from_utf8(buf.fileName().onlyFileName());
3527 file = buf.fileName().displayName(30);
3528 docstring const text = bformat(
3529 _("Last view on document %1$s is being closed.\n"
3530 "Would you like to close or hide the document?\n"
3532 "Hidden documents can be displayed back through\n"
3533 "the menu: View->Hidden->...\n"
3535 "To remove this question, set your preference in:\n"
3536 " Tools->Preferences->Look&Feel->UserInterface\n"
3538 int ret = Alert::prompt(_("Close or hide document?"),
3539 text, 0, 2, _("&Close"), _("&Hide"), _("&Cancel"));
3542 close_buffer = (ret == 0);
3546 return closeWorkArea(wa, close_buffer);
3550 bool GuiView::closeBuffer()
3552 GuiWorkArea * wa = currentMainWorkArea();
3553 // coverity complained about this
3554 // it seems unnecessary, but perhaps is worth the check
3555 LASSERT(wa, return false);
3557 setCurrentWorkArea(wa);
3558 Buffer & buf = wa->bufferView().buffer();
3559 return closeWorkArea(wa, !buf.parent());
3563 void GuiView::writeSession() const {
3564 GuiWorkArea const * active_wa = currentMainWorkArea();
3565 for (int i = 0; i < d.splitter_->count(); ++i) {
3566 TabWorkArea * twa = d.tabWorkArea(i);
3567 for (int j = 0; j < twa->count(); ++j) {
3568 GuiWorkArea * wa = twa->workArea(j);
3569 Buffer & buf = wa->bufferView().buffer();
3570 theSession().lastOpened().add(buf.fileName(), wa == active_wa);
3576 bool GuiView::closeBufferAll()
3579 for (auto & buf : theBufferList()) {
3580 if (!saveBufferIfNeeded(*buf, false)) {
3581 // Closing has been cancelled, so abort.
3586 // Close the workareas in all other views
3587 QList<int> const ids = guiApp->viewIds();
3588 for (int i = 0; i != ids.size(); ++i) {
3589 if (id_ != ids[i] && !guiApp->view(ids[i]).closeWorkAreaAll())
3593 // Close our own workareas
3594 if (!closeWorkAreaAll())
3601 bool GuiView::closeWorkAreaAll()
3603 setCurrentWorkArea(currentMainWorkArea());
3605 // We might be in a situation that there is still a tabWorkArea, but
3606 // there are no tabs anymore. This can happen when we get here after a
3607 // TabWorkArea::lastWorkAreaRemoved() signal. Therefore we count how
3608 // many TabWorkArea's have no documents anymore.
3611 // We have to call count() each time, because it can happen that
3612 // more than one splitter will disappear in one iteration (bug 5998).
3613 while (d.splitter_->count() > empty_twa) {
3614 TabWorkArea * twa = d.tabWorkArea(empty_twa);
3616 if (twa->count() == 0)
3619 setCurrentWorkArea(twa->currentWorkArea());
3620 if (!closeTabWorkArea(twa))
3628 bool GuiView::closeWorkArea(GuiWorkArea * wa, bool close_buffer)
3633 Buffer & buf = wa->bufferView().buffer();
3635 if (GuiViewPrivate::busyBuffers.contains(&buf)) {
3636 Alert::warning(_("Close document"),
3637 _("Document could not be closed because it is being processed by LyX."));
3642 return closeBuffer(buf);
3644 if (!inMultiTabs(wa))
3645 if (!saveBufferIfNeeded(buf, true))
3653 bool GuiView::closeBuffer(Buffer & buf)
3655 bool success = true;
3656 for (Buffer * child_buf : buf.getChildren()) {
3657 if (theBufferList().isOthersChild(&buf, child_buf)) {
3658 child_buf->setParent(nullptr);
3662 // FIXME: should we look in other tabworkareas?
3663 // ANSWER: I don't think so. I've tested, and if the child is
3664 // open in some other window, it closes without a problem.
3665 GuiWorkArea * child_wa = workArea(*child_buf);
3668 // If we are in a close_event all children will be closed in some time,
3669 // so no need to do it here. This will ensure that the children end up
3670 // in the session file in the correct order. If we close the master
3671 // buffer, we can close or release the child buffers here too.
3673 success = closeWorkArea(child_wa, true);
3677 // In this case the child buffer is open but hidden.
3678 // Even in this case, children can be dirty (e.g.,
3679 // after a label change in the master, see #11405).
3680 // Therefore, check this
3681 if (closing_ && (child_buf->isClean() || child_buf->paragraphs().empty())) {
3682 // If we are in a close_event all children will be closed in some time,
3683 // so no need to do it here. This will ensure that the children end up
3684 // in the session file in the correct order. If we close the master
3685 // buffer, we can close or release the child buffers here too.
3688 // Save dirty buffers also if closing_!
3689 if (saveBufferIfNeeded(*child_buf, false)) {
3690 child_buf->removeAutosaveFile();
3691 theBufferList().release(child_buf);
3693 // Saving of dirty children has been cancelled.
3694 // Cancel the whole process.
3701 // goto bookmark to update bookmark pit.
3702 // FIXME: we should update only the bookmarks related to this buffer!
3703 // FIXME: this is done also in LFUN_WINDOW_CLOSE!
3704 LYXERR(Debug::DEBUG, "GuiView::closeBuffer()");
3705 for (unsigned int i = 1; i < theSession().bookmarks().size(); ++i)
3706 guiApp->gotoBookmark(i, false, false);
3708 if (saveBufferIfNeeded(buf, false)) {
3709 buf.removeAutosaveFile();
3710 theBufferList().release(&buf);
3714 // open all children again to avoid a crash because of dangling
3715 // pointers (bug 6603)
3721 bool GuiView::closeTabWorkArea(TabWorkArea * twa)
3723 while (twa == d.currentTabWorkArea()) {
3724 twa->setCurrentIndex(twa->count() - 1);
3726 GuiWorkArea * wa = twa->currentWorkArea();
3727 Buffer & b = wa->bufferView().buffer();
3729 // We only want to close the buffer if the same buffer is not visible
3730 // in another view, and if this is not a child and if we are closing
3731 // a view (not a tabgroup).
3732 bool const close_buffer =
3733 !inOtherView(b) && !b.parent() && closing_;
3735 if (!closeWorkArea(wa, close_buffer))
3742 bool GuiView::saveBufferIfNeeded(Buffer & buf, bool hiding)
3744 if (buf.isClean() || buf.paragraphs().empty())
3747 // Switch to this Buffer.
3753 if (buf.isUnnamed()) {
3754 file = from_utf8(buf.fileName().onlyFileName());
3757 FileName filename = buf.fileName();
3759 file = filename.displayName(30);
3760 exists = filename.exists();
3763 // Bring this window to top before asking questions.
3768 if (hiding && buf.isUnnamed()) {
3769 docstring const text = bformat(_("The document %1$s has not been "
3770 "saved yet.\n\nDo you want to save "
3771 "the document?"), file);
3772 ret = Alert::prompt(_("Save new document?"),
3773 text, 0, 1, _("&Save"), _("&Cancel"));
3777 docstring const text = exists ?
3778 bformat(_("The document %1$s has unsaved changes."
3779 "\n\nDo you want to save the document or "
3780 "discard the changes?"), file) :
3781 bformat(_("The document %1$s has not been saved yet."
3782 "\n\nDo you want to save the document or "
3783 "discard it entirely?"), file);
3784 docstring const title = exists ?
3785 _("Save changed document?") : _("Save document?");
3786 ret = Alert::prompt(title, text, 0, 2,
3787 _("&Save"), _("&Discard"), _("&Cancel"));
3792 if (!saveBuffer(buf))
3796 // If we crash after this we could have no autosave file
3797 // but I guess this is really improbable (Jug).
3798 // Sometimes improbable things happen:
3799 // - see bug http://www.lyx.org/trac/ticket/6587 (ps)
3800 // buf.removeAutosaveFile();
3802 // revert all changes
3813 bool GuiView::inMultiTabs(GuiWorkArea * wa)
3815 Buffer & buf = wa->bufferView().buffer();
3817 for (int i = 0; i != d.splitter_->count(); ++i) {
3818 GuiWorkArea * wa_ = d.tabWorkArea(i)->workArea(buf);
3819 if (wa_ && wa_ != wa)
3822 return inOtherView(buf);
3826 bool GuiView::inOtherView(Buffer & buf)
3828 QList<int> const ids = guiApp->viewIds();
3830 for (int i = 0; i != ids.size(); ++i) {
3834 if (guiApp->view(ids[i]).workArea(buf))
3841 void GuiView::gotoNextOrPreviousBuffer(NextOrPrevious np, bool const move)
3843 if (!documentBufferView())
3846 if (TabWorkArea * twa = d.currentTabWorkArea()) {
3847 Buffer * const curbuf = &documentBufferView()->buffer();
3848 int nwa = twa->count();
3849 for (int i = 0; i < nwa; ++i) {
3850 if (&workArea(i)->bufferView().buffer() == curbuf) {
3853 next_index = (i == nwa - 1 ? 0 : i + 1);
3855 next_index = (i == 0 ? nwa - 1 : i - 1);
3857 twa->moveTab(i, next_index);
3859 setBuffer(&workArea(next_index)->bufferView().buffer());
3867 void GuiView::gotoNextTabWorkArea(NextOrPrevious np)
3869 int count = d.splitter_->count();
3870 for (int i = 0; i < count; ++i) {
3871 if (d.tabWorkArea(i) == d.currentTabWorkArea()) {
3874 new_index = (i == count - 1 ? 0 : i + 1);
3876 new_index = (i == 0 ? count - 1 : i - 1);
3877 setCurrentWorkArea(d.tabWorkArea(new_index)->currentWorkArea());
3884 /// make sure the document is saved
3885 static bool ensureBufferClean(Buffer * buffer)
3887 LASSERT(buffer, return false);
3888 if (buffer->isClean() && !buffer->isUnnamed())
3891 docstring const file = buffer->fileName().displayName(30);
3894 if (!buffer->isUnnamed()) {
3895 text = bformat(_("The document %1$s has unsaved "
3896 "changes.\n\nDo you want to save "
3897 "the document?"), file);
3898 title = _("Save changed document?");
3901 text = bformat(_("The document %1$s has not been "
3902 "saved yet.\n\nDo you want to save "
3903 "the document?"), file);
3904 title = _("Save new document?");
3906 int const ret = Alert::prompt(title, text, 0, 1, _("&Save"), _("&Cancel"));
3909 dispatch(FuncRequest(LFUN_BUFFER_WRITE));
3911 return buffer->isClean() && !buffer->isUnnamed();
3915 bool GuiView::reloadBuffer(Buffer & buf)
3917 currentBufferView()->cursor().reset();
3918 Buffer::ReadStatus status = buf.reload();
3919 return status == Buffer::ReadSuccess;
3923 void GuiView::checkExternallyModifiedBuffers()
3925 for (Buffer * buf : theBufferList()) {
3926 if (buf->fileName().exists() && buf->isChecksumModified()) {
3927 docstring text = bformat(_("Document \n%1$s\n has been externally modified."
3928 " Reload now? Any local changes will be lost."),
3929 from_utf8(buf->absFileName()));
3930 int const ret = Alert::prompt(_("Reload externally changed document?"),
3931 text, 0, 1, _("&Reload"), _("&Cancel"));
3939 void GuiView::dispatchVC(FuncRequest const & cmd, DispatchResult & dr)
3941 Buffer * buffer = documentBufferView()
3942 ? &(documentBufferView()->buffer()) : nullptr;
3944 switch (cmd.action()) {
3945 case LFUN_VC_REGISTER:
3946 if (!buffer || !ensureBufferClean(buffer))
3948 if (!buffer->lyxvc().inUse()) {
3949 if (buffer->lyxvc().registrer()) {
3950 reloadBuffer(*buffer);
3951 dr.clearMessageUpdate();
3956 case LFUN_VC_RENAME:
3957 case LFUN_VC_COPY: {
3958 if (!buffer || !ensureBufferClean(buffer))
3960 if (buffer->lyxvc().inUse() && !buffer->hasReadonlyFlag()) {
3961 if (buffer->lyxvc().isCheckInWithConfirmation()) {
3962 // Some changes are not yet committed.
3963 // We test here and not in getStatus(), since
3964 // this test is expensive.
3966 LyXVC::CommandResult ret =
3967 buffer->lyxvc().checkIn(log);
3969 if (ret == LyXVC::ErrorCommand ||
3970 ret == LyXVC::VCSuccess)
3971 reloadBuffer(*buffer);
3972 if (buffer->lyxvc().isCheckInWithConfirmation()) {
3973 frontend::Alert::error(
3974 _("Revision control error."),
3975 _("Document could not be checked in."));
3979 RenameKind const kind = (cmd.action() == LFUN_VC_RENAME) ?
3980 LV_VC_RENAME : LV_VC_COPY;
3981 renameBuffer(*buffer, cmd.argument(), kind);
3986 case LFUN_VC_CHECK_IN:
3987 if (!buffer || !ensureBufferClean(buffer))
3989 if (buffer->lyxvc().inUse() && !buffer->hasReadonlyFlag()) {
3991 LyXVC::CommandResult ret = buffer->lyxvc().checkIn(log);
3993 // Only skip reloading if the checkin was cancelled or
3994 // an error occurred before the real checkin VCS command
3995 // was executed, since the VCS might have changed the
3996 // file even if it could not checkin successfully.
3997 if (ret == LyXVC::ErrorCommand || ret == LyXVC::VCSuccess)
3998 reloadBuffer(*buffer);
4002 case LFUN_VC_CHECK_OUT:
4003 if (!buffer || !ensureBufferClean(buffer))
4005 if (buffer->lyxvc().inUse()) {
4006 dr.setMessage(buffer->lyxvc().checkOut());
4007 reloadBuffer(*buffer);
4011 case LFUN_VC_LOCKING_TOGGLE:
4012 if (!buffer || !ensureBufferClean(buffer) || buffer->hasReadonlyFlag())
4014 if (buffer->lyxvc().inUse()) {
4015 string res = buffer->lyxvc().lockingToggle();
4017 frontend::Alert::error(_("Revision control error."),
4018 _("Error when setting the locking property."));
4021 reloadBuffer(*buffer);
4026 case LFUN_VC_REVERT:
4029 if (buffer->lyxvc().revert()) {
4030 reloadBuffer(*buffer);
4031 dr.clearMessageUpdate();
4035 case LFUN_VC_UNDO_LAST:
4038 buffer->lyxvc().undoLast();
4039 reloadBuffer(*buffer);
4040 dr.clearMessageUpdate();
4043 case LFUN_VC_REPO_UPDATE:
4046 if (ensureBufferClean(buffer)) {
4047 dr.setMessage(buffer->lyxvc().repoUpdate());
4048 checkExternallyModifiedBuffers();
4052 case LFUN_VC_COMMAND: {
4053 string flag = cmd.getArg(0);
4054 if (buffer && contains(flag, 'R') && !ensureBufferClean(buffer))
4057 if (contains(flag, 'M')) {
4058 if (!Alert::askForText(message, _("LyX VC: Log Message")))
4061 string path = cmd.getArg(1);
4062 if (contains(path, "$$p") && buffer)
4063 path = subst(path, "$$p", buffer->filePath());
4064 LYXERR(Debug::LYXVC, "Directory: " << path);
4066 if (!pp.isReadableDirectory()) {
4067 lyxerr << _("Directory is not accessible.") << endl;
4070 support::PathChanger p(pp);
4072 string command = cmd.getArg(2);
4073 if (command.empty())
4076 command = subst(command, "$$i", buffer->absFileName());
4077 command = subst(command, "$$p", buffer->filePath());
4079 command = subst(command, "$$m", to_utf8(message));
4080 LYXERR(Debug::LYXVC, "Command: " << command);
4082 one.startscript(Systemcall::Wait, command);
4086 if (contains(flag, 'I'))
4087 buffer->markDirty();
4088 if (contains(flag, 'R'))
4089 reloadBuffer(*buffer);
4094 case LFUN_VC_COMPARE: {
4095 if (cmd.argument().empty()) {
4096 lyx::dispatch(FuncRequest(LFUN_DIALOG_SHOW, "comparehistory"));
4102 string rev1 = cmd.getArg(0);
4106 if (!buffer->lyxvc().prepareFileRevision(rev1, f1))
4109 if (isStrInt(rev1) && convert<int>(rev1) <= 0) {
4110 f2 = buffer->absFileName();
4112 string rev2 = cmd.getArg(1);
4116 if (!buffer->lyxvc().prepareFileRevision(rev2, f2))
4120 LYXERR(Debug::LYXVC, "Launching comparison for fetched revisions:\n" <<
4121 f1 << "\n" << f2 << "\n" );
4122 string par = "compare run " + quoteName(f1) + " " + quoteName(f2);
4123 lyx::dispatch(FuncRequest(LFUN_DIALOG_SHOW, par));
4133 void GuiView::openChildDocument(string const & fname)
4135 LASSERT(documentBufferView(), return);
4136 Buffer & buffer = documentBufferView()->buffer();
4137 FileName const filename = support::makeAbsPath(fname, buffer.filePath());
4138 documentBufferView()->saveBookmark(false);
4139 Buffer * child = nullptr;
4140 if (theBufferList().exists(filename)) {
4141 child = theBufferList().getBuffer(filename);
4144 message(bformat(_("Opening child document %1$s..."),
4145 makeDisplayPath(filename.absFileName())));
4146 child = loadDocument(filename, false);
4148 // Set the parent name of the child document.
4149 // This makes insertion of citations and references in the child work,
4150 // when the target is in the parent or another child document.
4152 child->setParent(&buffer);
4156 bool GuiView::goToFileRow(string const & argument)
4160 size_t i = argument.find_last_of(' ');
4161 if (i != string::npos) {
4162 file_name = os::internal_path(FileName(trim(argument.substr(0, i))).realPath());
4163 istringstream is(argument.substr(i + 1));
4168 if (i == string::npos) {
4169 LYXERR0("Wrong argument: " << argument);
4172 Buffer * buf = nullptr;
4173 string const realtmp = package().temp_dir().realPath();
4174 // We have to use os::path_prefix_is() here, instead of
4175 // simply prefixIs(), because the file name comes from
4176 // an external application and may need case adjustment.
4177 if (os::path_prefix_is(file_name, realtmp, os::CASE_ADJUSTED)) {
4178 buf = theBufferList().getBufferFromTmp(file_name, true);
4179 LYXERR(Debug::FILES, "goToFileRow: buffer lookup for " << file_name
4180 << (buf ? " success" : " failed"));
4182 // Must replace extension of the file to be .lyx
4183 // and get full path
4184 FileName const s = fileSearch(string(),
4185 support::changeExtension(file_name, ".lyx"), "lyx");
4186 // Either change buffer or load the file
4187 if (theBufferList().exists(s))
4188 buf = theBufferList().getBuffer(s);
4189 else if (s.exists()) {
4190 buf = loadDocument(s);
4195 _("File does not exist: %1$s"),
4196 makeDisplayPath(file_name)));
4202 _("No buffer for file: %1$s."),
4203 makeDisplayPath(file_name))
4208 bool success = documentBufferView()->setCursorFromRow(row);
4210 LYXERR(Debug::OUTFILE,
4211 "setCursorFromRow: invalid position for row " << row);
4212 frontend::Alert::error(_("Inverse Search Failed"),
4213 _("Invalid position requested by inverse search.\n"
4214 "You may need to update the viewed document."));
4221 Buffer::ExportStatus GuiView::GuiViewPrivate::runAndDestroy(const T& func,
4222 Buffer const * orig, Buffer * clone, string const & format)
4224 Buffer::ExportStatus const status = func(format);
4226 // the cloning operation will have produced a clone of the entire set of
4227 // documents, starting from the master. so we must delete those.
4228 Buffer * mbuf = const_cast<Buffer *>(clone->masterBuffer());
4230 busyBuffers.remove(orig);
4235 Buffer::ExportStatus GuiView::GuiViewPrivate::compileAndDestroy(
4236 Buffer const * orig, Buffer * clone, string const & format)
4238 Buffer::ExportStatus (Buffer::* mem_func)(std::string const &, bool) const =
4240 return runAndDestroy(lyx::bind(mem_func, clone, _1, true), orig, clone, format);
4244 Buffer::ExportStatus GuiView::GuiViewPrivate::exportAndDestroy(
4245 Buffer const * orig, Buffer * clone, string const & format)
4247 Buffer::ExportStatus (Buffer::* mem_func)(std::string const &, bool) const =
4249 return runAndDestroy(lyx::bind(mem_func, clone, _1, false), orig, clone, format);
4253 Buffer::ExportStatus GuiView::GuiViewPrivate::previewAndDestroy(
4254 Buffer const * orig, Buffer * clone, string const & format)
4256 Buffer::ExportStatus (Buffer::* mem_func)(std::string const &) const =
4258 return runAndDestroy(lyx::bind(mem_func, clone, _1), orig, clone, format);
4262 bool GuiView::GuiViewPrivate::asyncBufferProcessing(string const & argument,
4263 Buffer const * used_buffer,
4264 docstring const & msg,
4265 Buffer::ExportStatus (*asyncFunc)(Buffer const *, Buffer *, string const &),
4266 Buffer::ExportStatus (Buffer::*syncFunc)(string const &, bool) const,
4267 Buffer::ExportStatus (Buffer::*previewFunc)(string const &) const,
4268 bool allow_async, bool use_tmpdir)
4273 string format = argument;
4275 format = used_buffer->params().getDefaultOutputFormat();
4276 processing_format = format;
4278 progress_->clearMessages();
4281 #if EXPORT_in_THREAD
4283 GuiViewPrivate::busyBuffers.insert(used_buffer);
4284 Buffer * cloned_buffer = used_buffer->cloneWithChildren();
4285 if (!cloned_buffer) {
4286 Alert::error(_("Export Error"),
4287 _("Error cloning the Buffer."));
4290 QFuture<Buffer::ExportStatus> f = QtConcurrent::run(
4295 setPreviewFuture(f);
4296 last_export_format = used_buffer->params().bufferFormat();
4299 // We are asynchronous, so we don't know here anything about the success
4302 Buffer::ExportStatus status;
4304 status = (used_buffer->*syncFunc)(format, use_tmpdir);
4305 } else if (previewFunc) {
4306 status = (used_buffer->*previewFunc)(format);
4309 handleExportStatus(gv_, status, format);
4311 return (status == Buffer::ExportSuccess
4312 || status == Buffer::PreviewSuccess);
4316 Buffer::ExportStatus status;
4318 status = (used_buffer->*syncFunc)(format, true);
4319 } else if (previewFunc) {
4320 status = (used_buffer->*previewFunc)(format);
4323 handleExportStatus(gv_, status, format);
4325 return (status == Buffer::ExportSuccess
4326 || status == Buffer::PreviewSuccess);
4330 void GuiView::dispatchToBufferView(FuncRequest const & cmd, DispatchResult & dr)
4332 BufferView * bv = currentBufferView();
4333 LASSERT(bv, return);
4335 // Let the current BufferView dispatch its own actions.
4336 bv->dispatch(cmd, dr);
4337 if (dr.dispatched()) {
4338 if (cmd.action() == LFUN_REDO || cmd.action() == LFUN_UNDO)
4339 updateDialog("document", "");
4343 // Try with the document BufferView dispatch if any.
4344 BufferView * doc_bv = documentBufferView();
4345 if (doc_bv && doc_bv != bv) {
4346 doc_bv->dispatch(cmd, dr);
4347 if (dr.dispatched()) {
4348 if (cmd.action() == LFUN_REDO || cmd.action() == LFUN_UNDO)
4349 updateDialog("document", "");
4354 // Then let the current Cursor dispatch its own actions.
4355 bv->cursor().dispatch(cmd);
4357 // update completion. We do it here and not in
4358 // processKeySym to avoid another redraw just for a
4359 // changed inline completion
4360 if (cmd.origin() == FuncRequest::KEYBOARD) {
4361 if (cmd.action() == LFUN_SELF_INSERT
4362 || (cmd.action() == LFUN_ERT_INSERT && bv->cursor().inMathed()))
4363 updateCompletion(bv->cursor(), true, true);
4364 else if (cmd.action() == LFUN_CHAR_DELETE_BACKWARD)
4365 updateCompletion(bv->cursor(), false, true);
4367 updateCompletion(bv->cursor(), false, false);
4370 dr = bv->cursor().result();
4374 void GuiView::dispatch(FuncRequest const & cmd, DispatchResult & dr)
4376 BufferView * bv = currentBufferView();
4377 // By default we won't need any update.
4378 dr.screenUpdate(Update::None);
4379 // assume cmd will be dispatched
4380 dr.dispatched(true);
4382 Buffer * doc_buffer = documentBufferView()
4383 ? &(documentBufferView()->buffer()) : nullptr;
4385 if (cmd.origin() == FuncRequest::TOC) {
4386 GuiToc * toc = static_cast<GuiToc*>(findOrBuild("toc", false));
4387 toc->doDispatch(bv->cursor(), cmd, dr);
4391 string const argument = to_utf8(cmd.argument());
4393 switch(cmd.action()) {
4394 case LFUN_BUFFER_CHILD_OPEN:
4395 openChildDocument(to_utf8(cmd.argument()));
4398 case LFUN_BUFFER_IMPORT:
4399 importDocument(to_utf8(cmd.argument()));
4402 case LFUN_MASTER_BUFFER_EXPORT:
4404 doc_buffer = const_cast<Buffer *>(doc_buffer->masterBuffer());
4406 case LFUN_BUFFER_EXPORT: {
4409 // GCC only sees strfwd.h when building merged
4410 if (::lyx::operator==(cmd.argument(), "custom")) {
4411 // LFUN_MASTER_BUFFER_EXPORT is not enabled for this case,
4412 // so the following test should not be needed.
4413 // In principle, we could try to switch to such a view...
4414 // if (cmd.action() == LFUN_BUFFER_EXPORT)
4415 dispatch(FuncRequest(LFUN_DIALOG_SHOW, "sendto"), dr);
4419 string const dest = cmd.getArg(1);
4420 FileName target_dir;
4421 if (!dest.empty() && FileName::isAbsolute(dest))
4422 target_dir = FileName(support::onlyPath(dest));
4424 target_dir = doc_buffer->fileName().onlyPath();
4426 string const format = (argument.empty() || argument == "default") ?
4427 doc_buffer->params().getDefaultOutputFormat() : argument;
4429 if ((dest.empty() && doc_buffer->isUnnamed())
4430 || !target_dir.isDirWritable()) {
4431 exportBufferAs(*doc_buffer, from_utf8(format));
4434 /* TODO/Review: Is it a problem to also export the children?
4435 See the update_unincluded flag */
4436 d.asyncBufferProcessing(format,
4439 &GuiViewPrivate::exportAndDestroy,
4441 nullptr, cmd.allowAsync());
4442 // TODO Inform user about success
4446 case LFUN_BUFFER_EXPORT_AS: {
4447 LASSERT(doc_buffer, break);
4448 docstring f = cmd.argument();
4450 f = from_ascii(doc_buffer->params().getDefaultOutputFormat());
4451 exportBufferAs(*doc_buffer, f);
4455 case LFUN_BUFFER_UPDATE: {
4456 d.asyncBufferProcessing(argument,
4459 &GuiViewPrivate::compileAndDestroy,
4461 nullptr, cmd.allowAsync(), true);
4464 case LFUN_BUFFER_VIEW: {
4465 d.asyncBufferProcessing(argument,
4467 _("Previewing ..."),
4468 &GuiViewPrivate::previewAndDestroy,
4470 &Buffer::preview, cmd.allowAsync());
4473 case LFUN_MASTER_BUFFER_UPDATE: {
4474 d.asyncBufferProcessing(argument,
4475 (doc_buffer ? doc_buffer->masterBuffer() : nullptr),
4477 &GuiViewPrivate::compileAndDestroy,
4479 nullptr, cmd.allowAsync(), true);
4482 case LFUN_MASTER_BUFFER_VIEW: {
4483 d.asyncBufferProcessing(argument,
4484 (doc_buffer ? doc_buffer->masterBuffer() : nullptr),
4486 &GuiViewPrivate::previewAndDestroy,
4487 nullptr, &Buffer::preview, cmd.allowAsync());
4490 case LFUN_EXPORT_CANCEL: {
4494 case LFUN_BUFFER_SWITCH: {
4495 string const file_name = to_utf8(cmd.argument());
4496 if (!FileName::isAbsolute(file_name)) {
4498 dr.setMessage(_("Absolute filename expected."));
4502 Buffer * buffer = theBufferList().getBuffer(FileName(file_name));
4505 dr.setMessage(_("Document not loaded"));
4509 // Do we open or switch to the buffer in this view ?
4510 if (workArea(*buffer)
4511 || lyxrc.open_buffers_in_tabs || !documentBufferView()) {
4516 // Look for the buffer in other views
4517 QList<int> const ids = guiApp->viewIds();
4519 for (; i != ids.size(); ++i) {
4520 GuiView & gv = guiApp->view(ids[i]);
4521 if (gv.workArea(*buffer)) {
4523 gv.activateWindow();
4525 gv.setBuffer(buffer);
4530 // If necessary, open a new window as a last resort
4531 if (i == ids.size()) {
4532 lyx::dispatch(FuncRequest(LFUN_WINDOW_NEW));
4538 case LFUN_BUFFER_NEXT:
4539 gotoNextOrPreviousBuffer(NEXT, false);
4542 case LFUN_BUFFER_MOVE_NEXT:
4543 gotoNextOrPreviousBuffer(NEXT, true);
4546 case LFUN_BUFFER_PREVIOUS:
4547 gotoNextOrPreviousBuffer(PREV, false);
4550 case LFUN_BUFFER_MOVE_PREVIOUS:
4551 gotoNextOrPreviousBuffer(PREV, true);
4554 case LFUN_BUFFER_CHKTEX:
4555 LASSERT(doc_buffer, break);
4556 doc_buffer->runChktex();
4559 case LFUN_CHANGES_TRACK: {
4560 // the actual dispatch is done in Buffer
4561 dispatchToBufferView(cmd, dr);
4562 // but we inform the GUI (document settings) if this is toggled
4563 LASSERT(doc_buffer, break);
4564 Q_EMIT changeTrackingToggled(doc_buffer->params().track_changes);
4568 case LFUN_COMMAND_EXECUTE: {
4569 command_execute_ = true;
4570 minibuffer_focus_ = true;
4573 case LFUN_DROP_LAYOUTS_CHOICE:
4574 d.layout_->showPopup();
4577 case LFUN_MENU_OPEN:
4578 if (QMenu * menu = guiApp->menus().menu(toqstr(cmd.argument()), *this))
4579 menu->exec(QCursor::pos());
4582 case LFUN_FILE_INSERT: {
4583 bool const ignore_lang = cmd.getArg(1) == "ignorelang";
4584 if (insertLyXFile(from_utf8(cmd.getArg(0)), ignore_lang)) {
4585 dr.forceBufferUpdate();
4586 dr.screenUpdate(Update::Force);
4591 case LFUN_FILE_INSERT_PLAINTEXT:
4592 case LFUN_FILE_INSERT_PLAINTEXT_PARA: {
4593 string const fname = to_utf8(cmd.argument());
4594 if (!fname.empty() && !FileName::isAbsolute(fname)) {
4595 dr.setMessage(_("Absolute filename expected."));
4599 FileName filename(fname);
4600 if (fname.empty()) {
4601 FileDialog dlg(qt_("Select file to insert"));
4603 FileDialog::Result result = dlg.open(toqstr(bv->buffer().filePath()),
4604 QStringList(qt_("All Files")+ " " + wildcardAllFiles()));
4606 if (result.first == FileDialog::Later || result.second.isEmpty()) {
4607 dr.setMessage(_("Canceled."));
4611 filename.set(fromqstr(result.second));
4615 FuncRequest const new_cmd(cmd, filename.absoluteFilePath());
4616 bv->dispatch(new_cmd, dr);
4621 case LFUN_BUFFER_RELOAD: {
4622 LASSERT(doc_buffer, break);
4625 bool drop = (cmd.argument() == "dump");
4628 if (!drop && !doc_buffer->isClean()) {
4629 docstring const file =
4630 makeDisplayPath(doc_buffer->absFileName(), 20);
4631 if (doc_buffer->notifiesExternalModification()) {
4632 docstring text = _("The current version will be lost. "
4633 "Are you sure you want to load the version on disk "
4634 "of the document %1$s?");
4635 ret = Alert::prompt(_("Reload saved document?"),
4636 bformat(text, file), 1, 1,
4637 _("&Reload"), _("&Cancel"));
4639 docstring text = _("Any changes will be lost. "
4640 "Are you sure you want to revert to the saved version "
4641 "of the document %1$s?");
4642 ret = Alert::prompt(_("Revert to saved document?"),
4643 bformat(text, file), 1, 1,
4644 _("&Revert"), _("&Cancel"));
4649 doc_buffer->markClean();
4650 reloadBuffer(*doc_buffer);
4651 dr.forceBufferUpdate();
4656 case LFUN_BUFFER_RESET_EXPORT:
4657 LASSERT(doc_buffer, break);
4658 doc_buffer->requireFreshStart(true);
4659 dr.setMessage(_("Buffer export reset."));
4662 case LFUN_BUFFER_WRITE:
4663 LASSERT(doc_buffer, break);
4664 saveBuffer(*doc_buffer);
4667 case LFUN_BUFFER_WRITE_AS:
4668 LASSERT(doc_buffer, break);
4669 renameBuffer(*doc_buffer, cmd.argument());
4672 case LFUN_BUFFER_WRITE_AS_TEMPLATE:
4673 LASSERT(doc_buffer, break);
4674 renameBuffer(*doc_buffer, cmd.argument(),
4675 LV_WRITE_AS_TEMPLATE);
4678 case LFUN_BUFFER_WRITE_ALL: {
4679 Buffer * first = theBufferList().first();
4682 message(_("Saving all documents..."));
4683 // We cannot use a for loop as the buffer list cycles.
4686 if (!b->isClean()) {
4688 LYXERR(Debug::ACTION, "Saved " << b->absFileName());
4690 b = theBufferList().next(b);
4691 } while (b != first);
4692 dr.setMessage(_("All documents saved."));
4696 case LFUN_MASTER_BUFFER_FORALL: {
4700 FuncRequest funcToRun = lyxaction.lookupFunc(cmd.getLongArg(0));
4701 funcToRun.allowAsync(false);
4703 for (Buffer const * buf : doc_buffer->allRelatives()) {
4704 // Switch to other buffer view and resend cmd
4705 lyx::dispatch(FuncRequest(
4706 LFUN_BUFFER_SWITCH, buf->absFileName()));
4707 lyx::dispatch(funcToRun);
4710 lyx::dispatch(FuncRequest(
4711 LFUN_BUFFER_SWITCH, doc_buffer->absFileName()));
4715 case LFUN_BUFFER_EXTERNAL_MODIFICATION_CLEAR:
4716 LASSERT(doc_buffer, break);
4717 doc_buffer->clearExternalModification();
4720 case LFUN_BUFFER_CLOSE:
4724 case LFUN_BUFFER_CLOSE_ALL:
4728 case LFUN_DEVEL_MODE_TOGGLE:
4729 devel_mode_ = !devel_mode_;
4731 dr.setMessage(_("Developer mode is now enabled."));
4733 dr.setMessage(_("Developer mode is now disabled."));
4736 case LFUN_TOOLBAR_SET: {
4737 string const name = cmd.getArg(0);
4738 string const state = cmd.getArg(1);
4739 if (GuiToolbar * t = toolbar(name))
4744 case LFUN_TOOLBAR_TOGGLE: {
4745 string const name = cmd.getArg(0);
4746 if (GuiToolbar * t = toolbar(name))
4751 case LFUN_TOOLBAR_MOVABLE: {
4752 string const name = cmd.getArg(0);
4754 // toggle (all) toolbars movablility
4755 toolbarsMovable_ = !toolbarsMovable_;
4756 for (ToolbarInfo const & ti : guiApp->toolbars()) {
4757 GuiToolbar * tb = toolbar(ti.name);
4758 if (tb && tb->isMovable() != toolbarsMovable_)
4759 // toggle toolbar movablity if it does not fit lock
4760 // (all) toolbars positions state silent = true, since
4761 // status bar notifications are slow
4764 if (toolbarsMovable_)
4765 dr.setMessage(_("Toolbars unlocked."));
4767 dr.setMessage(_("Toolbars locked."));
4768 } else if (GuiToolbar * tb = toolbar(name))
4769 // toggle current toolbar movablity
4771 // update lock (all) toolbars positions
4772 updateLockToolbars();
4776 case LFUN_ICON_SIZE: {
4777 QSize size = d.iconSize(cmd.argument());
4779 dr.setMessage(bformat(_("Icon size set to %1$dx%2$d."),
4780 size.width(), size.height()));
4784 case LFUN_DIALOG_UPDATE: {
4785 string const name = to_utf8(cmd.argument());
4786 if (name == "prefs" || name == "document")
4787 updateDialog(name, string());
4788 else if (name == "paragraph")
4789 lyx::dispatch(FuncRequest(LFUN_PARAGRAPH_UPDATE));
4790 else if (currentBufferView()) {
4791 Inset * inset = currentBufferView()->editedInset(name);
4792 // Can only update a dialog connected to an existing inset
4794 // FIXME: get rid of this indirection; GuiView ask the inset
4795 // if he is kind enough to update itself...
4796 FuncRequest fr(LFUN_INSET_DIALOG_UPDATE, cmd.argument());
4797 //FIXME: pass DispatchResult here?
4798 inset->dispatch(currentBufferView()->cursor(), fr);
4804 case LFUN_DIALOG_TOGGLE: {
4805 FuncCode const func_code = isDialogVisible(cmd.getArg(0))
4806 ? LFUN_DIALOG_HIDE : LFUN_DIALOG_SHOW;
4807 dispatch(FuncRequest(func_code, cmd.argument()), dr);
4811 case LFUN_DIALOG_DISCONNECT_INSET:
4812 disconnectDialog(to_utf8(cmd.argument()));
4815 case LFUN_DIALOG_HIDE: {
4816 guiApp->hideDialogs(to_utf8(cmd.argument()), nullptr);
4820 case LFUN_DIALOG_SHOW: {
4821 string const name = cmd.getArg(0);
4822 string sdata = trim(to_utf8(cmd.argument()).substr(name.size()));
4824 if (name == "latexlog") {
4825 // getStatus checks that
4826 LASSERT(doc_buffer, break);
4827 Buffer::LogType type;
4828 string const logfile = doc_buffer->logName(&type);
4830 case Buffer::latexlog:
4833 case Buffer::buildlog:
4834 sdata = "literate ";
4837 sdata += Lexer::quoteString(logfile);
4838 showDialog("log", sdata);
4839 } else if (name == "vclog") {
4840 // getStatus checks that
4841 LASSERT(doc_buffer, break);
4842 string const sdata2 = "vc " +
4843 Lexer::quoteString(doc_buffer->lyxvc().getLogFile());
4844 showDialog("log", sdata2);
4845 } else if (name == "symbols") {
4846 sdata = bv->cursor().getEncoding()->name();
4848 showDialog("symbols", sdata);
4849 } else if (name == "findreplace") {
4850 sdata = to_utf8(bv->cursor().selectionAsString(false));
4851 showDialog(name, sdata);
4853 } else if (name == "prefs" && isFullScreen()) {
4854 lfunUiToggle("fullscreen");
4855 showDialog("prefs", sdata);
4857 showDialog(name, sdata);
4862 dr.setMessage(cmd.argument());
4865 case LFUN_UI_TOGGLE: {
4866 string arg = cmd.getArg(0);
4867 if (!lfunUiToggle(arg)) {
4868 docstring const msg = "ui-toggle " + _("%1$s unknown command!");
4869 dr.setMessage(bformat(msg, from_utf8(arg)));
4871 // Make sure the keyboard focus stays in the work area.
4876 case LFUN_VIEW_SPLIT: {
4877 LASSERT(doc_buffer, break);
4878 string const orientation = cmd.getArg(0);
4879 d.splitter_->setOrientation(orientation == "vertical"
4880 ? Qt::Vertical : Qt::Horizontal);
4881 TabWorkArea * twa = addTabWorkArea();
4882 GuiWorkArea * wa = twa->addWorkArea(*doc_buffer, *this);
4884 // set cursor to same position as current view.
4885 // TODO: would be good to *scroll* to same position also
4886 // so that the display is the same (#12689)
4887 DocIterator cur = bv->cursor();
4888 wa->bufferView().moveToPosition(cur.pit(), cur.pos(), 0, 0);
4890 setCurrentWorkArea(wa);
4894 case LFUN_TAB_GROUP_NEXT:
4895 gotoNextTabWorkArea(NEXT);
4898 case LFUN_TAB_GROUP_PREVIOUS:
4899 gotoNextTabWorkArea(PREV);
4902 case LFUN_TAB_GROUP_CLOSE:
4903 if (TabWorkArea * twa = d.currentTabWorkArea()) {
4904 closeTabWorkArea(twa);
4905 d.current_work_area_ = nullptr;
4906 twa = d.currentTabWorkArea();
4907 // Switch to the next GuiWorkArea in the found TabWorkArea.
4909 // Make sure the work area is up to date.
4910 setCurrentWorkArea(twa->currentWorkArea());
4912 setCurrentWorkArea(nullptr);
4917 case LFUN_VIEW_CLOSE:
4918 if (TabWorkArea * twa = d.currentTabWorkArea()) {
4919 closeWorkArea(twa->currentWorkArea());
4920 d.current_work_area_ = nullptr;
4921 twa = d.currentTabWorkArea();
4922 // Switch to the next GuiWorkArea in the found TabWorkArea.
4924 // Make sure the work area is up to date.
4925 setCurrentWorkArea(twa->currentWorkArea());
4927 setCurrentWorkArea(nullptr);
4932 case LFUN_COMPLETION_INLINE:
4933 if (d.current_work_area_)
4934 d.current_work_area_->completer().showInline();
4937 case LFUN_COMPLETION_POPUP:
4938 if (d.current_work_area_)
4939 d.current_work_area_->completer().showPopup();
4944 if (d.current_work_area_)
4945 d.current_work_area_->completer().tab();
4948 case LFUN_COMPLETION_CANCEL:
4949 if (d.current_work_area_) {
4950 if (d.current_work_area_->completer().popupVisible())
4951 d.current_work_area_->completer().hidePopup();
4953 d.current_work_area_->completer().hideInline();
4957 case LFUN_COMPLETION_ACCEPT:
4958 if (d.current_work_area_)
4959 d.current_work_area_->completer().activate();
4962 case LFUN_BUFFER_ZOOM_IN:
4963 case LFUN_BUFFER_ZOOM_OUT:
4964 case LFUN_BUFFER_ZOOM: {
4965 zoom_ratio_ = zoomRatio(cmd, zoom_ratio_);
4967 // Actual zoom value: default zoom + fractional extra value
4968 int zoom = (int)(lyxrc.defaultZoom * zoom_ratio_);
4969 zoom = min(max(zoom, zoom_min_), zoom_max_);
4971 setCurrentZoom(zoom);
4973 dr.setMessage(bformat(_("Zoom level is now %1$d% (default value: %2$d%)"),
4974 lyxrc.currentZoom, lyxrc.defaultZoom));
4976 guiApp->fontLoader().update();
4977 // Regenerate instant previews
4978 if (lyxrc.preview != LyXRC::PREVIEW_OFF
4979 && doc_buffer && doc_buffer->loader())
4980 doc_buffer->loader()->refreshPreviews();
4981 dr.screenUpdate(Update::ForceAll | Update::FitCursor);
4985 case LFUN_VC_REGISTER:
4986 case LFUN_VC_RENAME:
4988 case LFUN_VC_CHECK_IN:
4989 case LFUN_VC_CHECK_OUT:
4990 case LFUN_VC_REPO_UPDATE:
4991 case LFUN_VC_LOCKING_TOGGLE:
4992 case LFUN_VC_REVERT:
4993 case LFUN_VC_UNDO_LAST:
4994 case LFUN_VC_COMMAND:
4995 case LFUN_VC_COMPARE:
4996 dispatchVC(cmd, dr);
4999 case LFUN_SERVER_GOTO_FILE_ROW:
5000 if(goToFileRow(to_utf8(cmd.argument())))
5001 dr.screenUpdate(Update::Force | Update::FitCursor);
5004 case LFUN_LYX_ACTIVATE:
5008 case LFUN_WINDOW_RAISE:
5014 case LFUN_FORWARD_SEARCH: {
5015 // it seems safe to assume we have a document buffer, since
5016 // getStatus wants one.
5017 LASSERT(doc_buffer, break);
5018 Buffer const * doc_master = doc_buffer->masterBuffer();
5019 FileName const path(doc_master->temppath());
5020 string const texname = doc_master->isChild(doc_buffer)
5021 ? DocFileName(changeExtension(
5022 doc_buffer->absFileName(),
5023 "tex")).mangledFileName()
5024 : doc_buffer->latexName();
5025 string const fulltexname =
5026 support::makeAbsPath(texname, doc_master->temppath()).absFileName();
5027 string const mastername =
5028 removeExtension(doc_master->latexName());
5029 FileName const dviname(addName(path.absFileName(),
5030 addExtension(mastername, "dvi")));
5031 FileName const pdfname(addName(path.absFileName(),
5032 addExtension(mastername, "pdf")));
5033 bool const have_dvi = dviname.exists();
5034 bool const have_pdf = pdfname.exists();
5035 if (!have_dvi && !have_pdf) {
5036 dr.setMessage(_("Please, preview the document first."));
5039 bool const goto_dvi = have_dvi && !lyxrc.forward_search_dvi.empty();
5040 bool const goto_pdf = have_pdf && !lyxrc.forward_search_pdf.empty();
5041 string outname = dviname.onlyFileName();
5042 string command = lyxrc.forward_search_dvi;
5043 if ((!goto_dvi || goto_pdf) &&
5044 pdfname.lastModified() > dviname.lastModified()) {
5045 outname = pdfname.onlyFileName();
5046 command = lyxrc.forward_search_pdf;
5049 DocIterator cur = bv->cursor();
5050 int row = doc_buffer->texrow().rowFromDocIterator(cur).first;
5051 LYXERR(Debug::ACTION, "Forward search: row:" << row
5053 if (row == -1 || command.empty()) {
5054 dr.setMessage(_("Couldn't proceed."));
5057 string texrow = convert<string>(row);
5059 command = subst(command, "$$n", texrow);
5060 command = subst(command, "$$f", fulltexname);
5061 command = subst(command, "$$t", texname);
5062 command = subst(command, "$$o", outname);
5064 volatile PathChanger p(path);
5066 one.startscript(Systemcall::DontWait, command);
5070 case LFUN_SPELLING_CONTINUOUSLY:
5071 lyxrc.spellcheck_continuously = !lyxrc.spellcheck_continuously;
5072 dr.screenUpdate(Update::Force);
5075 case LFUN_CITATION_OPEN: {
5077 if (theFormats().getFormat("pdf"))
5078 pdfv = theFormats().getFormat("pdf")->viewer();
5079 if (theFormats().getFormat("ps"))
5080 psv = theFormats().getFormat("ps")->viewer();
5081 frontend::showTarget(argument, pdfv, psv);
5086 // The LFUN must be for one of BufferView, Buffer or Cursor;
5088 dispatchToBufferView(cmd, dr);
5092 // Need to update bv because many LFUNs here might have destroyed it
5093 bv = currentBufferView();
5095 // Clear non-empty selections
5096 // (e.g. from a "char-forward-select" followed by "char-backward-select")
5098 Cursor & cur = bv->cursor();
5099 if ((cur.selection() && cur.selBegin() == cur.selEnd())) {
5100 cur.clearSelection();
5106 bool GuiView::lfunUiToggle(string const & ui_component)
5108 if (ui_component == "scrollbar") {
5109 // hide() is of no help
5110 if (d.current_work_area_->verticalScrollBarPolicy() ==
5111 Qt::ScrollBarAlwaysOff)
5113 d.current_work_area_->setVerticalScrollBarPolicy(
5114 Qt::ScrollBarAsNeeded);
5116 d.current_work_area_->setVerticalScrollBarPolicy(
5117 Qt::ScrollBarAlwaysOff);
5118 } else if (ui_component == "statusbar") {
5119 statusBar()->setVisible(!statusBar()->isVisible());
5120 } else if (ui_component == "menubar") {
5121 menuBar()->setVisible(!menuBar()->isVisible());
5122 } else if (ui_component == "zoomlevel") {
5123 zoom_value_->setVisible(!zoom_value_->isVisible());
5124 } else if (ui_component == "zoomslider") {
5125 zoom_widget_->setVisible(!zoom_widget_->isVisible());
5126 } else if (ui_component == "statistics-w") {
5127 word_count_enabled_ = !word_count_enabled_;
5130 } else if (ui_component == "statistics-cb") {
5131 char_count_enabled_ = !char_count_enabled_;
5134 } else if (ui_component == "statistics-c") {
5135 char_nb_count_enabled_ = !char_nb_count_enabled_;
5138 } else if (ui_component == "frame") {
5139 int const l = contentsMargins().left();
5141 //are the frames in default state?
5142 d.current_work_area_->setFrameStyle(QFrame::NoFrame);
5144 #if QT_VERSION > 0x050903
5145 setAttribute(Qt::WA_ContentsMarginsRespectsSafeArea, false);
5147 setContentsMargins(-2, -2, -2, -2);
5149 #if QT_VERSION > 0x050903
5150 setAttribute(Qt::WA_ContentsMarginsRespectsSafeArea, true);
5152 setContentsMargins(0, 0, 0, 0);
5155 if (ui_component == "fullscreen") {
5159 stat_counts_->setVisible(statsEnabled());
5164 void GuiView::cancelExport()
5166 Systemcall::killscript();
5167 // stop busy signal immediately so that in the subsequent
5168 // "Export canceled" prompt the status bar icons are accurate.
5169 Q_EMIT scriptKilled();
5173 void GuiView::toggleFullScreen()
5175 setWindowState(windowState() ^ Qt::WindowFullScreen);
5179 Buffer const * GuiView::updateInset(Inset const * inset)
5184 Buffer const * inset_buffer = &(inset->buffer());
5186 for (int i = 0; i != d.splitter_->count(); ++i) {
5187 GuiWorkArea * wa = d.tabWorkArea(i)->currentWorkArea();
5190 Buffer const * buffer = &(wa->bufferView().buffer());
5191 if (inset_buffer == buffer)
5192 wa->scheduleRedraw(true);
5194 return inset_buffer;
5198 void GuiView::restartCaret()
5200 /* When we move around, or type, it's nice to be able to see
5201 * the caret immediately after the keypress.
5203 if (d.current_work_area_)
5204 d.current_work_area_->startBlinkingCaret();
5206 // Take this occasion to update the other GUI elements.
5212 void GuiView::updateCompletion(Cursor & cur, bool start, bool keep)
5214 if (d.current_work_area_)
5215 d.current_work_area_->completer().updateVisibility(cur, start, keep);
5220 // This list should be kept in sync with the list of insets in
5221 // src/insets/Inset.cpp. I.e., if a dialog goes with an inset, the
5222 // dialog should have the same name as the inset.
5223 // Changes should be also recorded in LFUN_DIALOG_SHOW doxygen
5224 // docs in LyXAction.cpp.
5226 char const * const dialognames[] = {
5228 "aboutlyx", "bibitem", "bibtex", "box", "branch", "changes", "character",
5229 "citation", "compare", "comparehistory", "counter", "document", "errorlist", "ert",
5230 "external", "file", "findreplace", "findreplaceadv", "float", "graphics",
5231 "href", "include", "index", "index_print", "info", "listings", "label", "line",
5232 "log", "lyxfiles", "mathdelimiter", "mathmatrix", "mathspace", "nomenclature",
5233 "nomencl_print", "note", "paragraph", "phantom", "prefs", "ref",
5234 "sendto", "space", "spellchecker", "symbols", "tabular", "tabularcreate",
5235 "thesaurus", "texinfo", "toc", "view-source", "vspace", "wrap", "progress"};
5237 char const * const * const end_dialognames =
5238 dialognames + (sizeof(dialognames) / sizeof(char *));
5242 cmpCStr(char const * name) : name_(name) {}
5243 bool operator()(char const * other) {
5244 return strcmp(other, name_) == 0;
5251 bool isValidName(string const & name)
5253 return find_if(dialognames, end_dialognames,
5254 cmpCStr(name.c_str())) != end_dialognames;
5260 void GuiView::resetDialogs()
5262 // Make sure that no LFUN uses any GuiView.
5263 guiApp->setCurrentView(nullptr);
5267 constructToolbars();
5268 guiApp->menus().fillMenuBar(menuBar(), this, false);
5269 d.layout_->updateContents(true);
5270 // Now update controls with current buffer.
5271 guiApp->setCurrentView(this);
5277 void GuiView::flatGroupBoxes(const QObject * widget, bool flag)
5279 for (QObject * child: widget->children()) {
5280 if (child->inherits("QGroupBox")) {
5281 QGroupBox * box = (QGroupBox*) child;
5284 flatGroupBoxes(child, flag);
5290 Dialog * GuiView::find(string const & name, bool hide_it) const
5292 if (!isValidName(name))
5295 map<string, DialogPtr>::iterator it = d.dialogs_.find(name);
5297 if (it != d.dialogs_.end()) {
5299 it->second->hideView();
5300 return it->second.get();
5306 Dialog * GuiView::findOrBuild(string const & name, bool hide_it)
5308 Dialog * dialog = find(name, hide_it);
5309 if (dialog != nullptr)
5312 dialog = build(name);
5315 d.dialogs_[name].reset(dialog);
5316 // Force a uniform style for group boxes
5317 // On Mac non-flat works better, on Linux flat is standard
5318 flatGroupBoxes(dialog->asQWidget(), guiApp->platformName() != "cocoa");
5319 if (lyxrc.allow_geometry_session)
5320 dialog->restoreSession();
5328 void GuiView::showDialog(string const & name, string const & sdata,
5331 triggerShowDialog(toqstr(name), toqstr(sdata), inset);
5335 void GuiView::doShowDialog(QString const & qname, QString const & qdata,
5341 const string name = fromqstr(qname);
5342 const string sdata = fromqstr(qdata);
5346 Dialog * dialog = findOrBuild(name, false);
5348 bool const visible = dialog->isVisibleView();
5349 dialog->showData(sdata);
5350 if (currentBufferView())
5351 currentBufferView()->editInset(name, inset);
5352 // We only set the focus to the new dialog if it was not yet
5353 // visible in order not to change the existing previous behaviour
5355 // activateWindow is needed for floating dockviews
5356 dialog->asQWidget()->raise();
5357 dialog->asQWidget()->activateWindow();
5358 if (dialog->wantInitialFocus())
5359 dialog->asQWidget()->setFocus();
5363 catch (ExceptionMessage const &) {
5371 bool GuiView::isDialogVisible(string const & name) const
5373 map<string, DialogPtr>::const_iterator it = d.dialogs_.find(name);
5374 if (it == d.dialogs_.end())
5376 return it->second.get()->isVisibleView() && !it->second.get()->isClosing();
5380 void GuiView::hideDialog(string const & name, Inset * inset)
5382 map<string, DialogPtr>::const_iterator it = d.dialogs_.find(name);
5383 if (it == d.dialogs_.end())
5387 if (!currentBufferView())
5389 if (inset != currentBufferView()->editedInset(name))
5393 Dialog * const dialog = it->second.get();
5394 if (dialog->isVisibleView())
5396 if (currentBufferView())
5397 currentBufferView()->editInset(name, nullptr);
5401 void GuiView::disconnectDialog(string const & name)
5403 if (!isValidName(name))
5405 if (currentBufferView())
5406 currentBufferView()->editInset(name, nullptr);
5410 void GuiView::hideAll() const
5412 for(auto const & dlg_p : d.dialogs_)
5413 dlg_p.second->hideView();
5417 void GuiView::updateDialogs()
5419 for(auto const & dlg_p : d.dialogs_) {
5420 Dialog * dialog = dlg_p.second.get();
5422 if (dialog->needBufferOpen() && !documentBufferView())
5423 hideDialog(fromqstr(dialog->name()), nullptr);
5424 else if (dialog->isVisibleView())
5425 dialog->checkStatus();
5433 Dialog * GuiView::build(string const & name)
5435 return createDialog(*this, name);
5439 SEMenu::SEMenu(QWidget * parent)
5441 QAction * action = addAction(qt_("Disable Shell Escape"));
5442 connect(action, SIGNAL(triggered()),
5443 parent, SLOT(disableShellEscape()));
5447 void PressableSvgWidget::mousePressEvent(QMouseEvent * event)
5449 if (event->button() == Qt::LeftButton) {
5454 } // namespace frontend
5457 #include "moc_GuiView.cpp"