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(clicked()), this, SLOT(checkCancelBackground()));
649 connect(this, SIGNAL(scriptKilled()), busySVG, SLOT(hide()));
650 connect(this, SIGNAL(scriptKilled()), processStop, SLOT(hide()));
652 stat_counts_ = new GuiClickableLabel(statusBar());
653 stat_counts_->setAlignment(Qt::AlignCenter);
654 stat_counts_->setFrameStyle(QFrame::StyledPanel);
655 stat_counts_->hide();
656 statusBar()->addPermanentWidget(stat_counts_);
658 connect(stat_counts_, SIGNAL(clicked()), this, SLOT(statsPressed()));
660 zoom_slider_ = new QSlider(Qt::Horizontal, statusBar());
661 // 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 // Forbid too small unresizable window because it can happen
773 // with some window manager under X11.
774 setMinimumSize(300, 200);
776 if (lyxrc.allow_geometry_session) {
777 // Now take care of session management.
782 // no session handling, default to a sane size.
783 setGeometry(50, 50, 690, 510);
786 // clear session data if any.
787 settings.remove("views");
797 void GuiView::disableShellEscape()
799 BufferView * bv = documentBufferView();
802 theSession().shellescapeFiles().remove(bv->buffer().absFileName());
803 bv->buffer().params().shell_escape = false;
804 bv->processUpdateFlags(Update::Force);
808 void GuiView::checkCancelBackground()
810 docstring const ttl = _("Cancel Export?");
811 docstring const msg = _("Do you want to cancel the background export process?");
813 Alert::prompt(ttl, msg, 1, 1,
814 _("&Cancel export"), _("Co&ntinue"));
820 void GuiView::statsPressed()
823 dispatch(FuncRequest(LFUN_STATISTICS), dr);
826 void GuiView::zoomSliderMoved(int value)
829 dispatch(FuncRequest(LFUN_BUFFER_ZOOM, convert<string>(value)), dr);
830 scheduleRedrawWorkAreas();
831 zoom_value_->setText(toqstr(bformat(_("[[ZOOM]]%1$d%"), value)));
835 void GuiView::zoomValueChanged(int value)
837 if (value != lyxrc.currentZoom)
838 zoomSliderMoved(value);
842 void GuiView::zoomInPressed()
845 dispatch(FuncRequest(LFUN_BUFFER_ZOOM_IN), dr);
846 scheduleRedrawWorkAreas();
850 void GuiView::zoomOutPressed()
853 dispatch(FuncRequest(LFUN_BUFFER_ZOOM_OUT), dr);
854 scheduleRedrawWorkAreas();
858 void GuiView::showZoomContextMenu()
860 QMenu * menu = guiApp->menus().menu(toqstr("context-zoom"), * this);
863 menu->exec(QCursor::pos());
867 void GuiView::showStatusBarContextMenu()
869 QMenu * menu = guiApp->menus().menu(toqstr("context-statusbar"), * this);
872 menu->exec(QCursor::pos());
876 void GuiView::scheduleRedrawWorkAreas()
878 for (int i = 0; i < d.tabWorkAreaCount(); i++) {
879 TabWorkArea* ta = d.tabWorkArea(i);
880 for (int u = 0; u < ta->count(); u++) {
881 ta->workArea(u)->scheduleRedraw(true);
887 QVector<GuiWorkArea*> GuiView::GuiViewPrivate::guiWorkAreas()
889 QVector<GuiWorkArea*> areas;
890 for (int i = 0; i < tabWorkAreaCount(); i++) {
891 TabWorkArea* ta = tabWorkArea(i);
892 for (int u = 0; u < ta->count(); u++) {
893 areas << ta->workArea(u);
899 static void handleExportStatus(GuiView * view, Buffer::ExportStatus status,
900 string const & format)
902 docstring const fmt = translateIfPossible(theFormats().prettyName(format));
905 case Buffer::ExportSuccess:
906 msg = bformat(_("Successful export to format: %1$s"), fmt);
908 case Buffer::ExportCancel:
909 msg = _("Document export cancelled.");
911 case Buffer::ExportError:
912 case Buffer::ExportNoPathToFormat:
913 case Buffer::ExportTexPathHasSpaces:
914 case Buffer::ExportConverterError:
915 msg = bformat(_("Error while exporting format: %1$s"), fmt);
917 case Buffer::PreviewSuccess:
918 msg = bformat(_("Successful preview of format: %1$s"), fmt);
920 case Buffer::PreviewError:
921 msg = bformat(_("Error while previewing format: %1$s"), fmt);
923 case Buffer::ExportKilled:
924 msg = bformat(_("Conversion cancelled while previewing format: %1$s"), fmt);
931 void GuiView::processingThreadStarted()
936 void GuiView::processingThreadFinished()
938 QFutureWatcher<Buffer::ExportStatus> const * watcher =
939 static_cast<QFutureWatcher<Buffer::ExportStatus> const *>(sender());
941 Buffer::ExportStatus const status = watcher->result();
942 handleExportStatus(this, status, d.processing_format);
945 BufferView const * const bv = currentBufferView();
946 if (bv && !bv->buffer().errorList("Export").empty()) {
951 bool const error = (status != Buffer::ExportSuccess &&
952 status != Buffer::PreviewSuccess &&
953 status != Buffer::ExportCancel);
955 ErrorList & el = bv->buffer().errorList(d.last_export_format);
956 // at this point, we do not know if buffer-view or
957 // master-buffer-view was called. If there was an export error,
958 // and the current buffer's error log is empty, we guess that
959 // it must be master-buffer-view that was called so we set
961 errors(d.last_export_format, el.empty());
966 void GuiView::autoSaveThreadFinished()
968 QFutureWatcher<docstring> const * watcher =
969 static_cast<QFutureWatcher<docstring> const *>(sender());
970 message(watcher->result());
975 void GuiView::saveLayout() const
978 settings.setValue("zoom_ratio", zoom_ratio_);
979 settings.setValue("devel_mode", devel_mode_);
980 settings.beginGroup("views");
981 settings.beginGroup(QString::number(id_));
982 if (guiApp->platformName() == "xcb") {
983 settings.setValue("pos", pos());
984 settings.setValue("size", size());
986 settings.setValue("geometry", saveGeometry());
987 settings.setValue("layout", saveState(0));
988 settings.setValue("icon_size", toqstr(d.iconSize(iconSize())));
989 settings.setValue("zoom_value_visible", zoom_value_->isVisible());
990 settings.setValue("zoom_slider_visible", zoom_slider_->isVisible());
991 settings.setValue("word_count_enabled", word_count_enabled_);
992 settings.setValue("char_count_enabled", char_count_enabled_);
993 settings.setValue("char_nb_count_enabled", char_nb_count_enabled_);
997 void GuiView::saveUISettings() const
1001 // Save the toolbar private states
1002 for (auto const & tb_p : d.toolbars_)
1003 tb_p.second->saveSession(settings);
1004 // Now take care of all other dialogs
1005 for (auto const & dlg_p : d.dialogs_)
1006 dlg_p.second->saveSession(settings);
1010 void GuiView::setCurrentZoom(const int v)
1012 lyxrc.currentZoom = v;
1013 zoom_value_->setText(toqstr(bformat(_("[[ZOOM]]%1$d%"), v)));
1014 Q_EMIT currentZoomChanged(v);
1018 bool GuiView::restoreLayout()
1021 zoom_ratio_ = settings.value("zoom_ratio", 1.0).toDouble();
1022 // Actual zoom value: default zoom + fractional offset
1023 int zoom = (int)(lyxrc.defaultZoom * zoom_ratio_);
1024 zoom = min(max(zoom, zoom_min_), zoom_max_);
1025 setCurrentZoom(zoom);
1026 devel_mode_ = settings.value("devel_mode", devel_mode_).toBool();
1027 settings.beginGroup("views");
1028 settings.beginGroup(QString::number(id_));
1029 QString const icon_key = "icon_size";
1030 if (!settings.contains(icon_key))
1033 //code below is skipped when when ~/.config/LyX is (re)created
1034 setIconSize(d.iconSize(settings.value(icon_key).toString()));
1036 zoom_value_->setVisible(settings.value("zoom_value_visible", true).toBool());
1038 bool const show_zoom_slider = settings.value("zoom_slider_visible", true).toBool();
1039 zoom_slider_->setVisible(show_zoom_slider);
1040 zoom_in_->setVisible(show_zoom_slider);
1041 zoom_out_->setVisible(show_zoom_slider);
1043 word_count_enabled_ = settings.value("word_count_enabled", true).toBool();
1044 char_count_enabled_ = settings.value("char_count_enabled", true).toBool();
1045 char_nb_count_enabled_ = settings.value("char_nb_count_enabled", true).toBool();
1046 stat_counts_->setVisible(word_count_enabled_ || char_count_enabled_ || char_nb_count_enabled_);
1048 if (guiApp->platformName() == "xcb") {
1049 QPoint pos = settings.value("pos", QPoint(50, 50)).toPoint();
1050 QSize size = settings.value("size", QSize(690, 510)).toSize();
1054 // Work-around for bug #6034: the window ends up in an undetermined
1055 // state when trying to restore a maximized window when it is
1056 // already maximized.
1057 if (!(windowState() & Qt::WindowMaximized))
1058 if (!restoreGeometry(settings.value("geometry").toByteArray()))
1059 setGeometry(50, 50, 690, 510);
1062 // Make sure layout is correctly oriented.
1063 setLayoutDirection(qApp->layoutDirection());
1065 // Allow the toc and view-source dock widget to be restored if needed.
1067 if ((dialog = findOrBuild("toc", true)))
1068 // see bug 5082. At least setup title and enabled state.
1069 // Visibility will be adjusted by restoreState below.
1070 dialog->prepareView();
1071 if ((dialog = findOrBuild("view-source", true)))
1072 dialog->prepareView();
1073 if ((dialog = findOrBuild("progress", true)))
1074 dialog->prepareView();
1076 if (!restoreState(settings.value("layout").toByteArray(), 0))
1079 // init the toolbars that have not been restored
1080 for (auto const & tb_p : guiApp->toolbars()) {
1081 GuiToolbar * tb = toolbar(tb_p.name);
1082 if (tb && !tb->isRestored())
1083 initToolbar(tb_p.name);
1086 // update lock (all) toolbars positions
1087 updateLockToolbars();
1094 GuiToolbar * GuiView::toolbar(string const & name)
1096 ToolbarMap::iterator it = d.toolbars_.find(name);
1097 if (it != d.toolbars_.end())
1100 LYXERR(Debug::GUI, "Toolbar::display: no toolbar named " << name);
1105 void GuiView::updateLockToolbars()
1107 toolbarsMovable_ = false;
1108 for (ToolbarInfo const & info : guiApp->toolbars()) {
1109 GuiToolbar * tb = toolbar(info.name);
1110 if (tb && tb->isMovable())
1111 toolbarsMovable_ = true;
1113 #if QT_VERSION >= 0x050200
1114 // set unified mac toolbars only when not movable as recommended:
1115 // https://doc.qt.io/qt-5/qmainwindow.html#unifiedTitleAndToolBarOnMac-prop
1116 setUnifiedTitleAndToolBarOnMac(!toolbarsMovable_);
1121 void GuiView::constructToolbars()
1123 for (auto const & tb_p : d.toolbars_)
1125 d.toolbars_.clear();
1127 // I don't like doing this here, but the standard toolbar
1128 // destroys this object when it's destroyed itself (vfr)
1129 d.layout_ = new LayoutBox(*this);
1130 d.stack_widget_->addWidget(d.layout_);
1131 d.layout_->move(0,0);
1133 // extracts the toolbars from the backend
1134 for (ToolbarInfo const & inf : guiApp->toolbars())
1135 d.toolbars_[inf.name] = new GuiToolbar(inf, *this);
1137 DynamicMenuButton::resetIconCache();
1141 void GuiView::initToolbars()
1143 // extracts the toolbars from the backend
1144 for (ToolbarInfo const & inf : guiApp->toolbars())
1145 initToolbar(inf.name);
1149 void GuiView::initToolbar(string const & name)
1151 GuiToolbar * tb = toolbar(name);
1154 int const visibility = guiApp->toolbars().defaultVisibility(name);
1155 bool newline = !(visibility & Toolbars::SAMEROW);
1156 tb->setVisible(false);
1157 tb->setVisibility(visibility);
1159 if (visibility & Toolbars::TOP) {
1161 addToolBarBreak(Qt::TopToolBarArea);
1162 addToolBar(Qt::TopToolBarArea, tb);
1165 if (visibility & Toolbars::BOTTOM) {
1167 addToolBarBreak(Qt::BottomToolBarArea);
1168 addToolBar(Qt::BottomToolBarArea, tb);
1171 if (visibility & Toolbars::LEFT) {
1173 addToolBarBreak(Qt::LeftToolBarArea);
1174 addToolBar(Qt::LeftToolBarArea, tb);
1177 if (visibility & Toolbars::RIGHT) {
1179 addToolBarBreak(Qt::RightToolBarArea);
1180 addToolBar(Qt::RightToolBarArea, tb);
1183 if (visibility & Toolbars::ON)
1184 tb->setVisible(true);
1186 tb->setMovable(true);
1190 TocModels & GuiView::tocModels()
1192 return d.toc_models_;
1196 void GuiView::setFocus()
1198 LYXERR(Debug::DEBUG, "GuiView::setFocus()" << this);
1199 QMainWindow::setFocus();
1203 bool GuiView::hasFocus() const
1205 if (currentWorkArea())
1206 return currentWorkArea()->hasFocus();
1207 if (currentMainWorkArea())
1208 return currentMainWorkArea()->hasFocus();
1209 return d.bg_widget_->hasFocus();
1213 void GuiView::focusInEvent(QFocusEvent * e)
1215 LYXERR(Debug::DEBUG, "GuiView::focusInEvent()" << this);
1216 QMainWindow::focusInEvent(e);
1217 // Make sure guiApp points to the correct view.
1218 guiApp->setCurrentView(this);
1219 if (currentWorkArea())
1220 currentWorkArea()->setFocus();
1221 else if (currentMainWorkArea())
1222 currentMainWorkArea()->setFocus();
1224 d.bg_widget_->setFocus();
1228 void GuiView::showEvent(QShowEvent * e)
1230 LYXERR(Debug::GUI, "Passed Geometry "
1231 << size().height() << "x" << size().width()
1232 << "+" << pos().x() << "+" << pos().y());
1234 if (d.splitter_->count() == 0)
1235 // No work area, switch to the background widget.
1239 QMainWindow::showEvent(e);
1243 bool GuiView::closeScheduled()
1250 bool GuiView::prepareAllBuffersForLogout()
1252 Buffer * first = theBufferList().first();
1256 // First, iterate over all buffers and ask the users if unsaved
1257 // changes should be saved.
1258 // We cannot use a for loop as the buffer list cycles.
1261 if (!saveBufferIfNeeded(*b, false))
1263 b = theBufferList().next(b);
1264 } while (b != first);
1266 // Next, save session state
1267 // When a view/window was closed before without quitting LyX, there
1268 // are already entries in the lastOpened list.
1269 theSession().lastOpened().clear();
1276 /** Destroy only all tabbed WorkAreas. Destruction of other WorkAreas
1277 ** is responsibility of the container (e.g., dialog)
1279 void GuiView::closeEvent(QCloseEvent * close_event)
1281 LYXERR(Debug::DEBUG, "GuiView::closeEvent()");
1283 if (!GuiViewPrivate::busyBuffers.isEmpty()) {
1284 Alert::warning(_("Exit LyX"),
1285 _("LyX could not be closed because documents are being processed by LyX."));
1286 close_event->setAccepted(false);
1290 // If the user pressed the x (so we didn't call closeView
1291 // programmatically), we want to clear all existing entries.
1293 theSession().lastOpened().clear();
1298 // it can happen that this event arrives without selecting the view,
1299 // e.g. when clicking the close button on a background window.
1301 if (!closeWorkAreaAll()) {
1303 close_event->ignore();
1307 // Make sure that nothing will use this to be closed View.
1308 guiApp->unregisterView(this);
1310 if (isFullScreen()) {
1311 // Switch off fullscreen before closing.
1316 // Make sure the timer time out will not trigger a statusbar update.
1317 d.statusbar_timer_.stop();
1318 d.statusbar_stats_timer_.stop();
1320 // Saving fullscreen requires additional tweaks in the toolbar code.
1321 // It wouldn't also work under linux natively.
1322 if (lyxrc.allow_geometry_session) {
1327 close_event->accept();
1331 void GuiView::dragEnterEvent(QDragEnterEvent * event)
1333 if (event->mimeData()->hasUrls())
1335 /// \todo Ask lyx-devel is this is enough:
1336 /// if (event->mimeData()->hasFormat("text/plain"))
1337 /// event->acceptProposedAction();
1341 void GuiView::dropEvent(QDropEvent * event)
1343 QList<QUrl> files = event->mimeData()->urls();
1344 if (files.isEmpty())
1347 LYXERR(Debug::GUI, "GuiView::dropEvent: got URLs!");
1348 for (int i = 0; i != files.size(); ++i) {
1349 string const file = os::internal_path(fromqstr(
1350 files.at(i).toLocalFile()));
1354 string const ext = support::getExtension(file);
1355 vector<const Format *> found_formats;
1357 // Find all formats that have the correct extension.
1358 for (const Format * fmt : theConverters().importableFormats())
1359 if (fmt->hasExtension(ext))
1360 found_formats.push_back(fmt);
1363 if (!found_formats.empty()) {
1364 if (found_formats.size() > 1) {
1365 //FIXME: show a dialog to choose the correct importable format
1366 LYXERR(Debug::FILES,
1367 "Multiple importable formats found, selecting first");
1369 string const arg = found_formats[0]->name() + " " + file;
1370 cmd = FuncRequest(LFUN_BUFFER_IMPORT, arg);
1373 //FIXME: do we have to explicitly check whether it's a lyx file?
1374 LYXERR(Debug::FILES,
1375 "No formats found, trying to open it as a lyx file");
1376 cmd = FuncRequest(LFUN_FILE_OPEN, file);
1378 // add the functions to the queue
1379 guiApp->addToFuncRequestQueue(cmd);
1382 // now process the collected functions. We perform the events
1383 // asynchronously. This prevents potential problems in case the
1384 // BufferView is closed within an event.
1385 guiApp->processFuncRequestQueueAsync();
1389 void GuiView::message(docstring const & str)
1391 if (ForkedProcess::iAmAChild())
1394 // call is moved to GUI-thread by GuiProgress
1395 d.progress_->appendMessage(toqstr(str));
1399 void GuiView::clearMessageText()
1401 message(docstring());
1405 void GuiView::updateStatusBarMessage(QString const & str)
1407 statusBar()->showMessage(str);
1408 d.statusbar_timer_.stop();
1409 d.statusbar_timer_.start(3000);
1413 void GuiView::clearMessage()
1415 // FIXME: This code was introduced in r19643 to fix bug #4123. However,
1416 // the hasFocus function mostly returns false, even if the focus is on
1417 // a workarea in this view.
1421 d.statusbar_timer_.stop();
1424 void GuiView::showStats()
1426 if (!statsEnabled())
1429 d.time_to_update -= d.timer_rate;
1431 BufferView * bv = currentBufferView();
1432 Buffer * buf = bv ? &bv->buffer() : nullptr;
1434 stat_counts_->hide();
1438 Cursor const & cur = bv->cursor();
1440 // we start new selection and need faster update
1441 if (!d.already_in_selection_ && cur.selection())
1442 d.time_to_update = 0;
1444 if (d.time_to_update > 0)
1447 DocIterator from, to;
1448 if (cur.selection()) {
1449 from = cur.selectionBegin();
1450 to = cur.selectionEnd();
1451 d.already_in_selection_ = true;
1453 from = doc_iterator_begin(buf);
1454 to = doc_iterator_end(buf);
1455 d.already_in_selection_ = false;
1458 buf->updateStatistics(from, to);
1461 if (word_count_enabled_) {
1462 int const words = buf->wordCount();
1464 stats << toqstr(bformat(_("%1$d Word"), words));
1466 stats << toqstr(bformat(_("%1$d Words"), words));
1468 int const chars_with_blanks = buf->charCount(true);
1469 if (char_count_enabled_) {
1470 if (chars_with_blanks == 1)
1471 stats << toqstr(bformat(_("%1$d Character"), chars_with_blanks));
1473 stats << toqstr(bformat(_("%1$d Characters"), chars_with_blanks));
1475 if (char_nb_count_enabled_) {
1476 int const chars = buf->charCount(false);
1478 stats << toqstr(bformat(_("%1$d Character (no Blanks)"), chars));
1480 stats << toqstr(bformat(_("%1$d Characters (no Blanks)"), chars));
1482 stat_counts_->setText(stats.join(qt_(", [[stats separator]]")));
1483 stat_counts_->show();
1485 d.time_to_update = d.default_stats_rate;
1486 // fast updates for small selections
1487 if (chars_with_blanks < d.max_sel_chars && cur.selection())
1488 d.time_to_update = d.timer_rate;
1492 void GuiView::updateWindowTitle(GuiWorkArea * wa)
1494 if (wa != d.current_work_area_
1495 || wa->bufferView().buffer().isInternal())
1497 Buffer const & buf = wa->bufferView().buffer();
1498 // Set the windows title
1499 docstring title = buf.fileName().displayName(130) + from_ascii("[*]");
1500 if (buf.notifiesExternalModification()) {
1501 title = bformat(_("%1$s (modified externally)"), title);
1502 // If the external modification status has changed, then maybe the status of
1503 // buffer-save has changed too.
1506 title += from_ascii(" - LyX");
1507 setWindowTitle(toqstr(title));
1508 // Sets the path for the window: this is used by OSX to
1509 // allow a context click on the title bar showing a menu
1510 // with the path up to the file
1511 setWindowFilePath(toqstr(buf.absFileName()));
1512 // Tell Qt whether the current document is changed
1513 setWindowModified(!buf.isClean());
1515 if (buf.params().shell_escape)
1516 shell_escape_->show();
1518 shell_escape_->hide();
1520 if (buf.hasReadonlyFlag())
1525 if (buf.lyxvc().inUse()) {
1526 version_control_->show();
1527 version_control_->setText(toqstr(buf.lyxvc().vcstatus()));
1529 version_control_->hide();
1533 void GuiView::on_currentWorkAreaChanged(GuiWorkArea * wa)
1535 if (d.current_work_area_)
1536 // disconnect the current work area from all slots
1537 QObject::disconnect(d.current_work_area_, nullptr, this, nullptr);
1539 disconnectBufferView();
1540 connectBufferView(wa->bufferView());
1541 connectBuffer(wa->bufferView().buffer());
1542 d.current_work_area_ = wa;
1543 QObject::connect(wa, SIGNAL(titleChanged(GuiWorkArea *)),
1544 this, SLOT(updateWindowTitle(GuiWorkArea *)));
1545 QObject::connect(wa, SIGNAL(busy(bool)),
1546 this, SLOT(setBusy(bool)));
1547 // connection of a signal to a signal
1548 QObject::connect(wa, SIGNAL(bufferViewChanged()),
1549 this, SIGNAL(bufferViewChanged()));
1550 Q_EMIT updateWindowTitle(wa);
1551 Q_EMIT bufferViewChanged();
1555 void GuiView::onBufferViewChanged()
1558 // Buffer-dependent dialogs must be updated. This is done here because
1559 // some dialogs require buffer()->text.
1561 zoom_slider_->setEnabled(currentBufferView());
1562 zoom_value_->setEnabled(currentBufferView());
1563 zoom_in_->setEnabled(currentBufferView());
1564 zoom_out_->setEnabled(currentBufferView());
1568 void GuiView::on_lastWorkAreaRemoved()
1571 // We already are in a close event. Nothing more to do.
1574 if (d.splitter_->count() > 1)
1575 // We have a splitter so don't close anything.
1578 // Reset and updates the dialogs.
1579 Q_EMIT bufferViewChanged();
1584 if (lyxrc.open_buffers_in_tabs)
1585 // Nothing more to do, the window should stay open.
1588 if (guiApp->viewIds().size() > 1) {
1594 // On Mac we also close the last window because the application stay
1595 // resident in memory. On other platforms we don't close the last
1596 // window because this would quit the application.
1602 void GuiView::updateStatusBar()
1604 // let the user see the explicit message
1605 if (d.statusbar_timer_.isActive())
1612 void GuiView::showMessage()
1616 QString msg = toqstr(theGuiApp()->viewStatusMessage());
1617 if (msg.isEmpty()) {
1618 BufferView const * bv = currentBufferView();
1620 msg = toqstr(bv->cursor().currentState(devel_mode_));
1622 msg = qt_("Welcome to LyX!");
1624 statusBar()->showMessage(msg);
1628 bool GuiView::statsEnabled() const
1630 return word_count_enabled_ || char_count_enabled_ || char_nb_count_enabled_;
1634 bool GuiView::event(QEvent * e)
1638 // Useful debug code:
1639 //case QEvent::ActivationChange:
1640 //case QEvent::WindowDeactivate:
1641 //case QEvent::Paint:
1642 //case QEvent::Enter:
1643 //case QEvent::Leave:
1644 //case QEvent::HoverEnter:
1645 //case QEvent::HoverLeave:
1646 //case QEvent::HoverMove:
1647 //case QEvent::StatusTip:
1648 //case QEvent::DragEnter:
1649 //case QEvent::DragLeave:
1650 //case QEvent::Drop:
1653 case QEvent::WindowStateChange: {
1654 QWindowStateChangeEvent * ev = (QWindowStateChangeEvent*)e;
1655 bool ofstate = (ev->oldState() & Qt::WindowFullScreen);
1656 bool result = QMainWindow::event(e);
1657 bool nfstate = (windowState() & Qt::WindowFullScreen);
1658 if (!ofstate && nfstate) {
1659 LYXERR(Debug::DEBUG, "GuiView: WindowStateChange(): full-screen " << nfstate);
1660 // switch to full-screen state
1661 if (lyxrc.full_screen_statusbar)
1662 statusBar()->hide();
1663 if (lyxrc.full_screen_menubar)
1665 if (lyxrc.full_screen_toolbars) {
1666 for (auto const & tb_p : d.toolbars_)
1667 if (tb_p.second->isVisibilityOn() && tb_p.second->isVisible())
1668 tb_p.second->hide();
1670 for (int i = 0; i != d.splitter_->count(); ++i)
1671 d.tabWorkArea(i)->setFullScreen(true);
1672 #if QT_VERSION > 0x050903
1673 //Qt's 5.9.4 ba44cdae38406c safe area measures won't allow us to go negative in margins
1674 setAttribute(Qt::WA_ContentsMarginsRespectsSafeArea, false);
1676 setContentsMargins(-2, -2, -2, -2);
1678 hideDialogs("prefs", nullptr);
1679 } else if (ofstate && !nfstate) {
1680 LYXERR(Debug::DEBUG, "GuiView: WindowStateChange(): full-screen " << nfstate);
1681 // switch back from full-screen state
1682 if (lyxrc.full_screen_statusbar && !statusBar()->isVisible())
1683 statusBar()->show();
1684 if (lyxrc.full_screen_menubar && !menuBar()->isVisible())
1686 if (lyxrc.full_screen_toolbars) {
1687 for (auto const & tb_p : d.toolbars_)
1688 if (tb_p.second->isVisibilityOn() && !tb_p.second->isVisible())
1689 tb_p.second->show();
1692 for (int i = 0; i != d.splitter_->count(); ++i)
1693 d.tabWorkArea(i)->setFullScreen(false);
1694 #if QT_VERSION > 0x050903
1695 setAttribute(Qt::WA_ContentsMarginsRespectsSafeArea, true);
1697 setContentsMargins(0, 0, 0, 0);
1702 case QEvent::WindowActivate: {
1703 GuiView * old_view = guiApp->currentView();
1704 if (this == old_view) {
1706 return QMainWindow::event(e);
1708 if (old_view && old_view->currentBufferView()) {
1709 // save current selection to the selection buffer to allow
1710 // middle-button paste in this window.
1711 cap::saveSelection(old_view->currentBufferView()->cursor());
1713 guiApp->setCurrentView(this);
1714 if (d.current_work_area_)
1715 on_currentWorkAreaChanged(d.current_work_area_);
1719 return QMainWindow::event(e);
1722 case QEvent::ShortcutOverride: {
1724 if (isFullScreen() && menuBar()->isHidden()) {
1725 QKeyEvent * ke = static_cast<QKeyEvent*>(e);
1726 // FIXME: we should also try to detect special LyX shortcut such as
1727 // Alt-P and Alt-M. Right now there is a hack in
1728 // GuiWorkArea::processKeySym() that hides again the menubar for
1730 if (ke->modifiers() & Qt::AltModifier && ke->key() != Qt::Key_Alt) {
1732 return QMainWindow::event(e);
1735 return QMainWindow::event(e);
1738 case QEvent::ApplicationPaletteChange: {
1739 // runtime switch from/to dark mode
1741 return QMainWindow::event(e);
1744 case QEvent::Gesture: {
1745 QGestureEvent *ge = static_cast<QGestureEvent*>(e);
1746 QGesture *gp = ge->gesture(Qt::PinchGesture);
1748 QPinchGesture *pinch = static_cast<QPinchGesture *>(gp);
1749 QPinchGesture::ChangeFlags changeFlags = pinch->changeFlags();
1750 qreal totalScaleFactor = pinch->totalScaleFactor();
1751 LYXERR(Debug::GUI, "totalScaleFactor: " << totalScaleFactor);
1752 if (pinch->state() == Qt::GestureStarted) {
1753 initialZoom_ = lyxrc.currentZoom;
1754 LYXERR(Debug::GUI, "initialZoom_: " << initialZoom_);
1756 if (changeFlags & QPinchGesture::ScaleFactorChanged) {
1757 qreal factor = initialZoom_ * totalScaleFactor;
1758 LYXERR(Debug::GUI, "scaleFactor: " << factor);
1759 zoomValueChanged(factor);
1762 return QMainWindow::event(e);
1766 return QMainWindow::event(e);
1770 void GuiView::resetWindowTitle()
1772 setWindowTitle(qt_("LyX"));
1775 bool GuiView::focusNextPrevChild(bool /*next*/)
1782 bool GuiView::busy() const
1788 void GuiView::setBusy(bool busy)
1790 bool const busy_before = busy_ > 0;
1791 busy ? ++busy_ : --busy_;
1792 if ((busy_ > 0) == busy_before)
1793 // busy state didn't change
1797 QApplication::setOverrideCursor(Qt::WaitCursor);
1800 QApplication::restoreOverrideCursor();
1805 void GuiView::resetCommandExecute()
1807 command_execute_ = false;
1812 double GuiView::pixelRatio() const
1814 return qt_scale_factor * devicePixelRatio();
1818 GuiWorkArea * GuiView::workArea(int index)
1820 if (TabWorkArea * twa = d.currentTabWorkArea())
1821 if (index < twa->count())
1822 return twa->workArea(index);
1827 GuiWorkArea * GuiView::workArea(Buffer & buffer)
1829 if (currentWorkArea()
1830 && ¤tWorkArea()->bufferView().buffer() == &buffer)
1831 return currentWorkArea();
1832 if (TabWorkArea * twa = d.currentTabWorkArea())
1833 return twa->workArea(buffer);
1838 GuiWorkArea * GuiView::addWorkArea(Buffer & buffer)
1840 // Automatically create a TabWorkArea if there are none yet.
1841 TabWorkArea * tab_widget = d.splitter_->count()
1842 ? d.currentTabWorkArea() : addTabWorkArea();
1843 return tab_widget->addWorkArea(buffer, *this);
1847 TabWorkArea * GuiView::addTabWorkArea()
1849 TabWorkArea * twa = new TabWorkArea;
1850 QObject::connect(twa, SIGNAL(currentWorkAreaChanged(GuiWorkArea *)),
1851 this, SLOT(on_currentWorkAreaChanged(GuiWorkArea *)));
1852 QObject::connect(twa, SIGNAL(lastWorkAreaRemoved()),
1853 this, SLOT(on_lastWorkAreaRemoved()));
1855 d.splitter_->addWidget(twa);
1856 d.stack_widget_->setCurrentWidget(d.splitter_);
1861 GuiWorkArea const * GuiView::currentWorkArea() const
1863 return d.current_work_area_;
1867 GuiWorkArea * GuiView::currentWorkArea()
1869 return d.current_work_area_;
1873 GuiWorkArea const * GuiView::currentMainWorkArea() const
1875 if (!d.currentTabWorkArea())
1877 return d.currentTabWorkArea()->currentWorkArea();
1881 GuiWorkArea * GuiView::currentMainWorkArea()
1883 if (!d.currentTabWorkArea())
1885 return d.currentTabWorkArea()->currentWorkArea();
1889 void GuiView::setCurrentWorkArea(GuiWorkArea * wa)
1891 LYXERR(Debug::DEBUG, "Setting current wa: " << wa << endl);
1893 d.current_work_area_ = nullptr;
1895 Q_EMIT bufferViewChanged();
1899 // FIXME: I've no clue why this is here and why it accesses
1900 // theGuiApp()->currentView, which might be 0 (bug 6464).
1901 // See also 27525 (vfr).
1902 if (theGuiApp()->currentView() == this
1903 && theGuiApp()->currentView()->currentWorkArea() == wa)
1906 if (currentBufferView())
1907 cap::saveSelection(currentBufferView()->cursor());
1909 theGuiApp()->setCurrentView(this);
1910 d.current_work_area_ = wa;
1912 // We need to reset this now, because it will need to be
1913 // right if the tabWorkArea gets reset in the for loop. We
1914 // will change it back if we aren't in that case.
1915 GuiWorkArea * const old_cmwa = d.current_main_work_area_;
1916 d.current_main_work_area_ = wa;
1918 for (int i = 0; i != d.splitter_->count(); ++i) {
1919 if (d.tabWorkArea(i)->setCurrentWorkArea(wa)) {
1920 LYXERR(Debug::DEBUG, "Current wa: " << currentWorkArea()
1921 << ", Current main wa: " << currentMainWorkArea());
1926 d.current_main_work_area_ = old_cmwa;
1928 LYXERR(Debug::DEBUG, "This is not a tabbed wa");
1929 on_currentWorkAreaChanged(wa);
1930 BufferView & bv = wa->bufferView();
1931 bv.cursor().fixIfBroken();
1933 wa->setUpdatesEnabled(true);
1934 LYXERR(Debug::DEBUG, "Current wa: " << currentWorkArea() << ", Current main wa: " << currentMainWorkArea());
1938 void GuiView::removeWorkArea(GuiWorkArea * wa)
1940 LASSERT(wa, return);
1941 if (wa == d.current_work_area_) {
1943 disconnectBufferView();
1944 d.current_work_area_ = nullptr;
1945 d.current_main_work_area_ = nullptr;
1948 bool found_twa = false;
1949 for (int i = 0; i != d.splitter_->count(); ++i) {
1950 TabWorkArea * twa = d.tabWorkArea(i);
1951 if (twa->removeWorkArea(wa)) {
1952 // Found in this tab group, and deleted the GuiWorkArea.
1954 if (twa->count() != 0) {
1955 if (d.current_work_area_ == nullptr)
1956 // This means that we are closing the current GuiWorkArea, so
1957 // switch to the next GuiWorkArea in the found TabWorkArea.
1958 setCurrentWorkArea(twa->currentWorkArea());
1960 // No more WorkAreas in this tab group, so delete it.
1967 // It is not a tabbed work area (i.e., the search work area), so it
1968 // should be deleted by other means.
1969 LASSERT(found_twa, return);
1971 if (d.current_work_area_ == nullptr) {
1972 if (d.splitter_->count() != 0) {
1973 TabWorkArea * twa = d.currentTabWorkArea();
1974 setCurrentWorkArea(twa->currentWorkArea());
1976 // No more work areas, switch to the background widget.
1977 setCurrentWorkArea(nullptr);
1983 bool GuiView::hasVisibleWorkArea(GuiWorkArea * wa) const
1985 for (int i = 0; i < d.splitter_->count(); ++i)
1986 if (d.tabWorkArea(i)->currentWorkArea() == wa)
1989 FindAndReplace * fr = static_cast<FindAndReplace*>(find("findreplaceadv", false));
1990 return fr->isVisible() && fr->hasWorkArea(wa);
1994 LayoutBox * GuiView::getLayoutDialog() const
2000 void GuiView::updateLayoutList()
2003 d.layout_->updateContents(false);
2007 void GuiView::updateToolbars()
2009 if (d.current_work_area_) {
2011 if (d.current_work_area_->bufferView().cursor().inMathed()
2012 && !d.current_work_area_->bufferView().cursor().inRegexped())
2013 context |= Toolbars::MATH;
2014 if (lyx::getStatus(FuncRequest(LFUN_LAYOUT_TABULAR)).enabled())
2015 context |= Toolbars::TABLE;
2016 if (currentBufferView()->buffer().areChangesPresent()
2017 || (lyx::getStatus(FuncRequest(LFUN_CHANGES_TRACK)).enabled()
2018 && lyx::getStatus(FuncRequest(LFUN_CHANGES_TRACK)).onOff(true))
2019 || (lyx::getStatus(FuncRequest(LFUN_CHANGES_OUTPUT)).enabled()
2020 && lyx::getStatus(FuncRequest(LFUN_CHANGES_OUTPUT)).onOff(true)))
2021 context |= Toolbars::REVIEW;
2022 if (lyx::getStatus(FuncRequest(LFUN_IN_MATHMACROTEMPLATE)).enabled())
2023 context |= Toolbars::MATHMACROTEMPLATE;
2024 if (lyx::getStatus(FuncRequest(LFUN_IN_IPA)).enabled())
2025 context |= Toolbars::IPA;
2026 if (command_execute_)
2027 context |= Toolbars::MINIBUFFER;
2028 if (minibuffer_focus_) {
2029 context |= Toolbars::MINIBUFFER_FOCUS;
2030 minibuffer_focus_ = false;
2033 for (auto const & tb_p : d.toolbars_)
2034 tb_p.second->update(context);
2036 for (auto const & tb_p : d.toolbars_)
2037 tb_p.second->update();
2041 void GuiView::refillToolbars()
2043 DynamicMenuButton::resetIconCache();
2044 for (auto const & tb_p : d.toolbars_)
2045 tb_p.second->refill();
2049 void GuiView::setBuffer(Buffer * newBuffer, bool switch_to)
2051 LYXERR(Debug::DEBUG, "Setting buffer: " << newBuffer << endl);
2052 LASSERT(newBuffer, return);
2054 GuiWorkArea * wa = workArea(*newBuffer);
2055 if (wa == nullptr) {
2057 newBuffer->masterBuffer()->updateBuffer();
2059 wa = addWorkArea(*newBuffer);
2060 // scroll to the position when the BufferView was last closed
2061 if (lyxrc.use_lastfilepos) {
2062 LastFilePosSection::FilePos filepos =
2063 theSession().lastFilePos().load(newBuffer->fileName());
2064 wa->bufferView().moveToPosition(filepos.pit, filepos.pos, 0, 0);
2067 //Disconnect the old buffer...there's no new one.
2070 connectBuffer(*newBuffer);
2071 connectBufferView(wa->bufferView());
2073 setCurrentWorkArea(wa);
2077 void GuiView::connectBuffer(Buffer & buf)
2079 buf.setGuiDelegate(this);
2083 void GuiView::disconnectBuffer()
2085 if (d.current_work_area_)
2086 d.current_work_area_->bufferView().buffer().setGuiDelegate(nullptr);
2090 void GuiView::connectBufferView(BufferView & bv)
2092 bv.setGuiDelegate(this);
2096 void GuiView::disconnectBufferView()
2098 if (d.current_work_area_)
2099 d.current_work_area_->bufferView().setGuiDelegate(nullptr);
2103 void GuiView::errors(string const & error_type, bool from_master)
2105 BufferView const * const bv = currentBufferView();
2109 ErrorList const & el = from_master ?
2110 bv->buffer().masterBuffer()->errorList(error_type) :
2111 bv->buffer().errorList(error_type);
2116 string err = error_type;
2118 err = "from_master|" + error_type;
2119 showDialog("errorlist", err);
2123 void GuiView::updateTocItem(string const & type, DocIterator const & dit)
2125 d.toc_models_.updateItem(toqstr(type), dit);
2129 void GuiView::structureChanged()
2131 // This is called from the Buffer, which has no way to ensure that cursors
2132 // in BufferView remain valid.
2133 if (documentBufferView())
2134 documentBufferView()->cursor().sanitize();
2135 // FIXME: This is slightly expensive, though less than the tocBackend update
2136 // (#9880). This also resets the view in the Toc Widget (#6675).
2137 d.toc_models_.reset(documentBufferView());
2138 // Navigator needs more than a simple update in this case. It needs to be
2140 updateDialog("toc", "");
2144 void GuiView::updateDialog(string const & name, string const & sdata)
2146 if (!isDialogVisible(name))
2149 map<string, DialogPtr>::const_iterator it = d.dialogs_.find(name);
2150 if (it == d.dialogs_.end())
2153 Dialog * const dialog = it->second.get();
2154 if (dialog->isVisibleView())
2155 dialog->initialiseParams(sdata);
2159 BufferView * GuiView::documentBufferView()
2161 return currentMainWorkArea()
2162 ? ¤tMainWorkArea()->bufferView()
2167 BufferView const * GuiView::documentBufferView() const
2169 return currentMainWorkArea()
2170 ? ¤tMainWorkArea()->bufferView()
2175 BufferView * GuiView::currentBufferView()
2177 return d.current_work_area_ ? &d.current_work_area_->bufferView() : nullptr;
2181 BufferView const * GuiView::currentBufferView() const
2183 return d.current_work_area_ ? &d.current_work_area_->bufferView() : nullptr;
2187 docstring GuiView::GuiViewPrivate::autosaveAndDestroy(
2188 Buffer const * orig, Buffer * clone)
2190 bool const success = clone->autoSave();
2192 busyBuffers.remove(orig);
2194 ? _("Automatic save done.")
2195 : _("Automatic save failed!");
2199 void GuiView::autoSave()
2201 LYXERR(Debug::INFO, "Running autoSave()");
2203 Buffer * buffer = documentBufferView()
2204 ? &documentBufferView()->buffer() : nullptr;
2206 resetAutosaveTimers();
2210 GuiViewPrivate::busyBuffers.insert(buffer);
2211 QFuture<docstring> f = QtConcurrent::run(GuiViewPrivate::autosaveAndDestroy,
2212 buffer, buffer->cloneBufferOnly());
2213 d.autosave_watcher_.setFuture(f);
2214 resetAutosaveTimers();
2218 void GuiView::resetAutosaveTimers()
2221 d.autosave_timeout_.restart();
2227 double zoomRatio(FuncRequest const & cmd, double const zr)
2229 if (cmd.argument().empty()) {
2230 if (cmd.action() == LFUN_BUFFER_ZOOM)
2232 else if (cmd.action() == LFUN_BUFFER_ZOOM_IN)
2234 else // cmd.action() == LFUN_BUFFER_ZOOM_OUT
2237 if (cmd.action() == LFUN_BUFFER_ZOOM)
2238 return convert<int>(cmd.argument()) / double(lyxrc.defaultZoom);
2239 else if (cmd.action() == LFUN_BUFFER_ZOOM_IN)
2240 return zr + convert<int>(cmd.argument()) / 100.0;
2241 else // cmd.action() == LFUN_BUFFER_ZOOM_OUT
2242 return zr - convert<int>(cmd.argument()) / 100.0;
2249 bool GuiView::getStatus(FuncRequest const & cmd, FuncStatus & flag)
2252 Buffer * buf = currentBufferView()
2253 ? ¤tBufferView()->buffer() : nullptr;
2254 Buffer * doc_buffer = documentBufferView()
2255 ? &(documentBufferView()->buffer()) : nullptr;
2258 /* In LyX/Mac, when a dialog is open, the menus of the
2259 application can still be accessed without giving focus to
2260 the main window. In this case, we want to disable the menu
2261 entries that are buffer-related.
2262 This code must not be used on Linux and Windows, since it
2263 would disable buffer-related entries when hovering over the
2264 menu (see bug #9574).
2266 if (cmd.origin() == FuncRequest::MENU && !hasFocus()) {
2272 // Check whether we need a buffer
2273 if (!lyxaction.funcHasFlag(cmd.action(), LyXAction::NoBuffer) && !buf) {
2274 // no, exit directly
2275 flag.message(from_utf8(N_("Command not allowed with"
2276 "out any document open")));
2277 flag.setEnabled(false);
2281 if (cmd.origin() == FuncRequest::TOC) {
2282 GuiToc * toc = static_cast<GuiToc*>(findOrBuild("toc", false));
2283 if (!toc || !toc->getStatus(documentBufferView()->cursor(), cmd, flag))
2284 flag.setEnabled(false);
2288 switch(cmd.action()) {
2289 case LFUN_BUFFER_IMPORT:
2292 case LFUN_MASTER_BUFFER_EXPORT:
2294 && (doc_buffer->parent() != nullptr
2295 || doc_buffer->hasChildren())
2296 && !d.processing_thread_watcher_.isRunning()
2297 // this launches a dialog, which would be in the wrong Buffer
2298 && !(::lyx::operator==(cmd.argument(), "custom"));
2301 case LFUN_MASTER_BUFFER_UPDATE:
2302 case LFUN_MASTER_BUFFER_VIEW:
2304 && (doc_buffer->parent() != nullptr
2305 || doc_buffer->hasChildren())
2306 && !d.processing_thread_watcher_.isRunning();
2309 case LFUN_BUFFER_UPDATE:
2310 case LFUN_BUFFER_VIEW: {
2311 if (!doc_buffer || d.processing_thread_watcher_.isRunning()) {
2315 string format = to_utf8(cmd.argument());
2316 if (cmd.argument().empty())
2317 format = doc_buffer->params().getDefaultOutputFormat();
2318 enable = doc_buffer->params().isExportable(format, true);
2322 case LFUN_BUFFER_RELOAD:
2323 enable = doc_buffer && !doc_buffer->isUnnamed()
2324 && doc_buffer->fileName().exists()
2325 && (!doc_buffer->isClean() || doc_buffer->notifiesExternalModification());
2328 case LFUN_BUFFER_RESET_EXPORT:
2329 enable = doc_buffer != nullptr;
2332 case LFUN_BUFFER_CHILD_OPEN:
2333 enable = doc_buffer != nullptr;
2336 case LFUN_MASTER_BUFFER_FORALL: {
2337 if (doc_buffer == nullptr) {
2338 flag.message(from_utf8(N_("Command not allowed without a buffer open")));
2342 FuncRequest const cmdToPass = lyxaction.lookupFunc(cmd.getLongArg(0));
2343 if (cmdToPass.action() == LFUN_UNKNOWN_ACTION) {
2344 flag.message(from_utf8(N_("Invalid argument of master-buffer-forall")));
2349 for (Buffer * buf : doc_buffer->allRelatives()) {
2350 GuiWorkArea * wa = workArea(*buf);
2353 if (wa->bufferView().getStatus(cmdToPass, flag)) {
2354 enable = flag.enabled();
2361 case LFUN_BUFFER_WRITE:
2362 enable = doc_buffer && (doc_buffer->isUnnamed()
2363 || (!doc_buffer->isClean()
2364 || cmd.argument() == "force"));
2367 //FIXME: This LFUN should be moved to GuiApplication.
2368 case LFUN_BUFFER_WRITE_ALL: {
2369 // We enable the command only if there are some modified buffers
2370 Buffer * first = theBufferList().first();
2375 // We cannot use a for loop as the buffer list is a cycle.
2377 if (!b->isClean()) {
2381 b = theBufferList().next(b);
2382 } while (b != first);
2386 case LFUN_BUFFER_EXTERNAL_MODIFICATION_CLEAR:
2387 enable = doc_buffer && doc_buffer->notifiesExternalModification();
2390 case LFUN_BUFFER_EXPORT: {
2391 if (!doc_buffer || d.processing_thread_watcher_.isRunning()) {
2395 return doc_buffer->getStatus(cmd, flag);
2398 case LFUN_BUFFER_EXPORT_AS:
2399 if (!doc_buffer || d.processing_thread_watcher_.isRunning()) {
2404 case LFUN_BUFFER_WRITE_AS:
2405 case LFUN_BUFFER_WRITE_AS_TEMPLATE:
2406 enable = doc_buffer != nullptr;
2409 case LFUN_EXPORT_CANCEL:
2410 enable = d.processing_thread_watcher_.isRunning();
2413 case LFUN_BUFFER_CLOSE:
2414 case LFUN_VIEW_CLOSE:
2415 enable = doc_buffer != nullptr;
2418 case LFUN_BUFFER_CLOSE_ALL:
2419 enable = theBufferList().last() != theBufferList().first();
2422 case LFUN_BUFFER_CHKTEX: {
2423 // hide if we have no checktex command
2424 if (lyxrc.chktex_command.empty()) {
2425 flag.setUnknown(true);
2429 if (!doc_buffer || !doc_buffer->params().isLatex()
2430 || d.processing_thread_watcher_.isRunning()) {
2431 // grey out, don't hide
2439 case LFUN_VIEW_SPLIT:
2440 if (cmd.getArg(0) == "vertical")
2441 enable = doc_buffer && (d.splitter_->count() == 1 ||
2442 d.splitter_->orientation() == Qt::Vertical);
2444 enable = doc_buffer && (d.splitter_->count() == 1 ||
2445 d.splitter_->orientation() == Qt::Horizontal);
2448 case LFUN_TAB_GROUP_NEXT:
2449 case LFUN_TAB_GROUP_PREVIOUS:
2450 enable = (d.splitter_->count() > 1);
2453 case LFUN_TAB_GROUP_CLOSE:
2454 enable = d.tabWorkAreaCount() > 1;
2457 case LFUN_DEVEL_MODE_TOGGLE:
2458 flag.setOnOff(devel_mode_);
2461 case LFUN_TOOLBAR_SET: {
2462 string const name = cmd.getArg(0);
2463 string const state = cmd.getArg(1);
2464 if (name.empty() || state.empty()) {
2466 docstring const msg =
2467 _("Function toolbar-set requires two arguments!");
2471 if (state != "on" && state != "off" && state != "auto") {
2473 docstring const msg =
2474 bformat(_("Invalid argument \"%1$s\" to function toolbar-set!"),
2479 if (GuiToolbar * t = toolbar(name)) {
2480 bool const autovis = t->visibility() & Toolbars::AUTO;
2482 flag.setOnOff(t->isVisible() && !autovis);
2483 else if (state == "off")
2484 flag.setOnOff(!t->isVisible() && !autovis);
2485 else if (state == "auto")
2486 flag.setOnOff(autovis);
2489 docstring const msg =
2490 bformat(_("Unknown toolbar \"%1$s\""), from_utf8(name));
2496 case LFUN_TOOLBAR_TOGGLE: {
2497 string const name = cmd.getArg(0);
2498 if (GuiToolbar * t = toolbar(name))
2499 flag.setOnOff(t->isVisible());
2502 docstring const msg =
2503 bformat(_("Unknown toolbar \"%1$s\""), from_utf8(name));
2509 case LFUN_TOOLBAR_MOVABLE: {
2510 string const name = cmd.getArg(0);
2511 // use negation since locked == !movable
2513 // toolbar name * locks all toolbars
2514 flag.setOnOff(!toolbarsMovable_);
2515 else if (GuiToolbar * t = toolbar(name))
2516 flag.setOnOff(!(t->isMovable()));
2519 docstring const msg =
2520 bformat(_("Unknown toolbar \"%1$s\""), from_utf8(name));
2526 case LFUN_ICON_SIZE:
2527 flag.setOnOff(d.iconSize(cmd.argument()) == iconSize());
2530 case LFUN_DROP_LAYOUTS_CHOICE:
2531 enable = buf != nullptr;
2534 case LFUN_UI_TOGGLE:
2535 if (cmd.argument() == "zoomlevel") {
2536 flag.setOnOff(zoom_value_ ? zoom_value_->isVisible() : false);
2537 } else if (cmd.argument() == "zoomslider") {
2538 flag.setOnOff(zoom_slider_ ? zoom_slider_->isVisible() : false);
2539 } else if (cmd.argument() == "statistics-w") {
2540 flag.setOnOff(word_count_enabled_);
2541 } else if (cmd.argument() == "statistics-cb") {
2542 flag.setOnOff(char_count_enabled_);
2543 } else if (cmd.argument() == "statistics-c") {
2544 flag.setOnOff(char_nb_count_enabled_);
2546 flag.setOnOff(isFullScreen());
2549 case LFUN_DIALOG_DISCONNECT_INSET:
2552 case LFUN_DIALOG_HIDE:
2553 // FIXME: should we check if the dialog is shown?
2556 case LFUN_DIALOG_TOGGLE:
2557 flag.setOnOff(isDialogVisible(cmd.getArg(0)));
2560 case LFUN_DIALOG_SHOW: {
2561 string const name = cmd.getArg(0);
2563 enable = name == "aboutlyx"
2564 || name == "file" //FIXME: should be removed.
2565 || name == "lyxfiles"
2567 || name == "texinfo"
2568 || name == "progress"
2569 || name == "compare";
2570 else if (name == "character" || name == "symbols"
2571 || name == "mathdelimiter" || name == "mathmatrix") {
2572 if (!buf || buf->isReadonly())
2575 Cursor const & cur = currentBufferView()->cursor();
2576 enable = !(cur.inTexted() && cur.paragraph().isPassThru());
2579 else if (name == "latexlog")
2580 enable = FileName(doc_buffer->logName()).isReadableFile();
2581 else if (name == "spellchecker")
2582 enable = theSpellChecker()
2583 && !doc_buffer->text().empty();
2584 else if (name == "vclog")
2585 enable = doc_buffer->lyxvc().inUse();
2589 case LFUN_DIALOG_UPDATE: {
2590 string const name = cmd.getArg(0);
2592 enable = name == "prefs";
2596 case LFUN_COMMAND_EXECUTE:
2598 case LFUN_MENU_OPEN:
2599 // Nothing to check.
2602 case LFUN_COMPLETION_INLINE:
2603 if (!d.current_work_area_
2604 || !d.current_work_area_->completer().inlinePossible(
2605 currentBufferView()->cursor()))
2609 case LFUN_COMPLETION_POPUP:
2610 if (!d.current_work_area_
2611 || !d.current_work_area_->completer().popupPossible(
2612 currentBufferView()->cursor()))
2617 if (!d.current_work_area_
2618 || !d.current_work_area_->completer().inlinePossible(
2619 currentBufferView()->cursor()))
2623 case LFUN_COMPLETION_ACCEPT:
2624 if (!d.current_work_area_
2625 || (!d.current_work_area_->completer().popupVisible()
2626 && !d.current_work_area_->completer().inlineVisible()
2627 && !d.current_work_area_->completer().completionAvailable()))
2631 case LFUN_COMPLETION_CANCEL:
2632 if (!d.current_work_area_
2633 || (!d.current_work_area_->completer().popupVisible()
2634 && !d.current_work_area_->completer().inlineVisible()))
2638 case LFUN_BUFFER_ZOOM_OUT:
2639 case LFUN_BUFFER_ZOOM_IN:
2640 case LFUN_BUFFER_ZOOM: {
2641 int const zoom = (int)(lyxrc.defaultZoom * zoomRatio(cmd, zoom_ratio_));
2642 if (zoom < zoom_min_) {
2643 docstring const msg =
2644 bformat(_("Zoom level cannot be less than %1$d%."), zoom_min_);
2647 } else if (zoom > zoom_max_) {
2648 docstring const msg =
2649 bformat(_("Zoom level cannot be more than %1$d%."), zoom_max_);
2653 enable = doc_buffer;
2658 case LFUN_BUFFER_MOVE_NEXT:
2659 case LFUN_BUFFER_MOVE_PREVIOUS:
2660 // we do not cycle when moving
2661 case LFUN_BUFFER_NEXT:
2662 case LFUN_BUFFER_PREVIOUS:
2663 // because we cycle, it doesn't matter whether on first or last
2664 enable = (d.currentTabWorkArea()->count() > 1);
2666 case LFUN_BUFFER_SWITCH:
2667 // toggle on the current buffer, but do not toggle off
2668 // the other ones (is that a good idea?)
2670 && to_utf8(cmd.argument()) == doc_buffer->absFileName())
2671 flag.setOnOff(true);
2674 case LFUN_VC_REGISTER:
2675 enable = doc_buffer && !doc_buffer->lyxvc().inUse();
2677 case LFUN_VC_RENAME:
2678 enable = doc_buffer && doc_buffer->lyxvc().renameEnabled();
2681 enable = doc_buffer && doc_buffer->lyxvc().copyEnabled();
2683 case LFUN_VC_CHECK_IN:
2684 enable = doc_buffer && doc_buffer->lyxvc().checkInEnabled();
2686 case LFUN_VC_CHECK_OUT:
2687 enable = doc_buffer && doc_buffer->lyxvc().checkOutEnabled();
2689 case LFUN_VC_LOCKING_TOGGLE:
2690 enable = doc_buffer && !doc_buffer->hasReadonlyFlag()
2691 && doc_buffer->lyxvc().lockingToggleEnabled();
2692 flag.setOnOff(enable && doc_buffer->lyxvc().locking());
2694 case LFUN_VC_REVERT:
2695 enable = doc_buffer && doc_buffer->lyxvc().inUse()
2696 && !doc_buffer->hasReadonlyFlag();
2698 case LFUN_VC_UNDO_LAST:
2699 enable = doc_buffer && doc_buffer->lyxvc().undoLastEnabled();
2701 case LFUN_VC_REPO_UPDATE:
2702 enable = doc_buffer && doc_buffer->lyxvc().repoUpdateEnabled();
2704 case LFUN_VC_COMMAND: {
2705 if (cmd.argument().empty())
2707 if (!doc_buffer && contains(cmd.getArg(0), 'D'))
2711 case LFUN_VC_COMPARE:
2712 enable = doc_buffer && doc_buffer->lyxvc().prepareFileRevisionEnabled();
2715 case LFUN_SERVER_GOTO_FILE_ROW:
2716 case LFUN_LYX_ACTIVATE:
2717 case LFUN_WINDOW_RAISE:
2719 case LFUN_FORWARD_SEARCH:
2720 enable = !(lyxrc.forward_search_dvi.empty() && lyxrc.forward_search_pdf.empty()) &&
2721 doc_buffer && doc_buffer->isSyncTeXenabled();
2724 case LFUN_FILE_INSERT_PLAINTEXT:
2725 case LFUN_FILE_INSERT_PLAINTEXT_PARA:
2726 enable = documentBufferView() && documentBufferView()->cursor().inTexted();
2729 case LFUN_SPELLING_CONTINUOUSLY:
2730 flag.setOnOff(lyxrc.spellcheck_continuously);
2733 case LFUN_CITATION_OPEN:
2742 flag.setEnabled(false);
2748 static FileName selectTemplateFile()
2750 FileDialog dlg(qt_("Select template file"));
2751 dlg.setButton1(qt_("D&ocuments"), toqstr(lyxrc.document_path));
2752 dlg.setButton2(qt_("&Templates"), toqstr(lyxrc.template_path));
2754 FileDialog::Result result = dlg.open(toqstr(lyxrc.template_path),
2755 QStringList(qt_("LyX Documents (*.lyx)")));
2757 if (result.first == FileDialog::Later)
2759 if (result.second.isEmpty())
2761 return FileName(fromqstr(result.second));
2765 Buffer * GuiView::loadDocument(FileName const & filename, bool tolastfiles)
2769 Buffer * newBuffer = nullptr;
2771 newBuffer = checkAndLoadLyXFile(filename);
2772 } catch (ExceptionMessage const &) {
2779 message(_("Document not loaded."));
2783 setBuffer(newBuffer);
2784 newBuffer->errors("Parse");
2787 theSession().lastFiles().add(filename);
2788 theSession().writeFile();
2795 void GuiView::openDocuments(string const & fname, int origin)
2797 string initpath = lyxrc.document_path;
2799 if (documentBufferView()) {
2800 string const trypath = documentBufferView()->buffer().filePath();
2801 // If directory is writeable, use this as default.
2802 if (FileName(trypath).isDirWritable())
2808 if (fname.empty()) {
2809 FileDialog dlg(qt_("Select documents to open"));
2810 dlg.setButton1(qt_("D&ocuments"), toqstr(lyxrc.document_path));
2811 dlg.setButton2(qt_("&Examples"), toqstr(lyxrc.example_path));
2813 QStringList const filter({
2814 qt_("LyX Documents (*.lyx)"),
2815 qt_("LyX Document Backups (*.lyx~)"),
2816 qt_("All Files (*.*)")
2818 FileDialog::Results results =
2819 dlg.openMulti(toqstr(initpath), filter);
2821 if (results.first == FileDialog::Later)
2824 files = results.second;
2826 // check selected filename
2827 if (files.isEmpty()) {
2828 message(_("Canceled."));
2832 files << toqstr(fname);
2834 // iterate over all selected files
2835 for (auto const & file : files) {
2836 string filename = fromqstr(file);
2838 // get absolute path of file and add ".lyx" to the filename if
2840 FileName const fullname =
2841 fileSearch(string(), filename, "lyx", support::may_not_exist);
2842 if (!fullname.empty())
2843 filename = fullname.absFileName();
2845 if (!fullname.onlyPath().isDirectory()) {
2846 Alert::warning(_("Invalid filename"),
2847 bformat(_("The directory in the given path\n%1$s\ndoes not exist."),
2848 from_utf8(fullname.absFileName())));
2852 // if the file doesn't exist and isn't already open (bug 6645),
2853 // let the user create one
2854 if (!fullname.exists() && !theBufferList().exists(fullname) &&
2855 !LyXVC::file_not_found_hook(fullname)) {
2857 if (origin == FuncRequest::MENU) {
2858 docstring const & msg =
2861 "does not exist. Create empty file?"),
2862 from_utf8(filename));
2863 int ret = Alert::prompt(_("File does not exist"),
2870 Buffer * const b = newFile(filename, string(), true);
2876 docstring const disp_fn = makeDisplayPath(filename);
2877 message(bformat(_("Opening document %1$s..."), disp_fn));
2880 Buffer * buf = loadDocument(fullname);
2882 str2 = bformat(_("Document %1$s opened."), disp_fn);
2883 if (buf->lyxvc().inUse())
2884 str2 += " " + from_utf8(buf->lyxvc().versionString()) +
2885 " " + _("Version control detected.");
2887 str2 = bformat(_("Could not open document %1$s"), disp_fn);
2893 // FIXME: clean that
2894 static bool import(GuiView * lv, FileName const & filename,
2895 string const & format, ErrorList & errorList)
2897 FileName const lyxfile(support::changeExtension(filename.absFileName(), ".lyx"));
2899 string loader_format;
2900 vector<string> loaders = theConverters().loaders();
2901 if (find(loaders.begin(), loaders.end(), format) == loaders.end()) {
2902 for (string const & loader : loaders) {
2903 if (!theConverters().isReachable(format, loader))
2906 string const tofile =
2907 support::changeExtension(filename.absFileName(),
2908 theFormats().extension(loader));
2909 if (theConverters().convert(nullptr, filename, FileName(tofile),
2910 filename, format, loader, errorList) != Converters::SUCCESS)
2912 loader_format = loader;
2915 if (loader_format.empty()) {
2916 frontend::Alert::error(_("Couldn't import file"),
2917 bformat(_("No information for importing the format %1$s."),
2918 translateIfPossible(theFormats().prettyName(format))));
2922 loader_format = format;
2924 if (loader_format == "lyx") {
2925 Buffer * buf = lv->loadDocument(lyxfile);
2929 Buffer * const b = newFile(lyxfile.absFileName(), string(), true);
2933 bool as_paragraphs = loader_format == "textparagraph";
2934 string filename2 = (loader_format == format) ? filename.absFileName()
2935 : support::changeExtension(filename.absFileName(),
2936 theFormats().extension(loader_format));
2937 lv->currentBufferView()->insertPlaintextFile(FileName(filename2),
2939 guiApp->setCurrentView(lv);
2940 lyx::dispatch(FuncRequest(LFUN_MARK_OFF));
2947 void GuiView::importDocument(string const & argument)
2950 string filename = split(argument, format, ' ');
2952 LYXERR(Debug::INFO, format << " file: " << filename);
2954 // need user interaction
2955 if (filename.empty()) {
2956 string initpath = lyxrc.document_path;
2957 if (documentBufferView()) {
2958 string const trypath = documentBufferView()->buffer().filePath();
2959 // If directory is writeable, use this as default.
2960 if (FileName(trypath).isDirWritable())
2964 docstring const text = bformat(_("Select %1$s file to import"),
2965 translateIfPossible(theFormats().prettyName(format)));
2967 FileDialog dlg(toqstr(text));
2968 dlg.setButton1(qt_("D&ocuments"), toqstr(lyxrc.document_path));
2969 dlg.setButton2(qt_("&Examples"), toqstr(lyxrc.example_path));
2971 docstring filter = translateIfPossible(theFormats().prettyName(format));
2974 filter += from_utf8(theFormats().extensions(format));
2977 FileDialog::Result result =
2978 dlg.open(toqstr(initpath), fileFilters(toqstr(filter)));
2980 if (result.first == FileDialog::Later)
2983 filename = fromqstr(result.second);
2985 // check selected filename
2986 if (filename.empty())
2987 message(_("Canceled."));
2990 if (filename.empty())
2993 // get absolute path of file
2994 FileName const fullname(support::makeAbsPath(filename));
2996 // Can happen if the user entered a path into the dialog
2998 if (fullname.onlyFileName().empty()) {
2999 docstring msg = bformat(_("The file name '%1$s' is invalid!\n"
3000 "Aborting import."),
3001 from_utf8(fullname.absFileName()));
3002 frontend::Alert::error(_("File name error"), msg);
3003 message(_("Canceled."));
3008 FileName const lyxfile(support::changeExtension(fullname.absFileName(), ".lyx"));
3010 // Check if the document already is open
3011 Buffer * buf = theBufferList().getBuffer(lyxfile);
3014 if (!closeBuffer()) {
3015 message(_("Canceled."));
3020 docstring const displaypath = makeDisplayPath(lyxfile.absFileName(), 30);
3022 // if the file exists already, and we didn't do
3023 // -i lyx thefile.lyx, warn
3024 if (lyxfile.exists() && fullname != lyxfile) {
3026 docstring text = bformat(_("The document %1$s already exists.\n\n"
3027 "Do you want to overwrite that document?"), displaypath);
3028 int const ret = Alert::prompt(_("Overwrite document?"),
3029 text, 0, 1, _("&Overwrite"), _("&Cancel"));
3032 message(_("Canceled."));
3037 message(bformat(_("Importing %1$s..."), displaypath));
3038 ErrorList errorList;
3039 if (import(this, fullname, format, errorList))
3040 message(_("imported."));
3042 message(_("file not imported!"));
3044 // FIXME (Abdel 12/08/06): Is there a need to display the error list here?
3048 void GuiView::newDocument(string const & filename, string templatefile,
3051 FileName initpath(lyxrc.document_path);
3052 if (documentBufferView()) {
3053 FileName const trypath(documentBufferView()->buffer().filePath());
3054 // If directory is writeable, use this as default.
3055 if (trypath.isDirWritable())
3059 if (from_template) {
3060 if (templatefile.empty())
3061 templatefile = selectTemplateFile().absFileName();
3062 if (templatefile.empty())
3067 if (filename.empty())
3068 b = newUnnamedFile(initpath, to_utf8(_("newfile")), templatefile);
3070 b = newFile(filename, templatefile, true);
3075 // If no new document could be created, it is unsure
3076 // whether there is a valid BufferView.
3077 if (currentBufferView())
3078 // Ensure the cursor is correctly positioned on screen.
3079 currentBufferView()->showCursor();
3083 bool GuiView::insertLyXFile(docstring const & fname, bool ignorelang)
3085 BufferView * bv = documentBufferView();
3090 FileName filename(to_utf8(fname));
3091 if (filename.empty()) {
3092 // Launch a file browser
3094 string initpath = lyxrc.document_path;
3095 string const trypath = bv->buffer().filePath();
3096 // If directory is writeable, use this as default.
3097 if (FileName(trypath).isDirWritable())
3101 FileDialog dlg(qt_("Select LyX document to insert"));
3102 dlg.setButton1(qt_("D&ocuments"), toqstr(lyxrc.document_path));
3103 dlg.setButton2(qt_("&Examples"), toqstr(lyxrc.example_path));
3105 FileDialog::Result result = dlg.open(toqstr(initpath),
3106 QStringList(qt_("LyX Documents (*.lyx)")));
3108 if (result.first == FileDialog::Later)
3112 filename.set(fromqstr(result.second));
3114 // check selected filename
3115 if (filename.empty()) {
3116 // emit message signal.
3117 message(_("Canceled."));
3122 bv->insertLyXFile(filename, ignorelang);
3123 bv->buffer().errors("Parse");
3128 string const GuiView::getTemplatesPath(Buffer & b)
3130 // We start off with the user's templates path
3131 string result = addPath(package().user_support().absFileName(), "templates");
3132 // Check for the document language
3133 string const langcode = b.params().language->code();
3134 string const shortcode = langcode.substr(0, 2);
3135 if (!langcode.empty() && shortcode != "en") {
3136 string subpath = addPath(result, shortcode);
3137 string subpath_long = addPath(result, langcode);
3138 // If we have a subdirectory for the language already,
3140 FileName sp = FileName(subpath);
3141 if (sp.isDirectory())
3143 else if (FileName(subpath_long).isDirectory())
3144 result = subpath_long;
3146 // Ask whether we should create such a subdirectory
3147 docstring const text =
3148 bformat(_("It is suggested to save the template in a subdirectory\n"
3149 "appropriate to the document language (%1$s).\n"
3150 "This subdirectory does not exists yet.\n"
3151 "Do you want to create it?"),
3152 _(b.params().language->display()));
3153 if (Alert::prompt(_("Create Language Directory?"),
3154 text, 0, 1, _("&Yes, Create"), _("&No, Save Template in Parent Directory")) == 0) {
3155 // If the user agreed, we try to create it and report if this failed.
3156 if (!sp.createDirectory(0777))
3157 Alert::error(_("Subdirectory creation failed!"),
3158 _("Could not create subdirectory.\n"
3159 "The template will be saved in the parent directory."));
3165 // Do we have a layout category?
3166 string const cat = b.params().baseClass() ?
3167 b.params().baseClass()->category()
3170 string subpath = addPath(result, cat);
3171 // If we have a subdirectory for the category already,
3173 FileName sp = FileName(subpath);
3174 if (sp.isDirectory())
3177 // Ask whether we should create such a subdirectory
3178 docstring const text =
3179 bformat(_("It is suggested to save the template in a subdirectory\n"
3180 "appropriate to the layout category (%1$s).\n"
3181 "This subdirectory does not exists yet.\n"
3182 "Do you want to create it?"),
3184 if (Alert::prompt(_("Create Category Directory?"),
3185 text, 0, 1, _("&Yes, Create"), _("&No, Save Template in Parent Directory")) == 0) {
3186 // If the user agreed, we try to create it and report if this failed.
3187 if (!sp.createDirectory(0777))
3188 Alert::error(_("Subdirectory creation failed!"),
3189 _("Could not create subdirectory.\n"
3190 "The template will be saved in the parent directory."));
3200 bool GuiView::renameBuffer(Buffer & b, docstring const & newname, RenameKind kind)
3202 FileName fname = b.fileName();
3203 FileName const oldname = fname;
3204 bool const as_template = (kind == LV_WRITE_AS_TEMPLATE);
3206 if (!newname.empty()) {
3209 fname = support::makeAbsPath(to_utf8(newname), getTemplatesPath(b));
3211 fname = support::makeAbsPath(to_utf8(newname),
3212 oldname.onlyPath().absFileName());
3214 // Switch to this Buffer.
3217 // No argument? Ask user through dialog.
3219 QString const title = as_template ? qt_("Choose a filename to save template as")
3220 : qt_("Choose a filename to save document as");
3221 FileDialog dlg(title);
3222 dlg.setButton1(qt_("D&ocuments"), toqstr(lyxrc.document_path));
3223 dlg.setButton2(qt_("&Templates"), toqstr(lyxrc.template_path));
3225 fname.ensureExtension(".lyx");
3227 string const path = as_template ?
3229 : fname.onlyPath().absFileName();
3230 FileDialog::Result result =
3231 dlg.save(toqstr(path),
3232 QStringList(qt_("LyX Documents (*.lyx)")),
3233 toqstr(fname.onlyFileName()));
3235 if (result.first == FileDialog::Later)
3238 fname.set(fromqstr(result.second));
3243 fname.ensureExtension(".lyx");
3246 // fname is now the new Buffer location.
3248 // if there is already a Buffer open with this name, we do not want
3249 // to have another one. (the second test makes sure we're not just
3250 // trying to overwrite ourselves, which is fine.)
3251 if (theBufferList().exists(fname) && fname != oldname
3252 && theBufferList().getBuffer(fname) != &b) {
3253 docstring const text =
3254 bformat(_("The file\n%1$s\nis already open in your current session.\n"
3255 "Please close it before attempting to overwrite it.\n"
3256 "Do you want to choose a new filename?"),
3257 from_utf8(fname.absFileName()));
3258 int const ret = Alert::prompt(_("Chosen File Already Open"),
3259 text, 0, 1, _("&Rename"), _("&Cancel"));
3261 case 0: return renameBuffer(b, docstring(), kind);
3262 case 1: return false;
3267 bool const existsLocal = fname.exists();
3268 bool const existsInVC = LyXVC::fileInVC(fname);
3269 if (existsLocal || existsInVC) {
3270 docstring const file = makeDisplayPath(fname.absFileName(), 30);
3271 if (kind != LV_WRITE_AS && existsInVC) {
3272 // renaming to a name that is already in VC
3274 docstring text = bformat(_("The document %1$s "
3275 "is already registered.\n\n"
3276 "Do you want to choose a new name?"),
3278 docstring const title = (kind == LV_VC_RENAME) ?
3279 _("Rename document?") : _("Copy document?");
3280 docstring const button = (kind == LV_VC_RENAME) ?
3281 _("&Rename") : _("&Copy");
3282 int const ret = Alert::prompt(title, text, 0, 1,
3283 button, _("&Cancel"));
3285 case 0: return renameBuffer(b, docstring(), kind);
3286 case 1: return false;
3291 docstring text = bformat(_("The document %1$s "
3292 "already exists.\n\n"
3293 "Do you want to overwrite that document?"),
3295 int const ret = Alert::prompt(_("Overwrite document?"),
3296 text, 0, 2, _("&Overwrite"),
3297 _("&Rename"), _("&Cancel"));
3300 case 1: return renameBuffer(b, docstring(), kind);
3301 case 2: return false;
3307 case LV_VC_RENAME: {
3308 string msg = b.lyxvc().rename(fname);
3311 message(from_utf8(msg));
3315 string msg = b.lyxvc().copy(fname);
3318 message(from_utf8(msg));
3322 case LV_WRITE_AS_TEMPLATE:
3325 // LyXVC created the file already in case of LV_VC_RENAME or
3326 // LV_VC_COPY, but call saveBuffer() nevertheless to get
3327 // relative paths of included stuff right if we moved e.g. from
3328 // /a/b.lyx to /a/c/b.lyx.
3330 bool const saved = saveBuffer(b, fname);
3337 bool GuiView::exportBufferAs(Buffer & b, docstring const & iformat)
3339 FileName fname = b.fileName();
3341 FileDialog dlg(qt_("Choose a filename to export the document as"));
3342 dlg.setButton1(qt_("D&ocuments"), toqstr(lyxrc.document_path));
3345 QString const anyformat = qt_("Guess from extension (*.*)");
3348 vector<Format const *> export_formats;
3349 for (Format const & f : theFormats())
3350 if (f.documentFormat())
3351 export_formats.push_back(&f);
3352 sort(export_formats.begin(), export_formats.end(), Format::formatSorter);
3353 map<QString, string> fmap;
3356 for (Format const * f : export_formats) {
3357 docstring const loc_prettyname = translateIfPossible(f->prettyname());
3358 QString const loc_filter = toqstr(bformat(from_ascii("%1$s (*.%2$s)"),
3360 from_ascii(f->extension())));
3361 types << loc_filter;
3362 fmap[loc_filter] = f->name();
3363 if (from_ascii(f->name()) == iformat) {
3364 filter = loc_filter;
3365 ext = f->extension();
3368 string ofname = fname.onlyFileName();
3370 ofname = support::changeExtension(ofname, ext);
3371 FileDialog::Result result =
3372 dlg.save(toqstr(fname.onlyPath().absFileName()),
3376 if (result.first != FileDialog::Chosen)
3380 fname.set(fromqstr(result.second));
3381 if (filter == anyformat)
3382 fmt_name = theFormats().getFormatFromExtension(fname.extension());
3384 fmt_name = fmap[filter];
3385 LYXERR(Debug::FILES, "filter=" << fromqstr(filter)
3386 << ", fmt_name=" << fmt_name << ", fname=" << fname.absFileName());
3388 if (fmt_name.empty() || fname.empty())
3391 fname.ensureExtension(theFormats().extension(fmt_name));
3393 // fname is now the new Buffer location.
3394 if (fname.exists()) {
3395 docstring const file = makeDisplayPath(fname.absFileName(), 30);
3396 docstring text = bformat(_("The document %1$s already "
3397 "exists.\n\nDo you want to "
3398 "overwrite that document?"),
3400 int const ret = Alert::prompt(_("Overwrite document?"),
3401 text, 0, 2, _("&Overwrite"), _("&Rename"), _("&Cancel"));
3404 case 1: return exportBufferAs(b, from_ascii(fmt_name));
3405 case 2: return false;
3409 FuncRequest cmd(LFUN_BUFFER_EXPORT, fmt_name + " " + fname.absFileName());
3412 return dr.dispatched();
3416 bool GuiView::saveBuffer(Buffer & b)
3418 return saveBuffer(b, FileName());
3422 bool GuiView::saveBuffer(Buffer & b, FileName const & fn)
3424 if (workArea(b) && workArea(b)->inDialogMode())
3427 if (fn.empty() && b.isUnnamed())
3428 return renameBuffer(b, docstring());
3430 bool const success = (fn.empty() ? b.save() : b.saveAs(fn));
3432 theSession().lastFiles().add(b.fileName());
3433 theSession().writeFile();
3437 // Switch to this Buffer.
3440 // FIXME: we don't tell the user *WHY* the save failed !!
3441 docstring const file = makeDisplayPath(b.absFileName(), 30);
3442 docstring text = bformat(_("The document %1$s could not be saved.\n\n"
3443 "Do you want to rename the document and "
3444 "try again?"), file);
3445 int const ret = Alert::prompt(_("Rename and save?"),
3446 text, 0, 2, _("&Rename"), _("&Retry"), _("&Cancel"));
3449 if (!renameBuffer(b, docstring()))
3458 return saveBuffer(b, fn);
3462 bool GuiView::hideWorkArea(GuiWorkArea * wa)
3464 return closeWorkArea(wa, false);
3468 // We only want to close the buffer if it is not visible in other workareas
3469 // of the same view, nor in other views, and if this is not a child
3470 bool GuiView::closeWorkArea(GuiWorkArea * wa)
3472 Buffer & buf = wa->bufferView().buffer();
3474 bool last_wa = d.countWorkAreasOf(buf) == 1
3475 && !inOtherView(buf) && !buf.parent();
3477 bool close_buffer = last_wa;
3480 if (lyxrc.close_buffer_with_last_view == "yes")
3482 else if (lyxrc.close_buffer_with_last_view == "no")
3483 close_buffer = false;
3486 if (buf.isUnnamed())
3487 file = from_utf8(buf.fileName().onlyFileName());
3489 file = buf.fileName().displayName(30);
3490 docstring const text = bformat(
3491 _("Last view on document %1$s is being closed.\n"
3492 "Would you like to close or hide the document?\n"
3494 "Hidden documents can be displayed back through\n"
3495 "the menu: View->Hidden->...\n"
3497 "To remove this question, set your preference in:\n"
3498 " Tools->Preferences->Look&Feel->UserInterface\n"
3500 int ret = Alert::prompt(_("Close or hide document?"),
3501 text, 0, 2, _("&Close"), _("&Hide"), _("&Cancel"));
3504 close_buffer = (ret == 0);
3508 return closeWorkArea(wa, close_buffer);
3512 bool GuiView::closeBuffer()
3514 GuiWorkArea * wa = currentMainWorkArea();
3515 // coverity complained about this
3516 // it seems unnecessary, but perhaps is worth the check
3517 LASSERT(wa, return false);
3519 setCurrentWorkArea(wa);
3520 Buffer & buf = wa->bufferView().buffer();
3521 return closeWorkArea(wa, !buf.parent());
3525 void GuiView::writeSession() const {
3526 GuiWorkArea const * active_wa = currentMainWorkArea();
3527 for (int i = 0; i < d.splitter_->count(); ++i) {
3528 TabWorkArea * twa = d.tabWorkArea(i);
3529 for (int j = 0; j < twa->count(); ++j) {
3530 GuiWorkArea * wa = twa->workArea(j);
3531 Buffer & buf = wa->bufferView().buffer();
3532 theSession().lastOpened().add(buf.fileName(), wa == active_wa);
3538 bool GuiView::closeBufferAll()
3541 for (auto & buf : theBufferList()) {
3542 if (!saveBufferIfNeeded(*buf, false)) {
3543 // Closing has been cancelled, so abort.
3548 // Close the workareas in all other views
3549 QList<int> const ids = guiApp->viewIds();
3550 for (int i = 0; i != ids.size(); ++i) {
3551 if (id_ != ids[i] && !guiApp->view(ids[i]).closeWorkAreaAll())
3555 // Close our own workareas
3556 if (!closeWorkAreaAll())
3563 bool GuiView::closeWorkAreaAll()
3565 setCurrentWorkArea(currentMainWorkArea());
3567 // We might be in a situation that there is still a tabWorkArea, but
3568 // there are no tabs anymore. This can happen when we get here after a
3569 // TabWorkArea::lastWorkAreaRemoved() signal. Therefore we count how
3570 // many TabWorkArea's have no documents anymore.
3573 // We have to call count() each time, because it can happen that
3574 // more than one splitter will disappear in one iteration (bug 5998).
3575 while (d.splitter_->count() > empty_twa) {
3576 TabWorkArea * twa = d.tabWorkArea(empty_twa);
3578 if (twa->count() == 0)
3581 setCurrentWorkArea(twa->currentWorkArea());
3582 if (!closeTabWorkArea(twa))
3590 bool GuiView::closeWorkArea(GuiWorkArea * wa, bool close_buffer)
3595 Buffer & buf = wa->bufferView().buffer();
3597 if (GuiViewPrivate::busyBuffers.contains(&buf)) {
3598 Alert::warning(_("Close document"),
3599 _("Document could not be closed because it is being processed by LyX."));
3604 return closeBuffer(buf);
3606 if (!inMultiTabs(wa))
3607 if (!saveBufferIfNeeded(buf, true))
3615 bool GuiView::closeBuffer(Buffer & buf)
3617 bool success = true;
3618 for (Buffer * child_buf : buf.getChildren()) {
3619 if (theBufferList().isOthersChild(&buf, child_buf)) {
3620 child_buf->setParent(nullptr);
3624 // FIXME: should we look in other tabworkareas?
3625 // ANSWER: I don't think so. I've tested, and if the child is
3626 // open in some other window, it closes without a problem.
3627 GuiWorkArea * child_wa = workArea(*child_buf);
3630 // If we are in a close_event all children will be closed in some time,
3631 // so no need to do it here. This will ensure that the children end up
3632 // in the session file in the correct order. If we close the master
3633 // buffer, we can close or release the child buffers here too.
3635 success = closeWorkArea(child_wa, true);
3639 // In this case the child buffer is open but hidden.
3640 // Even in this case, children can be dirty (e.g.,
3641 // after a label change in the master, see #11405).
3642 // Therefore, check this
3643 if (closing_ && (child_buf->isClean() || child_buf->paragraphs().empty())) {
3644 // If we are in a close_event all children will be closed in some time,
3645 // so no need to do it here. This will ensure that the children end up
3646 // in the session file in the correct order. If we close the master
3647 // buffer, we can close or release the child buffers here too.
3650 // Save dirty buffers also if closing_!
3651 if (saveBufferIfNeeded(*child_buf, false)) {
3652 child_buf->removeAutosaveFile();
3653 theBufferList().release(child_buf);
3655 // Saving of dirty children has been cancelled.
3656 // Cancel the whole process.
3663 // goto bookmark to update bookmark pit.
3664 // FIXME: we should update only the bookmarks related to this buffer!
3665 // FIXME: this is done also in LFUN_WINDOW_CLOSE!
3666 LYXERR(Debug::DEBUG, "GuiView::closeBuffer()");
3667 for (unsigned int i = 1; i < theSession().bookmarks().size(); ++i)
3668 guiApp->gotoBookmark(i, false, false);
3670 if (saveBufferIfNeeded(buf, false)) {
3671 buf.removeAutosaveFile();
3672 theBufferList().release(&buf);
3676 // open all children again to avoid a crash because of dangling
3677 // pointers (bug 6603)
3683 bool GuiView::closeTabWorkArea(TabWorkArea * twa)
3685 while (twa == d.currentTabWorkArea()) {
3686 twa->setCurrentIndex(twa->count() - 1);
3688 GuiWorkArea * wa = twa->currentWorkArea();
3689 Buffer & b = wa->bufferView().buffer();
3691 // We only want to close the buffer if the same buffer is not visible
3692 // in another view, and if this is not a child and if we are closing
3693 // a view (not a tabgroup).
3694 bool const close_buffer =
3695 !inOtherView(b) && !b.parent() && closing_;
3697 if (!closeWorkArea(wa, close_buffer))
3704 bool GuiView::saveBufferIfNeeded(Buffer & buf, bool hiding)
3706 if (buf.isClean() || buf.paragraphs().empty())
3709 // Switch to this Buffer.
3715 if (buf.isUnnamed()) {
3716 file = from_utf8(buf.fileName().onlyFileName());
3719 FileName filename = buf.fileName();
3721 file = filename.displayName(30);
3722 exists = filename.exists();
3725 // Bring this window to top before asking questions.
3730 if (hiding && buf.isUnnamed()) {
3731 docstring const text = bformat(_("The document %1$s has not been "
3732 "saved yet.\n\nDo you want to save "
3733 "the document?"), file);
3734 ret = Alert::prompt(_("Save new document?"),
3735 text, 0, 1, _("&Save"), _("&Cancel"));
3739 docstring const text = exists ?
3740 bformat(_("The document %1$s has unsaved changes."
3741 "\n\nDo you want to save the document or "
3742 "discard the changes?"), file) :
3743 bformat(_("The document %1$s has not been saved yet."
3744 "\n\nDo you want to save the document or "
3745 "discard it entirely?"), file);
3746 docstring const title = exists ?
3747 _("Save changed document?") : _("Save document?");
3748 ret = Alert::prompt(title, text, 0, 2,
3749 _("&Save"), _("&Discard"), _("&Cancel"));
3754 if (!saveBuffer(buf))
3758 // If we crash after this we could have no autosave file
3759 // but I guess this is really improbable (Jug).
3760 // Sometimes improbable things happen:
3761 // - see bug http://www.lyx.org/trac/ticket/6587 (ps)
3762 // buf.removeAutosaveFile();
3764 // revert all changes
3775 bool GuiView::inMultiTabs(GuiWorkArea * wa)
3777 Buffer & buf = wa->bufferView().buffer();
3779 for (int i = 0; i != d.splitter_->count(); ++i) {
3780 GuiWorkArea * wa_ = d.tabWorkArea(i)->workArea(buf);
3781 if (wa_ && wa_ != wa)
3784 return inOtherView(buf);
3788 bool GuiView::inOtherView(Buffer & buf)
3790 QList<int> const ids = guiApp->viewIds();
3792 for (int i = 0; i != ids.size(); ++i) {
3796 if (guiApp->view(ids[i]).workArea(buf))
3803 void GuiView::gotoNextOrPreviousBuffer(NextOrPrevious np, bool const move)
3805 if (!documentBufferView())
3808 if (TabWorkArea * twa = d.currentTabWorkArea()) {
3809 Buffer * const curbuf = &documentBufferView()->buffer();
3810 int nwa = twa->count();
3811 for (int i = 0; i < nwa; ++i) {
3812 if (&workArea(i)->bufferView().buffer() == curbuf) {
3815 next_index = (i == nwa - 1 ? 0 : i + 1);
3817 next_index = (i == 0 ? nwa - 1 : i - 1);
3819 twa->moveTab(i, next_index);
3821 setBuffer(&workArea(next_index)->bufferView().buffer());
3829 void GuiView::gotoNextTabWorkArea(NextOrPrevious np)
3831 int count = d.splitter_->count();
3832 for (int i = 0; i < count; ++i) {
3833 if (d.tabWorkArea(i) == d.currentTabWorkArea()) {
3836 new_index = (i == count - 1 ? 0 : i + 1);
3838 new_index = (i == 0 ? count - 1 : i - 1);
3839 setCurrentWorkArea(d.tabWorkArea(new_index)->currentWorkArea());
3846 /// make sure the document is saved
3847 static bool ensureBufferClean(Buffer * buffer)
3849 LASSERT(buffer, return false);
3850 if (buffer->isClean() && !buffer->isUnnamed())
3853 docstring const file = buffer->fileName().displayName(30);
3856 if (!buffer->isUnnamed()) {
3857 text = bformat(_("The document %1$s has unsaved "
3858 "changes.\n\nDo you want to save "
3859 "the document?"), file);
3860 title = _("Save changed document?");
3863 text = bformat(_("The document %1$s has not been "
3864 "saved yet.\n\nDo you want to save "
3865 "the document?"), file);
3866 title = _("Save new document?");
3868 int const ret = Alert::prompt(title, text, 0, 1, _("&Save"), _("&Cancel"));
3871 dispatch(FuncRequest(LFUN_BUFFER_WRITE));
3873 return buffer->isClean() && !buffer->isUnnamed();
3877 bool GuiView::reloadBuffer(Buffer & buf)
3879 currentBufferView()->cursor().reset();
3880 Buffer::ReadStatus status = buf.reload();
3881 return status == Buffer::ReadSuccess;
3885 void GuiView::checkExternallyModifiedBuffers()
3887 for (Buffer * buf : theBufferList()) {
3888 if (buf->fileName().exists() && buf->isChecksumModified()) {
3889 docstring text = bformat(_("Document \n%1$s\n has been externally modified."
3890 " Reload now? Any local changes will be lost."),
3891 from_utf8(buf->absFileName()));
3892 int const ret = Alert::prompt(_("Reload externally changed document?"),
3893 text, 0, 1, _("&Reload"), _("&Cancel"));
3901 void GuiView::dispatchVC(FuncRequest const & cmd, DispatchResult & dr)
3903 Buffer * buffer = documentBufferView()
3904 ? &(documentBufferView()->buffer()) : nullptr;
3906 switch (cmd.action()) {
3907 case LFUN_VC_REGISTER:
3908 if (!buffer || !ensureBufferClean(buffer))
3910 if (!buffer->lyxvc().inUse()) {
3911 if (buffer->lyxvc().registrer()) {
3912 reloadBuffer(*buffer);
3913 dr.clearMessageUpdate();
3918 case LFUN_VC_RENAME:
3919 case LFUN_VC_COPY: {
3920 if (!buffer || !ensureBufferClean(buffer))
3922 if (buffer->lyxvc().inUse() && !buffer->hasReadonlyFlag()) {
3923 if (buffer->lyxvc().isCheckInWithConfirmation()) {
3924 // Some changes are not yet committed.
3925 // We test here and not in getStatus(), since
3926 // this test is expensive.
3928 LyXVC::CommandResult ret =
3929 buffer->lyxvc().checkIn(log);
3931 if (ret == LyXVC::ErrorCommand ||
3932 ret == LyXVC::VCSuccess)
3933 reloadBuffer(*buffer);
3934 if (buffer->lyxvc().isCheckInWithConfirmation()) {
3935 frontend::Alert::error(
3936 _("Revision control error."),
3937 _("Document could not be checked in."));
3941 RenameKind const kind = (cmd.action() == LFUN_VC_RENAME) ?
3942 LV_VC_RENAME : LV_VC_COPY;
3943 renameBuffer(*buffer, cmd.argument(), kind);
3948 case LFUN_VC_CHECK_IN:
3949 if (!buffer || !ensureBufferClean(buffer))
3951 if (buffer->lyxvc().inUse() && !buffer->hasReadonlyFlag()) {
3953 LyXVC::CommandResult ret = buffer->lyxvc().checkIn(log);
3955 // Only skip reloading if the checkin was cancelled or
3956 // an error occurred before the real checkin VCS command
3957 // was executed, since the VCS might have changed the
3958 // file even if it could not checkin successfully.
3959 if (ret == LyXVC::ErrorCommand || ret == LyXVC::VCSuccess)
3960 reloadBuffer(*buffer);
3964 case LFUN_VC_CHECK_OUT:
3965 if (!buffer || !ensureBufferClean(buffer))
3967 if (buffer->lyxvc().inUse()) {
3968 dr.setMessage(buffer->lyxvc().checkOut());
3969 reloadBuffer(*buffer);
3973 case LFUN_VC_LOCKING_TOGGLE:
3974 if (!buffer || !ensureBufferClean(buffer) || buffer->hasReadonlyFlag())
3976 if (buffer->lyxvc().inUse()) {
3977 string res = buffer->lyxvc().lockingToggle();
3979 frontend::Alert::error(_("Revision control error."),
3980 _("Error when setting the locking property."));
3983 reloadBuffer(*buffer);
3988 case LFUN_VC_REVERT:
3991 if (buffer->lyxvc().revert()) {
3992 reloadBuffer(*buffer);
3993 dr.clearMessageUpdate();
3997 case LFUN_VC_UNDO_LAST:
4000 buffer->lyxvc().undoLast();
4001 reloadBuffer(*buffer);
4002 dr.clearMessageUpdate();
4005 case LFUN_VC_REPO_UPDATE:
4008 if (ensureBufferClean(buffer)) {
4009 dr.setMessage(buffer->lyxvc().repoUpdate());
4010 checkExternallyModifiedBuffers();
4014 case LFUN_VC_COMMAND: {
4015 string flag = cmd.getArg(0);
4016 if (buffer && contains(flag, 'R') && !ensureBufferClean(buffer))
4019 if (contains(flag, 'M')) {
4020 if (!Alert::askForText(message, _("LyX VC: Log Message")))
4023 string path = cmd.getArg(1);
4024 if (contains(path, "$$p") && buffer)
4025 path = subst(path, "$$p", buffer->filePath());
4026 LYXERR(Debug::LYXVC, "Directory: " << path);
4028 if (!pp.isReadableDirectory()) {
4029 lyxerr << _("Directory is not accessible.") << endl;
4032 support::PathChanger p(pp);
4034 string command = cmd.getArg(2);
4035 if (command.empty())
4038 command = subst(command, "$$i", buffer->absFileName());
4039 command = subst(command, "$$p", buffer->filePath());
4041 command = subst(command, "$$m", to_utf8(message));
4042 LYXERR(Debug::LYXVC, "Command: " << command);
4044 one.startscript(Systemcall::Wait, command);
4048 if (contains(flag, 'I'))
4049 buffer->markDirty();
4050 if (contains(flag, 'R'))
4051 reloadBuffer(*buffer);
4056 case LFUN_VC_COMPARE: {
4057 if (cmd.argument().empty()) {
4058 lyx::dispatch(FuncRequest(LFUN_DIALOG_SHOW, "comparehistory"));
4064 string rev1 = cmd.getArg(0);
4068 if (!buffer->lyxvc().prepareFileRevision(rev1, f1))
4071 if (isStrInt(rev1) && convert<int>(rev1) <= 0) {
4072 f2 = buffer->absFileName();
4074 string rev2 = cmd.getArg(1);
4078 if (!buffer->lyxvc().prepareFileRevision(rev2, f2))
4082 LYXERR(Debug::LYXVC, "Launching comparison for fetched revisions:\n" <<
4083 f1 << "\n" << f2 << "\n" );
4084 string par = "compare run " + quoteName(f1) + " " + quoteName(f2);
4085 lyx::dispatch(FuncRequest(LFUN_DIALOG_SHOW, par));
4095 void GuiView::openChildDocument(string const & fname)
4097 LASSERT(documentBufferView(), return);
4098 Buffer & buffer = documentBufferView()->buffer();
4099 FileName const filename = support::makeAbsPath(fname, buffer.filePath());
4100 documentBufferView()->saveBookmark(false);
4101 Buffer * child = nullptr;
4102 if (theBufferList().exists(filename)) {
4103 child = theBufferList().getBuffer(filename);
4106 message(bformat(_("Opening child document %1$s..."),
4107 makeDisplayPath(filename.absFileName())));
4108 child = loadDocument(filename, false);
4110 // Set the parent name of the child document.
4111 // This makes insertion of citations and references in the child work,
4112 // when the target is in the parent or another child document.
4114 child->setParent(&buffer);
4118 bool GuiView::goToFileRow(string const & argument)
4122 size_t i = argument.find_last_of(' ');
4123 if (i != string::npos) {
4124 file_name = os::internal_path(FileName(trim(argument.substr(0, i))).realPath());
4125 istringstream is(argument.substr(i + 1));
4130 if (i == string::npos) {
4131 LYXERR0("Wrong argument: " << argument);
4134 Buffer * buf = nullptr;
4135 string const realtmp = package().temp_dir().realPath();
4136 // We have to use os::path_prefix_is() here, instead of
4137 // simply prefixIs(), because the file name comes from
4138 // an external application and may need case adjustment.
4139 if (os::path_prefix_is(file_name, realtmp, os::CASE_ADJUSTED)) {
4140 buf = theBufferList().getBufferFromTmp(file_name, true);
4141 LYXERR(Debug::FILES, "goToFileRow: buffer lookup for " << file_name
4142 << (buf ? " success" : " failed"));
4144 // Must replace extension of the file to be .lyx
4145 // and get full path
4146 FileName const s = fileSearch(string(),
4147 support::changeExtension(file_name, ".lyx"), "lyx");
4148 // Either change buffer or load the file
4149 if (theBufferList().exists(s))
4150 buf = theBufferList().getBuffer(s);
4151 else if (s.exists()) {
4152 buf = loadDocument(s);
4157 _("File does not exist: %1$s"),
4158 makeDisplayPath(file_name)));
4164 _("No buffer for file: %1$s."),
4165 makeDisplayPath(file_name))
4170 bool success = documentBufferView()->setCursorFromRow(row);
4172 LYXERR(Debug::OUTFILE,
4173 "setCursorFromRow: invalid position for row " << row);
4174 frontend::Alert::error(_("Inverse Search Failed"),
4175 _("Invalid position requested by inverse search.\n"
4176 "You may need to update the viewed document."));
4183 Buffer::ExportStatus GuiView::GuiViewPrivate::runAndDestroy(const T& func,
4184 Buffer const * orig, Buffer * clone, string const & format)
4186 Buffer::ExportStatus const status = func(format);
4188 // the cloning operation will have produced a clone of the entire set of
4189 // documents, starting from the master. so we must delete those.
4190 Buffer * mbuf = const_cast<Buffer *>(clone->masterBuffer());
4192 busyBuffers.remove(orig);
4197 Buffer::ExportStatus GuiView::GuiViewPrivate::compileAndDestroy(
4198 Buffer const * orig, Buffer * clone, string const & format)
4200 Buffer::ExportStatus (Buffer::* mem_func)(std::string const &, bool) const =
4202 return runAndDestroy(lyx::bind(mem_func, clone, _1, true), orig, clone, format);
4206 Buffer::ExportStatus GuiView::GuiViewPrivate::exportAndDestroy(
4207 Buffer const * orig, Buffer * clone, string const & format)
4209 Buffer::ExportStatus (Buffer::* mem_func)(std::string const &, bool) const =
4211 return runAndDestroy(lyx::bind(mem_func, clone, _1, false), orig, clone, format);
4215 Buffer::ExportStatus GuiView::GuiViewPrivate::previewAndDestroy(
4216 Buffer const * orig, Buffer * clone, string const & format)
4218 Buffer::ExportStatus (Buffer::* mem_func)(std::string const &) const =
4220 return runAndDestroy(lyx::bind(mem_func, clone, _1), orig, clone, format);
4224 bool GuiView::GuiViewPrivate::asyncBufferProcessing(string const & argument,
4225 Buffer const * used_buffer,
4226 docstring const & msg,
4227 Buffer::ExportStatus (*asyncFunc)(Buffer const *, Buffer *, string const &),
4228 Buffer::ExportStatus (Buffer::*syncFunc)(string const &, bool) const,
4229 Buffer::ExportStatus (Buffer::*previewFunc)(string const &) const,
4230 bool allow_async, bool use_tmpdir)
4235 string format = argument;
4237 format = used_buffer->params().getDefaultOutputFormat();
4238 processing_format = format;
4240 progress_->clearMessages();
4243 #if EXPORT_in_THREAD
4245 GuiViewPrivate::busyBuffers.insert(used_buffer);
4246 Buffer * cloned_buffer = used_buffer->cloneWithChildren();
4247 if (!cloned_buffer) {
4248 Alert::error(_("Export Error"),
4249 _("Error cloning the Buffer."));
4252 QFuture<Buffer::ExportStatus> f = QtConcurrent::run(
4257 setPreviewFuture(f);
4258 last_export_format = used_buffer->params().bufferFormat();
4261 // We are asynchronous, so we don't know here anything about the success
4264 Buffer::ExportStatus status;
4266 status = (used_buffer->*syncFunc)(format, use_tmpdir);
4267 } else if (previewFunc) {
4268 status = (used_buffer->*previewFunc)(format);
4271 handleExportStatus(gv_, status, format);
4273 return (status == Buffer::ExportSuccess
4274 || status == Buffer::PreviewSuccess);
4278 Buffer::ExportStatus status;
4280 status = (used_buffer->*syncFunc)(format, true);
4281 } else if (previewFunc) {
4282 status = (used_buffer->*previewFunc)(format);
4285 handleExportStatus(gv_, status, format);
4287 return (status == Buffer::ExportSuccess
4288 || status == Buffer::PreviewSuccess);
4292 void GuiView::dispatchToBufferView(FuncRequest const & cmd, DispatchResult & dr)
4294 BufferView * bv = currentBufferView();
4295 LASSERT(bv, return);
4297 // Let the current BufferView dispatch its own actions.
4298 bv->dispatch(cmd, dr);
4299 if (dr.dispatched()) {
4300 if (cmd.action() == LFUN_REDO || cmd.action() == LFUN_UNDO)
4301 updateDialog("document", "");
4305 // Try with the document BufferView dispatch if any.
4306 BufferView * doc_bv = documentBufferView();
4307 if (doc_bv && doc_bv != bv) {
4308 doc_bv->dispatch(cmd, dr);
4309 if (dr.dispatched()) {
4310 if (cmd.action() == LFUN_REDO || cmd.action() == LFUN_UNDO)
4311 updateDialog("document", "");
4316 // Then let the current Cursor dispatch its own actions.
4317 bv->cursor().dispatch(cmd);
4319 // update completion. We do it here and not in
4320 // processKeySym to avoid another redraw just for a
4321 // changed inline completion
4322 if (cmd.origin() == FuncRequest::KEYBOARD) {
4323 if (cmd.action() == LFUN_SELF_INSERT
4324 || (cmd.action() == LFUN_ERT_INSERT && bv->cursor().inMathed()))
4325 updateCompletion(bv->cursor(), true, true);
4326 else if (cmd.action() == LFUN_CHAR_DELETE_BACKWARD)
4327 updateCompletion(bv->cursor(), false, true);
4329 updateCompletion(bv->cursor(), false, false);
4332 dr = bv->cursor().result();
4336 void GuiView::dispatch(FuncRequest const & cmd, DispatchResult & dr)
4338 BufferView * bv = currentBufferView();
4339 // By default we won't need any update.
4340 dr.screenUpdate(Update::None);
4341 // assume cmd will be dispatched
4342 dr.dispatched(true);
4344 Buffer * doc_buffer = documentBufferView()
4345 ? &(documentBufferView()->buffer()) : nullptr;
4347 if (cmd.origin() == FuncRequest::TOC) {
4348 GuiToc * toc = static_cast<GuiToc*>(findOrBuild("toc", false));
4349 toc->doDispatch(bv->cursor(), cmd, dr);
4353 string const argument = to_utf8(cmd.argument());
4355 switch(cmd.action()) {
4356 case LFUN_BUFFER_CHILD_OPEN:
4357 openChildDocument(to_utf8(cmd.argument()));
4360 case LFUN_BUFFER_IMPORT:
4361 importDocument(to_utf8(cmd.argument()));
4364 case LFUN_MASTER_BUFFER_EXPORT:
4366 doc_buffer = const_cast<Buffer *>(doc_buffer->masterBuffer());
4368 case LFUN_BUFFER_EXPORT: {
4371 // GCC only sees strfwd.h when building merged
4372 if (::lyx::operator==(cmd.argument(), "custom")) {
4373 // LFUN_MASTER_BUFFER_EXPORT is not enabled for this case,
4374 // so the following test should not be needed.
4375 // In principle, we could try to switch to such a view...
4376 // if (cmd.action() == LFUN_BUFFER_EXPORT)
4377 dispatch(FuncRequest(LFUN_DIALOG_SHOW, "sendto"), dr);
4381 string const dest = cmd.getArg(1);
4382 FileName target_dir;
4383 if (!dest.empty() && FileName::isAbsolute(dest))
4384 target_dir = FileName(support::onlyPath(dest));
4386 target_dir = doc_buffer->fileName().onlyPath();
4388 string const format = (argument.empty() || argument == "default") ?
4389 doc_buffer->params().getDefaultOutputFormat() : argument;
4391 if ((dest.empty() && doc_buffer->isUnnamed())
4392 || !target_dir.isDirWritable()) {
4393 exportBufferAs(*doc_buffer, from_utf8(format));
4396 /* TODO/Review: Is it a problem to also export the children?
4397 See the update_unincluded flag */
4398 d.asyncBufferProcessing(format,
4401 &GuiViewPrivate::exportAndDestroy,
4403 nullptr, cmd.allowAsync());
4404 // TODO Inform user about success
4408 case LFUN_BUFFER_EXPORT_AS: {
4409 LASSERT(doc_buffer, break);
4410 docstring f = cmd.argument();
4412 f = from_ascii(doc_buffer->params().getDefaultOutputFormat());
4413 exportBufferAs(*doc_buffer, f);
4417 case LFUN_BUFFER_UPDATE: {
4418 d.asyncBufferProcessing(argument,
4421 &GuiViewPrivate::compileAndDestroy,
4423 nullptr, cmd.allowAsync(), true);
4426 case LFUN_BUFFER_VIEW: {
4427 d.asyncBufferProcessing(argument,
4429 _("Previewing ..."),
4430 &GuiViewPrivate::previewAndDestroy,
4432 &Buffer::preview, cmd.allowAsync());
4435 case LFUN_MASTER_BUFFER_UPDATE: {
4436 d.asyncBufferProcessing(argument,
4437 (doc_buffer ? doc_buffer->masterBuffer() : nullptr),
4439 &GuiViewPrivate::compileAndDestroy,
4441 nullptr, cmd.allowAsync(), true);
4444 case LFUN_MASTER_BUFFER_VIEW: {
4445 d.asyncBufferProcessing(argument,
4446 (doc_buffer ? doc_buffer->masterBuffer() : nullptr),
4448 &GuiViewPrivate::previewAndDestroy,
4449 nullptr, &Buffer::preview, cmd.allowAsync());
4452 case LFUN_EXPORT_CANCEL: {
4456 case LFUN_BUFFER_SWITCH: {
4457 string const file_name = to_utf8(cmd.argument());
4458 if (!FileName::isAbsolute(file_name)) {
4460 dr.setMessage(_("Absolute filename expected."));
4464 Buffer * buffer = theBufferList().getBuffer(FileName(file_name));
4467 dr.setMessage(_("Document not loaded"));
4471 // Do we open or switch to the buffer in this view ?
4472 if (workArea(*buffer)
4473 || lyxrc.open_buffers_in_tabs || !documentBufferView()) {
4478 // Look for the buffer in other views
4479 QList<int> const ids = guiApp->viewIds();
4481 for (; i != ids.size(); ++i) {
4482 GuiView & gv = guiApp->view(ids[i]);
4483 if (gv.workArea(*buffer)) {
4485 gv.activateWindow();
4487 gv.setBuffer(buffer);
4492 // If necessary, open a new window as a last resort
4493 if (i == ids.size()) {
4494 lyx::dispatch(FuncRequest(LFUN_WINDOW_NEW));
4500 case LFUN_BUFFER_NEXT:
4501 gotoNextOrPreviousBuffer(NEXT, false);
4504 case LFUN_BUFFER_MOVE_NEXT:
4505 gotoNextOrPreviousBuffer(NEXT, true);
4508 case LFUN_BUFFER_PREVIOUS:
4509 gotoNextOrPreviousBuffer(PREV, false);
4512 case LFUN_BUFFER_MOVE_PREVIOUS:
4513 gotoNextOrPreviousBuffer(PREV, true);
4516 case LFUN_BUFFER_CHKTEX:
4517 LASSERT(doc_buffer, break);
4518 doc_buffer->runChktex();
4521 case LFUN_COMMAND_EXECUTE: {
4522 command_execute_ = true;
4523 minibuffer_focus_ = true;
4526 case LFUN_DROP_LAYOUTS_CHOICE:
4527 d.layout_->showPopup();
4530 case LFUN_MENU_OPEN:
4531 if (QMenu * menu = guiApp->menus().menu(toqstr(cmd.argument()), *this))
4532 menu->exec(QCursor::pos());
4535 case LFUN_FILE_INSERT: {
4536 bool const ignore_lang = cmd.getArg(1) == "ignorelang";
4537 if (insertLyXFile(from_utf8(cmd.getArg(0)), ignore_lang)) {
4538 dr.forceBufferUpdate();
4539 dr.screenUpdate(Update::Force);
4544 case LFUN_FILE_INSERT_PLAINTEXT:
4545 case LFUN_FILE_INSERT_PLAINTEXT_PARA: {
4546 string const fname = to_utf8(cmd.argument());
4547 if (!fname.empty() && !FileName::isAbsolute(fname)) {
4548 dr.setMessage(_("Absolute filename expected."));
4552 FileName filename(fname);
4553 if (fname.empty()) {
4554 FileDialog dlg(qt_("Select file to insert"));
4556 FileDialog::Result result = dlg.open(toqstr(bv->buffer().filePath()),
4557 QStringList(qt_("All Files (*)")));
4559 if (result.first == FileDialog::Later || result.second.isEmpty()) {
4560 dr.setMessage(_("Canceled."));
4564 filename.set(fromqstr(result.second));
4568 FuncRequest const new_cmd(cmd, filename.absoluteFilePath());
4569 bv->dispatch(new_cmd, dr);
4574 case LFUN_BUFFER_RELOAD: {
4575 LASSERT(doc_buffer, break);
4578 bool drop = (cmd.argument() == "dump");
4581 if (!drop && !doc_buffer->isClean()) {
4582 docstring const file =
4583 makeDisplayPath(doc_buffer->absFileName(), 20);
4584 if (doc_buffer->notifiesExternalModification()) {
4585 docstring text = _("The current version will be lost. "
4586 "Are you sure you want to load the version on disk "
4587 "of the document %1$s?");
4588 ret = Alert::prompt(_("Reload saved document?"),
4589 bformat(text, file), 1, 1,
4590 _("&Reload"), _("&Cancel"));
4592 docstring text = _("Any changes will be lost. "
4593 "Are you sure you want to revert to the saved version "
4594 "of the document %1$s?");
4595 ret = Alert::prompt(_("Revert to saved document?"),
4596 bformat(text, file), 1, 1,
4597 _("&Revert"), _("&Cancel"));
4602 doc_buffer->markClean();
4603 reloadBuffer(*doc_buffer);
4604 dr.forceBufferUpdate();
4609 case LFUN_BUFFER_RESET_EXPORT:
4610 LASSERT(doc_buffer, break);
4611 doc_buffer->requireFreshStart(true);
4612 dr.setMessage(_("Buffer export reset."));
4615 case LFUN_BUFFER_WRITE:
4616 LASSERT(doc_buffer, break);
4617 saveBuffer(*doc_buffer);
4620 case LFUN_BUFFER_WRITE_AS:
4621 LASSERT(doc_buffer, break);
4622 renameBuffer(*doc_buffer, cmd.argument());
4625 case LFUN_BUFFER_WRITE_AS_TEMPLATE:
4626 LASSERT(doc_buffer, break);
4627 renameBuffer(*doc_buffer, cmd.argument(),
4628 LV_WRITE_AS_TEMPLATE);
4631 case LFUN_BUFFER_WRITE_ALL: {
4632 Buffer * first = theBufferList().first();
4635 message(_("Saving all documents..."));
4636 // We cannot use a for loop as the buffer list cycles.
4639 if (!b->isClean()) {
4641 LYXERR(Debug::ACTION, "Saved " << b->absFileName());
4643 b = theBufferList().next(b);
4644 } while (b != first);
4645 dr.setMessage(_("All documents saved."));
4649 case LFUN_MASTER_BUFFER_FORALL: {
4653 FuncRequest funcToRun = lyxaction.lookupFunc(cmd.getLongArg(0));
4654 funcToRun.allowAsync(false);
4656 for (Buffer const * buf : doc_buffer->allRelatives()) {
4657 // Switch to other buffer view and resend cmd
4658 lyx::dispatch(FuncRequest(
4659 LFUN_BUFFER_SWITCH, buf->absFileName()));
4660 lyx::dispatch(funcToRun);
4663 lyx::dispatch(FuncRequest(
4664 LFUN_BUFFER_SWITCH, doc_buffer->absFileName()));
4668 case LFUN_BUFFER_EXTERNAL_MODIFICATION_CLEAR:
4669 LASSERT(doc_buffer, break);
4670 doc_buffer->clearExternalModification();
4673 case LFUN_BUFFER_CLOSE:
4677 case LFUN_BUFFER_CLOSE_ALL:
4681 case LFUN_DEVEL_MODE_TOGGLE:
4682 devel_mode_ = !devel_mode_;
4684 dr.setMessage(_("Developer mode is now enabled."));
4686 dr.setMessage(_("Developer mode is now disabled."));
4689 case LFUN_TOOLBAR_SET: {
4690 string const name = cmd.getArg(0);
4691 string const state = cmd.getArg(1);
4692 if (GuiToolbar * t = toolbar(name))
4697 case LFUN_TOOLBAR_TOGGLE: {
4698 string const name = cmd.getArg(0);
4699 if (GuiToolbar * t = toolbar(name))
4704 case LFUN_TOOLBAR_MOVABLE: {
4705 string const name = cmd.getArg(0);
4707 // toggle (all) toolbars movablility
4708 toolbarsMovable_ = !toolbarsMovable_;
4709 for (ToolbarInfo const & ti : guiApp->toolbars()) {
4710 GuiToolbar * tb = toolbar(ti.name);
4711 if (tb && tb->isMovable() != toolbarsMovable_)
4712 // toggle toolbar movablity if it does not fit lock
4713 // (all) toolbars positions state silent = true, since
4714 // status bar notifications are slow
4717 if (toolbarsMovable_)
4718 dr.setMessage(_("Toolbars unlocked."));
4720 dr.setMessage(_("Toolbars locked."));
4721 } else if (GuiToolbar * tb = toolbar(name))
4722 // toggle current toolbar movablity
4724 // update lock (all) toolbars positions
4725 updateLockToolbars();
4729 case LFUN_ICON_SIZE: {
4730 QSize size = d.iconSize(cmd.argument());
4732 dr.setMessage(bformat(_("Icon size set to %1$dx%2$d."),
4733 size.width(), size.height()));
4737 case LFUN_DIALOG_UPDATE: {
4738 string const name = to_utf8(cmd.argument());
4739 if (name == "prefs" || name == "document")
4740 updateDialog(name, string());
4741 else if (name == "paragraph")
4742 lyx::dispatch(FuncRequest(LFUN_PARAGRAPH_UPDATE));
4743 else if (currentBufferView()) {
4744 Inset * inset = currentBufferView()->editedInset(name);
4745 // Can only update a dialog connected to an existing inset
4747 // FIXME: get rid of this indirection; GuiView ask the inset
4748 // if he is kind enough to update itself...
4749 FuncRequest fr(LFUN_INSET_DIALOG_UPDATE, cmd.argument());
4750 //FIXME: pass DispatchResult here?
4751 inset->dispatch(currentBufferView()->cursor(), fr);
4757 case LFUN_DIALOG_TOGGLE: {
4758 FuncCode const func_code = isDialogVisible(cmd.getArg(0))
4759 ? LFUN_DIALOG_HIDE : LFUN_DIALOG_SHOW;
4760 dispatch(FuncRequest(func_code, cmd.argument()), dr);
4764 case LFUN_DIALOG_DISCONNECT_INSET:
4765 disconnectDialog(to_utf8(cmd.argument()));
4768 case LFUN_DIALOG_HIDE: {
4769 guiApp->hideDialogs(to_utf8(cmd.argument()), nullptr);
4773 case LFUN_DIALOG_SHOW: {
4774 string const name = cmd.getArg(0);
4775 string sdata = trim(to_utf8(cmd.argument()).substr(name.size()));
4777 if (name == "latexlog") {
4778 // getStatus checks that
4779 LASSERT(doc_buffer, break);
4780 Buffer::LogType type;
4781 string const logfile = doc_buffer->logName(&type);
4783 case Buffer::latexlog:
4786 case Buffer::buildlog:
4787 sdata = "literate ";
4790 sdata += Lexer::quoteString(logfile);
4791 showDialog("log", sdata);
4792 } else if (name == "vclog") {
4793 // getStatus checks that
4794 LASSERT(doc_buffer, break);
4795 string const sdata2 = "vc " +
4796 Lexer::quoteString(doc_buffer->lyxvc().getLogFile());
4797 showDialog("log", sdata2);
4798 } else if (name == "symbols") {
4799 sdata = bv->cursor().getEncoding()->name();
4801 showDialog("symbols", sdata);
4802 } else if (name == "findreplace") {
4803 sdata = to_utf8(bv->cursor().selectionAsString(false));
4804 showDialog(name, sdata);
4806 } else if (name == "prefs" && isFullScreen()) {
4807 lfunUiToggle("fullscreen");
4808 showDialog("prefs", sdata);
4810 showDialog(name, sdata);
4815 dr.setMessage(cmd.argument());
4818 case LFUN_UI_TOGGLE: {
4819 string arg = cmd.getArg(0);
4820 if (!lfunUiToggle(arg)) {
4821 docstring const msg = "ui-toggle " + _("%1$s unknown command!");
4822 dr.setMessage(bformat(msg, from_utf8(arg)));
4824 // Make sure the keyboard focus stays in the work area.
4829 case LFUN_VIEW_SPLIT: {
4830 LASSERT(doc_buffer, break);
4831 string const orientation = cmd.getArg(0);
4832 d.splitter_->setOrientation(orientation == "vertical"
4833 ? Qt::Vertical : Qt::Horizontal);
4834 TabWorkArea * twa = addTabWorkArea();
4835 GuiWorkArea * wa = twa->addWorkArea(*doc_buffer, *this);
4836 setCurrentWorkArea(wa);
4840 case LFUN_TAB_GROUP_NEXT:
4841 gotoNextTabWorkArea(NEXT);
4844 case LFUN_TAB_GROUP_PREVIOUS:
4845 gotoNextTabWorkArea(PREV);
4848 case LFUN_TAB_GROUP_CLOSE:
4849 if (TabWorkArea * twa = d.currentTabWorkArea()) {
4850 closeTabWorkArea(twa);
4851 d.current_work_area_ = nullptr;
4852 twa = d.currentTabWorkArea();
4853 // Switch to the next GuiWorkArea in the found TabWorkArea.
4855 // Make sure the work area is up to date.
4856 setCurrentWorkArea(twa->currentWorkArea());
4858 setCurrentWorkArea(nullptr);
4863 case LFUN_VIEW_CLOSE:
4864 if (TabWorkArea * twa = d.currentTabWorkArea()) {
4865 closeWorkArea(twa->currentWorkArea());
4866 d.current_work_area_ = nullptr;
4867 twa = d.currentTabWorkArea();
4868 // Switch to the next GuiWorkArea in the found TabWorkArea.
4870 // Make sure the work area is up to date.
4871 setCurrentWorkArea(twa->currentWorkArea());
4873 setCurrentWorkArea(nullptr);
4878 case LFUN_COMPLETION_INLINE:
4879 if (d.current_work_area_)
4880 d.current_work_area_->completer().showInline();
4883 case LFUN_COMPLETION_POPUP:
4884 if (d.current_work_area_)
4885 d.current_work_area_->completer().showPopup();
4890 if (d.current_work_area_)
4891 d.current_work_area_->completer().tab();
4894 case LFUN_COMPLETION_CANCEL:
4895 if (d.current_work_area_) {
4896 if (d.current_work_area_->completer().popupVisible())
4897 d.current_work_area_->completer().hidePopup();
4899 d.current_work_area_->completer().hideInline();
4903 case LFUN_COMPLETION_ACCEPT:
4904 if (d.current_work_area_)
4905 d.current_work_area_->completer().activate();
4908 case LFUN_BUFFER_ZOOM_IN:
4909 case LFUN_BUFFER_ZOOM_OUT:
4910 case LFUN_BUFFER_ZOOM: {
4911 zoom_ratio_ = zoomRatio(cmd, zoom_ratio_);
4913 // Actual zoom value: default zoom + fractional extra value
4914 int zoom = (int)(lyxrc.defaultZoom * zoom_ratio_);
4915 zoom = min(max(zoom, zoom_min_), zoom_max_);
4917 setCurrentZoom(zoom);
4919 dr.setMessage(bformat(_("Zoom level is now %1$d% (default value: %2$d%)"),
4920 lyxrc.currentZoom, lyxrc.defaultZoom));
4922 guiApp->fontLoader().update();
4923 // Regenerate instant previews
4924 if (lyxrc.preview != LyXRC::PREVIEW_OFF
4925 && doc_buffer && doc_buffer->loader())
4926 doc_buffer->loader()->refreshPreviews();
4927 dr.screenUpdate(Update::ForceAll | Update::FitCursor);
4931 case LFUN_VC_REGISTER:
4932 case LFUN_VC_RENAME:
4934 case LFUN_VC_CHECK_IN:
4935 case LFUN_VC_CHECK_OUT:
4936 case LFUN_VC_REPO_UPDATE:
4937 case LFUN_VC_LOCKING_TOGGLE:
4938 case LFUN_VC_REVERT:
4939 case LFUN_VC_UNDO_LAST:
4940 case LFUN_VC_COMMAND:
4941 case LFUN_VC_COMPARE:
4942 dispatchVC(cmd, dr);
4945 case LFUN_SERVER_GOTO_FILE_ROW:
4946 if(goToFileRow(to_utf8(cmd.argument())))
4947 dr.screenUpdate(Update::Force | Update::FitCursor);
4950 case LFUN_LYX_ACTIVATE:
4954 case LFUN_WINDOW_RAISE:
4960 case LFUN_FORWARD_SEARCH: {
4961 // it seems safe to assume we have a document buffer, since
4962 // getStatus wants one.
4963 LASSERT(doc_buffer, break);
4964 Buffer const * doc_master = doc_buffer->masterBuffer();
4965 FileName const path(doc_master->temppath());
4966 string const texname = doc_master->isChild(doc_buffer)
4967 ? DocFileName(changeExtension(
4968 doc_buffer->absFileName(),
4969 "tex")).mangledFileName()
4970 : doc_buffer->latexName();
4971 string const fulltexname =
4972 support::makeAbsPath(texname, doc_master->temppath()).absFileName();
4973 string const mastername =
4974 removeExtension(doc_master->latexName());
4975 FileName const dviname(addName(path.absFileName(),
4976 addExtension(mastername, "dvi")));
4977 FileName const pdfname(addName(path.absFileName(),
4978 addExtension(mastername, "pdf")));
4979 bool const have_dvi = dviname.exists();
4980 bool const have_pdf = pdfname.exists();
4981 if (!have_dvi && !have_pdf) {
4982 dr.setMessage(_("Please, preview the document first."));
4985 bool const goto_dvi = have_dvi && !lyxrc.forward_search_dvi.empty();
4986 bool const goto_pdf = have_pdf && !lyxrc.forward_search_pdf.empty();
4987 string outname = dviname.onlyFileName();
4988 string command = lyxrc.forward_search_dvi;
4989 if ((!goto_dvi || goto_pdf) &&
4990 pdfname.lastModified() > dviname.lastModified()) {
4991 outname = pdfname.onlyFileName();
4992 command = lyxrc.forward_search_pdf;
4995 DocIterator cur = bv->cursor();
4996 int row = doc_buffer->texrow().rowFromDocIterator(cur).first;
4997 LYXERR(Debug::ACTION, "Forward search: row:" << row
4999 if (row == -1 || command.empty()) {
5000 dr.setMessage(_("Couldn't proceed."));
5003 string texrow = convert<string>(row);
5005 command = subst(command, "$$n", texrow);
5006 command = subst(command, "$$f", fulltexname);
5007 command = subst(command, "$$t", texname);
5008 command = subst(command, "$$o", outname);
5010 volatile PathChanger p(path);
5012 one.startscript(Systemcall::DontWait, command);
5016 case LFUN_SPELLING_CONTINUOUSLY:
5017 lyxrc.spellcheck_continuously = !lyxrc.spellcheck_continuously;
5018 dr.screenUpdate(Update::Force);
5021 case LFUN_CITATION_OPEN: {
5023 if (theFormats().getFormat("pdf"))
5024 pdfv = theFormats().getFormat("pdf")->viewer();
5025 if (theFormats().getFormat("ps"))
5026 psv = theFormats().getFormat("ps")->viewer();
5027 frontend::showTarget(argument, pdfv, psv);
5032 // The LFUN must be for one of BufferView, Buffer or Cursor;
5034 dispatchToBufferView(cmd, dr);
5038 // Need to update bv because many LFUNs here might have destroyed it
5039 bv = currentBufferView();
5041 // Clear non-empty selections
5042 // (e.g. from a "char-forward-select" followed by "char-backward-select")
5044 Cursor & cur = bv->cursor();
5045 if ((cur.selection() && cur.selBegin() == cur.selEnd())) {
5046 cur.clearSelection();
5052 bool GuiView::lfunUiToggle(string const & ui_component)
5054 if (ui_component == "scrollbar") {
5055 // hide() is of no help
5056 if (d.current_work_area_->verticalScrollBarPolicy() ==
5057 Qt::ScrollBarAlwaysOff)
5059 d.current_work_area_->setVerticalScrollBarPolicy(
5060 Qt::ScrollBarAsNeeded);
5062 d.current_work_area_->setVerticalScrollBarPolicy(
5063 Qt::ScrollBarAlwaysOff);
5064 } else if (ui_component == "statusbar") {
5065 statusBar()->setVisible(!statusBar()->isVisible());
5066 } else if (ui_component == "menubar") {
5067 menuBar()->setVisible(!menuBar()->isVisible());
5068 } else if (ui_component == "zoomlevel") {
5069 zoom_value_->setVisible(!zoom_value_->isVisible());
5070 } else if (ui_component == "zoomslider") {
5071 zoom_slider_->setVisible(!zoom_slider_->isVisible());
5072 zoom_in_->setVisible(zoom_slider_->isVisible());
5073 zoom_out_->setVisible(zoom_slider_->isVisible());
5074 } else if (ui_component == "statistics-w") {
5075 word_count_enabled_ = !word_count_enabled_;
5078 } else if (ui_component == "statistics-cb") {
5079 char_count_enabled_ = !char_count_enabled_;
5082 } else if (ui_component == "statistics-c") {
5083 char_nb_count_enabled_ = !char_nb_count_enabled_;
5086 } else if (ui_component == "frame") {
5087 int const l = contentsMargins().left();
5089 //are the frames in default state?
5090 d.current_work_area_->setFrameStyle(QFrame::NoFrame);
5092 #if QT_VERSION > 0x050903
5093 setAttribute(Qt::WA_ContentsMarginsRespectsSafeArea, false);
5095 setContentsMargins(-2, -2, -2, -2);
5097 #if QT_VERSION > 0x050903
5098 setAttribute(Qt::WA_ContentsMarginsRespectsSafeArea, true);
5100 setContentsMargins(0, 0, 0, 0);
5103 if (ui_component == "fullscreen") {
5107 stat_counts_->setVisible(statsEnabled());
5112 void GuiView::cancelExport()
5114 Systemcall::killscript();
5115 // stop busy signal immediately so that in the subsequent
5116 // "Export canceled" prompt the status bar icons are accurate.
5117 Q_EMIT scriptKilled();
5121 void GuiView::toggleFullScreen()
5123 setWindowState(windowState() ^ Qt::WindowFullScreen);
5127 Buffer const * GuiView::updateInset(Inset const * inset)
5132 Buffer const * inset_buffer = &(inset->buffer());
5134 for (int i = 0; i != d.splitter_->count(); ++i) {
5135 GuiWorkArea * wa = d.tabWorkArea(i)->currentWorkArea();
5138 Buffer const * buffer = &(wa->bufferView().buffer());
5139 if (inset_buffer == buffer)
5140 wa->scheduleRedraw(true);
5142 return inset_buffer;
5146 void GuiView::restartCaret()
5148 /* When we move around, or type, it's nice to be able to see
5149 * the caret immediately after the keypress.
5151 if (d.current_work_area_)
5152 d.current_work_area_->startBlinkingCaret();
5154 // Take this occasion to update the other GUI elements.
5160 void GuiView::updateCompletion(Cursor & cur, bool start, bool keep)
5162 if (d.current_work_area_)
5163 d.current_work_area_->completer().updateVisibility(cur, start, keep);
5168 // This list should be kept in sync with the list of insets in
5169 // src/insets/Inset.cpp. I.e., if a dialog goes with an inset, the
5170 // dialog should have the same name as the inset.
5171 // Changes should be also recorded in LFUN_DIALOG_SHOW doxygen
5172 // docs in LyXAction.cpp.
5174 char const * const dialognames[] = {
5176 "aboutlyx", "bibitem", "bibtex", "box", "branch", "changes", "character",
5177 "citation", "compare", "comparehistory", "counter", "document", "errorlist", "ert",
5178 "external", "file", "findreplace", "findreplaceadv", "float", "graphics",
5179 "href", "include", "index", "index_print", "info", "listings", "label", "line",
5180 "log", "lyxfiles", "mathdelimiter", "mathmatrix", "mathspace", "nomenclature",
5181 "nomencl_print", "note", "paragraph", "phantom", "prefs", "ref",
5182 "sendto", "space", "spellchecker", "symbols", "tabular", "tabularcreate",
5183 "thesaurus", "texinfo", "toc", "view-source", "vspace", "wrap", "progress"};
5185 char const * const * const end_dialognames =
5186 dialognames + (sizeof(dialognames) / sizeof(char *));
5190 cmpCStr(char const * name) : name_(name) {}
5191 bool operator()(char const * other) {
5192 return strcmp(other, name_) == 0;
5199 bool isValidName(string const & name)
5201 return find_if(dialognames, end_dialognames,
5202 cmpCStr(name.c_str())) != end_dialognames;
5208 void GuiView::resetDialogs()
5210 // Make sure that no LFUN uses any GuiView.
5211 guiApp->setCurrentView(nullptr);
5215 constructToolbars();
5216 guiApp->menus().fillMenuBar(menuBar(), this, false);
5217 d.layout_->updateContents(true);
5218 // Now update controls with current buffer.
5219 guiApp->setCurrentView(this);
5225 void GuiView::flatGroupBoxes(const QObject * widget, bool flag)
5227 for (QObject * child: widget->children()) {
5228 if (child->inherits("QGroupBox")) {
5229 QGroupBox * box = (QGroupBox*) child;
5232 flatGroupBoxes(child, flag);
5238 Dialog * GuiView::find(string const & name, bool hide_it) const
5240 if (!isValidName(name))
5243 map<string, DialogPtr>::iterator it = d.dialogs_.find(name);
5245 if (it != d.dialogs_.end()) {
5247 it->second->hideView();
5248 return it->second.get();
5254 Dialog * GuiView::findOrBuild(string const & name, bool hide_it)
5256 Dialog * dialog = find(name, hide_it);
5257 if (dialog != nullptr)
5260 dialog = build(name);
5261 d.dialogs_[name].reset(dialog);
5262 // Force a uniform style for group boxes
5263 // On Mac non-flat works better, on Linux flat is standard
5264 flatGroupBoxes(dialog->asQWidget(), guiApp->platformName() != "cocoa");
5265 if (lyxrc.allow_geometry_session)
5266 dialog->restoreSession();
5273 void GuiView::showDialog(string const & name, string const & sdata,
5276 triggerShowDialog(toqstr(name), toqstr(sdata), inset);
5280 void GuiView::doShowDialog(QString const & qname, QString const & qdata,
5286 const string name = fromqstr(qname);
5287 const string sdata = fromqstr(qdata);
5291 Dialog * dialog = findOrBuild(name, false);
5293 bool const visible = dialog->isVisibleView();
5294 dialog->showData(sdata);
5295 if (currentBufferView())
5296 currentBufferView()->editInset(name, inset);
5297 // We only set the focus to the new dialog if it was not yet
5298 // visible in order not to change the existing previous behaviour
5300 // activateWindow is needed for floating dockviews
5301 dialog->asQWidget()->raise();
5302 dialog->asQWidget()->activateWindow();
5303 if (dialog->wantInitialFocus())
5304 dialog->asQWidget()->setFocus();
5308 catch (ExceptionMessage const &) {
5316 bool GuiView::isDialogVisible(string const & name) const
5318 map<string, DialogPtr>::const_iterator it = d.dialogs_.find(name);
5319 if (it == d.dialogs_.end())
5321 return it->second.get()->isVisibleView() && !it->second.get()->isClosing();
5325 void GuiView::hideDialog(string const & name, Inset * inset)
5327 map<string, DialogPtr>::const_iterator it = d.dialogs_.find(name);
5328 if (it == d.dialogs_.end())
5332 if (!currentBufferView())
5334 if (inset != currentBufferView()->editedInset(name))
5338 Dialog * const dialog = it->second.get();
5339 if (dialog->isVisibleView())
5341 if (currentBufferView())
5342 currentBufferView()->editInset(name, nullptr);
5346 void GuiView::disconnectDialog(string const & name)
5348 if (!isValidName(name))
5350 if (currentBufferView())
5351 currentBufferView()->editInset(name, nullptr);
5355 void GuiView::hideAll() const
5357 for(auto const & dlg_p : d.dialogs_)
5358 dlg_p.second->hideView();
5362 void GuiView::updateDialogs()
5364 for(auto const & dlg_p : d.dialogs_) {
5365 Dialog * dialog = dlg_p.second.get();
5367 if (dialog->needBufferOpen() && !documentBufferView())
5368 hideDialog(fromqstr(dialog->name()), nullptr);
5369 else if (dialog->isVisibleView())
5370 dialog->checkStatus();
5378 Dialog * GuiView::build(string const & name)
5380 return createDialog(*this, name);
5384 SEMenu::SEMenu(QWidget * parent)
5386 QAction * action = addAction(qt_("Disable Shell Escape"));
5387 connect(action, SIGNAL(triggered()),
5388 parent, SLOT(disableShellEscape()));
5392 void PressableSvgWidget::mousePressEvent(QMouseEvent * event)
5394 if (event->button() == Qt::LeftButton) {
5399 } // namespace frontend
5402 #include "moc_GuiView.cpp"