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[[possibly scale the welcome banner text size]]");
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(pressed()), 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 // Small size slider for macOS to prevent the status bar from enlarging
662 zoom_slider_->setAttribute(Qt::WA_MacSmallSize);
663 #if (QT_VERSION >= QT_VERSION_CHECK(5, 11, 0))
664 zoom_slider_->setFixedWidth(fm.horizontalAdvance('x') * 15);
666 zoom_slider_->setFixedWidth(fm.width('x') * 15);
668 // Make the defaultZoom center
669 zoom_slider_->setRange(10, (lyxrc.defaultZoom * 2) - 10);
670 // Initialize proper zoom value
672 zoom_ratio_ = settings.value("zoom_ratio", 1.0).toDouble();
673 // Actual zoom value: default zoom + fractional offset
674 int zoom = (int)(lyxrc.defaultZoom * zoom_ratio_);
675 zoom = min(max(zoom, zoom_min_), zoom_max_);
676 zoom_slider_->setValue(zoom);
677 zoom_slider_->setToolTip(qt_("Workarea zoom level. Drag, use Ctrl-+/- or Shift-Mousewheel to adjust."));
679 // Buttons to change zoom stepwise
680 #if (QT_VERSION >= QT_VERSION_CHECK(5, 11, 0))
681 QSize s(fm.horizontalAdvance('+'), fm.height());
683 QSize s(fm.width('+'), fm.height());
685 zoom_in_ = new GuiClickableLabel(statusBar());
686 zoom_in_->setText("+");
687 zoom_in_->setFixedSize(s);
688 zoom_in_->setAlignment(Qt::AlignCenter);
689 zoom_out_ = new GuiClickableLabel(statusBar());
690 zoom_out_->setText(QString(QChar(0x2212)));
691 zoom_out_->setFixedSize(s);
692 zoom_out_->setAlignment(Qt::AlignCenter);
694 statusBar()->addPermanentWidget(zoom_out_);
695 zoom_out_->setEnabled(currentBufferView());
696 statusBar()->addPermanentWidget(zoom_slider_);
697 zoom_slider_->setEnabled(currentBufferView());
698 zoom_in_->setEnabled(currentBufferView());
699 statusBar()->addPermanentWidget(zoom_in_);
701 connect(zoom_slider_, SIGNAL(sliderMoved(int)), this, SLOT(zoomSliderMoved(int)));
702 connect(zoom_slider_, SIGNAL(valueChanged(int)), this, SLOT(zoomValueChanged(int)));
703 connect(this, SIGNAL(currentZoomChanged(int)), zoom_slider_, SLOT(setValue(int)));
704 connect(zoom_in_, SIGNAL(clicked()), this, SLOT(zoomInPressed()));
705 connect(zoom_out_, SIGNAL(clicked()), this, SLOT(zoomOutPressed()));
707 // QPalette palette = statusBar()->palette();
709 zoom_value_ = new GuiClickableLabel(statusBar());
710 connect(zoom_value_, SIGNAL(pressed()), this, SLOT(showZoomContextMenu()));
711 // zoom_value_->setPalette(palette);
712 zoom_value_->setForegroundRole(statusBar()->foregroundRole());
713 zoom_value_->setFixedHeight(fm.height());
714 #if (QT_VERSION >= QT_VERSION_CHECK(5, 11, 0))
715 zoom_value_->setMinimumWidth(fm.horizontalAdvance("444\%"));
717 zoom_value_->setMinimumWidth(fm.width("444\%"));
719 zoom_value_->setAlignment(Qt::AlignCenter);
720 zoom_value_->setText(toqstr(bformat(_("[[ZOOM]]%1$d%"), zoom)));
721 statusBar()->addPermanentWidget(zoom_value_);
722 zoom_value_->setEnabled(currentBufferView());
724 statusBar()->setContextMenuPolicy(Qt::CustomContextMenu);
725 connect(statusBar(), SIGNAL(customContextMenuRequested(QPoint)),
726 this, SLOT(showStatusBarContextMenu()));
728 // enable pinch to zoom
729 grabGesture(Qt::PinchGesture);
731 QPixmap shellescape = QIcon(getPixmap("images/", "emblem-shellescape", "svgz,png")).pixmap(iconsize);
732 shell_escape_ = new QLabel(statusBar());
733 shell_escape_->setPixmap(shellescape);
734 shell_escape_->setAlignment(Qt::AlignCenter);
735 shell_escape_->setContextMenuPolicy(Qt::CustomContextMenu);
736 shell_escape_->setToolTip(qt_("WARNING: LaTeX is allowed to execute "
737 "external commands for this document. "
738 "Right click to change."));
739 SEMenu * menu = new SEMenu(this);
740 connect(shell_escape_, SIGNAL(customContextMenuRequested(QPoint)),
741 menu, SLOT(showMenu(QPoint)));
742 shell_escape_->hide();
743 statusBar()->addPermanentWidget(shell_escape_);
745 QPixmap readonly = QIcon(getPixmap("images/", "emblem-readonly", "svgz,png")).pixmap(iconsize);
746 read_only_ = new QLabel(statusBar());
747 read_only_->setPixmap(readonly);
748 read_only_->setAlignment(Qt::AlignCenter);
750 statusBar()->addPermanentWidget(read_only_);
752 version_control_ = new QLabel(statusBar());
753 version_control_->setAlignment(Qt::AlignCenter);
754 version_control_->setFrameStyle(QFrame::StyledPanel);
755 version_control_->hide();
756 statusBar()->addPermanentWidget(version_control_);
758 statusBar()->setSizeGripEnabled(true);
761 connect(&d.autosave_watcher_, SIGNAL(finished()), this,
762 SLOT(autoSaveThreadFinished()));
764 connect(&d.processing_thread_watcher_, SIGNAL(started()), this,
765 SLOT(processingThreadStarted()));
766 connect(&d.processing_thread_watcher_, SIGNAL(finished()), this,
767 SLOT(processingThreadFinished()));
769 connect(this, SIGNAL(triggerShowDialog(QString const &, QString const &, Inset *)),
770 SLOT(doShowDialog(QString const &, QString const &, Inset *)));
772 // set custom application bars context menu, e.g. tool bar and menu bar
773 setContextMenuPolicy(Qt::CustomContextMenu);
774 connect(this, SIGNAL(customContextMenuRequested(const QPoint &)),
775 SLOT(toolBarPopup(const QPoint &)));
777 // Forbid too small unresizable window because it can happen
778 // with some window manager under X11.
779 setMinimumSize(300, 200);
781 if (lyxrc.allow_geometry_session) {
782 // Now take care of session management.
787 // no session handling, default to a sane size.
788 setGeometry(50, 50, 690, 510);
791 // clear session data if any.
792 settings.remove("views");
802 void GuiView::disableShellEscape()
804 BufferView * bv = documentBufferView();
807 theSession().shellescapeFiles().remove(bv->buffer().absFileName());
808 bv->buffer().params().shell_escape = false;
809 bv->processUpdateFlags(Update::Force);
813 void GuiView::checkCancelBackground()
815 docstring const ttl = _("Cancel Export?");
816 docstring const msg = _("Do you want to cancel the background export process?");
818 Alert::prompt(ttl, msg, 1, 1,
819 _("&Cancel export"), _("Co&ntinue"));
825 void GuiView::statsPressed()
828 dispatch(FuncRequest(LFUN_STATISTICS), dr);
831 void GuiView::zoomSliderMoved(int value)
834 dispatch(FuncRequest(LFUN_BUFFER_ZOOM, convert<string>(value)), dr);
835 scheduleRedrawWorkAreas();
836 zoom_value_->setText(toqstr(bformat(_("[[ZOOM]]%1$d%"), value)));
840 void GuiView::zoomValueChanged(int value)
842 if (value != lyxrc.currentZoom)
843 zoomSliderMoved(value);
847 void GuiView::zoomInPressed()
850 dispatch(FuncRequest(LFUN_BUFFER_ZOOM_IN), dr);
851 scheduleRedrawWorkAreas();
855 void GuiView::zoomOutPressed()
858 dispatch(FuncRequest(LFUN_BUFFER_ZOOM_OUT), dr);
859 scheduleRedrawWorkAreas();
863 void GuiView::showZoomContextMenu()
865 QMenu * menu = guiApp->menus().menu(toqstr("context-zoom"), * this);
868 menu->exec(QCursor::pos());
872 void GuiView::showStatusBarContextMenu()
874 QMenu * menu = guiApp->menus().menu(toqstr("context-statusbar"), * this);
877 menu->exec(QCursor::pos());
881 void GuiView::scheduleRedrawWorkAreas()
883 for (int i = 0; i < d.tabWorkAreaCount(); i++) {
884 TabWorkArea* ta = d.tabWorkArea(i);
885 for (int u = 0; u < ta->count(); u++) {
886 ta->workArea(u)->scheduleRedraw(true);
892 QVector<GuiWorkArea*> GuiView::GuiViewPrivate::guiWorkAreas()
894 QVector<GuiWorkArea*> areas;
895 for (int i = 0; i < tabWorkAreaCount(); i++) {
896 TabWorkArea* ta = tabWorkArea(i);
897 for (int u = 0; u < ta->count(); u++) {
898 areas << ta->workArea(u);
904 static void handleExportStatus(GuiView * view, Buffer::ExportStatus status,
905 string const & format)
907 docstring const fmt = translateIfPossible(theFormats().prettyName(format));
910 case Buffer::ExportSuccess:
911 msg = bformat(_("Successful export to format: %1$s"), fmt);
913 case Buffer::ExportCancel:
914 msg = _("Document export cancelled.");
916 case Buffer::ExportError:
917 case Buffer::ExportNoPathToFormat:
918 case Buffer::ExportTexPathHasSpaces:
919 case Buffer::ExportConverterError:
920 msg = bformat(_("Error while exporting format: %1$s"), fmt);
922 case Buffer::PreviewSuccess:
923 msg = bformat(_("Successful preview of format: %1$s"), fmt);
925 case Buffer::PreviewError:
926 msg = bformat(_("Error while previewing format: %1$s"), fmt);
928 case Buffer::ExportKilled:
929 msg = bformat(_("Conversion cancelled while previewing format: %1$s"), fmt);
936 void GuiView::processingThreadStarted()
941 void GuiView::processingThreadFinished()
943 QFutureWatcher<Buffer::ExportStatus> const * watcher =
944 static_cast<QFutureWatcher<Buffer::ExportStatus> const *>(sender());
946 Buffer::ExportStatus const status = watcher->result();
947 handleExportStatus(this, status, d.processing_format);
950 BufferView const * const bv = currentBufferView();
951 if (bv && !bv->buffer().errorList("Export").empty()) {
956 bool const error = (status != Buffer::ExportSuccess &&
957 status != Buffer::PreviewSuccess &&
958 status != Buffer::ExportCancel);
960 ErrorList & el = bv->buffer().errorList(d.last_export_format);
961 // at this point, we do not know if buffer-view or
962 // master-buffer-view was called. If there was an export error,
963 // and the current buffer's error log is empty, we guess that
964 // it must be master-buffer-view that was called so we set
966 errors(d.last_export_format, el.empty());
971 void GuiView::autoSaveThreadFinished()
973 QFutureWatcher<docstring> const * watcher =
974 static_cast<QFutureWatcher<docstring> const *>(sender());
975 message(watcher->result());
980 void GuiView::saveLayout() const
983 settings.setValue("zoom_ratio", zoom_ratio_);
984 settings.setValue("devel_mode", devel_mode_);
985 settings.beginGroup("views");
986 settings.beginGroup(QString::number(id_));
987 if (guiApp->platformName() == "xcb") {
988 settings.setValue("pos", pos());
989 settings.setValue("size", size());
991 settings.setValue("geometry", saveGeometry());
992 settings.setValue("layout", saveState(0));
993 settings.setValue("icon_size", toqstr(d.iconSize(iconSize())));
994 settings.setValue("zoom_value_visible", zoom_value_->isVisible());
995 settings.setValue("zoom_slider_visible", zoom_slider_->isVisible());
996 settings.setValue("word_count_enabled", word_count_enabled_);
997 settings.setValue("char_count_enabled", char_count_enabled_);
998 settings.setValue("char_nb_count_enabled", char_nb_count_enabled_);
1002 void GuiView::saveUISettings() const
1006 // Save the toolbar private states
1007 for (auto const & tb_p : d.toolbars_)
1008 tb_p.second->saveSession(settings);
1009 // Now take care of all other dialogs
1010 for (auto const & dlg_p : d.dialogs_)
1011 dlg_p.second->saveSession(settings);
1015 void GuiView::setCurrentZoom(const int v)
1017 lyxrc.currentZoom = v;
1018 zoom_value_->setText(toqstr(bformat(_("[[ZOOM]]%1$d%"), v)));
1019 Q_EMIT currentZoomChanged(v);
1023 bool GuiView::restoreLayout()
1026 zoom_ratio_ = settings.value("zoom_ratio", 1.0).toDouble();
1027 // Actual zoom value: default zoom + fractional offset
1028 int zoom = (int)(lyxrc.defaultZoom * zoom_ratio_);
1029 zoom = min(max(zoom, zoom_min_), zoom_max_);
1030 setCurrentZoom(zoom);
1031 devel_mode_ = settings.value("devel_mode", devel_mode_).toBool();
1032 settings.beginGroup("views");
1033 settings.beginGroup(QString::number(id_));
1034 QString const icon_key = "icon_size";
1035 if (!settings.contains(icon_key))
1038 //code below is skipped when when ~/.config/LyX is (re)created
1039 setIconSize(d.iconSize(settings.value(icon_key).toString()));
1041 zoom_value_->setVisible(settings.value("zoom_value_visible", true).toBool());
1043 bool const show_zoom_slider = settings.value("zoom_slider_visible", true).toBool();
1044 zoom_slider_->setVisible(show_zoom_slider);
1045 zoom_in_->setVisible(show_zoom_slider);
1046 zoom_out_->setVisible(show_zoom_slider);
1048 word_count_enabled_ = settings.value("word_count_enabled", true).toBool();
1049 char_count_enabled_ = settings.value("char_count_enabled", true).toBool();
1050 char_nb_count_enabled_ = settings.value("char_nb_count_enabled", true).toBool();
1051 stat_counts_->setVisible(word_count_enabled_ || char_count_enabled_ || char_nb_count_enabled_);
1053 if (guiApp->platformName() == "xcb") {
1054 QPoint pos = settings.value("pos", QPoint(50, 50)).toPoint();
1055 QSize size = settings.value("size", QSize(690, 510)).toSize();
1059 // Work-around for bug #6034: the window ends up in an undetermined
1060 // state when trying to restore a maximized window when it is
1061 // already maximized.
1062 if (!(windowState() & Qt::WindowMaximized))
1063 if (!restoreGeometry(settings.value("geometry").toByteArray()))
1064 setGeometry(50, 50, 690, 510);
1067 // Make sure layout is correctly oriented.
1068 setLayoutDirection(qApp->layoutDirection());
1070 // Allow the toc and view-source dock widget to be restored if needed.
1072 if ((dialog = findOrBuild("toc", true)))
1073 // see bug 5082. At least setup title and enabled state.
1074 // Visibility will be adjusted by restoreState below.
1075 dialog->prepareView();
1076 if ((dialog = findOrBuild("view-source", true)))
1077 dialog->prepareView();
1078 if ((dialog = findOrBuild("progress", true)))
1079 dialog->prepareView();
1081 if (!restoreState(settings.value("layout").toByteArray(), 0))
1084 // init the toolbars that have not been restored
1085 for (auto const & tb_p : guiApp->toolbars()) {
1086 GuiToolbar * tb = toolbar(tb_p.name);
1087 if (tb && !tb->isRestored())
1088 initToolbar(tb_p.name);
1091 // update lock (all) toolbars positions
1092 updateLockToolbars();
1099 GuiToolbar * GuiView::toolbar(string const & name)
1101 ToolbarMap::iterator it = d.toolbars_.find(name);
1102 if (it != d.toolbars_.end())
1105 LYXERR(Debug::GUI, "Toolbar::display: no toolbar named " << name);
1110 void GuiView::updateLockToolbars()
1112 toolbarsMovable_ = false;
1113 for (ToolbarInfo const & info : guiApp->toolbars()) {
1114 GuiToolbar * tb = toolbar(info.name);
1115 if (tb && tb->isMovable())
1116 toolbarsMovable_ = true;
1118 #if QT_VERSION >= 0x050200
1119 // set unified mac toolbars only when not movable as recommended:
1120 // https://doc.qt.io/qt-5/qmainwindow.html#unifiedTitleAndToolBarOnMac-prop
1121 setUnifiedTitleAndToolBarOnMac(!toolbarsMovable_);
1126 void GuiView::constructToolbars()
1128 for (auto const & tb_p : d.toolbars_)
1130 d.toolbars_.clear();
1132 // I don't like doing this here, but the standard toolbar
1133 // destroys this object when it's destroyed itself (vfr)
1134 d.layout_ = new LayoutBox(*this);
1135 d.stack_widget_->addWidget(d.layout_);
1136 d.layout_->move(0,0);
1138 // extracts the toolbars from the backend
1139 for (ToolbarInfo const & inf : guiApp->toolbars())
1140 d.toolbars_[inf.name] = new GuiToolbar(inf, *this);
1142 DynamicMenuButton::resetIconCache();
1146 void GuiView::initToolbars()
1148 // extracts the toolbars from the backend
1149 for (ToolbarInfo const & inf : guiApp->toolbars())
1150 initToolbar(inf.name);
1154 void GuiView::initToolbar(string const & name)
1156 GuiToolbar * tb = toolbar(name);
1159 int const visibility = guiApp->toolbars().defaultVisibility(name);
1160 bool newline = !(visibility & Toolbars::SAMEROW);
1161 tb->setVisible(false);
1162 tb->setVisibility(visibility);
1164 if (visibility & Toolbars::TOP) {
1166 addToolBarBreak(Qt::TopToolBarArea);
1167 addToolBar(Qt::TopToolBarArea, tb);
1170 if (visibility & Toolbars::BOTTOM) {
1172 addToolBarBreak(Qt::BottomToolBarArea);
1173 addToolBar(Qt::BottomToolBarArea, tb);
1176 if (visibility & Toolbars::LEFT) {
1178 addToolBarBreak(Qt::LeftToolBarArea);
1179 addToolBar(Qt::LeftToolBarArea, tb);
1182 if (visibility & Toolbars::RIGHT) {
1184 addToolBarBreak(Qt::RightToolBarArea);
1185 addToolBar(Qt::RightToolBarArea, tb);
1188 if (visibility & Toolbars::ON)
1189 tb->setVisible(true);
1191 tb->setMovable(true);
1195 TocModels & GuiView::tocModels()
1197 return d.toc_models_;
1201 void GuiView::setFocus()
1203 LYXERR(Debug::DEBUG, "GuiView::setFocus()" << this);
1204 QMainWindow::setFocus();
1208 bool GuiView::hasFocus() const
1210 if (currentWorkArea())
1211 return currentWorkArea()->hasFocus();
1212 if (currentMainWorkArea())
1213 return currentMainWorkArea()->hasFocus();
1214 return d.bg_widget_->hasFocus();
1218 void GuiView::focusInEvent(QFocusEvent * e)
1220 LYXERR(Debug::DEBUG, "GuiView::focusInEvent()" << this);
1221 QMainWindow::focusInEvent(e);
1222 // Make sure guiApp points to the correct view.
1223 guiApp->setCurrentView(this);
1224 if (currentWorkArea())
1225 currentWorkArea()->setFocus();
1226 else if (currentMainWorkArea())
1227 currentMainWorkArea()->setFocus();
1229 d.bg_widget_->setFocus();
1233 void GuiView::showEvent(QShowEvent * e)
1235 LYXERR(Debug::GUI, "Passed Geometry "
1236 << size().height() << "x" << size().width()
1237 << "+" << pos().x() << "+" << pos().y());
1239 if (d.splitter_->count() == 0)
1240 // No work area, switch to the background widget.
1244 QMainWindow::showEvent(e);
1248 bool GuiView::closeScheduled()
1255 bool GuiView::prepareAllBuffersForLogout()
1257 Buffer * first = theBufferList().first();
1261 // First, iterate over all buffers and ask the users if unsaved
1262 // changes should be saved.
1263 // We cannot use a for loop as the buffer list cycles.
1266 if (!saveBufferIfNeeded(*b, false))
1268 b = theBufferList().next(b);
1269 } while (b != first);
1271 // Next, save session state
1272 // When a view/window was closed before without quitting LyX, there
1273 // are already entries in the lastOpened list.
1274 theSession().lastOpened().clear();
1281 /** Destroy only all tabbed WorkAreas. Destruction of other WorkAreas
1282 ** is responsibility of the container (e.g., dialog)
1284 void GuiView::closeEvent(QCloseEvent * close_event)
1286 LYXERR(Debug::DEBUG, "GuiView::closeEvent()");
1288 if (!GuiViewPrivate::busyBuffers.isEmpty()) {
1289 Alert::warning(_("Exit LyX"),
1290 _("LyX could not be closed because documents are being processed by LyX."));
1291 close_event->setAccepted(false);
1295 // If the user pressed the x (so we didn't call closeView
1296 // programmatically), we want to clear all existing entries.
1298 theSession().lastOpened().clear();
1303 // it can happen that this event arrives without selecting the view,
1304 // e.g. when clicking the close button on a background window.
1306 if (!closeWorkAreaAll()) {
1308 close_event->ignore();
1312 // Make sure that nothing will use this to be closed View.
1313 guiApp->unregisterView(this);
1315 if (isFullScreen()) {
1316 // Switch off fullscreen before closing.
1321 // Make sure the timer time out will not trigger a statusbar update.
1322 d.statusbar_timer_.stop();
1323 d.statusbar_stats_timer_.stop();
1325 // Saving fullscreen requires additional tweaks in the toolbar code.
1326 // It wouldn't also work under linux natively.
1327 if (lyxrc.allow_geometry_session) {
1332 close_event->accept();
1336 void GuiView::dragEnterEvent(QDragEnterEvent * event)
1338 if (event->mimeData()->hasUrls())
1340 /// \todo Ask lyx-devel is this is enough:
1341 /// if (event->mimeData()->hasFormat("text/plain"))
1342 /// event->acceptProposedAction();
1346 void GuiView::dropEvent(QDropEvent * event)
1348 QList<QUrl> files = event->mimeData()->urls();
1349 if (files.isEmpty())
1352 LYXERR(Debug::GUI, "GuiView::dropEvent: got URLs!");
1353 for (int i = 0; i != files.size(); ++i) {
1354 string const file = os::internal_path(fromqstr(
1355 files.at(i).toLocalFile()));
1359 string const ext = support::getExtension(file);
1360 vector<const Format *> found_formats;
1362 // Find all formats that have the correct extension.
1363 for (const Format * fmt : theConverters().importableFormats())
1364 if (fmt->hasExtension(ext))
1365 found_formats.push_back(fmt);
1368 if (!found_formats.empty()) {
1369 if (found_formats.size() > 1) {
1370 //FIXME: show a dialog to choose the correct importable format
1371 LYXERR(Debug::FILES,
1372 "Multiple importable formats found, selecting first");
1374 string const arg = found_formats[0]->name() + " " + file;
1375 cmd = FuncRequest(LFUN_BUFFER_IMPORT, arg);
1378 //FIXME: do we have to explicitly check whether it's a lyx file?
1379 LYXERR(Debug::FILES,
1380 "No formats found, trying to open it as a lyx file");
1381 cmd = FuncRequest(LFUN_FILE_OPEN, file);
1383 // add the functions to the queue
1384 guiApp->addToFuncRequestQueue(cmd);
1387 // now process the collected functions. We perform the events
1388 // asynchronously. This prevents potential problems in case the
1389 // BufferView is closed within an event.
1390 guiApp->processFuncRequestQueueAsync();
1394 void GuiView::message(docstring const & str)
1396 if (ForkedProcess::iAmAChild())
1399 // call is moved to GUI-thread by GuiProgress
1400 d.progress_->appendMessage(toqstr(str));
1404 void GuiView::clearMessageText()
1406 message(docstring());
1410 void GuiView::updateStatusBarMessage(QString const & str)
1412 statusBar()->showMessage(str);
1413 d.statusbar_timer_.stop();
1414 d.statusbar_timer_.start(3000);
1418 void GuiView::clearMessage()
1420 // FIXME: This code was introduced in r19643 to fix bug #4123. However,
1421 // the hasFocus function mostly returns false, even if the focus is on
1422 // a workarea in this view.
1426 d.statusbar_timer_.stop();
1429 void GuiView::showStats()
1431 if (!statsEnabled())
1434 d.time_to_update -= d.timer_rate;
1436 BufferView * bv = currentBufferView();
1437 Buffer * buf = bv ? &bv->buffer() : nullptr;
1439 stat_counts_->hide();
1443 Cursor const & cur = bv->cursor();
1445 // we start new selection and need faster update
1446 if (!d.already_in_selection_ && cur.selection())
1447 d.time_to_update = 0;
1449 if (d.time_to_update > 0)
1452 DocIterator from, to;
1453 if (cur.selection()) {
1454 from = cur.selectionBegin();
1455 to = cur.selectionEnd();
1456 d.already_in_selection_ = true;
1458 from = doc_iterator_begin(buf);
1459 to = doc_iterator_end(buf);
1460 d.already_in_selection_ = false;
1463 buf->updateStatistics(from, to);
1466 if (word_count_enabled_) {
1467 int const words = buf->wordCount();
1469 stats << toqstr(bformat(_("%1$d Word"), words));
1471 stats << toqstr(bformat(_("%1$d Words"), words));
1473 int const chars_with_blanks = buf->charCount(true);
1474 if (char_count_enabled_) {
1475 if (chars_with_blanks == 1)
1476 stats << toqstr(bformat(_("%1$d Character"), chars_with_blanks));
1478 stats << toqstr(bformat(_("%1$d Characters"), chars_with_blanks));
1480 if (char_nb_count_enabled_) {
1481 int const chars = buf->charCount(false);
1483 stats << toqstr(bformat(_("%1$d Character (no Blanks)"), chars));
1485 stats << toqstr(bformat(_("%1$d Characters (no Blanks)"), chars));
1487 stat_counts_->setText(stats.join(qt_(", [[stats separator]]")));
1488 stat_counts_->show();
1490 d.time_to_update = d.default_stats_rate;
1491 // fast updates for small selections
1492 if (chars_with_blanks < d.max_sel_chars && cur.selection())
1493 d.time_to_update = d.timer_rate;
1497 void GuiView::updateWindowTitle(GuiWorkArea * wa)
1499 if (wa != d.current_work_area_
1500 || wa->bufferView().buffer().isInternal())
1502 Buffer const & buf = wa->bufferView().buffer();
1503 // Set the windows title
1504 docstring title = buf.fileName().displayName(130) + from_ascii("[*]");
1505 if (buf.notifiesExternalModification()) {
1506 title = bformat(_("%1$s (modified externally)"), title);
1507 // If the external modification status has changed, then maybe the status of
1508 // buffer-save has changed too.
1511 title += from_ascii(" - LyX");
1512 setWindowTitle(toqstr(title));
1513 // Sets the path for the window: this is used by OSX to
1514 // allow a context click on the title bar showing a menu
1515 // with the path up to the file
1516 setWindowFilePath(toqstr(buf.absFileName()));
1517 // Tell Qt whether the current document is changed
1518 setWindowModified(!buf.isClean());
1520 if (buf.params().shell_escape)
1521 shell_escape_->show();
1523 shell_escape_->hide();
1525 if (buf.hasReadonlyFlag())
1530 if (buf.lyxvc().inUse()) {
1531 version_control_->show();
1532 version_control_->setText(toqstr(buf.lyxvc().vcstatus()));
1534 version_control_->hide();
1538 void GuiView::on_currentWorkAreaChanged(GuiWorkArea * wa)
1540 if (d.current_work_area_)
1541 // disconnect the current work area from all slots
1542 QObject::disconnect(d.current_work_area_, nullptr, this, nullptr);
1544 disconnectBufferView();
1545 connectBufferView(wa->bufferView());
1546 connectBuffer(wa->bufferView().buffer());
1547 d.current_work_area_ = wa;
1548 QObject::connect(wa, SIGNAL(titleChanged(GuiWorkArea *)),
1549 this, SLOT(updateWindowTitle(GuiWorkArea *)));
1550 QObject::connect(wa, SIGNAL(busy(bool)),
1551 this, SLOT(setBusy(bool)));
1552 // connection of a signal to a signal
1553 QObject::connect(wa, SIGNAL(bufferViewChanged()),
1554 this, SIGNAL(bufferViewChanged()));
1555 Q_EMIT updateWindowTitle(wa);
1556 Q_EMIT bufferViewChanged();
1560 void GuiView::onBufferViewChanged()
1563 // Buffer-dependent dialogs must be updated. This is done here because
1564 // some dialogs require buffer()->text.
1566 zoom_slider_->setEnabled(currentBufferView());
1567 zoom_value_->setEnabled(currentBufferView());
1568 zoom_in_->setEnabled(currentBufferView());
1569 zoom_out_->setEnabled(currentBufferView());
1573 void GuiView::on_lastWorkAreaRemoved()
1576 // We already are in a close event. Nothing more to do.
1579 if (d.splitter_->count() > 1)
1580 // We have a splitter so don't close anything.
1583 // Reset and updates the dialogs.
1584 Q_EMIT bufferViewChanged();
1589 if (lyxrc.open_buffers_in_tabs)
1590 // Nothing more to do, the window should stay open.
1593 if (guiApp->viewIds().size() > 1) {
1599 // On Mac we also close the last window because the application stay
1600 // resident in memory. On other platforms we don't close the last
1601 // window because this would quit the application.
1607 void GuiView::updateStatusBar()
1609 // let the user see the explicit message
1610 if (d.statusbar_timer_.isActive())
1617 void GuiView::showMessage()
1621 QString msg = toqstr(theGuiApp()->viewStatusMessage());
1622 if (msg.isEmpty()) {
1623 BufferView const * bv = currentBufferView();
1625 msg = toqstr(bv->cursor().currentState(devel_mode_));
1627 msg = qt_("Welcome to LyX!");
1629 statusBar()->showMessage(msg);
1633 bool GuiView::statsEnabled() const
1635 return word_count_enabled_ || char_count_enabled_ || char_nb_count_enabled_;
1639 bool GuiView::event(QEvent * e)
1643 // Useful debug code:
1644 //case QEvent::ActivationChange:
1645 //case QEvent::WindowDeactivate:
1646 //case QEvent::Paint:
1647 //case QEvent::Enter:
1648 //case QEvent::Leave:
1649 //case QEvent::HoverEnter:
1650 //case QEvent::HoverLeave:
1651 //case QEvent::HoverMove:
1652 //case QEvent::StatusTip:
1653 //case QEvent::DragEnter:
1654 //case QEvent::DragLeave:
1655 //case QEvent::Drop:
1658 case QEvent::WindowStateChange: {
1659 QWindowStateChangeEvent * ev = (QWindowStateChangeEvent*)e;
1660 bool ofstate = (ev->oldState() & Qt::WindowFullScreen);
1661 bool result = QMainWindow::event(e);
1662 bool nfstate = (windowState() & Qt::WindowFullScreen);
1663 if (!ofstate && nfstate) {
1664 LYXERR(Debug::DEBUG, "GuiView: WindowStateChange(): full-screen " << nfstate);
1665 // switch to full-screen state
1666 if (lyxrc.full_screen_statusbar)
1667 statusBar()->hide();
1668 if (lyxrc.full_screen_menubar)
1670 if (lyxrc.full_screen_toolbars) {
1671 for (auto const & tb_p : d.toolbars_)
1672 if (tb_p.second->isVisibilityOn() && tb_p.second->isVisible())
1673 tb_p.second->hide();
1675 for (int i = 0; i != d.splitter_->count(); ++i)
1676 d.tabWorkArea(i)->setFullScreen(true);
1677 #if QT_VERSION > 0x050903
1678 //Qt's 5.9.4 ba44cdae38406c safe area measures won't allow us to go negative in margins
1679 setAttribute(Qt::WA_ContentsMarginsRespectsSafeArea, false);
1681 setContentsMargins(-2, -2, -2, -2);
1683 hideDialogs("prefs", nullptr);
1684 } else if (ofstate && !nfstate) {
1685 LYXERR(Debug::DEBUG, "GuiView: WindowStateChange(): full-screen " << nfstate);
1686 // switch back from full-screen state
1687 if (lyxrc.full_screen_statusbar && !statusBar()->isVisible())
1688 statusBar()->show();
1689 if (lyxrc.full_screen_menubar && !menuBar()->isVisible())
1691 if (lyxrc.full_screen_toolbars) {
1692 for (auto const & tb_p : d.toolbars_)
1693 if (tb_p.second->isVisibilityOn() && !tb_p.second->isVisible())
1694 tb_p.second->show();
1697 for (int i = 0; i != d.splitter_->count(); ++i)
1698 d.tabWorkArea(i)->setFullScreen(false);
1699 #if QT_VERSION > 0x050903
1700 setAttribute(Qt::WA_ContentsMarginsRespectsSafeArea, true);
1702 setContentsMargins(0, 0, 0, 0);
1707 case QEvent::WindowActivate: {
1708 GuiView * old_view = guiApp->currentView();
1709 if (this == old_view) {
1711 return QMainWindow::event(e);
1713 if (old_view && old_view->currentBufferView()) {
1714 // save current selection to the selection buffer to allow
1715 // middle-button paste in this window.
1716 cap::saveSelection(old_view->currentBufferView()->cursor());
1718 guiApp->setCurrentView(this);
1719 if (d.current_work_area_)
1720 on_currentWorkAreaChanged(d.current_work_area_);
1724 return QMainWindow::event(e);
1727 case QEvent::ShortcutOverride: {
1729 if (isFullScreen() && menuBar()->isHidden()) {
1730 QKeyEvent * ke = static_cast<QKeyEvent*>(e);
1731 // FIXME: we should also try to detect special LyX shortcut such as
1732 // Alt-P and Alt-M. Right now there is a hack in
1733 // GuiWorkArea::processKeySym() that hides again the menubar for
1735 if (ke->modifiers() & Qt::AltModifier && ke->key() != Qt::Key_Alt) {
1737 return QMainWindow::event(e);
1740 return QMainWindow::event(e);
1743 case QEvent::ApplicationPaletteChange: {
1744 // runtime switch from/to dark mode
1746 return QMainWindow::event(e);
1749 case QEvent::Gesture: {
1750 QGestureEvent *ge = static_cast<QGestureEvent*>(e);
1751 QGesture *gp = ge->gesture(Qt::PinchGesture);
1753 QPinchGesture *pinch = static_cast<QPinchGesture *>(gp);
1754 QPinchGesture::ChangeFlags changeFlags = pinch->changeFlags();
1755 qreal totalScaleFactor = pinch->totalScaleFactor();
1756 LYXERR(Debug::GUI, "totalScaleFactor: " << totalScaleFactor);
1757 if (pinch->state() == Qt::GestureStarted) {
1758 initialZoom_ = lyxrc.currentZoom;
1759 LYXERR(Debug::GUI, "initialZoom_: " << initialZoom_);
1761 if (changeFlags & QPinchGesture::ScaleFactorChanged) {
1762 qreal factor = initialZoom_ * totalScaleFactor;
1763 LYXERR(Debug::GUI, "scaleFactor: " << factor);
1764 zoomValueChanged(factor);
1767 return QMainWindow::event(e);
1771 return QMainWindow::event(e);
1775 void GuiView::resetWindowTitle()
1777 setWindowTitle(qt_("LyX"));
1780 bool GuiView::focusNextPrevChild(bool /*next*/)
1787 bool GuiView::busy() const
1793 void GuiView::setBusy(bool busy)
1795 bool const busy_before = busy_ > 0;
1796 busy ? ++busy_ : --busy_;
1797 if ((busy_ > 0) == busy_before)
1798 // busy state didn't change
1802 QApplication::setOverrideCursor(Qt::WaitCursor);
1805 QApplication::restoreOverrideCursor();
1810 void GuiView::resetCommandExecute()
1812 command_execute_ = false;
1817 double GuiView::pixelRatio() const
1819 return qt_scale_factor * devicePixelRatio();
1823 GuiWorkArea * GuiView::workArea(int index)
1825 if (TabWorkArea * twa = d.currentTabWorkArea())
1826 if (index < twa->count())
1827 return twa->workArea(index);
1832 GuiWorkArea * GuiView::workArea(Buffer & buffer)
1834 if (currentWorkArea()
1835 && ¤tWorkArea()->bufferView().buffer() == &buffer)
1836 return currentWorkArea();
1837 if (TabWorkArea * twa = d.currentTabWorkArea())
1838 return twa->workArea(buffer);
1843 GuiWorkArea * GuiView::addWorkArea(Buffer & buffer)
1845 // Automatically create a TabWorkArea if there are none yet.
1846 TabWorkArea * tab_widget = d.splitter_->count()
1847 ? d.currentTabWorkArea() : addTabWorkArea();
1848 return tab_widget->addWorkArea(buffer, *this);
1852 TabWorkArea * GuiView::addTabWorkArea()
1854 TabWorkArea * twa = new TabWorkArea;
1855 QObject::connect(twa, SIGNAL(currentWorkAreaChanged(GuiWorkArea *)),
1856 this, SLOT(on_currentWorkAreaChanged(GuiWorkArea *)));
1857 QObject::connect(twa, SIGNAL(lastWorkAreaRemoved()),
1858 this, SLOT(on_lastWorkAreaRemoved()));
1860 d.splitter_->addWidget(twa);
1861 d.stack_widget_->setCurrentWidget(d.splitter_);
1866 GuiWorkArea const * GuiView::currentWorkArea() const
1868 return d.current_work_area_;
1872 GuiWorkArea * GuiView::currentWorkArea()
1874 return d.current_work_area_;
1878 GuiWorkArea const * GuiView::currentMainWorkArea() const
1880 if (!d.currentTabWorkArea())
1882 return d.currentTabWorkArea()->currentWorkArea();
1886 GuiWorkArea * GuiView::currentMainWorkArea()
1888 if (!d.currentTabWorkArea())
1890 return d.currentTabWorkArea()->currentWorkArea();
1894 void GuiView::setCurrentWorkArea(GuiWorkArea * wa)
1896 LYXERR(Debug::DEBUG, "Setting current wa: " << wa << endl);
1898 d.current_work_area_ = nullptr;
1900 Q_EMIT bufferViewChanged();
1904 // FIXME: I've no clue why this is here and why it accesses
1905 // theGuiApp()->currentView, which might be 0 (bug 6464).
1906 // See also 27525 (vfr).
1907 if (theGuiApp()->currentView() == this
1908 && theGuiApp()->currentView()->currentWorkArea() == wa)
1911 if (currentBufferView())
1912 cap::saveSelection(currentBufferView()->cursor());
1914 theGuiApp()->setCurrentView(this);
1915 d.current_work_area_ = wa;
1917 // We need to reset this now, because it will need to be
1918 // right if the tabWorkArea gets reset in the for loop. We
1919 // will change it back if we aren't in that case.
1920 GuiWorkArea * const old_cmwa = d.current_main_work_area_;
1921 d.current_main_work_area_ = wa;
1923 for (int i = 0; i != d.splitter_->count(); ++i) {
1924 if (d.tabWorkArea(i)->setCurrentWorkArea(wa)) {
1925 LYXERR(Debug::DEBUG, "Current wa: " << currentWorkArea()
1926 << ", Current main wa: " << currentMainWorkArea());
1931 d.current_main_work_area_ = old_cmwa;
1933 LYXERR(Debug::DEBUG, "This is not a tabbed wa");
1934 on_currentWorkAreaChanged(wa);
1935 BufferView & bv = wa->bufferView();
1936 bv.cursor().fixIfBroken();
1938 wa->setUpdatesEnabled(true);
1939 LYXERR(Debug::DEBUG, "Current wa: " << currentWorkArea() << ", Current main wa: " << currentMainWorkArea());
1943 void GuiView::removeWorkArea(GuiWorkArea * wa)
1945 LASSERT(wa, return);
1946 if (wa == d.current_work_area_) {
1948 disconnectBufferView();
1949 d.current_work_area_ = nullptr;
1950 d.current_main_work_area_ = nullptr;
1953 bool found_twa = false;
1954 for (int i = 0; i != d.splitter_->count(); ++i) {
1955 TabWorkArea * twa = d.tabWorkArea(i);
1956 if (twa->removeWorkArea(wa)) {
1957 // Found in this tab group, and deleted the GuiWorkArea.
1959 if (twa->count() != 0) {
1960 if (d.current_work_area_ == nullptr)
1961 // This means that we are closing the current GuiWorkArea, so
1962 // switch to the next GuiWorkArea in the found TabWorkArea.
1963 setCurrentWorkArea(twa->currentWorkArea());
1965 // No more WorkAreas in this tab group, so delete it.
1972 // It is not a tabbed work area (i.e., the search work area), so it
1973 // should be deleted by other means.
1974 LASSERT(found_twa, return);
1976 if (d.current_work_area_ == nullptr) {
1977 if (d.splitter_->count() != 0) {
1978 TabWorkArea * twa = d.currentTabWorkArea();
1979 setCurrentWorkArea(twa->currentWorkArea());
1981 // No more work areas, switch to the background widget.
1982 setCurrentWorkArea(nullptr);
1988 bool GuiView::hasVisibleWorkArea(GuiWorkArea * wa) const
1990 for (int i = 0; i < d.splitter_->count(); ++i)
1991 if (d.tabWorkArea(i)->currentWorkArea() == wa)
1994 FindAndReplace * fr = static_cast<FindAndReplace*>(find("findreplaceadv", false));
1995 return fr->isVisible() && fr->hasWorkArea(wa);
1999 LayoutBox * GuiView::getLayoutDialog() const
2005 void GuiView::updateLayoutList()
2008 d.layout_->updateContents(false);
2012 void GuiView::updateToolbars()
2014 if (d.current_work_area_) {
2016 if (d.current_work_area_->bufferView().cursor().inMathed()
2017 && !d.current_work_area_->bufferView().cursor().inRegexped())
2018 context |= Toolbars::MATH;
2019 if (lyx::getStatus(FuncRequest(LFUN_LAYOUT_TABULAR)).enabled())
2020 context |= Toolbars::TABLE;
2021 if (currentBufferView()->buffer().areChangesPresent()
2022 || (lyx::getStatus(FuncRequest(LFUN_CHANGES_TRACK)).enabled()
2023 && lyx::getStatus(FuncRequest(LFUN_CHANGES_TRACK)).onOff(true))
2024 || (lyx::getStatus(FuncRequest(LFUN_CHANGES_OUTPUT)).enabled()
2025 && lyx::getStatus(FuncRequest(LFUN_CHANGES_OUTPUT)).onOff(true)))
2026 context |= Toolbars::REVIEW;
2027 if (lyx::getStatus(FuncRequest(LFUN_IN_MATHMACROTEMPLATE)).enabled())
2028 context |= Toolbars::MATHMACROTEMPLATE;
2029 if (lyx::getStatus(FuncRequest(LFUN_IN_IPA)).enabled())
2030 context |= Toolbars::IPA;
2031 if (command_execute_)
2032 context |= Toolbars::MINIBUFFER;
2033 if (minibuffer_focus_) {
2034 context |= Toolbars::MINIBUFFER_FOCUS;
2035 minibuffer_focus_ = false;
2038 for (auto const & tb_p : d.toolbars_)
2039 tb_p.second->update(context);
2041 for (auto const & tb_p : d.toolbars_)
2042 tb_p.second->update();
2046 void GuiView::refillToolbars()
2048 DynamicMenuButton::resetIconCache();
2049 for (auto const & tb_p : d.toolbars_)
2050 tb_p.second->refill();
2054 void GuiView::setBuffer(Buffer * newBuffer, bool switch_to)
2056 LYXERR(Debug::DEBUG, "Setting buffer: " << newBuffer << endl);
2057 LASSERT(newBuffer, return);
2059 GuiWorkArea * wa = workArea(*newBuffer);
2060 if (wa == nullptr) {
2062 newBuffer->masterBuffer()->updateBuffer();
2064 wa = addWorkArea(*newBuffer);
2065 // scroll to the position when the BufferView was last closed
2066 if (lyxrc.use_lastfilepos) {
2067 LastFilePosSection::FilePos filepos =
2068 theSession().lastFilePos().load(newBuffer->fileName());
2069 wa->bufferView().moveToPosition(filepos.pit, filepos.pos, 0, 0);
2072 //Disconnect the old buffer...there's no new one.
2075 connectBuffer(*newBuffer);
2076 connectBufferView(wa->bufferView());
2078 setCurrentWorkArea(wa);
2082 void GuiView::connectBuffer(Buffer & buf)
2084 buf.setGuiDelegate(this);
2088 void GuiView::disconnectBuffer()
2090 if (d.current_work_area_)
2091 d.current_work_area_->bufferView().buffer().setGuiDelegate(nullptr);
2095 void GuiView::connectBufferView(BufferView & bv)
2097 bv.setGuiDelegate(this);
2101 void GuiView::disconnectBufferView()
2103 if (d.current_work_area_)
2104 d.current_work_area_->bufferView().setGuiDelegate(nullptr);
2108 void GuiView::errors(string const & error_type, bool from_master)
2110 BufferView const * const bv = currentBufferView();
2114 ErrorList const & el = from_master ?
2115 bv->buffer().masterBuffer()->errorList(error_type) :
2116 bv->buffer().errorList(error_type);
2121 string err = error_type;
2123 err = "from_master|" + error_type;
2124 showDialog("errorlist", err);
2128 void GuiView::updateTocItem(string const & type, DocIterator const & dit)
2130 d.toc_models_.updateItem(toqstr(type), dit);
2134 void GuiView::structureChanged()
2136 // This is called from the Buffer, which has no way to ensure that cursors
2137 // in BufferView remain valid.
2138 if (documentBufferView())
2139 documentBufferView()->cursor().sanitize();
2140 // FIXME: This is slightly expensive, though less than the tocBackend update
2141 // (#9880). This also resets the view in the Toc Widget (#6675).
2142 d.toc_models_.reset(documentBufferView());
2143 // Navigator needs more than a simple update in this case. It needs to be
2145 updateDialog("toc", "");
2149 void GuiView::updateDialog(string const & name, string const & sdata)
2151 if (!isDialogVisible(name))
2154 map<string, DialogPtr>::const_iterator it = d.dialogs_.find(name);
2155 if (it == d.dialogs_.end())
2158 Dialog * const dialog = it->second.get();
2159 if (dialog->isVisibleView())
2160 dialog->initialiseParams(sdata);
2164 BufferView * GuiView::documentBufferView()
2166 return currentMainWorkArea()
2167 ? ¤tMainWorkArea()->bufferView()
2172 BufferView const * GuiView::documentBufferView() const
2174 return currentMainWorkArea()
2175 ? ¤tMainWorkArea()->bufferView()
2180 BufferView * GuiView::currentBufferView()
2182 return d.current_work_area_ ? &d.current_work_area_->bufferView() : nullptr;
2186 BufferView const * GuiView::currentBufferView() const
2188 return d.current_work_area_ ? &d.current_work_area_->bufferView() : nullptr;
2192 docstring GuiView::GuiViewPrivate::autosaveAndDestroy(
2193 Buffer const * orig, Buffer * clone)
2195 bool const success = clone->autoSave();
2197 busyBuffers.remove(orig);
2199 ? _("Automatic save done.")
2200 : _("Automatic save failed!");
2204 void GuiView::autoSave()
2206 LYXERR(Debug::INFO, "Running autoSave()");
2208 Buffer * buffer = documentBufferView()
2209 ? &documentBufferView()->buffer() : nullptr;
2211 resetAutosaveTimers();
2215 GuiViewPrivate::busyBuffers.insert(buffer);
2216 QFuture<docstring> f = QtConcurrent::run(GuiViewPrivate::autosaveAndDestroy,
2217 buffer, buffer->cloneBufferOnly());
2218 d.autosave_watcher_.setFuture(f);
2219 resetAutosaveTimers();
2223 void GuiView::resetAutosaveTimers()
2226 d.autosave_timeout_.restart();
2232 double zoomRatio(FuncRequest const & cmd, double const zr)
2234 if (cmd.argument().empty()) {
2235 if (cmd.action() == LFUN_BUFFER_ZOOM)
2237 else if (cmd.action() == LFUN_BUFFER_ZOOM_IN)
2239 else // cmd.action() == LFUN_BUFFER_ZOOM_OUT
2242 if (cmd.action() == LFUN_BUFFER_ZOOM)
2243 return convert<int>(cmd.argument()) / double(lyxrc.defaultZoom);
2244 else if (cmd.action() == LFUN_BUFFER_ZOOM_IN)
2245 return zr + convert<int>(cmd.argument()) / 100.0;
2246 else // cmd.action() == LFUN_BUFFER_ZOOM_OUT
2247 return zr - convert<int>(cmd.argument()) / 100.0;
2254 bool GuiView::getStatus(FuncRequest const & cmd, FuncStatus & flag)
2257 Buffer * buf = currentBufferView()
2258 ? ¤tBufferView()->buffer() : nullptr;
2259 Buffer * doc_buffer = documentBufferView()
2260 ? &(documentBufferView()->buffer()) : nullptr;
2263 /* In LyX/Mac, when a dialog is open, the menus of the
2264 application can still be accessed without giving focus to
2265 the main window. In this case, we want to disable the menu
2266 entries that are buffer-related.
2267 This code must not be used on Linux and Windows, since it
2268 would disable buffer-related entries when hovering over the
2269 menu (see bug #9574).
2271 if (cmd.origin() == FuncRequest::MENU && !hasFocus()) {
2277 // Check whether we need a buffer
2278 if (!lyxaction.funcHasFlag(cmd.action(), LyXAction::NoBuffer) && !buf) {
2279 // no, exit directly
2280 flag.message(from_utf8(N_("Command not allowed with"
2281 "out any document open")));
2282 flag.setEnabled(false);
2286 if (cmd.origin() == FuncRequest::TOC) {
2287 GuiToc * toc = static_cast<GuiToc*>(findOrBuild("toc", false));
2288 if (!toc || !toc->getStatus(documentBufferView()->cursor(), cmd, flag))
2289 flag.setEnabled(false);
2293 switch(cmd.action()) {
2294 case LFUN_BUFFER_IMPORT:
2297 case LFUN_MASTER_BUFFER_EXPORT:
2299 && (doc_buffer->parent() != nullptr
2300 || doc_buffer->hasChildren())
2301 && !d.processing_thread_watcher_.isRunning()
2302 // this launches a dialog, which would be in the wrong Buffer
2303 && !(::lyx::operator==(cmd.argument(), "custom"));
2306 case LFUN_MASTER_BUFFER_UPDATE:
2307 case LFUN_MASTER_BUFFER_VIEW:
2309 && (doc_buffer->parent() != nullptr
2310 || doc_buffer->hasChildren())
2311 && !d.processing_thread_watcher_.isRunning();
2314 case LFUN_BUFFER_UPDATE:
2315 case LFUN_BUFFER_VIEW: {
2316 if (!doc_buffer || d.processing_thread_watcher_.isRunning()) {
2320 string format = to_utf8(cmd.argument());
2321 if (cmd.argument().empty())
2322 format = doc_buffer->params().getDefaultOutputFormat();
2323 enable = doc_buffer->params().isExportable(format, true);
2327 case LFUN_BUFFER_RELOAD:
2328 enable = doc_buffer && !doc_buffer->isUnnamed()
2329 && doc_buffer->fileName().exists()
2330 && (!doc_buffer->isClean() || doc_buffer->notifiesExternalModification());
2333 case LFUN_BUFFER_RESET_EXPORT:
2334 enable = doc_buffer != nullptr;
2337 case LFUN_BUFFER_CHILD_OPEN:
2338 enable = doc_buffer != nullptr;
2341 case LFUN_MASTER_BUFFER_FORALL: {
2342 if (doc_buffer == nullptr) {
2343 flag.message(from_utf8(N_("Command not allowed without a buffer open")));
2347 FuncRequest const cmdToPass = lyxaction.lookupFunc(cmd.getLongArg(0));
2348 if (cmdToPass.action() == LFUN_UNKNOWN_ACTION) {
2349 flag.message(from_utf8(N_("Invalid argument of master-buffer-forall")));
2354 for (Buffer * buf : doc_buffer->allRelatives()) {
2355 GuiWorkArea * wa = workArea(*buf);
2358 if (wa->bufferView().getStatus(cmdToPass, flag)) {
2359 enable = flag.enabled();
2366 case LFUN_BUFFER_WRITE:
2367 enable = doc_buffer && (doc_buffer->isUnnamed()
2368 || (!doc_buffer->isClean()
2369 || cmd.argument() == "force"));
2372 //FIXME: This LFUN should be moved to GuiApplication.
2373 case LFUN_BUFFER_WRITE_ALL: {
2374 // We enable the command only if there are some modified buffers
2375 Buffer * first = theBufferList().first();
2380 // We cannot use a for loop as the buffer list is a cycle.
2382 if (!b->isClean()) {
2386 b = theBufferList().next(b);
2387 } while (b != first);
2391 case LFUN_BUFFER_EXTERNAL_MODIFICATION_CLEAR:
2392 enable = doc_buffer && doc_buffer->notifiesExternalModification();
2395 case LFUN_BUFFER_EXPORT: {
2396 if (!doc_buffer || d.processing_thread_watcher_.isRunning()) {
2400 return doc_buffer->getStatus(cmd, flag);
2403 case LFUN_BUFFER_EXPORT_AS:
2404 if (!doc_buffer || d.processing_thread_watcher_.isRunning()) {
2409 case LFUN_BUFFER_WRITE_AS:
2410 case LFUN_BUFFER_WRITE_AS_TEMPLATE:
2411 enable = doc_buffer != nullptr;
2414 case LFUN_EXPORT_CANCEL:
2415 enable = d.processing_thread_watcher_.isRunning();
2418 case LFUN_BUFFER_CLOSE:
2419 case LFUN_VIEW_CLOSE:
2420 enable = doc_buffer != nullptr;
2423 case LFUN_BUFFER_CLOSE_ALL:
2424 enable = theBufferList().last() != theBufferList().first();
2427 case LFUN_BUFFER_CHKTEX: {
2428 // hide if we have no checktex command
2429 if (lyxrc.chktex_command.empty()) {
2430 flag.setUnknown(true);
2434 if (!doc_buffer || !doc_buffer->params().isLatex()
2435 || d.processing_thread_watcher_.isRunning()) {
2436 // grey out, don't hide
2444 case LFUN_VIEW_SPLIT:
2445 if (cmd.getArg(0) == "vertical")
2446 enable = doc_buffer && (d.splitter_->count() == 1 ||
2447 d.splitter_->orientation() == Qt::Vertical);
2449 enable = doc_buffer && (d.splitter_->count() == 1 ||
2450 d.splitter_->orientation() == Qt::Horizontal);
2453 case LFUN_TAB_GROUP_NEXT:
2454 case LFUN_TAB_GROUP_PREVIOUS:
2455 enable = (d.splitter_->count() > 1);
2458 case LFUN_TAB_GROUP_CLOSE:
2459 enable = d.tabWorkAreaCount() > 1;
2462 case LFUN_DEVEL_MODE_TOGGLE:
2463 flag.setOnOff(devel_mode_);
2466 case LFUN_TOOLBAR_SET: {
2467 string const name = cmd.getArg(0);
2468 string const state = cmd.getArg(1);
2469 if (name.empty() || state.empty()) {
2471 docstring const msg =
2472 _("Function toolbar-set requires two arguments!");
2476 if (state != "on" && state != "off" && state != "auto") {
2478 docstring const msg =
2479 bformat(_("Invalid argument \"%1$s\" to function toolbar-set!"),
2484 if (GuiToolbar * t = toolbar(name)) {
2485 bool const autovis = t->visibility() & Toolbars::AUTO;
2487 flag.setOnOff(t->isVisible() && !autovis);
2488 else if (state == "off")
2489 flag.setOnOff(!t->isVisible() && !autovis);
2490 else if (state == "auto")
2491 flag.setOnOff(autovis);
2494 docstring const msg =
2495 bformat(_("Unknown toolbar \"%1$s\""), from_utf8(name));
2501 case LFUN_TOOLBAR_TOGGLE: {
2502 string const name = cmd.getArg(0);
2503 if (GuiToolbar * t = toolbar(name))
2504 flag.setOnOff(t->isVisible());
2507 docstring const msg =
2508 bformat(_("Unknown toolbar \"%1$s\""), from_utf8(name));
2514 case LFUN_TOOLBAR_MOVABLE: {
2515 string const name = cmd.getArg(0);
2516 // use negation since locked == !movable
2518 // toolbar name * locks all toolbars
2519 flag.setOnOff(!toolbarsMovable_);
2520 else if (GuiToolbar * t = toolbar(name))
2521 flag.setOnOff(!(t->isMovable()));
2524 docstring const msg =
2525 bformat(_("Unknown toolbar \"%1$s\""), from_utf8(name));
2531 case LFUN_ICON_SIZE:
2532 flag.setOnOff(d.iconSize(cmd.argument()) == iconSize());
2535 case LFUN_DROP_LAYOUTS_CHOICE:
2536 enable = buf != nullptr;
2539 case LFUN_UI_TOGGLE:
2540 if (cmd.argument() == "zoomlevel") {
2541 flag.setOnOff(zoom_value_ ? zoom_value_->isVisible() : false);
2542 } else if (cmd.argument() == "zoomslider") {
2543 flag.setOnOff(zoom_slider_ ? zoom_slider_->isVisible() : false);
2544 } else if (cmd.argument() == "statistics-w") {
2545 flag.setOnOff(word_count_enabled_);
2546 } else if (cmd.argument() == "statistics-cb") {
2547 flag.setOnOff(char_count_enabled_);
2548 } else if (cmd.argument() == "statistics-c") {
2549 flag.setOnOff(char_nb_count_enabled_);
2551 flag.setOnOff(isFullScreen());
2554 case LFUN_DIALOG_DISCONNECT_INSET:
2557 case LFUN_DIALOG_HIDE:
2558 // FIXME: should we check if the dialog is shown?
2561 case LFUN_DIALOG_TOGGLE:
2562 flag.setOnOff(isDialogVisible(cmd.getArg(0)));
2565 case LFUN_DIALOG_SHOW: {
2566 string const name = cmd.getArg(0);
2568 enable = name == "aboutlyx"
2569 || name == "file" //FIXME: should be removed.
2570 || name == "lyxfiles"
2572 || name == "texinfo"
2573 || name == "progress"
2574 || name == "compare";
2575 else if (name == "character" || name == "symbols"
2576 || name == "mathdelimiter" || name == "mathmatrix") {
2577 if (!buf || buf->isReadonly())
2580 Cursor const & cur = currentBufferView()->cursor();
2581 enable = !(cur.inTexted() && cur.paragraph().isPassThru());
2584 else if (name == "latexlog")
2585 enable = FileName(doc_buffer->logName()).isReadableFile();
2586 else if (name == "spellchecker")
2587 enable = theSpellChecker()
2588 && !doc_buffer->text().empty();
2589 else if (name == "vclog")
2590 enable = doc_buffer->lyxvc().inUse();
2594 case LFUN_DIALOG_UPDATE: {
2595 string const name = cmd.getArg(0);
2597 enable = name == "prefs";
2601 case LFUN_COMMAND_EXECUTE:
2603 case LFUN_MENU_OPEN:
2604 // Nothing to check.
2607 case LFUN_COMPLETION_INLINE:
2608 if (!d.current_work_area_
2609 || !d.current_work_area_->completer().inlinePossible(
2610 currentBufferView()->cursor()))
2614 case LFUN_COMPLETION_POPUP:
2615 if (!d.current_work_area_
2616 || !d.current_work_area_->completer().popupPossible(
2617 currentBufferView()->cursor()))
2622 if (!d.current_work_area_
2623 || !d.current_work_area_->completer().inlinePossible(
2624 currentBufferView()->cursor()))
2628 case LFUN_COMPLETION_ACCEPT:
2629 if (!d.current_work_area_
2630 || (!d.current_work_area_->completer().popupVisible()
2631 && !d.current_work_area_->completer().inlineVisible()
2632 && !d.current_work_area_->completer().completionAvailable()))
2636 case LFUN_COMPLETION_CANCEL:
2637 if (!d.current_work_area_
2638 || (!d.current_work_area_->completer().popupVisible()
2639 && !d.current_work_area_->completer().inlineVisible()))
2643 case LFUN_BUFFER_ZOOM_OUT:
2644 case LFUN_BUFFER_ZOOM_IN:
2645 case LFUN_BUFFER_ZOOM: {
2646 int const zoom = (int)(lyxrc.defaultZoom * zoomRatio(cmd, zoom_ratio_));
2647 if (zoom < zoom_min_) {
2648 docstring const msg =
2649 bformat(_("Zoom level cannot be less than %1$d%."), zoom_min_);
2652 } else if (zoom > zoom_max_) {
2653 docstring const msg =
2654 bformat(_("Zoom level cannot be more than %1$d%."), zoom_max_);
2658 enable = doc_buffer;
2663 case LFUN_BUFFER_MOVE_NEXT:
2664 case LFUN_BUFFER_MOVE_PREVIOUS:
2665 // we do not cycle when moving
2666 case LFUN_BUFFER_NEXT:
2667 case LFUN_BUFFER_PREVIOUS:
2668 // because we cycle, it doesn't matter whether on first or last
2669 enable = (d.currentTabWorkArea()->count() > 1);
2671 case LFUN_BUFFER_SWITCH:
2672 // toggle on the current buffer, but do not toggle off
2673 // the other ones (is that a good idea?)
2675 && to_utf8(cmd.argument()) == doc_buffer->absFileName())
2676 flag.setOnOff(true);
2679 case LFUN_VC_REGISTER:
2680 enable = doc_buffer && !doc_buffer->lyxvc().inUse();
2682 case LFUN_VC_RENAME:
2683 enable = doc_buffer && doc_buffer->lyxvc().renameEnabled();
2686 enable = doc_buffer && doc_buffer->lyxvc().copyEnabled();
2688 case LFUN_VC_CHECK_IN:
2689 enable = doc_buffer && doc_buffer->lyxvc().checkInEnabled();
2691 case LFUN_VC_CHECK_OUT:
2692 enable = doc_buffer && doc_buffer->lyxvc().checkOutEnabled();
2694 case LFUN_VC_LOCKING_TOGGLE:
2695 enable = doc_buffer && !doc_buffer->hasReadonlyFlag()
2696 && doc_buffer->lyxvc().lockingToggleEnabled();
2697 flag.setOnOff(enable && doc_buffer->lyxvc().locking());
2699 case LFUN_VC_REVERT:
2700 enable = doc_buffer && doc_buffer->lyxvc().inUse()
2701 && !doc_buffer->hasReadonlyFlag();
2703 case LFUN_VC_UNDO_LAST:
2704 enable = doc_buffer && doc_buffer->lyxvc().undoLastEnabled();
2706 case LFUN_VC_REPO_UPDATE:
2707 enable = doc_buffer && doc_buffer->lyxvc().repoUpdateEnabled();
2709 case LFUN_VC_COMMAND: {
2710 if (cmd.argument().empty())
2712 if (!doc_buffer && contains(cmd.getArg(0), 'D'))
2716 case LFUN_VC_COMPARE:
2717 enable = doc_buffer && doc_buffer->lyxvc().prepareFileRevisionEnabled();
2720 case LFUN_SERVER_GOTO_FILE_ROW:
2721 case LFUN_LYX_ACTIVATE:
2722 case LFUN_WINDOW_RAISE:
2724 case LFUN_FORWARD_SEARCH:
2725 enable = !(lyxrc.forward_search_dvi.empty() && lyxrc.forward_search_pdf.empty()) &&
2726 doc_buffer && doc_buffer->isSyncTeXenabled();
2729 case LFUN_FILE_INSERT_PLAINTEXT:
2730 case LFUN_FILE_INSERT_PLAINTEXT_PARA:
2731 enable = documentBufferView() && documentBufferView()->cursor().inTexted();
2734 case LFUN_SPELLING_CONTINUOUSLY:
2735 flag.setOnOff(lyxrc.spellcheck_continuously);
2738 case LFUN_CITATION_OPEN:
2747 flag.setEnabled(false);
2753 static FileName selectTemplateFile()
2755 FileDialog dlg(qt_("Select template file"));
2756 dlg.setButton1(qt_("D&ocuments"), toqstr(lyxrc.document_path));
2757 dlg.setButton2(qt_("&Templates"), toqstr(lyxrc.template_path));
2759 FileDialog::Result result = dlg.open(toqstr(lyxrc.template_path),
2760 QStringList(qt_("LyX Documents (*.lyx)")));
2762 if (result.first == FileDialog::Later)
2764 if (result.second.isEmpty())
2766 return FileName(fromqstr(result.second));
2770 Buffer * GuiView::loadDocument(FileName const & filename, bool tolastfiles)
2774 Buffer * newBuffer = nullptr;
2776 newBuffer = checkAndLoadLyXFile(filename);
2777 } catch (ExceptionMessage const &) {
2784 message(_("Document not loaded."));
2788 setBuffer(newBuffer);
2789 newBuffer->errors("Parse");
2792 theSession().lastFiles().add(filename);
2793 theSession().writeFile();
2800 void GuiView::openDocument(string const & fname)
2802 string initpath = lyxrc.document_path;
2804 if (documentBufferView()) {
2805 string const trypath = documentBufferView()->buffer().filePath();
2806 // If directory is writeable, use this as default.
2807 if (FileName(trypath).isDirWritable())
2813 if (fname.empty()) {
2814 FileDialog dlg(qt_("Select document to open"));
2815 dlg.setButton1(qt_("D&ocuments"), toqstr(lyxrc.document_path));
2816 dlg.setButton2(qt_("&Examples"), toqstr(lyxrc.example_path));
2818 QStringList const filter({
2819 qt_("LyX Documents (*.lyx)"),
2820 qt_("LyX Document Backups (*.lyx~)"),
2821 qt_("All Files (*.*)")
2823 FileDialog::Result result =
2824 dlg.open(toqstr(initpath), filter);
2826 if (result.first == FileDialog::Later)
2829 filename = fromqstr(result.second);
2831 // check selected filename
2832 if (filename.empty()) {
2833 message(_("Canceled."));
2839 // get absolute path of file and add ".lyx" to the filename if
2841 FileName const fullname =
2842 fileSearch(string(), filename, "lyx", support::may_not_exist);
2843 if (!fullname.empty())
2844 filename = fullname.absFileName();
2846 if (!fullname.onlyPath().isDirectory()) {
2847 Alert::warning(_("Invalid filename"),
2848 bformat(_("The directory in the given path\n%1$s\ndoes not exist."),
2849 from_utf8(fullname.absFileName())));
2853 // if the file doesn't exist and isn't already open (bug 6645),
2854 // let the user create one
2855 if (!fullname.exists() && !theBufferList().exists(fullname) &&
2856 !LyXVC::file_not_found_hook(fullname)) {
2857 // the user specifically chose this name. Believe him.
2858 Buffer * const b = newFile(filename, string(), true);
2864 docstring const disp_fn = makeDisplayPath(filename);
2865 message(bformat(_("Opening document %1$s..."), disp_fn));
2868 Buffer * buf = loadDocument(fullname);
2870 str2 = bformat(_("Document %1$s opened."), disp_fn);
2871 if (buf->lyxvc().inUse())
2872 str2 += " " + from_utf8(buf->lyxvc().versionString()) +
2873 " " + _("Version control detected.");
2875 str2 = bformat(_("Could not open document %1$s"), disp_fn);
2880 // FIXME: clean that
2881 static bool import(GuiView * lv, FileName const & filename,
2882 string const & format, ErrorList & errorList)
2884 FileName const lyxfile(support::changeExtension(filename.absFileName(), ".lyx"));
2886 string loader_format;
2887 vector<string> loaders = theConverters().loaders();
2888 if (find(loaders.begin(), loaders.end(), format) == loaders.end()) {
2889 for (string const & loader : loaders) {
2890 if (!theConverters().isReachable(format, loader))
2893 string const tofile =
2894 support::changeExtension(filename.absFileName(),
2895 theFormats().extension(loader));
2896 if (theConverters().convert(nullptr, filename, FileName(tofile),
2897 filename, format, loader, errorList) != Converters::SUCCESS)
2899 loader_format = loader;
2902 if (loader_format.empty()) {
2903 frontend::Alert::error(_("Couldn't import file"),
2904 bformat(_("No information for importing the format %1$s."),
2905 translateIfPossible(theFormats().prettyName(format))));
2909 loader_format = format;
2911 if (loader_format == "lyx") {
2912 Buffer * buf = lv->loadDocument(lyxfile);
2916 Buffer * const b = newFile(lyxfile.absFileName(), string(), true);
2920 bool as_paragraphs = loader_format == "textparagraph";
2921 string filename2 = (loader_format == format) ? filename.absFileName()
2922 : support::changeExtension(filename.absFileName(),
2923 theFormats().extension(loader_format));
2924 lv->currentBufferView()->insertPlaintextFile(FileName(filename2),
2926 guiApp->setCurrentView(lv);
2927 lyx::dispatch(FuncRequest(LFUN_MARK_OFF));
2934 void GuiView::importDocument(string const & argument)
2937 string filename = split(argument, format, ' ');
2939 LYXERR(Debug::INFO, format << " file: " << filename);
2941 // need user interaction
2942 if (filename.empty()) {
2943 string initpath = lyxrc.document_path;
2944 if (documentBufferView()) {
2945 string const trypath = documentBufferView()->buffer().filePath();
2946 // If directory is writeable, use this as default.
2947 if (FileName(trypath).isDirWritable())
2951 docstring const text = bformat(_("Select %1$s file to import"),
2952 translateIfPossible(theFormats().prettyName(format)));
2954 FileDialog dlg(toqstr(text));
2955 dlg.setButton1(qt_("D&ocuments"), toqstr(lyxrc.document_path));
2956 dlg.setButton2(qt_("&Examples"), toqstr(lyxrc.example_path));
2958 docstring filter = translateIfPossible(theFormats().prettyName(format));
2961 filter += from_utf8(theFormats().extensions(format));
2964 FileDialog::Result result =
2965 dlg.open(toqstr(initpath), fileFilters(toqstr(filter)));
2967 if (result.first == FileDialog::Later)
2970 filename = fromqstr(result.second);
2972 // check selected filename
2973 if (filename.empty())
2974 message(_("Canceled."));
2977 if (filename.empty())
2980 // get absolute path of file
2981 FileName const fullname(support::makeAbsPath(filename));
2983 // Can happen if the user entered a path into the dialog
2985 if (fullname.onlyFileName().empty()) {
2986 docstring msg = bformat(_("The file name '%1$s' is invalid!\n"
2987 "Aborting import."),
2988 from_utf8(fullname.absFileName()));
2989 frontend::Alert::error(_("File name error"), msg);
2990 message(_("Canceled."));
2995 FileName const lyxfile(support::changeExtension(fullname.absFileName(), ".lyx"));
2997 // Check if the document already is open
2998 Buffer * buf = theBufferList().getBuffer(lyxfile);
3001 if (!closeBuffer()) {
3002 message(_("Canceled."));
3007 docstring const displaypath = makeDisplayPath(lyxfile.absFileName(), 30);
3009 // if the file exists already, and we didn't do
3010 // -i lyx thefile.lyx, warn
3011 if (lyxfile.exists() && fullname != lyxfile) {
3013 docstring text = bformat(_("The document %1$s already exists.\n\n"
3014 "Do you want to overwrite that document?"), displaypath);
3015 int const ret = Alert::prompt(_("Overwrite document?"),
3016 text, 0, 1, _("&Overwrite"), _("&Cancel"));
3019 message(_("Canceled."));
3024 message(bformat(_("Importing %1$s..."), displaypath));
3025 ErrorList errorList;
3026 if (import(this, fullname, format, errorList))
3027 message(_("imported."));
3029 message(_("file not imported!"));
3031 // FIXME (Abdel 12/08/06): Is there a need to display the error list here?
3035 void GuiView::newDocument(string const & filename, string templatefile,
3038 FileName initpath(lyxrc.document_path);
3039 if (documentBufferView()) {
3040 FileName const trypath(documentBufferView()->buffer().filePath());
3041 // If directory is writeable, use this as default.
3042 if (trypath.isDirWritable())
3046 if (from_template) {
3047 if (templatefile.empty())
3048 templatefile = selectTemplateFile().absFileName();
3049 if (templatefile.empty())
3054 if (filename.empty())
3055 b = newUnnamedFile(initpath, to_utf8(_("newfile")), templatefile);
3057 b = newFile(filename, templatefile, true);
3062 // If no new document could be created, it is unsure
3063 // whether there is a valid BufferView.
3064 if (currentBufferView())
3065 // Ensure the cursor is correctly positioned on screen.
3066 currentBufferView()->showCursor();
3070 bool GuiView::insertLyXFile(docstring const & fname, bool ignorelang)
3072 BufferView * bv = documentBufferView();
3077 FileName filename(to_utf8(fname));
3078 if (filename.empty()) {
3079 // Launch a file browser
3081 string initpath = lyxrc.document_path;
3082 string const trypath = bv->buffer().filePath();
3083 // If directory is writeable, use this as default.
3084 if (FileName(trypath).isDirWritable())
3088 FileDialog dlg(qt_("Select LyX document to insert"));
3089 dlg.setButton1(qt_("D&ocuments"), toqstr(lyxrc.document_path));
3090 dlg.setButton2(qt_("&Examples"), toqstr(lyxrc.example_path));
3092 FileDialog::Result result = dlg.open(toqstr(initpath),
3093 QStringList(qt_("LyX Documents (*.lyx)")));
3095 if (result.first == FileDialog::Later)
3099 filename.set(fromqstr(result.second));
3101 // check selected filename
3102 if (filename.empty()) {
3103 // emit message signal.
3104 message(_("Canceled."));
3109 bv->insertLyXFile(filename, ignorelang);
3110 bv->buffer().errors("Parse");
3115 string const GuiView::getTemplatesPath(Buffer & b)
3117 // We start off with the user's templates path
3118 string result = addPath(package().user_support().absFileName(), "templates");
3119 // Check for the document language
3120 string const langcode = b.params().language->code();
3121 string const shortcode = langcode.substr(0, 2);
3122 if (!langcode.empty() && shortcode != "en") {
3123 string subpath = addPath(result, shortcode);
3124 string subpath_long = addPath(result, langcode);
3125 // If we have a subdirectory for the language already,
3127 FileName sp = FileName(subpath);
3128 if (sp.isDirectory())
3130 else if (FileName(subpath_long).isDirectory())
3131 result = subpath_long;
3133 // Ask whether we should create such a subdirectory
3134 docstring const text =
3135 bformat(_("It is suggested to save the template in a subdirectory\n"
3136 "appropriate to the document language (%1$s).\n"
3137 "This subdirectory does not exists yet.\n"
3138 "Do you want to create it?"),
3139 _(b.params().language->display()));
3140 if (Alert::prompt(_("Create Language Directory?"),
3141 text, 0, 1, _("&Yes, Create"), _("&No, Save Template in Parent Directory")) == 0) {
3142 // If the user agreed, we try to create it and report if this failed.
3143 if (!sp.createDirectory(0777))
3144 Alert::error(_("Subdirectory creation failed!"),
3145 _("Could not create subdirectory.\n"
3146 "The template will be saved in the parent directory."));
3152 // Do we have a layout category?
3153 string const cat = b.params().baseClass() ?
3154 b.params().baseClass()->category()
3157 string subpath = addPath(result, cat);
3158 // If we have a subdirectory for the category already,
3160 FileName sp = FileName(subpath);
3161 if (sp.isDirectory())
3164 // Ask whether we should create such a subdirectory
3165 docstring const text =
3166 bformat(_("It is suggested to save the template in a subdirectory\n"
3167 "appropriate to the layout category (%1$s).\n"
3168 "This subdirectory does not exists yet.\n"
3169 "Do you want to create it?"),
3171 if (Alert::prompt(_("Create Category Directory?"),
3172 text, 0, 1, _("&Yes, Create"), _("&No, Save Template in Parent Directory")) == 0) {
3173 // If the user agreed, we try to create it and report if this failed.
3174 if (!sp.createDirectory(0777))
3175 Alert::error(_("Subdirectory creation failed!"),
3176 _("Could not create subdirectory.\n"
3177 "The template will be saved in the parent directory."));
3187 bool GuiView::renameBuffer(Buffer & b, docstring const & newname, RenameKind kind)
3189 FileName fname = b.fileName();
3190 FileName const oldname = fname;
3191 bool const as_template = (kind == LV_WRITE_AS_TEMPLATE);
3193 if (!newname.empty()) {
3196 fname = support::makeAbsPath(to_utf8(newname), getTemplatesPath(b));
3198 fname = support::makeAbsPath(to_utf8(newname),
3199 oldname.onlyPath().absFileName());
3201 // Switch to this Buffer.
3204 // No argument? Ask user through dialog.
3206 QString const title = as_template ? qt_("Choose a filename to save template as")
3207 : qt_("Choose a filename to save document as");
3208 FileDialog dlg(title);
3209 dlg.setButton1(qt_("D&ocuments"), toqstr(lyxrc.document_path));
3210 dlg.setButton2(qt_("&Templates"), toqstr(lyxrc.template_path));
3212 fname.ensureExtension(".lyx");
3214 string const path = as_template ?
3216 : fname.onlyPath().absFileName();
3217 FileDialog::Result result =
3218 dlg.save(toqstr(path),
3219 QStringList(qt_("LyX Documents (*.lyx)")),
3220 toqstr(fname.onlyFileName()));
3222 if (result.first == FileDialog::Later)
3225 fname.set(fromqstr(result.second));
3230 fname.ensureExtension(".lyx");
3233 // fname is now the new Buffer location.
3235 // if there is already a Buffer open with this name, we do not want
3236 // to have another one. (the second test makes sure we're not just
3237 // trying to overwrite ourselves, which is fine.)
3238 if (theBufferList().exists(fname) && fname != oldname
3239 && theBufferList().getBuffer(fname) != &b) {
3240 docstring const text =
3241 bformat(_("The file\n%1$s\nis already open in your current session.\n"
3242 "Please close it before attempting to overwrite it.\n"
3243 "Do you want to choose a new filename?"),
3244 from_utf8(fname.absFileName()));
3245 int const ret = Alert::prompt(_("Chosen File Already Open"),
3246 text, 0, 1, _("&Rename"), _("&Cancel"));
3248 case 0: return renameBuffer(b, docstring(), kind);
3249 case 1: return false;
3254 bool const existsLocal = fname.exists();
3255 bool const existsInVC = LyXVC::fileInVC(fname);
3256 if (existsLocal || existsInVC) {
3257 docstring const file = makeDisplayPath(fname.absFileName(), 30);
3258 if (kind != LV_WRITE_AS && existsInVC) {
3259 // renaming to a name that is already in VC
3261 docstring text = bformat(_("The document %1$s "
3262 "is already registered.\n\n"
3263 "Do you want to choose a new name?"),
3265 docstring const title = (kind == LV_VC_RENAME) ?
3266 _("Rename document?") : _("Copy document?");
3267 docstring const button = (kind == LV_VC_RENAME) ?
3268 _("&Rename") : _("&Copy");
3269 int const ret = Alert::prompt(title, text, 0, 1,
3270 button, _("&Cancel"));
3272 case 0: return renameBuffer(b, docstring(), kind);
3273 case 1: return false;
3278 docstring text = bformat(_("The document %1$s "
3279 "already exists.\n\n"
3280 "Do you want to overwrite that document?"),
3282 int const ret = Alert::prompt(_("Overwrite document?"),
3283 text, 0, 2, _("&Overwrite"),
3284 _("&Rename"), _("&Cancel"));
3287 case 1: return renameBuffer(b, docstring(), kind);
3288 case 2: return false;
3294 case LV_VC_RENAME: {
3295 string msg = b.lyxvc().rename(fname);
3298 message(from_utf8(msg));
3302 string msg = b.lyxvc().copy(fname);
3305 message(from_utf8(msg));
3309 case LV_WRITE_AS_TEMPLATE:
3312 // LyXVC created the file already in case of LV_VC_RENAME or
3313 // LV_VC_COPY, but call saveBuffer() nevertheless to get
3314 // relative paths of included stuff right if we moved e.g. from
3315 // /a/b.lyx to /a/c/b.lyx.
3317 bool const saved = saveBuffer(b, fname);
3324 bool GuiView::exportBufferAs(Buffer & b, docstring const & iformat)
3326 FileName fname = b.fileName();
3328 FileDialog dlg(qt_("Choose a filename to export the document as"));
3329 dlg.setButton1(qt_("D&ocuments"), toqstr(lyxrc.document_path));
3332 QString const anyformat = qt_("Guess from extension (*.*)");
3335 vector<Format const *> export_formats;
3336 for (Format const & f : theFormats())
3337 if (f.documentFormat())
3338 export_formats.push_back(&f);
3339 sort(export_formats.begin(), export_formats.end(), Format::formatSorter);
3340 map<QString, string> fmap;
3343 for (Format const * f : export_formats) {
3344 docstring const loc_prettyname = translateIfPossible(f->prettyname());
3345 QString const loc_filter = toqstr(bformat(from_ascii("%1$s (*.%2$s)"),
3347 from_ascii(f->extension())));
3348 types << loc_filter;
3349 fmap[loc_filter] = f->name();
3350 if (from_ascii(f->name()) == iformat) {
3351 filter = loc_filter;
3352 ext = f->extension();
3355 string ofname = fname.onlyFileName();
3357 ofname = support::changeExtension(ofname, ext);
3358 FileDialog::Result result =
3359 dlg.save(toqstr(fname.onlyPath().absFileName()),
3363 if (result.first != FileDialog::Chosen)
3367 fname.set(fromqstr(result.second));
3368 if (filter == anyformat)
3369 fmt_name = theFormats().getFormatFromExtension(fname.extension());
3371 fmt_name = fmap[filter];
3372 LYXERR(Debug::FILES, "filter=" << fromqstr(filter)
3373 << ", fmt_name=" << fmt_name << ", fname=" << fname.absFileName());
3375 if (fmt_name.empty() || fname.empty())
3378 fname.ensureExtension(theFormats().extension(fmt_name));
3380 // fname is now the new Buffer location.
3381 if (fname.exists()) {
3382 docstring const file = makeDisplayPath(fname.absFileName(), 30);
3383 docstring text = bformat(_("The document %1$s already "
3384 "exists.\n\nDo you want to "
3385 "overwrite that document?"),
3387 int const ret = Alert::prompt(_("Overwrite document?"),
3388 text, 0, 2, _("&Overwrite"), _("&Rename"), _("&Cancel"));
3391 case 1: return exportBufferAs(b, from_ascii(fmt_name));
3392 case 2: return false;
3396 FuncRequest cmd(LFUN_BUFFER_EXPORT, fmt_name + " " + fname.absFileName());
3399 return dr.dispatched();
3403 bool GuiView::saveBuffer(Buffer & b)
3405 return saveBuffer(b, FileName());
3409 bool GuiView::saveBuffer(Buffer & b, FileName const & fn)
3411 if (workArea(b) && workArea(b)->inDialogMode())
3414 if (fn.empty() && b.isUnnamed())
3415 return renameBuffer(b, docstring());
3417 bool const success = (fn.empty() ? b.save() : b.saveAs(fn));
3419 theSession().lastFiles().add(b.fileName());
3420 theSession().writeFile();
3424 // Switch to this Buffer.
3427 // FIXME: we don't tell the user *WHY* the save failed !!
3428 docstring const file = makeDisplayPath(b.absFileName(), 30);
3429 docstring text = bformat(_("The document %1$s could not be saved.\n\n"
3430 "Do you want to rename the document and "
3431 "try again?"), file);
3432 int const ret = Alert::prompt(_("Rename and save?"),
3433 text, 0, 2, _("&Rename"), _("&Retry"), _("&Cancel"));
3436 if (!renameBuffer(b, docstring()))
3445 return saveBuffer(b, fn);
3449 bool GuiView::hideWorkArea(GuiWorkArea * wa)
3451 return closeWorkArea(wa, false);
3455 // We only want to close the buffer if it is not visible in other workareas
3456 // of the same view, nor in other views, and if this is not a child
3457 bool GuiView::closeWorkArea(GuiWorkArea * wa)
3459 Buffer & buf = wa->bufferView().buffer();
3461 bool last_wa = d.countWorkAreasOf(buf) == 1
3462 && !inOtherView(buf) && !buf.parent();
3464 bool close_buffer = last_wa;
3467 if (lyxrc.close_buffer_with_last_view == "yes")
3469 else if (lyxrc.close_buffer_with_last_view == "no")
3470 close_buffer = false;
3473 if (buf.isUnnamed())
3474 file = from_utf8(buf.fileName().onlyFileName());
3476 file = buf.fileName().displayName(30);
3477 docstring const text = bformat(
3478 _("Last view on document %1$s is being closed.\n"
3479 "Would you like to close or hide the document?\n"
3481 "Hidden documents can be displayed back through\n"
3482 "the menu: View->Hidden->...\n"
3484 "To remove this question, set your preference in:\n"
3485 " Tools->Preferences->Look&Feel->UserInterface\n"
3487 int ret = Alert::prompt(_("Close or hide document?"),
3488 text, 0, 2, _("&Close"), _("&Hide"), _("&Cancel"));
3491 close_buffer = (ret == 0);
3495 return closeWorkArea(wa, close_buffer);
3499 bool GuiView::closeBuffer()
3501 GuiWorkArea * wa = currentMainWorkArea();
3502 // coverity complained about this
3503 // it seems unnecessary, but perhaps is worth the check
3504 LASSERT(wa, return false);
3506 setCurrentWorkArea(wa);
3507 Buffer & buf = wa->bufferView().buffer();
3508 return closeWorkArea(wa, !buf.parent());
3512 void GuiView::writeSession() const {
3513 GuiWorkArea const * active_wa = currentMainWorkArea();
3514 for (int i = 0; i < d.splitter_->count(); ++i) {
3515 TabWorkArea * twa = d.tabWorkArea(i);
3516 for (int j = 0; j < twa->count(); ++j) {
3517 GuiWorkArea * wa = twa->workArea(j);
3518 Buffer & buf = wa->bufferView().buffer();
3519 theSession().lastOpened().add(buf.fileName(), wa == active_wa);
3525 bool GuiView::closeBufferAll()
3528 for (auto & buf : theBufferList()) {
3529 if (!saveBufferIfNeeded(*buf, false)) {
3530 // Closing has been cancelled, so abort.
3535 // Close the workareas in all other views
3536 QList<int> const ids = guiApp->viewIds();
3537 for (int i = 0; i != ids.size(); ++i) {
3538 if (id_ != ids[i] && !guiApp->view(ids[i]).closeWorkAreaAll())
3542 // Close our own workareas
3543 if (!closeWorkAreaAll())
3550 bool GuiView::closeWorkAreaAll()
3552 setCurrentWorkArea(currentMainWorkArea());
3554 // We might be in a situation that there is still a tabWorkArea, but
3555 // there are no tabs anymore. This can happen when we get here after a
3556 // TabWorkArea::lastWorkAreaRemoved() signal. Therefore we count how
3557 // many TabWorkArea's have no documents anymore.
3560 // We have to call count() each time, because it can happen that
3561 // more than one splitter will disappear in one iteration (bug 5998).
3562 while (d.splitter_->count() > empty_twa) {
3563 TabWorkArea * twa = d.tabWorkArea(empty_twa);
3565 if (twa->count() == 0)
3568 setCurrentWorkArea(twa->currentWorkArea());
3569 if (!closeTabWorkArea(twa))
3577 bool GuiView::closeWorkArea(GuiWorkArea * wa, bool close_buffer)
3582 Buffer & buf = wa->bufferView().buffer();
3584 if (GuiViewPrivate::busyBuffers.contains(&buf)) {
3585 Alert::warning(_("Close document"),
3586 _("Document could not be closed because it is being processed by LyX."));
3591 return closeBuffer(buf);
3593 if (!inMultiTabs(wa))
3594 if (!saveBufferIfNeeded(buf, true))
3602 bool GuiView::closeBuffer(Buffer & buf)
3604 bool success = true;
3605 for (Buffer * child_buf : buf.getChildren()) {
3606 if (theBufferList().isOthersChild(&buf, child_buf)) {
3607 child_buf->setParent(nullptr);
3611 // FIXME: should we look in other tabworkareas?
3612 // ANSWER: I don't think so. I've tested, and if the child is
3613 // open in some other window, it closes without a problem.
3614 GuiWorkArea * child_wa = workArea(*child_buf);
3617 // If we are in a close_event all children will be closed in some time,
3618 // so no need to do it here. This will ensure that the children end up
3619 // in the session file in the correct order. If we close the master
3620 // buffer, we can close or release the child buffers here too.
3622 success = closeWorkArea(child_wa, true);
3626 // In this case the child buffer is open but hidden.
3627 // Even in this case, children can be dirty (e.g.,
3628 // after a label change in the master, see #11405).
3629 // Therefore, check this
3630 if (closing_ && (child_buf->isClean() || child_buf->paragraphs().empty())) {
3631 // If we are in a close_event all children will be closed in some time,
3632 // so no need to do it here. This will ensure that the children end up
3633 // in the session file in the correct order. If we close the master
3634 // buffer, we can close or release the child buffers here too.
3637 // Save dirty buffers also if closing_!
3638 if (saveBufferIfNeeded(*child_buf, false)) {
3639 child_buf->removeAutosaveFile();
3640 theBufferList().release(child_buf);
3642 // Saving of dirty children has been cancelled.
3643 // Cancel the whole process.
3650 // goto bookmark to update bookmark pit.
3651 // FIXME: we should update only the bookmarks related to this buffer!
3652 // FIXME: this is done also in LFUN_WINDOW_CLOSE!
3653 LYXERR(Debug::DEBUG, "GuiView::closeBuffer()");
3654 for (unsigned int i = 1; i < theSession().bookmarks().size(); ++i)
3655 guiApp->gotoBookmark(i, false, false);
3657 if (saveBufferIfNeeded(buf, false)) {
3658 buf.removeAutosaveFile();
3659 theBufferList().release(&buf);
3663 // open all children again to avoid a crash because of dangling
3664 // pointers (bug 6603)
3670 bool GuiView::closeTabWorkArea(TabWorkArea * twa)
3672 while (twa == d.currentTabWorkArea()) {
3673 twa->setCurrentIndex(twa->count() - 1);
3675 GuiWorkArea * wa = twa->currentWorkArea();
3676 Buffer & b = wa->bufferView().buffer();
3678 // We only want to close the buffer if the same buffer is not visible
3679 // in another view, and if this is not a child and if we are closing
3680 // a view (not a tabgroup).
3681 bool const close_buffer =
3682 !inOtherView(b) && !b.parent() && closing_;
3684 if (!closeWorkArea(wa, close_buffer))
3691 bool GuiView::saveBufferIfNeeded(Buffer & buf, bool hiding)
3693 if (buf.isClean() || buf.paragraphs().empty())
3696 // Switch to this Buffer.
3702 if (buf.isUnnamed()) {
3703 file = from_utf8(buf.fileName().onlyFileName());
3706 FileName filename = buf.fileName();
3708 file = filename.displayName(30);
3709 exists = filename.exists();
3712 // Bring this window to top before asking questions.
3717 if (hiding && buf.isUnnamed()) {
3718 docstring const text = bformat(_("The document %1$s has not been "
3719 "saved yet.\n\nDo you want to save "
3720 "the document?"), file);
3721 ret = Alert::prompt(_("Save new document?"),
3722 text, 0, 1, _("&Save"), _("&Cancel"));
3726 docstring const text = exists ?
3727 bformat(_("The document %1$s has unsaved changes."
3728 "\n\nDo you want to save the document or "
3729 "discard the changes?"), file) :
3730 bformat(_("The document %1$s has not been saved yet."
3731 "\n\nDo you want to save the document or "
3732 "discard it entirely?"), file);
3733 docstring const title = exists ?
3734 _("Save changed document?") : _("Save document?");
3735 ret = Alert::prompt(title, text, 0, 2,
3736 _("&Save"), _("&Discard"), _("&Cancel"));
3741 if (!saveBuffer(buf))
3745 // If we crash after this we could have no autosave file
3746 // but I guess this is really improbable (Jug).
3747 // Sometimes improbable things happen:
3748 // - see bug http://www.lyx.org/trac/ticket/6587 (ps)
3749 // buf.removeAutosaveFile();
3751 // revert all changes
3762 bool GuiView::inMultiTabs(GuiWorkArea * wa)
3764 Buffer & buf = wa->bufferView().buffer();
3766 for (int i = 0; i != d.splitter_->count(); ++i) {
3767 GuiWorkArea * wa_ = d.tabWorkArea(i)->workArea(buf);
3768 if (wa_ && wa_ != wa)
3771 return inOtherView(buf);
3775 bool GuiView::inOtherView(Buffer & buf)
3777 QList<int> const ids = guiApp->viewIds();
3779 for (int i = 0; i != ids.size(); ++i) {
3783 if (guiApp->view(ids[i]).workArea(buf))
3790 void GuiView::gotoNextOrPreviousBuffer(NextOrPrevious np, bool const move)
3792 if (!documentBufferView())
3795 if (TabWorkArea * twa = d.currentTabWorkArea()) {
3796 Buffer * const curbuf = &documentBufferView()->buffer();
3797 int nwa = twa->count();
3798 for (int i = 0; i < nwa; ++i) {
3799 if (&workArea(i)->bufferView().buffer() == curbuf) {
3802 next_index = (i == nwa - 1 ? 0 : i + 1);
3804 next_index = (i == 0 ? nwa - 1 : i - 1);
3806 twa->moveTab(i, next_index);
3808 setBuffer(&workArea(next_index)->bufferView().buffer());
3816 void GuiView::gotoNextTabWorkArea(NextOrPrevious np)
3818 int count = d.splitter_->count();
3819 for (int i = 0; i < count; ++i) {
3820 if (d.tabWorkArea(i) == d.currentTabWorkArea()) {
3823 new_index = (i == count - 1 ? 0 : i + 1);
3825 new_index = (i == 0 ? count - 1 : i - 1);
3826 setCurrentWorkArea(d.tabWorkArea(new_index)->currentWorkArea());
3833 /// make sure the document is saved
3834 static bool ensureBufferClean(Buffer * buffer)
3836 LASSERT(buffer, return false);
3837 if (buffer->isClean() && !buffer->isUnnamed())
3840 docstring const file = buffer->fileName().displayName(30);
3843 if (!buffer->isUnnamed()) {
3844 text = bformat(_("The document %1$s has unsaved "
3845 "changes.\n\nDo you want to save "
3846 "the document?"), file);
3847 title = _("Save changed document?");
3850 text = bformat(_("The document %1$s has not been "
3851 "saved yet.\n\nDo you want to save "
3852 "the document?"), file);
3853 title = _("Save new document?");
3855 int const ret = Alert::prompt(title, text, 0, 1, _("&Save"), _("&Cancel"));
3858 dispatch(FuncRequest(LFUN_BUFFER_WRITE));
3860 return buffer->isClean() && !buffer->isUnnamed();
3864 bool GuiView::reloadBuffer(Buffer & buf)
3866 currentBufferView()->cursor().reset();
3867 Buffer::ReadStatus status = buf.reload();
3868 return status == Buffer::ReadSuccess;
3872 void GuiView::checkExternallyModifiedBuffers()
3874 for (Buffer * buf : theBufferList()) {
3875 if (buf->fileName().exists() && buf->isChecksumModified()) {
3876 docstring text = bformat(_("Document \n%1$s\n has been externally modified."
3877 " Reload now? Any local changes will be lost."),
3878 from_utf8(buf->absFileName()));
3879 int const ret = Alert::prompt(_("Reload externally changed document?"),
3880 text, 0, 1, _("&Reload"), _("&Cancel"));
3888 void GuiView::dispatchVC(FuncRequest const & cmd, DispatchResult & dr)
3890 Buffer * buffer = documentBufferView()
3891 ? &(documentBufferView()->buffer()) : nullptr;
3893 switch (cmd.action()) {
3894 case LFUN_VC_REGISTER:
3895 if (!buffer || !ensureBufferClean(buffer))
3897 if (!buffer->lyxvc().inUse()) {
3898 if (buffer->lyxvc().registrer()) {
3899 reloadBuffer(*buffer);
3900 dr.clearMessageUpdate();
3905 case LFUN_VC_RENAME:
3906 case LFUN_VC_COPY: {
3907 if (!buffer || !ensureBufferClean(buffer))
3909 if (buffer->lyxvc().inUse() && !buffer->hasReadonlyFlag()) {
3910 if (buffer->lyxvc().isCheckInWithConfirmation()) {
3911 // Some changes are not yet committed.
3912 // We test here and not in getStatus(), since
3913 // this test is expensive.
3915 LyXVC::CommandResult ret =
3916 buffer->lyxvc().checkIn(log);
3918 if (ret == LyXVC::ErrorCommand ||
3919 ret == LyXVC::VCSuccess)
3920 reloadBuffer(*buffer);
3921 if (buffer->lyxvc().isCheckInWithConfirmation()) {
3922 frontend::Alert::error(
3923 _("Revision control error."),
3924 _("Document could not be checked in."));
3928 RenameKind const kind = (cmd.action() == LFUN_VC_RENAME) ?
3929 LV_VC_RENAME : LV_VC_COPY;
3930 renameBuffer(*buffer, cmd.argument(), kind);
3935 case LFUN_VC_CHECK_IN:
3936 if (!buffer || !ensureBufferClean(buffer))
3938 if (buffer->lyxvc().inUse() && !buffer->hasReadonlyFlag()) {
3940 LyXVC::CommandResult ret = buffer->lyxvc().checkIn(log);
3942 // Only skip reloading if the checkin was cancelled or
3943 // an error occurred before the real checkin VCS command
3944 // was executed, since the VCS might have changed the
3945 // file even if it could not checkin successfully.
3946 if (ret == LyXVC::ErrorCommand || ret == LyXVC::VCSuccess)
3947 reloadBuffer(*buffer);
3951 case LFUN_VC_CHECK_OUT:
3952 if (!buffer || !ensureBufferClean(buffer))
3954 if (buffer->lyxvc().inUse()) {
3955 dr.setMessage(buffer->lyxvc().checkOut());
3956 reloadBuffer(*buffer);
3960 case LFUN_VC_LOCKING_TOGGLE:
3961 if (!buffer || !ensureBufferClean(buffer) || buffer->hasReadonlyFlag())
3963 if (buffer->lyxvc().inUse()) {
3964 string res = buffer->lyxvc().lockingToggle();
3966 frontend::Alert::error(_("Revision control error."),
3967 _("Error when setting the locking property."));
3970 reloadBuffer(*buffer);
3975 case LFUN_VC_REVERT:
3978 if (buffer->lyxvc().revert()) {
3979 reloadBuffer(*buffer);
3980 dr.clearMessageUpdate();
3984 case LFUN_VC_UNDO_LAST:
3987 buffer->lyxvc().undoLast();
3988 reloadBuffer(*buffer);
3989 dr.clearMessageUpdate();
3992 case LFUN_VC_REPO_UPDATE:
3995 if (ensureBufferClean(buffer)) {
3996 dr.setMessage(buffer->lyxvc().repoUpdate());
3997 checkExternallyModifiedBuffers();
4001 case LFUN_VC_COMMAND: {
4002 string flag = cmd.getArg(0);
4003 if (buffer && contains(flag, 'R') && !ensureBufferClean(buffer))
4006 if (contains(flag, 'M')) {
4007 if (!Alert::askForText(message, _("LyX VC: Log Message")))
4010 string path = cmd.getArg(1);
4011 if (contains(path, "$$p") && buffer)
4012 path = subst(path, "$$p", buffer->filePath());
4013 LYXERR(Debug::LYXVC, "Directory: " << path);
4015 if (!pp.isReadableDirectory()) {
4016 lyxerr << _("Directory is not accessible.") << endl;
4019 support::PathChanger p(pp);
4021 string command = cmd.getArg(2);
4022 if (command.empty())
4025 command = subst(command, "$$i", buffer->absFileName());
4026 command = subst(command, "$$p", buffer->filePath());
4028 command = subst(command, "$$m", to_utf8(message));
4029 LYXERR(Debug::LYXVC, "Command: " << command);
4031 one.startscript(Systemcall::Wait, command);
4035 if (contains(flag, 'I'))
4036 buffer->markDirty();
4037 if (contains(flag, 'R'))
4038 reloadBuffer(*buffer);
4043 case LFUN_VC_COMPARE: {
4044 if (cmd.argument().empty()) {
4045 lyx::dispatch(FuncRequest(LFUN_DIALOG_SHOW, "comparehistory"));
4051 string rev1 = cmd.getArg(0);
4055 if (!buffer->lyxvc().prepareFileRevision(rev1, f1))
4058 if (isStrInt(rev1) && convert<int>(rev1) <= 0) {
4059 f2 = buffer->absFileName();
4061 string rev2 = cmd.getArg(1);
4065 if (!buffer->lyxvc().prepareFileRevision(rev2, f2))
4069 LYXERR(Debug::LYXVC, "Launching comparison for fetched revisions:\n" <<
4070 f1 << "\n" << f2 << "\n" );
4071 string par = "compare run " + quoteName(f1) + " " + quoteName(f2);
4072 lyx::dispatch(FuncRequest(LFUN_DIALOG_SHOW, par));
4082 void GuiView::openChildDocument(string const & fname)
4084 LASSERT(documentBufferView(), return);
4085 Buffer & buffer = documentBufferView()->buffer();
4086 FileName const filename = support::makeAbsPath(fname, buffer.filePath());
4087 documentBufferView()->saveBookmark(false);
4088 Buffer * child = nullptr;
4089 if (theBufferList().exists(filename)) {
4090 child = theBufferList().getBuffer(filename);
4093 message(bformat(_("Opening child document %1$s..."),
4094 makeDisplayPath(filename.absFileName())));
4095 child = loadDocument(filename, false);
4097 // Set the parent name of the child document.
4098 // This makes insertion of citations and references in the child work,
4099 // when the target is in the parent or another child document.
4101 child->setParent(&buffer);
4105 bool GuiView::goToFileRow(string const & argument)
4109 size_t i = argument.find_last_of(' ');
4110 if (i != string::npos) {
4111 file_name = os::internal_path(FileName(trim(argument.substr(0, i))).realPath());
4112 istringstream is(argument.substr(i + 1));
4117 if (i == string::npos) {
4118 LYXERR0("Wrong argument: " << argument);
4121 Buffer * buf = nullptr;
4122 string const realtmp = package().temp_dir().realPath();
4123 // We have to use os::path_prefix_is() here, instead of
4124 // simply prefixIs(), because the file name comes from
4125 // an external application and may need case adjustment.
4126 if (os::path_prefix_is(file_name, realtmp, os::CASE_ADJUSTED)) {
4127 buf = theBufferList().getBufferFromTmp(file_name, true);
4128 LYXERR(Debug::FILES, "goToFileRow: buffer lookup for " << file_name
4129 << (buf ? " success" : " failed"));
4131 // Must replace extension of the file to be .lyx
4132 // and get full path
4133 FileName const s = fileSearch(string(),
4134 support::changeExtension(file_name, ".lyx"), "lyx");
4135 // Either change buffer or load the file
4136 if (theBufferList().exists(s))
4137 buf = theBufferList().getBuffer(s);
4138 else if (s.exists()) {
4139 buf = loadDocument(s);
4144 _("File does not exist: %1$s"),
4145 makeDisplayPath(file_name)));
4151 _("No buffer for file: %1$s."),
4152 makeDisplayPath(file_name))
4157 bool success = documentBufferView()->setCursorFromRow(row);
4159 LYXERR(Debug::OUTFILE,
4160 "setCursorFromRow: invalid position for row " << row);
4161 frontend::Alert::error(_("Inverse Search Failed"),
4162 _("Invalid position requested by inverse search.\n"
4163 "You may need to update the viewed document."));
4169 void GuiView::toolBarPopup(const QPoint & /*pos*/)
4171 QMenu * menu = guiApp->menus().menu(toqstr("context-toolbars"), * this);
4172 menu->exec(QCursor::pos());
4177 Buffer::ExportStatus GuiView::GuiViewPrivate::runAndDestroy(const T& func,
4178 Buffer const * orig, Buffer * clone, string const & format)
4180 Buffer::ExportStatus const status = func(format);
4182 // the cloning operation will have produced a clone of the entire set of
4183 // documents, starting from the master. so we must delete those.
4184 Buffer * mbuf = const_cast<Buffer *>(clone->masterBuffer());
4186 busyBuffers.remove(orig);
4191 Buffer::ExportStatus GuiView::GuiViewPrivate::compileAndDestroy(
4192 Buffer const * orig, Buffer * clone, string const & format)
4194 Buffer::ExportStatus (Buffer::* mem_func)(std::string const &, bool) const =
4196 return runAndDestroy(lyx::bind(mem_func, clone, _1, true), orig, clone, format);
4200 Buffer::ExportStatus GuiView::GuiViewPrivate::exportAndDestroy(
4201 Buffer const * orig, Buffer * clone, string const & format)
4203 Buffer::ExportStatus (Buffer::* mem_func)(std::string const &, bool) const =
4205 return runAndDestroy(lyx::bind(mem_func, clone, _1, false), orig, clone, format);
4209 Buffer::ExportStatus GuiView::GuiViewPrivate::previewAndDestroy(
4210 Buffer const * orig, Buffer * clone, string const & format)
4212 Buffer::ExportStatus (Buffer::* mem_func)(std::string const &) const =
4214 return runAndDestroy(lyx::bind(mem_func, clone, _1), orig, clone, format);
4218 bool GuiView::GuiViewPrivate::asyncBufferProcessing(string const & argument,
4219 Buffer const * used_buffer,
4220 docstring const & msg,
4221 Buffer::ExportStatus (*asyncFunc)(Buffer const *, Buffer *, string const &),
4222 Buffer::ExportStatus (Buffer::*syncFunc)(string const &, bool) const,
4223 Buffer::ExportStatus (Buffer::*previewFunc)(string const &) const,
4224 bool allow_async, bool use_tmpdir)
4229 string format = argument;
4231 format = used_buffer->params().getDefaultOutputFormat();
4232 processing_format = format;
4234 progress_->clearMessages();
4237 #if EXPORT_in_THREAD
4239 GuiViewPrivate::busyBuffers.insert(used_buffer);
4240 Buffer * cloned_buffer = used_buffer->cloneWithChildren();
4241 if (!cloned_buffer) {
4242 Alert::error(_("Export Error"),
4243 _("Error cloning the Buffer."));
4246 QFuture<Buffer::ExportStatus> f = QtConcurrent::run(
4251 setPreviewFuture(f);
4252 last_export_format = used_buffer->params().bufferFormat();
4255 // We are asynchronous, so we don't know here anything about the success
4258 Buffer::ExportStatus status;
4260 status = (used_buffer->*syncFunc)(format, use_tmpdir);
4261 } else if (previewFunc) {
4262 status = (used_buffer->*previewFunc)(format);
4265 handleExportStatus(gv_, status, format);
4267 return (status == Buffer::ExportSuccess
4268 || status == Buffer::PreviewSuccess);
4272 Buffer::ExportStatus status;
4274 status = (used_buffer->*syncFunc)(format, true);
4275 } else if (previewFunc) {
4276 status = (used_buffer->*previewFunc)(format);
4279 handleExportStatus(gv_, status, format);
4281 return (status == Buffer::ExportSuccess
4282 || status == Buffer::PreviewSuccess);
4286 void GuiView::dispatchToBufferView(FuncRequest const & cmd, DispatchResult & dr)
4288 BufferView * bv = currentBufferView();
4289 LASSERT(bv, return);
4291 // Let the current BufferView dispatch its own actions.
4292 bv->dispatch(cmd, dr);
4293 if (dr.dispatched()) {
4294 if (cmd.action() == LFUN_REDO || cmd.action() == LFUN_UNDO)
4295 updateDialog("document", "");
4299 // Try with the document BufferView dispatch if any.
4300 BufferView * doc_bv = documentBufferView();
4301 if (doc_bv && doc_bv != bv) {
4302 doc_bv->dispatch(cmd, dr);
4303 if (dr.dispatched()) {
4304 if (cmd.action() == LFUN_REDO || cmd.action() == LFUN_UNDO)
4305 updateDialog("document", "");
4310 // Then let the current Cursor dispatch its own actions.
4311 bv->cursor().dispatch(cmd);
4313 // update completion. We do it here and not in
4314 // processKeySym to avoid another redraw just for a
4315 // changed inline completion
4316 if (cmd.origin() == FuncRequest::KEYBOARD) {
4317 if (cmd.action() == LFUN_SELF_INSERT
4318 || (cmd.action() == LFUN_ERT_INSERT && bv->cursor().inMathed()))
4319 updateCompletion(bv->cursor(), true, true);
4320 else if (cmd.action() == LFUN_CHAR_DELETE_BACKWARD)
4321 updateCompletion(bv->cursor(), false, true);
4323 updateCompletion(bv->cursor(), false, false);
4326 dr = bv->cursor().result();
4330 void GuiView::dispatch(FuncRequest const & cmd, DispatchResult & dr)
4332 BufferView * bv = currentBufferView();
4333 // By default we won't need any update.
4334 dr.screenUpdate(Update::None);
4335 // assume cmd will be dispatched
4336 dr.dispatched(true);
4338 Buffer * doc_buffer = documentBufferView()
4339 ? &(documentBufferView()->buffer()) : nullptr;
4341 if (cmd.origin() == FuncRequest::TOC) {
4342 GuiToc * toc = static_cast<GuiToc*>(findOrBuild("toc", false));
4343 toc->doDispatch(bv->cursor(), cmd, dr);
4347 string const argument = to_utf8(cmd.argument());
4349 switch(cmd.action()) {
4350 case LFUN_BUFFER_CHILD_OPEN:
4351 openChildDocument(to_utf8(cmd.argument()));
4354 case LFUN_BUFFER_IMPORT:
4355 importDocument(to_utf8(cmd.argument()));
4358 case LFUN_MASTER_BUFFER_EXPORT:
4360 doc_buffer = const_cast<Buffer *>(doc_buffer->masterBuffer());
4362 case LFUN_BUFFER_EXPORT: {
4365 // GCC only sees strfwd.h when building merged
4366 if (::lyx::operator==(cmd.argument(), "custom")) {
4367 // LFUN_MASTER_BUFFER_EXPORT is not enabled for this case,
4368 // so the following test should not be needed.
4369 // In principle, we could try to switch to such a view...
4370 // if (cmd.action() == LFUN_BUFFER_EXPORT)
4371 dispatch(FuncRequest(LFUN_DIALOG_SHOW, "sendto"), dr);
4375 string const dest = cmd.getArg(1);
4376 FileName target_dir;
4377 if (!dest.empty() && FileName::isAbsolute(dest))
4378 target_dir = FileName(support::onlyPath(dest));
4380 target_dir = doc_buffer->fileName().onlyPath();
4382 string const format = (argument.empty() || argument == "default") ?
4383 doc_buffer->params().getDefaultOutputFormat() : argument;
4385 if ((dest.empty() && doc_buffer->isUnnamed())
4386 || !target_dir.isDirWritable()) {
4387 exportBufferAs(*doc_buffer, from_utf8(format));
4390 /* TODO/Review: Is it a problem to also export the children?
4391 See the update_unincluded flag */
4392 d.asyncBufferProcessing(format,
4395 &GuiViewPrivate::exportAndDestroy,
4397 nullptr, cmd.allowAsync());
4398 // TODO Inform user about success
4402 case LFUN_BUFFER_EXPORT_AS: {
4403 LASSERT(doc_buffer, break);
4404 docstring f = cmd.argument();
4406 f = from_ascii(doc_buffer->params().getDefaultOutputFormat());
4407 exportBufferAs(*doc_buffer, f);
4411 case LFUN_BUFFER_UPDATE: {
4412 d.asyncBufferProcessing(argument,
4415 &GuiViewPrivate::compileAndDestroy,
4417 nullptr, cmd.allowAsync(), true);
4420 case LFUN_BUFFER_VIEW: {
4421 d.asyncBufferProcessing(argument,
4423 _("Previewing ..."),
4424 &GuiViewPrivate::previewAndDestroy,
4426 &Buffer::preview, cmd.allowAsync());
4429 case LFUN_MASTER_BUFFER_UPDATE: {
4430 d.asyncBufferProcessing(argument,
4431 (doc_buffer ? doc_buffer->masterBuffer() : nullptr),
4433 &GuiViewPrivate::compileAndDestroy,
4435 nullptr, cmd.allowAsync(), true);
4438 case LFUN_MASTER_BUFFER_VIEW: {
4439 d.asyncBufferProcessing(argument,
4440 (doc_buffer ? doc_buffer->masterBuffer() : nullptr),
4442 &GuiViewPrivate::previewAndDestroy,
4443 nullptr, &Buffer::preview, cmd.allowAsync());
4446 case LFUN_EXPORT_CANCEL: {
4450 case LFUN_BUFFER_SWITCH: {
4451 string const file_name = to_utf8(cmd.argument());
4452 if (!FileName::isAbsolute(file_name)) {
4454 dr.setMessage(_("Absolute filename expected."));
4458 Buffer * buffer = theBufferList().getBuffer(FileName(file_name));
4461 dr.setMessage(_("Document not loaded"));
4465 // Do we open or switch to the buffer in this view ?
4466 if (workArea(*buffer)
4467 || lyxrc.open_buffers_in_tabs || !documentBufferView()) {
4472 // Look for the buffer in other views
4473 QList<int> const ids = guiApp->viewIds();
4475 for (; i != ids.size(); ++i) {
4476 GuiView & gv = guiApp->view(ids[i]);
4477 if (gv.workArea(*buffer)) {
4479 gv.activateWindow();
4481 gv.setBuffer(buffer);
4486 // If necessary, open a new window as a last resort
4487 if (i == ids.size()) {
4488 lyx::dispatch(FuncRequest(LFUN_WINDOW_NEW));
4494 case LFUN_BUFFER_NEXT:
4495 gotoNextOrPreviousBuffer(NEXT, false);
4498 case LFUN_BUFFER_MOVE_NEXT:
4499 gotoNextOrPreviousBuffer(NEXT, true);
4502 case LFUN_BUFFER_PREVIOUS:
4503 gotoNextOrPreviousBuffer(PREV, false);
4506 case LFUN_BUFFER_MOVE_PREVIOUS:
4507 gotoNextOrPreviousBuffer(PREV, true);
4510 case LFUN_BUFFER_CHKTEX:
4511 LASSERT(doc_buffer, break);
4512 doc_buffer->runChktex();
4515 case LFUN_COMMAND_EXECUTE: {
4516 command_execute_ = true;
4517 minibuffer_focus_ = true;
4520 case LFUN_DROP_LAYOUTS_CHOICE:
4521 d.layout_->showPopup();
4524 case LFUN_MENU_OPEN:
4525 if (QMenu * menu = guiApp->menus().menu(toqstr(cmd.argument()), *this))
4526 menu->exec(QCursor::pos());
4529 case LFUN_FILE_INSERT: {
4530 bool const ignore_lang = cmd.getArg(1) == "ignorelang";
4531 if (insertLyXFile(from_utf8(cmd.getArg(0)), ignore_lang)) {
4532 dr.forceBufferUpdate();
4533 dr.screenUpdate(Update::Force);
4538 case LFUN_FILE_INSERT_PLAINTEXT:
4539 case LFUN_FILE_INSERT_PLAINTEXT_PARA: {
4540 string const fname = to_utf8(cmd.argument());
4541 if (!fname.empty() && !FileName::isAbsolute(fname)) {
4542 dr.setMessage(_("Absolute filename expected."));
4546 FileName filename(fname);
4547 if (fname.empty()) {
4548 FileDialog dlg(qt_("Select file to insert"));
4550 FileDialog::Result result = dlg.open(toqstr(bv->buffer().filePath()),
4551 QStringList(qt_("All Files (*)")));
4553 if (result.first == FileDialog::Later || result.second.isEmpty()) {
4554 dr.setMessage(_("Canceled."));
4558 filename.set(fromqstr(result.second));
4562 FuncRequest const new_cmd(cmd, filename.absoluteFilePath());
4563 bv->dispatch(new_cmd, dr);
4568 case LFUN_BUFFER_RELOAD: {
4569 LASSERT(doc_buffer, break);
4572 bool drop = (cmd.argument() == "dump");
4575 if (!drop && !doc_buffer->isClean()) {
4576 docstring const file =
4577 makeDisplayPath(doc_buffer->absFileName(), 20);
4578 if (doc_buffer->notifiesExternalModification()) {
4579 docstring text = _("The current version will be lost. "
4580 "Are you sure you want to load the version on disk "
4581 "of the document %1$s?");
4582 ret = Alert::prompt(_("Reload saved document?"),
4583 bformat(text, file), 1, 1,
4584 _("&Reload"), _("&Cancel"));
4586 docstring text = _("Any changes will be lost. "
4587 "Are you sure you want to revert to the saved version "
4588 "of the document %1$s?");
4589 ret = Alert::prompt(_("Revert to saved document?"),
4590 bformat(text, file), 1, 1,
4591 _("&Revert"), _("&Cancel"));
4596 doc_buffer->markClean();
4597 reloadBuffer(*doc_buffer);
4598 dr.forceBufferUpdate();
4603 case LFUN_BUFFER_RESET_EXPORT:
4604 LASSERT(doc_buffer, break);
4605 doc_buffer->requireFreshStart(true);
4606 dr.setMessage(_("Buffer export reset."));
4609 case LFUN_BUFFER_WRITE:
4610 LASSERT(doc_buffer, break);
4611 saveBuffer(*doc_buffer);
4614 case LFUN_BUFFER_WRITE_AS:
4615 LASSERT(doc_buffer, break);
4616 renameBuffer(*doc_buffer, cmd.argument());
4619 case LFUN_BUFFER_WRITE_AS_TEMPLATE:
4620 LASSERT(doc_buffer, break);
4621 renameBuffer(*doc_buffer, cmd.argument(),
4622 LV_WRITE_AS_TEMPLATE);
4625 case LFUN_BUFFER_WRITE_ALL: {
4626 Buffer * first = theBufferList().first();
4629 message(_("Saving all documents..."));
4630 // We cannot use a for loop as the buffer list cycles.
4633 if (!b->isClean()) {
4635 LYXERR(Debug::ACTION, "Saved " << b->absFileName());
4637 b = theBufferList().next(b);
4638 } while (b != first);
4639 dr.setMessage(_("All documents saved."));
4643 case LFUN_MASTER_BUFFER_FORALL: {
4647 FuncRequest funcToRun = lyxaction.lookupFunc(cmd.getLongArg(0));
4648 funcToRun.allowAsync(false);
4650 for (Buffer const * buf : doc_buffer->allRelatives()) {
4651 // Switch to other buffer view and resend cmd
4652 lyx::dispatch(FuncRequest(
4653 LFUN_BUFFER_SWITCH, buf->absFileName()));
4654 lyx::dispatch(funcToRun);
4657 lyx::dispatch(FuncRequest(
4658 LFUN_BUFFER_SWITCH, doc_buffer->absFileName()));
4662 case LFUN_BUFFER_EXTERNAL_MODIFICATION_CLEAR:
4663 LASSERT(doc_buffer, break);
4664 doc_buffer->clearExternalModification();
4667 case LFUN_BUFFER_CLOSE:
4671 case LFUN_BUFFER_CLOSE_ALL:
4675 case LFUN_DEVEL_MODE_TOGGLE:
4676 devel_mode_ = !devel_mode_;
4678 dr.setMessage(_("Developer mode is now enabled."));
4680 dr.setMessage(_("Developer mode is now disabled."));
4683 case LFUN_TOOLBAR_SET: {
4684 string const name = cmd.getArg(0);
4685 string const state = cmd.getArg(1);
4686 if (GuiToolbar * t = toolbar(name))
4691 case LFUN_TOOLBAR_TOGGLE: {
4692 string const name = cmd.getArg(0);
4693 if (GuiToolbar * t = toolbar(name))
4698 case LFUN_TOOLBAR_MOVABLE: {
4699 string const name = cmd.getArg(0);
4701 // toggle (all) toolbars movablility
4702 toolbarsMovable_ = !toolbarsMovable_;
4703 for (ToolbarInfo const & ti : guiApp->toolbars()) {
4704 GuiToolbar * tb = toolbar(ti.name);
4705 if (tb && tb->isMovable() != toolbarsMovable_)
4706 // toggle toolbar movablity if it does not fit lock
4707 // (all) toolbars positions state silent = true, since
4708 // status bar notifications are slow
4711 if (toolbarsMovable_)
4712 dr.setMessage(_("Toolbars unlocked."));
4714 dr.setMessage(_("Toolbars locked."));
4715 } else if (GuiToolbar * tb = toolbar(name))
4716 // toggle current toolbar movablity
4718 // update lock (all) toolbars positions
4719 updateLockToolbars();
4723 case LFUN_ICON_SIZE: {
4724 QSize size = d.iconSize(cmd.argument());
4726 dr.setMessage(bformat(_("Icon size set to %1$dx%2$d."),
4727 size.width(), size.height()));
4731 case LFUN_DIALOG_UPDATE: {
4732 string const name = to_utf8(cmd.argument());
4733 if (name == "prefs" || name == "document")
4734 updateDialog(name, string());
4735 else if (name == "paragraph")
4736 lyx::dispatch(FuncRequest(LFUN_PARAGRAPH_UPDATE));
4737 else if (currentBufferView()) {
4738 Inset * inset = currentBufferView()->editedInset(name);
4739 // Can only update a dialog connected to an existing inset
4741 // FIXME: get rid of this indirection; GuiView ask the inset
4742 // if he is kind enough to update itself...
4743 FuncRequest fr(LFUN_INSET_DIALOG_UPDATE, cmd.argument());
4744 //FIXME: pass DispatchResult here?
4745 inset->dispatch(currentBufferView()->cursor(), fr);
4751 case LFUN_DIALOG_TOGGLE: {
4752 FuncCode const func_code = isDialogVisible(cmd.getArg(0))
4753 ? LFUN_DIALOG_HIDE : LFUN_DIALOG_SHOW;
4754 dispatch(FuncRequest(func_code, cmd.argument()), dr);
4758 case LFUN_DIALOG_DISCONNECT_INSET:
4759 disconnectDialog(to_utf8(cmd.argument()));
4762 case LFUN_DIALOG_HIDE: {
4763 guiApp->hideDialogs(to_utf8(cmd.argument()), nullptr);
4767 case LFUN_DIALOG_SHOW: {
4768 string const name = cmd.getArg(0);
4769 string sdata = trim(to_utf8(cmd.argument()).substr(name.size()));
4771 if (name == "latexlog") {
4772 // getStatus checks that
4773 LASSERT(doc_buffer, break);
4774 Buffer::LogType type;
4775 string const logfile = doc_buffer->logName(&type);
4777 case Buffer::latexlog:
4780 case Buffer::buildlog:
4781 sdata = "literate ";
4784 sdata += Lexer::quoteString(logfile);
4785 showDialog("log", sdata);
4786 } else if (name == "vclog") {
4787 // getStatus checks that
4788 LASSERT(doc_buffer, break);
4789 string const sdata2 = "vc " +
4790 Lexer::quoteString(doc_buffer->lyxvc().getLogFile());
4791 showDialog("log", sdata2);
4792 } else if (name == "symbols") {
4793 sdata = bv->cursor().getEncoding()->name();
4795 showDialog("symbols", sdata);
4796 } else if (name == "findreplace") {
4797 sdata = to_utf8(bv->cursor().selectionAsString(false));
4798 showDialog(name, sdata);
4800 } else if (name == "prefs" && isFullScreen()) {
4801 lfunUiToggle("fullscreen");
4802 showDialog("prefs", sdata);
4804 showDialog(name, sdata);
4809 dr.setMessage(cmd.argument());
4812 case LFUN_UI_TOGGLE: {
4813 string arg = cmd.getArg(0);
4814 if (!lfunUiToggle(arg)) {
4815 docstring const msg = "ui-toggle " + _("%1$s unknown command!");
4816 dr.setMessage(bformat(msg, from_utf8(arg)));
4818 // Make sure the keyboard focus stays in the work area.
4823 case LFUN_VIEW_SPLIT: {
4824 LASSERT(doc_buffer, break);
4825 string const orientation = cmd.getArg(0);
4826 d.splitter_->setOrientation(orientation == "vertical"
4827 ? Qt::Vertical : Qt::Horizontal);
4828 TabWorkArea * twa = addTabWorkArea();
4829 GuiWorkArea * wa = twa->addWorkArea(*doc_buffer, *this);
4830 setCurrentWorkArea(wa);
4834 case LFUN_TAB_GROUP_NEXT:
4835 gotoNextTabWorkArea(NEXT);
4838 case LFUN_TAB_GROUP_PREVIOUS:
4839 gotoNextTabWorkArea(PREV);
4842 case LFUN_TAB_GROUP_CLOSE:
4843 if (TabWorkArea * twa = d.currentTabWorkArea()) {
4844 closeTabWorkArea(twa);
4845 d.current_work_area_ = nullptr;
4846 twa = d.currentTabWorkArea();
4847 // Switch to the next GuiWorkArea in the found TabWorkArea.
4849 // Make sure the work area is up to date.
4850 setCurrentWorkArea(twa->currentWorkArea());
4852 setCurrentWorkArea(nullptr);
4857 case LFUN_VIEW_CLOSE:
4858 if (TabWorkArea * twa = d.currentTabWorkArea()) {
4859 closeWorkArea(twa->currentWorkArea());
4860 d.current_work_area_ = nullptr;
4861 twa = d.currentTabWorkArea();
4862 // Switch to the next GuiWorkArea in the found TabWorkArea.
4864 // Make sure the work area is up to date.
4865 setCurrentWorkArea(twa->currentWorkArea());
4867 setCurrentWorkArea(nullptr);
4872 case LFUN_COMPLETION_INLINE:
4873 if (d.current_work_area_)
4874 d.current_work_area_->completer().showInline();
4877 case LFUN_COMPLETION_POPUP:
4878 if (d.current_work_area_)
4879 d.current_work_area_->completer().showPopup();
4884 if (d.current_work_area_)
4885 d.current_work_area_->completer().tab();
4888 case LFUN_COMPLETION_CANCEL:
4889 if (d.current_work_area_) {
4890 if (d.current_work_area_->completer().popupVisible())
4891 d.current_work_area_->completer().hidePopup();
4893 d.current_work_area_->completer().hideInline();
4897 case LFUN_COMPLETION_ACCEPT:
4898 if (d.current_work_area_)
4899 d.current_work_area_->completer().activate();
4902 case LFUN_BUFFER_ZOOM_IN:
4903 case LFUN_BUFFER_ZOOM_OUT:
4904 case LFUN_BUFFER_ZOOM: {
4905 zoom_ratio_ = zoomRatio(cmd, zoom_ratio_);
4907 // Actual zoom value: default zoom + fractional extra value
4908 int zoom = (int)(lyxrc.defaultZoom * zoom_ratio_);
4909 zoom = min(max(zoom, zoom_min_), zoom_max_);
4911 setCurrentZoom(zoom);
4913 dr.setMessage(bformat(_("Zoom level is now %1$d% (default value: %2$d%)"),
4914 lyxrc.currentZoom, lyxrc.defaultZoom));
4916 guiApp->fontLoader().update();
4917 // Regenerate instant previews
4918 if (lyxrc.preview != LyXRC::PREVIEW_OFF
4919 && doc_buffer && doc_buffer->loader())
4920 doc_buffer->loader()->refreshPreviews();
4921 dr.screenUpdate(Update::ForceAll | Update::FitCursor);
4925 case LFUN_VC_REGISTER:
4926 case LFUN_VC_RENAME:
4928 case LFUN_VC_CHECK_IN:
4929 case LFUN_VC_CHECK_OUT:
4930 case LFUN_VC_REPO_UPDATE:
4931 case LFUN_VC_LOCKING_TOGGLE:
4932 case LFUN_VC_REVERT:
4933 case LFUN_VC_UNDO_LAST:
4934 case LFUN_VC_COMMAND:
4935 case LFUN_VC_COMPARE:
4936 dispatchVC(cmd, dr);
4939 case LFUN_SERVER_GOTO_FILE_ROW:
4940 if(goToFileRow(to_utf8(cmd.argument())))
4941 dr.screenUpdate(Update::Force | Update::FitCursor);
4944 case LFUN_LYX_ACTIVATE:
4948 case LFUN_WINDOW_RAISE:
4954 case LFUN_FORWARD_SEARCH: {
4955 // it seems safe to assume we have a document buffer, since
4956 // getStatus wants one.
4957 LASSERT(doc_buffer, break);
4958 Buffer const * doc_master = doc_buffer->masterBuffer();
4959 FileName const path(doc_master->temppath());
4960 string const texname = doc_master->isChild(doc_buffer)
4961 ? DocFileName(changeExtension(
4962 doc_buffer->absFileName(),
4963 "tex")).mangledFileName()
4964 : doc_buffer->latexName();
4965 string const fulltexname =
4966 support::makeAbsPath(texname, doc_master->temppath()).absFileName();
4967 string const mastername =
4968 removeExtension(doc_master->latexName());
4969 FileName const dviname(addName(path.absFileName(),
4970 addExtension(mastername, "dvi")));
4971 FileName const pdfname(addName(path.absFileName(),
4972 addExtension(mastername, "pdf")));
4973 bool const have_dvi = dviname.exists();
4974 bool const have_pdf = pdfname.exists();
4975 if (!have_dvi && !have_pdf) {
4976 dr.setMessage(_("Please, preview the document first."));
4979 bool const goto_dvi = have_dvi && !lyxrc.forward_search_dvi.empty();
4980 bool const goto_pdf = have_pdf && !lyxrc.forward_search_pdf.empty();
4981 string outname = dviname.onlyFileName();
4982 string command = lyxrc.forward_search_dvi;
4983 if ((!goto_dvi || goto_pdf) &&
4984 pdfname.lastModified() > dviname.lastModified()) {
4985 outname = pdfname.onlyFileName();
4986 command = lyxrc.forward_search_pdf;
4989 DocIterator cur = bv->cursor();
4990 int row = doc_buffer->texrow().rowFromDocIterator(cur).first;
4991 LYXERR(Debug::ACTION, "Forward search: row:" << row
4993 if (row == -1 || command.empty()) {
4994 dr.setMessage(_("Couldn't proceed."));
4997 string texrow = convert<string>(row);
4999 command = subst(command, "$$n", texrow);
5000 command = subst(command, "$$f", fulltexname);
5001 command = subst(command, "$$t", texname);
5002 command = subst(command, "$$o", outname);
5004 volatile PathChanger p(path);
5006 one.startscript(Systemcall::DontWait, command);
5010 case LFUN_SPELLING_CONTINUOUSLY:
5011 lyxrc.spellcheck_continuously = !lyxrc.spellcheck_continuously;
5012 dr.screenUpdate(Update::Force);
5015 case LFUN_CITATION_OPEN: {
5017 if (theFormats().getFormat("pdf"))
5018 pdfv = theFormats().getFormat("pdf")->viewer();
5019 if (theFormats().getFormat("ps"))
5020 psv = theFormats().getFormat("ps")->viewer();
5021 frontend::showTarget(argument, pdfv, psv);
5026 // The LFUN must be for one of BufferView, Buffer or Cursor;
5028 dispatchToBufferView(cmd, dr);
5032 // Need to update bv because many LFUNs here might have destroyed it
5033 bv = currentBufferView();
5035 // Clear non-empty selections
5036 // (e.g. from a "char-forward-select" followed by "char-backward-select")
5038 Cursor & cur = bv->cursor();
5039 if ((cur.selection() && cur.selBegin() == cur.selEnd())) {
5040 cur.clearSelection();
5046 bool GuiView::lfunUiToggle(string const & ui_component)
5048 if (ui_component == "scrollbar") {
5049 // hide() is of no help
5050 if (d.current_work_area_->verticalScrollBarPolicy() ==
5051 Qt::ScrollBarAlwaysOff)
5053 d.current_work_area_->setVerticalScrollBarPolicy(
5054 Qt::ScrollBarAsNeeded);
5056 d.current_work_area_->setVerticalScrollBarPolicy(
5057 Qt::ScrollBarAlwaysOff);
5058 } else if (ui_component == "statusbar") {
5059 statusBar()->setVisible(!statusBar()->isVisible());
5060 } else if (ui_component == "menubar") {
5061 menuBar()->setVisible(!menuBar()->isVisible());
5062 } else if (ui_component == "zoomlevel") {
5063 zoom_value_->setVisible(!zoom_value_->isVisible());
5064 } else if (ui_component == "zoomslider") {
5065 zoom_slider_->setVisible(!zoom_slider_->isVisible());
5066 zoom_in_->setVisible(zoom_slider_->isVisible());
5067 zoom_out_->setVisible(zoom_slider_->isVisible());
5068 } else if (ui_component == "statistics-w") {
5069 word_count_enabled_ = !word_count_enabled_;
5072 } else if (ui_component == "statistics-cb") {
5073 char_count_enabled_ = !char_count_enabled_;
5076 } else if (ui_component == "statistics-c") {
5077 char_nb_count_enabled_ = !char_nb_count_enabled_;
5080 } else if (ui_component == "frame") {
5081 int const l = contentsMargins().left();
5083 //are the frames in default state?
5084 d.current_work_area_->setFrameStyle(QFrame::NoFrame);
5086 #if QT_VERSION > 0x050903
5087 setAttribute(Qt::WA_ContentsMarginsRespectsSafeArea, false);
5089 setContentsMargins(-2, -2, -2, -2);
5091 #if QT_VERSION > 0x050903
5092 setAttribute(Qt::WA_ContentsMarginsRespectsSafeArea, true);
5094 setContentsMargins(0, 0, 0, 0);
5097 if (ui_component == "fullscreen") {
5101 stat_counts_->setVisible(statsEnabled());
5106 void GuiView::cancelExport()
5108 Systemcall::killscript();
5109 // stop busy signal immediately so that in the subsequent
5110 // "Export canceled" prompt the status bar icons are accurate.
5111 Q_EMIT scriptKilled();
5115 void GuiView::toggleFullScreen()
5117 setWindowState(windowState() ^ Qt::WindowFullScreen);
5121 Buffer const * GuiView::updateInset(Inset const * inset)
5126 Buffer const * inset_buffer = &(inset->buffer());
5128 for (int i = 0; i != d.splitter_->count(); ++i) {
5129 GuiWorkArea * wa = d.tabWorkArea(i)->currentWorkArea();
5132 Buffer const * buffer = &(wa->bufferView().buffer());
5133 if (inset_buffer == buffer)
5134 wa->scheduleRedraw(true);
5136 return inset_buffer;
5140 void GuiView::restartCaret()
5142 /* When we move around, or type, it's nice to be able to see
5143 * the caret immediately after the keypress.
5145 if (d.current_work_area_)
5146 d.current_work_area_->startBlinkingCaret();
5148 // Take this occasion to update the other GUI elements.
5154 void GuiView::updateCompletion(Cursor & cur, bool start, bool keep)
5156 if (d.current_work_area_)
5157 d.current_work_area_->completer().updateVisibility(cur, start, keep);
5162 // This list should be kept in sync with the list of insets in
5163 // src/insets/Inset.cpp. I.e., if a dialog goes with an inset, the
5164 // dialog should have the same name as the inset.
5165 // Changes should be also recorded in LFUN_DIALOG_SHOW doxygen
5166 // docs in LyXAction.cpp.
5168 char const * const dialognames[] = {
5170 "aboutlyx", "bibitem", "bibtex", "box", "branch", "changes", "character",
5171 "citation", "compare", "comparehistory", "counter", "document", "errorlist", "ert",
5172 "external", "file", "findreplace", "findreplaceadv", "float", "graphics",
5173 "href", "include", "index", "index_print", "info", "listings", "label", "line",
5174 "log", "lyxfiles", "mathdelimiter", "mathmatrix", "mathspace", "nomenclature",
5175 "nomencl_print", "note", "paragraph", "phantom", "prefs", "ref",
5176 "sendto", "space", "spellchecker", "symbols", "tabular", "tabularcreate",
5177 "thesaurus", "texinfo", "toc", "view-source", "vspace", "wrap", "progress"};
5179 char const * const * const end_dialognames =
5180 dialognames + (sizeof(dialognames) / sizeof(char *));
5184 cmpCStr(char const * name) : name_(name) {}
5185 bool operator()(char const * other) {
5186 return strcmp(other, name_) == 0;
5193 bool isValidName(string const & name)
5195 return find_if(dialognames, end_dialognames,
5196 cmpCStr(name.c_str())) != end_dialognames;
5202 void GuiView::resetDialogs()
5204 // Make sure that no LFUN uses any GuiView.
5205 guiApp->setCurrentView(nullptr);
5209 constructToolbars();
5210 guiApp->menus().fillMenuBar(menuBar(), this, false);
5211 d.layout_->updateContents(true);
5212 // Now update controls with current buffer.
5213 guiApp->setCurrentView(this);
5219 void GuiView::flatGroupBoxes(const QObject * widget, bool flag)
5221 for (QObject * child: widget->children()) {
5222 if (child->inherits("QGroupBox")) {
5223 QGroupBox * box = (QGroupBox*) child;
5226 flatGroupBoxes(child, flag);
5232 Dialog * GuiView::find(string const & name, bool hide_it) const
5234 if (!isValidName(name))
5237 map<string, DialogPtr>::iterator it = d.dialogs_.find(name);
5239 if (it != d.dialogs_.end()) {
5241 it->second->hideView();
5242 return it->second.get();
5248 Dialog * GuiView::findOrBuild(string const & name, bool hide_it)
5250 Dialog * dialog = find(name, hide_it);
5251 if (dialog != nullptr)
5254 dialog = build(name);
5255 d.dialogs_[name].reset(dialog);
5256 // Force a uniform style for group boxes
5257 // On Mac non-flat works better, on Linux flat is standard
5258 flatGroupBoxes(dialog->asQWidget(), guiApp->platformName() != "cocoa");
5259 if (lyxrc.allow_geometry_session)
5260 dialog->restoreSession();
5267 void GuiView::showDialog(string const & name, string const & sdata,
5270 triggerShowDialog(toqstr(name), toqstr(sdata), inset);
5274 void GuiView::doShowDialog(QString const & qname, QString const & qdata,
5280 const string name = fromqstr(qname);
5281 const string sdata = fromqstr(qdata);
5285 Dialog * dialog = findOrBuild(name, false);
5287 bool const visible = dialog->isVisibleView();
5288 dialog->showData(sdata);
5289 if (currentBufferView())
5290 currentBufferView()->editInset(name, inset);
5291 // We only set the focus to the new dialog if it was not yet
5292 // visible in order not to change the existing previous behaviour
5294 // activateWindow is needed for floating dockviews
5295 dialog->asQWidget()->raise();
5296 dialog->asQWidget()->activateWindow();
5297 if (dialog->wantInitialFocus())
5298 dialog->asQWidget()->setFocus();
5302 catch (ExceptionMessage const &) {
5310 bool GuiView::isDialogVisible(string const & name) const
5312 map<string, DialogPtr>::const_iterator it = d.dialogs_.find(name);
5313 if (it == d.dialogs_.end())
5315 return it->second.get()->isVisibleView() && !it->second.get()->isClosing();
5319 void GuiView::hideDialog(string const & name, Inset * inset)
5321 map<string, DialogPtr>::const_iterator it = d.dialogs_.find(name);
5322 if (it == d.dialogs_.end())
5326 if (!currentBufferView())
5328 if (inset != currentBufferView()->editedInset(name))
5332 Dialog * const dialog = it->second.get();
5333 if (dialog->isVisibleView())
5335 if (currentBufferView())
5336 currentBufferView()->editInset(name, nullptr);
5340 void GuiView::disconnectDialog(string const & name)
5342 if (!isValidName(name))
5344 if (currentBufferView())
5345 currentBufferView()->editInset(name, nullptr);
5349 void GuiView::hideAll() const
5351 for(auto const & dlg_p : d.dialogs_)
5352 dlg_p.second->hideView();
5356 void GuiView::updateDialogs()
5358 for(auto const & dlg_p : d.dialogs_) {
5359 Dialog * dialog = dlg_p.second.get();
5361 if (dialog->needBufferOpen() && !documentBufferView())
5362 hideDialog(fromqstr(dialog->name()), nullptr);
5363 else if (dialog->isVisibleView())
5364 dialog->checkStatus();
5372 Dialog * GuiView::build(string const & name)
5374 return createDialog(*this, name);
5378 SEMenu::SEMenu(QWidget * parent)
5380 QAction * action = addAction(qt_("Disable Shell Escape"));
5381 connect(action, SIGNAL(triggered()),
5382 parent, SLOT(disableShellEscape()));
5386 void PressableSvgWidget::mousePressEvent(QMouseEvent * event)
5388 if (event->button() == Qt::LeftButton) {
5393 } // namespace frontend
5396 #include "moc_GuiView.cpp"