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 "support/convert.h"
73 #include "support/debug.h"
74 #include "support/ExceptionMessage.h"
75 #include "support/FileName.h"
76 #include "support/gettext.h"
77 #include "support/ForkedCalls.h"
78 #include "support/lassert.h"
79 #include "support/lstrings.h"
80 #include "support/os.h"
81 #include "support/Package.h"
82 #include "support/PathChanger.h"
83 #include "support/Systemcall.h"
84 #include "support/Timeout.h"
85 #include "support/ProgressInterface.h"
88 #include <QApplication>
89 #include <QCloseEvent>
90 #include <QDragEnterEvent>
93 #include <QFutureWatcher>
104 #include <QShowEvent>
107 #include <QStackedWidget>
108 #include <QStatusBar>
109 #include <QSvgRenderer>
110 #include <QtConcurrentRun>
113 #include <QWindowStateChangeEvent>
114 #include <QGestureEvent>
115 #include <QPinchGesture>
118 // sync with GuiAlert.cpp
119 #define EXPORT_in_THREAD 1
122 #include "support/bind.h"
126 #ifdef HAVE_SYS_TIME_H
127 # include <sys/time.h>
135 using namespace lyx::support;
139 using support::addExtension;
140 using support::changeExtension;
141 using support::removeExtension;
147 class BackgroundWidget : public QWidget
150 BackgroundWidget(int width, int height)
151 : width_(width), height_(height)
153 LYXERR(Debug::GUI, "show banner: " << lyxrc.show_banner);
154 if (!lyxrc.show_banner)
156 /// The text to be written on top of the pixmap
157 QString const htext = qt_("The Document\nProcessor[[welcome banner]]");
158 QString const htextsize = qt_("1.0[[possibly scale the welcome banner text size]]");
159 /// The text to be written on top of the pixmap
160 QString const text = lyx_version ?
161 qt_("version ") + lyx_version : qt_("unknown version");
162 #if QT_VERSION >= 0x050000
163 QString imagedir = "images/";
164 FileName fname = imageLibFileSearch(imagedir, "banner", "svgz");
165 QSvgRenderer svgRenderer(toqstr(fname.absFileName()));
166 if (svgRenderer.isValid()) {
167 splash_ = QPixmap(splashSize());
168 QPainter painter(&splash_);
169 svgRenderer.render(&painter);
170 splash_.setDevicePixelRatio(pixelRatio());
172 splash_ = getPixmap("images/", "banner", "png");
175 splash_ = getPixmap("images/", "banner", "svgz,png");
178 QPainter pain(&splash_);
179 pain.setPen(QColor(0, 0, 0));
180 qreal const fsize = fontSize();
183 qreal locscale = htextsize.toFloat(&ok);
186 QPointF const position = textPosition(false);
187 QPointF const hposition = textPosition(true);
188 QRectF const hrect(hposition, splashSize());
190 "widget pixel ratio: " << pixelRatio() <<
191 " splash pixel ratio: " << splashPixelRatio() <<
192 " version text size,position: " << fsize << "@" << position.x() << "+" << position.y());
194 // The font used to display the version info
195 font.setStyleHint(QFont::SansSerif);
196 font.setWeight(QFont::Bold);
197 font.setPointSizeF(fsize);
199 pain.drawText(position, text);
200 // The font used to display the version info
201 font.setStyleHint(QFont::SansSerif);
202 font.setWeight(QFont::Normal);
203 font.setPointSizeF(hfsize);
204 // Check how long the logo gets with the current font
205 // and adapt if the font is running wider than what
207 GuiFontMetrics fm(font);
208 // Split the title into lines to measure the longest line
209 // in the current l7n.
210 QStringList titlesegs = htext.split('\n');
212 int hline = fm.maxHeight();
213 for (QString const & seg : titlesegs) {
214 if (fm.width(seg) > wline)
215 wline = fm.width(seg);
217 // The longest line in the reference font (for English)
218 // is 180. Calculate scale factor from that.
219 double const wscale = wline > 0 ? (180.0 / wline) : 1;
220 // Now do the same for the height (necessary for condensed fonts)
221 double const hscale = (34.0 / hline);
222 // take the lower of the two scale factors.
223 double const scale = min(wscale, hscale);
224 // Now rescale. Also consider l7n's offset factor.
225 font.setPointSizeF(hfsize * scale * locscale);
228 pain.drawText(hrect, Qt::AlignLeft, htext);
229 setFocusPolicy(Qt::StrongFocus);
232 void paintEvent(QPaintEvent *) override
234 int const w = width_;
235 int const h = height_;
236 int const x = (width() - w) / 2;
237 int const y = (height() - h) / 2;
239 "widget pixel ratio: " << pixelRatio() <<
240 " splash pixel ratio: " << splashPixelRatio() <<
241 " paint pixmap: " << w << "x" << h << "@" << x << "+" << y);
243 pain.drawPixmap(x, y, w, h, splash_);
246 void keyPressEvent(QKeyEvent * ev) override
249 setKeySymbol(&sym, ev);
251 guiApp->processKeySym(sym, q_key_state(ev->modifiers()));
263 /// Current ratio between physical pixels and device-independent pixels
264 double pixelRatio() const {
265 #if QT_VERSION >= 0x050000
266 return qt_scale_factor * devicePixelRatio();
272 qreal fontSize() const {
273 return toqstr(lyxrc.font_sizes[NORMAL_SIZE]).toDouble();
276 QPointF textPosition(bool const heading) const {
277 return heading ? QPointF(width_/2 - 18, height_/2 - 45)
278 : QPointF(width_/2 - 18, height_/2 + 45);
281 QSize splashSize() const {
283 static_cast<unsigned int>(width_ * pixelRatio()),
284 static_cast<unsigned int>(height_ * pixelRatio()));
287 /// Ratio between physical pixels and device-independent pixels of splash image
288 double splashPixelRatio() const {
289 #if QT_VERSION >= 0x050000
290 return splash_.devicePixelRatio();
298 /// Toolbar store providing access to individual toolbars by name.
299 typedef map<string, GuiToolbar *> ToolbarMap;
301 typedef shared_ptr<Dialog> DialogPtr;
306 class GuiView::GuiViewPrivate
309 GuiViewPrivate(GuiViewPrivate const &);
310 void operator=(GuiViewPrivate const &);
312 GuiViewPrivate(GuiView * gv)
313 : gv_(gv), current_work_area_(nullptr), current_main_work_area_(nullptr),
314 layout_(nullptr), autosave_timeout_(5000),
317 // hardcode here the platform specific icon size
318 smallIconSize = 16; // scaling problems
319 normalIconSize = 20; // ok, default if iconsize.png is missing
320 bigIconSize = 26; // better for some math icons
321 hugeIconSize = 32; // better for hires displays
324 // if it exists, use width of iconsize.png as normal size
325 QString const dir = toqstr(addPath("images", lyxrc.icon_set));
326 FileName const fn = lyx::libFileSearch(dir, "iconsize.png");
328 QImage image(toqstr(fn.absFileName()));
329 if (image.width() < int(smallIconSize))
330 normalIconSize = smallIconSize;
331 else if (image.width() > int(giantIconSize))
332 normalIconSize = giantIconSize;
334 normalIconSize = image.width();
337 splitter_ = new QSplitter;
338 bg_widget_ = new BackgroundWidget(400, 250);
339 stack_widget_ = new QStackedWidget;
340 stack_widget_->addWidget(bg_widget_);
341 stack_widget_->addWidget(splitter_);
344 // TODO cleanup, remove the singleton, handle multiple Windows?
345 progress_ = ProgressInterface::instance();
346 if (!dynamic_cast<GuiProgress*>(progress_)) {
347 progress_ = new GuiProgress; // TODO who deletes it
348 ProgressInterface::setInstance(progress_);
351 dynamic_cast<GuiProgress*>(progress_),
352 SIGNAL(updateStatusBarMessage(QString const&)),
353 gv, SLOT(updateStatusBarMessage(QString const&)));
355 dynamic_cast<GuiProgress*>(progress_),
356 SIGNAL(clearMessageText()),
357 gv, SLOT(clearMessageText()));
364 delete stack_widget_;
369 stack_widget_->setCurrentWidget(bg_widget_);
370 bg_widget_->setUpdatesEnabled(true);
371 bg_widget_->setFocus();
374 int tabWorkAreaCount()
376 return splitter_->count();
379 TabWorkArea * tabWorkArea(int i)
381 return dynamic_cast<TabWorkArea *>(splitter_->widget(i));
384 TabWorkArea * currentTabWorkArea()
386 int areas = tabWorkAreaCount();
388 // The first TabWorkArea is always the first one, if any.
389 return tabWorkArea(0);
391 for (int i = 0; i != areas; ++i) {
392 TabWorkArea * twa = tabWorkArea(i);
393 if (current_main_work_area_ == twa->currentWorkArea())
397 // None has the focus so we just take the first one.
398 return tabWorkArea(0);
401 int countWorkAreasOf(Buffer & buf)
403 int areas = tabWorkAreaCount();
405 for (int i = 0; i != areas; ++i) {
406 TabWorkArea * twa = tabWorkArea(i);
407 if (twa->workArea(buf))
413 void setPreviewFuture(QFuture<Buffer::ExportStatus> const & f)
415 if (processing_thread_watcher_.isRunning()) {
416 // we prefer to cancel this preview in order to keep a snappy
420 processing_thread_watcher_.setFuture(f);
423 QSize iconSize(docstring const & icon_size)
426 if (icon_size == "small")
427 size = smallIconSize;
428 else if (icon_size == "normal")
429 size = normalIconSize;
430 else if (icon_size == "big")
432 else if (icon_size == "huge")
434 else if (icon_size == "giant")
435 size = giantIconSize;
437 size = icon_size.empty() ? normalIconSize : convert<int>(icon_size);
439 if (size < smallIconSize)
440 size = smallIconSize;
442 return QSize(size, size);
445 QSize iconSize(QString const & icon_size)
447 return iconSize(qstring_to_ucs4(icon_size));
450 string & iconSize(QSize const & qsize)
452 LATTEST(qsize.width() == qsize.height());
454 static string icon_size;
456 unsigned int size = qsize.width();
458 if (size < smallIconSize)
459 size = smallIconSize;
461 if (size == smallIconSize)
463 else if (size == normalIconSize)
464 icon_size = "normal";
465 else if (size == bigIconSize)
467 else if (size == hugeIconSize)
469 else if (size == giantIconSize)
472 icon_size = convert<string>(size);
477 static Buffer::ExportStatus previewAndDestroy(Buffer const * orig,
478 Buffer * buffer, string const & format);
479 static Buffer::ExportStatus exportAndDestroy(Buffer const * orig,
480 Buffer * buffer, string const & format);
481 static Buffer::ExportStatus compileAndDestroy(Buffer const * orig,
482 Buffer * buffer, string const & format);
483 static docstring autosaveAndDestroy(Buffer const * orig, Buffer * buffer);
486 static Buffer::ExportStatus runAndDestroy(const T& func,
487 Buffer const * orig, Buffer * buffer, string const & format);
489 // TODO syncFunc/previewFunc: use bind
490 bool asyncBufferProcessing(string const & argument,
491 Buffer const * used_buffer,
492 docstring const & msg,
493 Buffer::ExportStatus (*asyncFunc)(Buffer const *, Buffer *, string const &),
494 Buffer::ExportStatus (Buffer::*syncFunc)(string const &, bool) const,
495 Buffer::ExportStatus (Buffer::*previewFunc)(string const &) const,
496 bool allow_async, bool use_tmpdir = false);
498 QVector<GuiWorkArea*> guiWorkAreas();
502 GuiWorkArea * current_work_area_;
503 GuiWorkArea * current_main_work_area_;
504 QSplitter * splitter_;
505 QStackedWidget * stack_widget_;
506 BackgroundWidget * bg_widget_;
508 ToolbarMap toolbars_;
509 ProgressInterface* progress_;
510 /// The main layout box.
512 * \warning Don't Delete! The layout box is actually owned by
513 * whichever toolbar contains it. All the GuiView class needs is a
514 * means of accessing it.
516 * FIXME: replace that with a proper model so that we are not limited
517 * to only one dialog.
522 map<string, DialogPtr> dialogs_;
525 QTimer statusbar_timer_;
526 QTimer statusbar_stats_timer_;
527 /// auto-saving of buffers
528 Timeout autosave_timeout_;
531 TocModels toc_models_;
534 QFutureWatcher<docstring> autosave_watcher_;
535 QFutureWatcher<Buffer::ExportStatus> processing_thread_watcher_;
537 string last_export_format;
538 string processing_format;
540 static QSet<Buffer const *> busyBuffers;
542 unsigned int smallIconSize;
543 unsigned int normalIconSize;
544 unsigned int bigIconSize;
545 unsigned int hugeIconSize;
546 unsigned int giantIconSize;
548 /// flag against a race condition due to multiclicks, see bug #1119
551 // Timers for statistic updates in buffer
552 /// Current time left to the nearest info update
553 int time_to_update = 1000;
554 ///Basic step for timer in ms. Basically reaction time for short selections
555 int const timer_rate = 500;
556 /// Real stats updates infrequently. First they take long time for big buffers, second
557 /// they are visible for fast-repeat keyboards even for mid documents.
558 int const default_stats_rate = 5000;
559 /// Detection of new selection, so we can react fast
560 bool already_in_selection_ = false;
561 /// Maximum size of "short" selection for which we can update with faster timer_rate
562 int const max_sel_chars = 5000;
566 QSet<Buffer const *> GuiView::GuiViewPrivate::busyBuffers;
569 GuiView::GuiView(int id)
570 : d(*new GuiViewPrivate(this)), id_(id), closing_(false), busy_(0),
571 command_execute_(false), minibuffer_focus_(false), word_count_enabled_(true),
572 char_count_enabled_(true), char_nb_count_enabled_(false),
573 toolbarsMovable_(true), devel_mode_(false)
575 connect(this, SIGNAL(bufferViewChanged()),
576 this, SLOT(onBufferViewChanged()));
578 // GuiToolbars *must* be initialised before the menu bar.
579 setIconSize(QSize(d.normalIconSize, d.normalIconSize)); // at least on Mac the default is 32 otherwise, which is huge
582 // set ourself as the current view. This is needed for the menu bar
583 // filling, at least for the static special menu item on Mac. Otherwise
584 // they are greyed out.
585 guiApp->setCurrentView(this);
587 // Fill up the menu bar.
588 guiApp->menus().fillMenuBar(menuBar(), this, true);
590 setCentralWidget(d.stack_widget_);
592 // Start autosave timer
593 if (lyxrc.autosave) {
594 // The connection is closed when this is destroyed.
595 d.autosave_timeout_.timeout.connect([this](){ autoSave();});
596 d.autosave_timeout_.setTimeout(lyxrc.autosave * 1000);
597 d.autosave_timeout_.start();
599 connect(&d.statusbar_timer_, SIGNAL(timeout()),
600 this, SLOT(clearMessage()));
601 connect(&d.statusbar_stats_timer_, SIGNAL(timeout()),
602 this, SLOT(showStats()));
603 d.statusbar_stats_timer_.start(d.timer_rate);
605 // We don't want to keep the window in memory if it is closed.
606 setAttribute(Qt::WA_DeleteOnClose, true);
608 #if !(defined(Q_OS_WIN) || defined(Q_CYGWIN_WIN)) && !defined(Q_OS_MAC)
609 // QIcon::fromTheme was introduced in Qt 4.6
610 #if (QT_VERSION >= 0x040600)
611 // assign an icon to main form. We do not do it under Qt/Win or Qt/Mac,
612 // since the icon is provided in the application bundle. We use a themed
613 // version when available and use the bundled one as fallback.
614 setWindowIcon(QIcon::fromTheme("lyx", getPixmap("images/", "lyx", "svg,png")));
616 setWindowIcon(getPixmap("images/", "lyx", "svg,png"));
622 // use tabbed dock area for multiple docks
623 // (such as "source" and "messages")
624 setDockOptions(QMainWindow::ForceTabbedDocks);
627 // use document mode tabs on docks
628 setDocumentMode(true);
632 setAcceptDrops(true);
634 // add busy indicator to statusbar
635 search_mode mode = theGuiApp()->imageSearchMode();
636 QString fn = toqstr(lyx::libFileSearch("images", "busy", "svgz", mode).absFileName());
637 PressableSvgWidget * busySVG = new PressableSvgWidget(fn);
638 statusBar()->addPermanentWidget(busySVG);
639 // make busy indicator square with 5px margins
640 busySVG->setMaximumSize(busySVG->height() - 5, busySVG->height() - 5);
643 connect(&d.processing_thread_watcher_, SIGNAL(started()),
644 busySVG, SLOT(show()));
645 connect(&d.processing_thread_watcher_, SIGNAL(finished()),
646 busySVG, SLOT(hide()));
647 connect(busySVG, SIGNAL(pressed()), this, SLOT(checkCancelBackground()));
649 stat_counts_ = new GuiClickableLabel(statusBar());
650 stat_counts_->setAlignment(Qt::AlignCenter);
651 stat_counts_->setFrameStyle(QFrame::StyledPanel);
652 stat_counts_->hide();
653 statusBar()->addPermanentWidget(stat_counts_);
655 connect(stat_counts_, SIGNAL(clicked()), this, SLOT(statsPressed()));
658 QFontMetrics const fm(statusBar()->fontMetrics());
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 int const iconheight = max(int(d.normalIconSize), fm.height());
732 QSize const iconsize(iconheight, iconheight);
734 QPixmap shellescape = QIcon(getPixmap("images/", "emblem-shellescape", "svgz,png")).pixmap(iconsize);
735 shell_escape_ = new QLabel(statusBar());
736 shell_escape_->setPixmap(shellescape);
737 shell_escape_->setAlignment(Qt::AlignCenter);
738 shell_escape_->setContextMenuPolicy(Qt::CustomContextMenu);
739 shell_escape_->setToolTip(qt_("WARNING: LaTeX is allowed to execute "
740 "external commands for this document. "
741 "Right click to change."));
742 SEMenu * menu = new SEMenu(this);
743 connect(shell_escape_, SIGNAL(customContextMenuRequested(QPoint)),
744 menu, SLOT(showMenu(QPoint)));
745 shell_escape_->hide();
746 statusBar()->addPermanentWidget(shell_escape_);
748 QPixmap readonly = QIcon(getPixmap("images/", "emblem-readonly", "svgz,png")).pixmap(iconsize);
749 read_only_ = new QLabel(statusBar());
750 read_only_->setPixmap(readonly);
751 read_only_->setAlignment(Qt::AlignCenter);
753 statusBar()->addPermanentWidget(read_only_);
755 version_control_ = new QLabel(statusBar());
756 version_control_->setAlignment(Qt::AlignCenter);
757 version_control_->setFrameStyle(QFrame::StyledPanel);
758 version_control_->hide();
759 statusBar()->addPermanentWidget(version_control_);
761 statusBar()->setSizeGripEnabled(true);
764 connect(&d.autosave_watcher_, SIGNAL(finished()), this,
765 SLOT(autoSaveThreadFinished()));
767 connect(&d.processing_thread_watcher_, SIGNAL(started()), this,
768 SLOT(processingThreadStarted()));
769 connect(&d.processing_thread_watcher_, SIGNAL(finished()), this,
770 SLOT(processingThreadFinished()));
772 connect(this, SIGNAL(triggerShowDialog(QString const &, QString const &, Inset *)),
773 SLOT(doShowDialog(QString const &, QString const &, Inset *)));
775 // set custom application bars context menu, e.g. tool bar and menu bar
776 setContextMenuPolicy(Qt::CustomContextMenu);
777 connect(this, SIGNAL(customContextMenuRequested(const QPoint &)),
778 SLOT(toolBarPopup(const QPoint &)));
780 // Forbid too small unresizable window because it can happen
781 // with some window manager under X11.
782 setMinimumSize(300, 200);
784 if (lyxrc.allow_geometry_session) {
785 // Now take care of session management.
790 // no session handling, default to a sane size.
791 setGeometry(50, 50, 690, 510);
794 // clear session data if any.
795 settings.remove("views");
805 void GuiView::disableShellEscape()
807 BufferView * bv = documentBufferView();
810 theSession().shellescapeFiles().remove(bv->buffer().absFileName());
811 bv->buffer().params().shell_escape = false;
812 bv->processUpdateFlags(Update::Force);
816 void GuiView::checkCancelBackground()
818 docstring const ttl = _("Cancel Export?");
819 docstring const msg = _("Do you want to cancel the background export process?");
821 Alert::prompt(ttl, msg, 1, 1,
822 _("&Cancel export"), _("Co&ntinue"));
824 Systemcall::killscript();
827 void GuiView::statsPressed()
830 dispatch(FuncRequest(LFUN_STATISTICS), dr);
833 void GuiView::zoomSliderMoved(int value)
836 dispatch(FuncRequest(LFUN_BUFFER_ZOOM, convert<string>(value)), dr);
837 scheduleRedrawWorkAreas();
838 zoom_value_->setText(toqstr(bformat(_("[[ZOOM]]%1$d%"), value)));
842 void GuiView::zoomValueChanged(int value)
844 if (value != lyxrc.currentZoom)
845 zoomSliderMoved(value);
849 void GuiView::zoomInPressed()
852 dispatch(FuncRequest(LFUN_BUFFER_ZOOM_IN), dr);
853 scheduleRedrawWorkAreas();
857 void GuiView::zoomOutPressed()
860 dispatch(FuncRequest(LFUN_BUFFER_ZOOM_OUT), dr);
861 scheduleRedrawWorkAreas();
865 void GuiView::showZoomContextMenu()
867 QMenu * menu = guiApp->menus().menu(toqstr("context-zoom"), * this);
870 menu->exec(QCursor::pos());
874 void GuiView::showStatusBarContextMenu()
876 QMenu * menu = guiApp->menus().menu(toqstr("context-statusbar"), * this);
879 menu->exec(QCursor::pos());
883 void GuiView::scheduleRedrawWorkAreas()
885 for (int i = 0; i < d.tabWorkAreaCount(); i++) {
886 TabWorkArea* ta = d.tabWorkArea(i);
887 for (int u = 0; u < ta->count(); u++) {
888 ta->workArea(u)->scheduleRedraw(true);
894 QVector<GuiWorkArea*> GuiView::GuiViewPrivate::guiWorkAreas()
896 QVector<GuiWorkArea*> areas;
897 for (int i = 0; i < tabWorkAreaCount(); i++) {
898 TabWorkArea* ta = tabWorkArea(i);
899 for (int u = 0; u < ta->count(); u++) {
900 areas << ta->workArea(u);
906 static void handleExportStatus(GuiView * view, Buffer::ExportStatus status,
907 string const & format)
909 docstring const fmt = translateIfPossible(theFormats().prettyName(format));
912 case Buffer::ExportSuccess:
913 msg = bformat(_("Successful export to format: %1$s"), fmt);
915 case Buffer::ExportCancel:
916 msg = _("Document export cancelled.");
918 case Buffer::ExportError:
919 case Buffer::ExportNoPathToFormat:
920 case Buffer::ExportTexPathHasSpaces:
921 case Buffer::ExportConverterError:
922 msg = bformat(_("Error while exporting format: %1$s"), fmt);
924 case Buffer::PreviewSuccess:
925 msg = bformat(_("Successful preview of format: %1$s"), fmt);
927 case Buffer::PreviewError:
928 msg = bformat(_("Error while previewing format: %1$s"), fmt);
930 case Buffer::ExportKilled:
931 msg = bformat(_("Conversion cancelled while previewing format: %1$s"), fmt);
938 void GuiView::processingThreadStarted()
943 void GuiView::processingThreadFinished()
945 QFutureWatcher<Buffer::ExportStatus> const * watcher =
946 static_cast<QFutureWatcher<Buffer::ExportStatus> const *>(sender());
948 Buffer::ExportStatus const status = watcher->result();
949 handleExportStatus(this, status, d.processing_format);
952 BufferView const * const bv = currentBufferView();
953 if (bv && !bv->buffer().errorList("Export").empty()) {
958 bool const error = (status != Buffer::ExportSuccess &&
959 status != Buffer::PreviewSuccess &&
960 status != Buffer::ExportCancel);
962 ErrorList & el = bv->buffer().errorList(d.last_export_format);
963 // at this point, we do not know if buffer-view or
964 // master-buffer-view was called. If there was an export error,
965 // and the current buffer's error log is empty, we guess that
966 // it must be master-buffer-view that was called so we set
968 errors(d.last_export_format, el.empty());
973 void GuiView::autoSaveThreadFinished()
975 QFutureWatcher<docstring> const * watcher =
976 static_cast<QFutureWatcher<docstring> const *>(sender());
977 message(watcher->result());
982 void GuiView::saveLayout() const
985 settings.setValue("zoom_ratio", zoom_ratio_);
986 settings.setValue("devel_mode", devel_mode_);
987 settings.beginGroup("views");
988 settings.beginGroup(QString::number(id_));
989 if (guiApp->platformName() == "qt4x11" || guiApp->platformName() == "xcb") {
990 settings.setValue("pos", pos());
991 settings.setValue("size", size());
993 settings.setValue("geometry", saveGeometry());
994 settings.setValue("layout", saveState(0));
995 settings.setValue("icon_size", toqstr(d.iconSize(iconSize())));
996 settings.setValue("zoom_value_visible", zoom_value_->isVisible());
997 settings.setValue("zoom_slider_visible", zoom_slider_->isVisible());
998 settings.setValue("word_count_enabled", word_count_enabled_);
999 settings.setValue("char_count_enabled", char_count_enabled_);
1000 settings.setValue("char_nb_count_enabled", char_nb_count_enabled_);
1004 void GuiView::saveUISettings() const
1008 // Save the toolbar private states
1009 for (auto const & tb_p : d.toolbars_)
1010 tb_p.second->saveSession(settings);
1011 // Now take care of all other dialogs
1012 for (auto const & dlg_p : d.dialogs_)
1013 dlg_p.second->saveSession(settings);
1017 void GuiView::setCurrentZoom(const int v)
1019 lyxrc.currentZoom = v;
1020 zoom_value_->setText(toqstr(bformat(_("[[ZOOM]]%1$d%"), v)));
1021 Q_EMIT currentZoomChanged(v);
1025 bool GuiView::restoreLayout()
1028 zoom_ratio_ = settings.value("zoom_ratio", 1.0).toDouble();
1029 // Actual zoom value: default zoom + fractional offset
1030 int zoom = (int)(lyxrc.defaultZoom * zoom_ratio_);
1031 zoom = min(max(zoom, zoom_min_), zoom_max_);
1032 setCurrentZoom(zoom);
1033 devel_mode_ = settings.value("devel_mode", devel_mode_).toBool();
1034 settings.beginGroup("views");
1035 settings.beginGroup(QString::number(id_));
1036 QString const icon_key = "icon_size";
1037 if (!settings.contains(icon_key))
1040 //code below is skipped when when ~/.config/LyX is (re)created
1041 setIconSize(d.iconSize(settings.value(icon_key).toString()));
1043 zoom_value_->setVisible(settings.value("zoom_value_visible", true).toBool());
1045 bool const show_zoom_slider = settings.value("zoom_slider_visible", true).toBool();
1046 zoom_slider_->setVisible(show_zoom_slider);
1047 zoom_in_->setVisible(show_zoom_slider);
1048 zoom_out_->setVisible(show_zoom_slider);
1050 word_count_enabled_ = settings.value("word_count_enabled", true).toBool();
1051 char_count_enabled_ = settings.value("char_count_enabled", true).toBool();
1052 char_nb_count_enabled_ = settings.value("char_nb_count_enabled", true).toBool();
1053 stat_counts_->setVisible(word_count_enabled_ || char_count_enabled_ || char_nb_count_enabled_);
1055 if (guiApp->platformName() == "qt4x11" || guiApp->platformName() == "xcb") {
1056 QPoint pos = settings.value("pos", QPoint(50, 50)).toPoint();
1057 QSize size = settings.value("size", QSize(690, 510)).toSize();
1061 // Work-around for bug #6034: the window ends up in an undetermined
1062 // state when trying to restore a maximized window when it is
1063 // already maximized.
1064 if (!(windowState() & Qt::WindowMaximized))
1065 if (!restoreGeometry(settings.value("geometry").toByteArray()))
1066 setGeometry(50, 50, 690, 510);
1069 // Make sure layout is correctly oriented.
1070 setLayoutDirection(qApp->layoutDirection());
1072 // Allow the toc and view-source dock widget to be restored if needed.
1074 if ((dialog = findOrBuild("toc", true)))
1075 // see bug 5082. At least setup title and enabled state.
1076 // Visibility will be adjusted by restoreState below.
1077 dialog->prepareView();
1078 if ((dialog = findOrBuild("view-source", true)))
1079 dialog->prepareView();
1080 if ((dialog = findOrBuild("progress", true)))
1081 dialog->prepareView();
1083 if (!restoreState(settings.value("layout").toByteArray(), 0))
1086 // init the toolbars that have not been restored
1087 for (auto const & tb_p : guiApp->toolbars()) {
1088 GuiToolbar * tb = toolbar(tb_p.name);
1089 if (tb && !tb->isRestored())
1090 initToolbar(tb_p.name);
1093 // update lock (all) toolbars positions
1094 updateLockToolbars();
1101 GuiToolbar * GuiView::toolbar(string const & name)
1103 ToolbarMap::iterator it = d.toolbars_.find(name);
1104 if (it != d.toolbars_.end())
1107 LYXERR(Debug::GUI, "Toolbar::display: no toolbar named " << name);
1112 void GuiView::updateLockToolbars()
1114 toolbarsMovable_ = false;
1115 for (ToolbarInfo const & info : guiApp->toolbars()) {
1116 GuiToolbar * tb = toolbar(info.name);
1117 if (tb && tb->isMovable())
1118 toolbarsMovable_ = true;
1120 #if QT_VERSION >= 0x050200
1121 // set unified mac toolbars only when not movable as recommended:
1122 // https://doc.qt.io/qt-5/qmainwindow.html#unifiedTitleAndToolBarOnMac-prop
1123 setUnifiedTitleAndToolBarOnMac(!toolbarsMovable_);
1128 void GuiView::constructToolbars()
1130 for (auto const & tb_p : d.toolbars_)
1132 d.toolbars_.clear();
1134 // I don't like doing this here, but the standard toolbar
1135 // destroys this object when it's destroyed itself (vfr)
1136 d.layout_ = new LayoutBox(*this);
1137 d.stack_widget_->addWidget(d.layout_);
1138 d.layout_->move(0,0);
1140 // extracts the toolbars from the backend
1141 for (ToolbarInfo const & inf : guiApp->toolbars())
1142 d.toolbars_[inf.name] = new GuiToolbar(inf, *this);
1144 DynamicMenuButton::resetIconCache();
1148 void GuiView::initToolbars()
1150 // extracts the toolbars from the backend
1151 for (ToolbarInfo const & inf : guiApp->toolbars())
1152 initToolbar(inf.name);
1156 void GuiView::initToolbar(string const & name)
1158 GuiToolbar * tb = toolbar(name);
1161 int const visibility = guiApp->toolbars().defaultVisibility(name);
1162 bool newline = !(visibility & Toolbars::SAMEROW);
1163 tb->setVisible(false);
1164 tb->setVisibility(visibility);
1166 if (visibility & Toolbars::TOP) {
1168 addToolBarBreak(Qt::TopToolBarArea);
1169 addToolBar(Qt::TopToolBarArea, tb);
1172 if (visibility & Toolbars::BOTTOM) {
1174 addToolBarBreak(Qt::BottomToolBarArea);
1175 addToolBar(Qt::BottomToolBarArea, tb);
1178 if (visibility & Toolbars::LEFT) {
1180 addToolBarBreak(Qt::LeftToolBarArea);
1181 addToolBar(Qt::LeftToolBarArea, tb);
1184 if (visibility & Toolbars::RIGHT) {
1186 addToolBarBreak(Qt::RightToolBarArea);
1187 addToolBar(Qt::RightToolBarArea, tb);
1190 if (visibility & Toolbars::ON)
1191 tb->setVisible(true);
1193 tb->setMovable(true);
1197 TocModels & GuiView::tocModels()
1199 return d.toc_models_;
1203 void GuiView::setFocus()
1205 LYXERR(Debug::DEBUG, "GuiView::setFocus()" << this);
1206 QMainWindow::setFocus();
1210 bool GuiView::hasFocus() const
1212 if (currentWorkArea())
1213 return currentWorkArea()->hasFocus();
1214 if (currentMainWorkArea())
1215 return currentMainWorkArea()->hasFocus();
1216 return d.bg_widget_->hasFocus();
1220 void GuiView::focusInEvent(QFocusEvent * e)
1222 LYXERR(Debug::DEBUG, "GuiView::focusInEvent()" << this);
1223 QMainWindow::focusInEvent(e);
1224 // Make sure guiApp points to the correct view.
1225 guiApp->setCurrentView(this);
1226 if (currentWorkArea())
1227 currentWorkArea()->setFocus();
1228 else if (currentMainWorkArea())
1229 currentMainWorkArea()->setFocus();
1231 d.bg_widget_->setFocus();
1235 void GuiView::showEvent(QShowEvent * e)
1237 LYXERR(Debug::GUI, "Passed Geometry "
1238 << size().height() << "x" << size().width()
1239 << "+" << pos().x() << "+" << pos().y());
1241 if (d.splitter_->count() == 0)
1242 // No work area, switch to the background widget.
1246 QMainWindow::showEvent(e);
1250 bool GuiView::closeScheduled()
1257 bool GuiView::prepareAllBuffersForLogout()
1259 Buffer * first = theBufferList().first();
1263 // First, iterate over all buffers and ask the users if unsaved
1264 // changes should be saved.
1265 // We cannot use a for loop as the buffer list cycles.
1268 if (!saveBufferIfNeeded(*b, false))
1270 b = theBufferList().next(b);
1271 } while (b != first);
1273 // Next, save session state
1274 // When a view/window was closed before without quitting LyX, there
1275 // are already entries in the lastOpened list.
1276 theSession().lastOpened().clear();
1283 /** Destroy only all tabbed WorkAreas. Destruction of other WorkAreas
1284 ** is responsibility of the container (e.g., dialog)
1286 void GuiView::closeEvent(QCloseEvent * close_event)
1288 LYXERR(Debug::DEBUG, "GuiView::closeEvent()");
1290 if (!GuiViewPrivate::busyBuffers.isEmpty()) {
1291 Alert::warning(_("Exit LyX"),
1292 _("LyX could not be closed because documents are being processed by LyX."));
1293 close_event->setAccepted(false);
1297 // If the user pressed the x (so we didn't call closeView
1298 // programmatically), we want to clear all existing entries.
1300 theSession().lastOpened().clear();
1305 // it can happen that this event arrives without selecting the view,
1306 // e.g. when clicking the close button on a background window.
1308 if (!closeWorkAreaAll()) {
1310 close_event->ignore();
1314 // Make sure that nothing will use this to be closed View.
1315 guiApp->unregisterView(this);
1317 if (isFullScreen()) {
1318 // Switch off fullscreen before closing.
1323 // Make sure the timer time out will not trigger a statusbar update.
1324 d.statusbar_timer_.stop();
1325 d.statusbar_stats_timer_.stop();
1327 // Saving fullscreen requires additional tweaks in the toolbar code.
1328 // It wouldn't also work under linux natively.
1329 if (lyxrc.allow_geometry_session) {
1334 close_event->accept();
1338 void GuiView::dragEnterEvent(QDragEnterEvent * event)
1340 if (event->mimeData()->hasUrls())
1342 /// \todo Ask lyx-devel is this is enough:
1343 /// if (event->mimeData()->hasFormat("text/plain"))
1344 /// event->acceptProposedAction();
1348 void GuiView::dropEvent(QDropEvent * event)
1350 QList<QUrl> files = event->mimeData()->urls();
1351 if (files.isEmpty())
1354 LYXERR(Debug::GUI, "GuiView::dropEvent: got URLs!");
1355 for (int i = 0; i != files.size(); ++i) {
1356 string const file = os::internal_path(fromqstr(
1357 files.at(i).toLocalFile()));
1361 string const ext = support::getExtension(file);
1362 vector<const Format *> found_formats;
1364 // Find all formats that have the correct extension.
1365 for (const Format * fmt : theConverters().importableFormats())
1366 if (fmt->hasExtension(ext))
1367 found_formats.push_back(fmt);
1370 if (!found_formats.empty()) {
1371 if (found_formats.size() > 1) {
1372 //FIXME: show a dialog to choose the correct importable format
1373 LYXERR(Debug::FILES,
1374 "Multiple importable formats found, selecting first");
1376 string const arg = found_formats[0]->name() + " " + file;
1377 cmd = FuncRequest(LFUN_BUFFER_IMPORT, arg);
1380 //FIXME: do we have to explicitly check whether it's a lyx file?
1381 LYXERR(Debug::FILES,
1382 "No formats found, trying to open it as a lyx file");
1383 cmd = FuncRequest(LFUN_FILE_OPEN, file);
1385 // add the functions to the queue
1386 guiApp->addToFuncRequestQueue(cmd);
1389 // now process the collected functions. We perform the events
1390 // asynchronously. This prevents potential problems in case the
1391 // BufferView is closed within an event.
1392 guiApp->processFuncRequestQueueAsync();
1396 void GuiView::message(docstring const & str)
1398 if (ForkedProcess::iAmAChild())
1401 // call is moved to GUI-thread by GuiProgress
1402 d.progress_->appendMessage(toqstr(str));
1406 void GuiView::clearMessageText()
1408 message(docstring());
1412 void GuiView::updateStatusBarMessage(QString const & str)
1414 statusBar()->showMessage(str);
1415 d.statusbar_timer_.stop();
1416 d.statusbar_timer_.start(3000);
1420 void GuiView::clearMessage()
1422 // FIXME: This code was introduced in r19643 to fix bug #4123. However,
1423 // the hasFocus function mostly returns false, even if the focus is on
1424 // a workarea in this view.
1428 d.statusbar_timer_.stop();
1431 void GuiView::showStats()
1433 if (!statsEnabled())
1436 d.time_to_update -= d.timer_rate;
1438 BufferView * bv = currentBufferView();
1439 Buffer * buf = bv ? &bv->buffer() : nullptr;
1441 stat_counts_->hide();
1445 Cursor const & cur = bv->cursor();
1447 // we start new selection and need faster update
1448 if (!d.already_in_selection_ && cur.selection())
1449 d.time_to_update = 0;
1451 if (d.time_to_update > 0)
1454 DocIterator from, to;
1455 if (cur.selection()) {
1456 from = cur.selectionBegin();
1457 to = cur.selectionEnd();
1458 d.already_in_selection_ = true;
1460 from = doc_iterator_begin(buf);
1461 to = doc_iterator_end(buf);
1462 d.already_in_selection_ = false;
1465 buf->updateStatistics(from, to);
1468 if (word_count_enabled_) {
1469 int const words = buf->wordCount();
1471 stats << toqstr(bformat(_("%1$d Word"), words));
1473 stats << toqstr(bformat(_("%1$d Words"), words));
1475 int const chars_with_blanks = buf->charCount(true);
1476 if (char_count_enabled_) {
1477 if (chars_with_blanks == 1)
1478 stats << toqstr(bformat(_("%1$d Character"), chars_with_blanks));
1480 stats << toqstr(bformat(_("%1$d Characters"), chars_with_blanks));
1482 if (char_nb_count_enabled_) {
1483 int const chars = buf->charCount(false);
1485 stats << toqstr(bformat(_("%1$d Character (no Blanks)"), chars));
1487 stats << toqstr(bformat(_("%1$d Characters (no Blanks)"), chars));
1489 stat_counts_->setText(stats.join(qt_(", [[stats separator]]")));
1490 stat_counts_->show();
1492 d.time_to_update = d.default_stats_rate;
1493 // fast updates for small selections
1494 if (chars_with_blanks < d.max_sel_chars && cur.selection())
1495 d.time_to_update = d.timer_rate;
1499 void GuiView::updateWindowTitle(GuiWorkArea * wa)
1501 if (wa != d.current_work_area_
1502 || wa->bufferView().buffer().isInternal())
1504 Buffer const & buf = wa->bufferView().buffer();
1505 // Set the windows title
1506 docstring title = buf.fileName().displayName(130) + from_ascii("[*]");
1507 if (buf.notifiesExternalModification()) {
1508 title = bformat(_("%1$s (modified externally)"), title);
1509 // If the external modification status has changed, then maybe the status of
1510 // buffer-save has changed too.
1514 title += from_ascii(" - LyX");
1516 setWindowTitle(toqstr(title));
1517 // Sets the path for the window: this is used by OSX to
1518 // allow a context click on the title bar showing a menu
1519 // with the path up to the file
1520 setWindowFilePath(toqstr(buf.absFileName()));
1521 // Tell Qt whether the current document is changed
1522 setWindowModified(!buf.isClean());
1524 if (buf.params().shell_escape)
1525 shell_escape_->show();
1527 shell_escape_->hide();
1529 if (buf.hasReadonlyFlag())
1534 if (buf.lyxvc().inUse()) {
1535 version_control_->show();
1536 version_control_->setText(toqstr(buf.lyxvc().vcstatus()));
1538 version_control_->hide();
1542 void GuiView::on_currentWorkAreaChanged(GuiWorkArea * wa)
1544 if (d.current_work_area_)
1545 // disconnect the current work area from all slots
1546 QObject::disconnect(d.current_work_area_, nullptr, this, nullptr);
1548 disconnectBufferView();
1549 connectBufferView(wa->bufferView());
1550 connectBuffer(wa->bufferView().buffer());
1551 d.current_work_area_ = wa;
1552 QObject::connect(wa, SIGNAL(titleChanged(GuiWorkArea *)),
1553 this, SLOT(updateWindowTitle(GuiWorkArea *)));
1554 QObject::connect(wa, SIGNAL(busy(bool)),
1555 this, SLOT(setBusy(bool)));
1556 // connection of a signal to a signal
1557 QObject::connect(wa, SIGNAL(bufferViewChanged()),
1558 this, SIGNAL(bufferViewChanged()));
1559 Q_EMIT updateWindowTitle(wa);
1560 Q_EMIT bufferViewChanged();
1564 void GuiView::onBufferViewChanged()
1567 // Buffer-dependent dialogs must be updated. This is done here because
1568 // some dialogs require buffer()->text.
1570 zoom_slider_->setEnabled(currentBufferView());
1571 zoom_value_->setEnabled(currentBufferView());
1572 zoom_in_->setEnabled(currentBufferView());
1573 zoom_out_->setEnabled(currentBufferView());
1577 void GuiView::on_lastWorkAreaRemoved()
1580 // We already are in a close event. Nothing more to do.
1583 if (d.splitter_->count() > 1)
1584 // We have a splitter so don't close anything.
1587 // Reset and updates the dialogs.
1588 Q_EMIT bufferViewChanged();
1593 if (lyxrc.open_buffers_in_tabs)
1594 // Nothing more to do, the window should stay open.
1597 if (guiApp->viewIds().size() > 1) {
1603 // On Mac we also close the last window because the application stay
1604 // resident in memory. On other platforms we don't close the last
1605 // window because this would quit the application.
1611 void GuiView::updateStatusBar()
1613 // let the user see the explicit message
1614 if (d.statusbar_timer_.isActive())
1621 void GuiView::showMessage()
1625 QString msg = toqstr(theGuiApp()->viewStatusMessage());
1626 if (msg.isEmpty()) {
1627 BufferView const * bv = currentBufferView();
1629 msg = toqstr(bv->cursor().currentState(devel_mode_));
1631 msg = qt_("Welcome to LyX!");
1633 statusBar()->showMessage(msg);
1637 bool GuiView::statsEnabled() const
1639 return word_count_enabled_ || char_count_enabled_ || char_nb_count_enabled_;
1643 bool GuiView::event(QEvent * e)
1647 // Useful debug code:
1648 //case QEvent::ActivationChange:
1649 //case QEvent::WindowDeactivate:
1650 //case QEvent::Paint:
1651 //case QEvent::Enter:
1652 //case QEvent::Leave:
1653 //case QEvent::HoverEnter:
1654 //case QEvent::HoverLeave:
1655 //case QEvent::HoverMove:
1656 //case QEvent::StatusTip:
1657 //case QEvent::DragEnter:
1658 //case QEvent::DragLeave:
1659 //case QEvent::Drop:
1662 case QEvent::WindowStateChange: {
1663 QWindowStateChangeEvent * ev = (QWindowStateChangeEvent*)e;
1664 bool ofstate = (ev->oldState() & Qt::WindowFullScreen);
1665 bool result = QMainWindow::event(e);
1666 bool nfstate = (windowState() & Qt::WindowFullScreen);
1667 if (!ofstate && nfstate) {
1668 LYXERR(Debug::DEBUG, "GuiView: WindowStateChange(): full-screen " << nfstate);
1669 // switch to full-screen state
1670 if (lyxrc.full_screen_statusbar)
1671 statusBar()->hide();
1672 if (lyxrc.full_screen_menubar)
1674 if (lyxrc.full_screen_toolbars) {
1675 for (auto const & tb_p : d.toolbars_)
1676 if (tb_p.second->isVisibilityOn() && tb_p.second->isVisible())
1677 tb_p.second->hide();
1679 for (int i = 0; i != d.splitter_->count(); ++i)
1680 d.tabWorkArea(i)->setFullScreen(true);
1681 #if QT_VERSION > 0x050903
1682 //Qt's 5.9.4 ba44cdae38406c safe area measures won't allow us to go negative in margins
1683 setAttribute(Qt::WA_ContentsMarginsRespectsSafeArea, false);
1685 setContentsMargins(-2, -2, -2, -2);
1687 hideDialogs("prefs", nullptr);
1688 } else if (ofstate && !nfstate) {
1689 LYXERR(Debug::DEBUG, "GuiView: WindowStateChange(): full-screen " << nfstate);
1690 // switch back from full-screen state
1691 if (lyxrc.full_screen_statusbar && !statusBar()->isVisible())
1692 statusBar()->show();
1693 if (lyxrc.full_screen_menubar && !menuBar()->isVisible())
1695 if (lyxrc.full_screen_toolbars) {
1696 for (auto const & tb_p : d.toolbars_)
1697 if (tb_p.second->isVisibilityOn() && !tb_p.second->isVisible())
1698 tb_p.second->show();
1701 for (int i = 0; i != d.splitter_->count(); ++i)
1702 d.tabWorkArea(i)->setFullScreen(false);
1703 #if QT_VERSION > 0x050903
1704 setAttribute(Qt::WA_ContentsMarginsRespectsSafeArea, true);
1706 setContentsMargins(0, 0, 0, 0);
1711 case QEvent::WindowActivate: {
1712 GuiView * old_view = guiApp->currentView();
1713 if (this == old_view) {
1715 return QMainWindow::event(e);
1717 if (old_view && old_view->currentBufferView()) {
1718 // save current selection to the selection buffer to allow
1719 // middle-button paste in this window.
1720 cap::saveSelection(old_view->currentBufferView()->cursor());
1722 guiApp->setCurrentView(this);
1723 if (d.current_work_area_)
1724 on_currentWorkAreaChanged(d.current_work_area_);
1728 return QMainWindow::event(e);
1731 case QEvent::ShortcutOverride: {
1733 if (isFullScreen() && menuBar()->isHidden()) {
1734 QKeyEvent * ke = static_cast<QKeyEvent*>(e);
1735 // FIXME: we should also try to detect special LyX shortcut such as
1736 // Alt-P and Alt-M. Right now there is a hack in
1737 // GuiWorkArea::processKeySym() that hides again the menubar for
1739 if (ke->modifiers() & Qt::AltModifier && ke->key() != Qt::Key_Alt) {
1741 return QMainWindow::event(e);
1744 return QMainWindow::event(e);
1747 case QEvent::ApplicationPaletteChange: {
1748 // runtime switch from/to dark mode
1750 return QMainWindow::event(e);
1753 case QEvent::Gesture: {
1754 QGestureEvent *ge = static_cast<QGestureEvent*>(e);
1755 QGesture *gp = ge->gesture(Qt::PinchGesture);
1757 QPinchGesture *pinch = static_cast<QPinchGesture *>(gp);
1758 QPinchGesture::ChangeFlags changeFlags = pinch->changeFlags();
1759 qreal totalScaleFactor = pinch->totalScaleFactor();
1760 LYXERR(Debug::GUI, "totalScaleFactor: " << totalScaleFactor);
1761 if (pinch->state() == Qt::GestureStarted) {
1762 initialZoom_ = lyxrc.currentZoom;
1763 LYXERR(Debug::GUI, "initialZoom_: " << initialZoom_);
1765 if (changeFlags & QPinchGesture::ScaleFactorChanged) {
1766 qreal factor = initialZoom_ * totalScaleFactor;
1767 LYXERR(Debug::GUI, "scaleFactor: " << factor);
1768 zoomValueChanged(factor);
1771 return QMainWindow::event(e);
1775 return QMainWindow::event(e);
1779 void GuiView::resetWindowTitle()
1781 setWindowTitle(qt_("LyX"));
1784 bool GuiView::focusNextPrevChild(bool /*next*/)
1791 bool GuiView::busy() const
1797 void GuiView::setBusy(bool busy)
1799 bool const busy_before = busy_ > 0;
1800 busy ? ++busy_ : --busy_;
1801 if ((busy_ > 0) == busy_before)
1802 // busy state didn't change
1806 QApplication::setOverrideCursor(Qt::WaitCursor);
1809 QApplication::restoreOverrideCursor();
1814 void GuiView::resetCommandExecute()
1816 command_execute_ = false;
1821 double GuiView::pixelRatio() const
1823 #if QT_VERSION >= 0x050000
1824 return qt_scale_factor * devicePixelRatio();
1831 GuiWorkArea * GuiView::workArea(int index)
1833 if (TabWorkArea * twa = d.currentTabWorkArea())
1834 if (index < twa->count())
1835 return twa->workArea(index);
1840 GuiWorkArea * GuiView::workArea(Buffer & buffer)
1842 if (currentWorkArea()
1843 && ¤tWorkArea()->bufferView().buffer() == &buffer)
1844 return currentWorkArea();
1845 if (TabWorkArea * twa = d.currentTabWorkArea())
1846 return twa->workArea(buffer);
1851 GuiWorkArea * GuiView::addWorkArea(Buffer & buffer)
1853 // Automatically create a TabWorkArea if there are none yet.
1854 TabWorkArea * tab_widget = d.splitter_->count()
1855 ? d.currentTabWorkArea() : addTabWorkArea();
1856 return tab_widget->addWorkArea(buffer, *this);
1860 TabWorkArea * GuiView::addTabWorkArea()
1862 TabWorkArea * twa = new TabWorkArea;
1863 QObject::connect(twa, SIGNAL(currentWorkAreaChanged(GuiWorkArea *)),
1864 this, SLOT(on_currentWorkAreaChanged(GuiWorkArea *)));
1865 QObject::connect(twa, SIGNAL(lastWorkAreaRemoved()),
1866 this, SLOT(on_lastWorkAreaRemoved()));
1868 d.splitter_->addWidget(twa);
1869 d.stack_widget_->setCurrentWidget(d.splitter_);
1874 GuiWorkArea const * GuiView::currentWorkArea() const
1876 return d.current_work_area_;
1880 GuiWorkArea * GuiView::currentWorkArea()
1882 return d.current_work_area_;
1886 GuiWorkArea const * GuiView::currentMainWorkArea() const
1888 if (!d.currentTabWorkArea())
1890 return d.currentTabWorkArea()->currentWorkArea();
1894 GuiWorkArea * GuiView::currentMainWorkArea()
1896 if (!d.currentTabWorkArea())
1898 return d.currentTabWorkArea()->currentWorkArea();
1902 void GuiView::setCurrentWorkArea(GuiWorkArea * wa)
1904 LYXERR(Debug::DEBUG, "Setting current wa: " << wa << endl);
1906 d.current_work_area_ = nullptr;
1908 Q_EMIT bufferViewChanged();
1912 // FIXME: I've no clue why this is here and why it accesses
1913 // theGuiApp()->currentView, which might be 0 (bug 6464).
1914 // See also 27525 (vfr).
1915 if (theGuiApp()->currentView() == this
1916 && theGuiApp()->currentView()->currentWorkArea() == wa)
1919 if (currentBufferView())
1920 cap::saveSelection(currentBufferView()->cursor());
1922 theGuiApp()->setCurrentView(this);
1923 d.current_work_area_ = wa;
1925 // We need to reset this now, because it will need to be
1926 // right if the tabWorkArea gets reset in the for loop. We
1927 // will change it back if we aren't in that case.
1928 GuiWorkArea * const old_cmwa = d.current_main_work_area_;
1929 d.current_main_work_area_ = wa;
1931 for (int i = 0; i != d.splitter_->count(); ++i) {
1932 if (d.tabWorkArea(i)->setCurrentWorkArea(wa)) {
1933 LYXERR(Debug::DEBUG, "Current wa: " << currentWorkArea()
1934 << ", Current main wa: " << currentMainWorkArea());
1939 d.current_main_work_area_ = old_cmwa;
1941 LYXERR(Debug::DEBUG, "This is not a tabbed wa");
1942 on_currentWorkAreaChanged(wa);
1943 BufferView & bv = wa->bufferView();
1944 bv.cursor().fixIfBroken();
1946 wa->setUpdatesEnabled(true);
1947 LYXERR(Debug::DEBUG, "Current wa: " << currentWorkArea() << ", Current main wa: " << currentMainWorkArea());
1951 void GuiView::removeWorkArea(GuiWorkArea * wa)
1953 LASSERT(wa, return);
1954 if (wa == d.current_work_area_) {
1956 disconnectBufferView();
1957 d.current_work_area_ = nullptr;
1958 d.current_main_work_area_ = nullptr;
1961 bool found_twa = false;
1962 for (int i = 0; i != d.splitter_->count(); ++i) {
1963 TabWorkArea * twa = d.tabWorkArea(i);
1964 if (twa->removeWorkArea(wa)) {
1965 // Found in this tab group, and deleted the GuiWorkArea.
1967 if (twa->count() != 0) {
1968 if (d.current_work_area_ == nullptr)
1969 // This means that we are closing the current GuiWorkArea, so
1970 // switch to the next GuiWorkArea in the found TabWorkArea.
1971 setCurrentWorkArea(twa->currentWorkArea());
1973 // No more WorkAreas in this tab group, so delete it.
1980 // It is not a tabbed work area (i.e., the search work area), so it
1981 // should be deleted by other means.
1982 LASSERT(found_twa, return);
1984 if (d.current_work_area_ == nullptr) {
1985 if (d.splitter_->count() != 0) {
1986 TabWorkArea * twa = d.currentTabWorkArea();
1987 setCurrentWorkArea(twa->currentWorkArea());
1989 // No more work areas, switch to the background widget.
1990 setCurrentWorkArea(nullptr);
1996 bool GuiView::hasVisibleWorkArea(GuiWorkArea * wa) const
1998 for (int i = 0; i < d.splitter_->count(); ++i)
1999 if (d.tabWorkArea(i)->currentWorkArea() == wa)
2002 FindAndReplace * fr = static_cast<FindAndReplace*>(find("findreplaceadv", false));
2003 return fr->isVisible() && fr->hasWorkArea(wa);
2007 LayoutBox * GuiView::getLayoutDialog() const
2013 void GuiView::updateLayoutList()
2016 d.layout_->updateContents(false);
2020 void GuiView::updateToolbars()
2022 if (d.current_work_area_) {
2024 if (d.current_work_area_->bufferView().cursor().inMathed()
2025 && !d.current_work_area_->bufferView().cursor().inRegexped())
2026 context |= Toolbars::MATH;
2027 if (lyx::getStatus(FuncRequest(LFUN_LAYOUT_TABULAR)).enabled())
2028 context |= Toolbars::TABLE;
2029 if (currentBufferView()->buffer().areChangesPresent()
2030 || (lyx::getStatus(FuncRequest(LFUN_CHANGES_TRACK)).enabled()
2031 && lyx::getStatus(FuncRequest(LFUN_CHANGES_TRACK)).onOff(true))
2032 || (lyx::getStatus(FuncRequest(LFUN_CHANGES_OUTPUT)).enabled()
2033 && lyx::getStatus(FuncRequest(LFUN_CHANGES_OUTPUT)).onOff(true)))
2034 context |= Toolbars::REVIEW;
2035 if (lyx::getStatus(FuncRequest(LFUN_IN_MATHMACROTEMPLATE)).enabled())
2036 context |= Toolbars::MATHMACROTEMPLATE;
2037 if (lyx::getStatus(FuncRequest(LFUN_IN_IPA)).enabled())
2038 context |= Toolbars::IPA;
2039 if (command_execute_)
2040 context |= Toolbars::MINIBUFFER;
2041 if (minibuffer_focus_) {
2042 context |= Toolbars::MINIBUFFER_FOCUS;
2043 minibuffer_focus_ = false;
2046 for (auto const & tb_p : d.toolbars_)
2047 tb_p.second->update(context);
2049 for (auto const & tb_p : d.toolbars_)
2050 tb_p.second->update();
2054 void GuiView::refillToolbars()
2056 DynamicMenuButton::resetIconCache();
2057 for (auto const & tb_p : d.toolbars_)
2058 tb_p.second->refill();
2062 void GuiView::setBuffer(Buffer * newBuffer, bool switch_to)
2064 LYXERR(Debug::DEBUG, "Setting buffer: " << newBuffer << endl);
2065 LASSERT(newBuffer, return);
2067 GuiWorkArea * wa = workArea(*newBuffer);
2068 if (wa == nullptr) {
2070 newBuffer->masterBuffer()->updateBuffer();
2072 wa = addWorkArea(*newBuffer);
2073 // scroll to the position when the BufferView was last closed
2074 if (lyxrc.use_lastfilepos) {
2075 LastFilePosSection::FilePos filepos =
2076 theSession().lastFilePos().load(newBuffer->fileName());
2077 wa->bufferView().moveToPosition(filepos.pit, filepos.pos, 0, 0);
2080 //Disconnect the old buffer...there's no new one.
2083 connectBuffer(*newBuffer);
2084 connectBufferView(wa->bufferView());
2086 setCurrentWorkArea(wa);
2090 void GuiView::connectBuffer(Buffer & buf)
2092 buf.setGuiDelegate(this);
2096 void GuiView::disconnectBuffer()
2098 if (d.current_work_area_)
2099 d.current_work_area_->bufferView().buffer().setGuiDelegate(nullptr);
2103 void GuiView::connectBufferView(BufferView & bv)
2105 bv.setGuiDelegate(this);
2109 void GuiView::disconnectBufferView()
2111 if (d.current_work_area_)
2112 d.current_work_area_->bufferView().setGuiDelegate(nullptr);
2116 void GuiView::errors(string const & error_type, bool from_master)
2118 BufferView const * const bv = currentBufferView();
2122 ErrorList const & el = from_master ?
2123 bv->buffer().masterBuffer()->errorList(error_type) :
2124 bv->buffer().errorList(error_type);
2129 string err = error_type;
2131 err = "from_master|" + error_type;
2132 showDialog("errorlist", err);
2136 void GuiView::updateTocItem(string const & type, DocIterator const & dit)
2138 d.toc_models_.updateItem(toqstr(type), dit);
2142 void GuiView::structureChanged()
2144 // This is called from the Buffer, which has no way to ensure that cursors
2145 // in BufferView remain valid.
2146 if (documentBufferView())
2147 documentBufferView()->cursor().sanitize();
2148 // FIXME: This is slightly expensive, though less than the tocBackend update
2149 // (#9880). This also resets the view in the Toc Widget (#6675).
2150 d.toc_models_.reset(documentBufferView());
2151 // Navigator needs more than a simple update in this case. It needs to be
2153 updateDialog("toc", "");
2157 void GuiView::updateDialog(string const & name, string const & sdata)
2159 if (!isDialogVisible(name))
2162 map<string, DialogPtr>::const_iterator it = d.dialogs_.find(name);
2163 if (it == d.dialogs_.end())
2166 Dialog * const dialog = it->second.get();
2167 if (dialog->isVisibleView())
2168 dialog->initialiseParams(sdata);
2172 BufferView * GuiView::documentBufferView()
2174 return currentMainWorkArea()
2175 ? ¤tMainWorkArea()->bufferView()
2180 BufferView const * GuiView::documentBufferView() const
2182 return currentMainWorkArea()
2183 ? ¤tMainWorkArea()->bufferView()
2188 BufferView * GuiView::currentBufferView()
2190 return d.current_work_area_ ? &d.current_work_area_->bufferView() : nullptr;
2194 BufferView const * GuiView::currentBufferView() const
2196 return d.current_work_area_ ? &d.current_work_area_->bufferView() : nullptr;
2200 docstring GuiView::GuiViewPrivate::autosaveAndDestroy(
2201 Buffer const * orig, Buffer * clone)
2203 bool const success = clone->autoSave();
2205 busyBuffers.remove(orig);
2207 ? _("Automatic save done.")
2208 : _("Automatic save failed!");
2212 void GuiView::autoSave()
2214 LYXERR(Debug::INFO, "Running autoSave()");
2216 Buffer * buffer = documentBufferView()
2217 ? &documentBufferView()->buffer() : nullptr;
2219 resetAutosaveTimers();
2223 GuiViewPrivate::busyBuffers.insert(buffer);
2224 QFuture<docstring> f = QtConcurrent::run(GuiViewPrivate::autosaveAndDestroy,
2225 buffer, buffer->cloneBufferOnly());
2226 d.autosave_watcher_.setFuture(f);
2227 resetAutosaveTimers();
2231 void GuiView::resetAutosaveTimers()
2234 d.autosave_timeout_.restart();
2240 double zoomRatio(FuncRequest const & cmd, double const zr)
2242 if (cmd.argument().empty()) {
2243 if (cmd.action() == LFUN_BUFFER_ZOOM)
2245 else if (cmd.action() == LFUN_BUFFER_ZOOM_IN)
2247 else // cmd.action() == LFUN_BUFFER_ZOOM_OUT
2250 if (cmd.action() == LFUN_BUFFER_ZOOM)
2251 return convert<int>(cmd.argument()) / double(lyxrc.defaultZoom);
2252 else if (cmd.action() == LFUN_BUFFER_ZOOM_IN)
2253 return zr + convert<int>(cmd.argument()) / 100.0;
2254 else // cmd.action() == LFUN_BUFFER_ZOOM_OUT
2255 return zr - convert<int>(cmd.argument()) / 100.0;
2262 bool GuiView::getStatus(FuncRequest const & cmd, FuncStatus & flag)
2265 Buffer * buf = currentBufferView()
2266 ? ¤tBufferView()->buffer() : nullptr;
2267 Buffer * doc_buffer = documentBufferView()
2268 ? &(documentBufferView()->buffer()) : nullptr;
2271 /* In LyX/Mac, when a dialog is open, the menus of the
2272 application can still be accessed without giving focus to
2273 the main window. In this case, we want to disable the menu
2274 entries that are buffer-related.
2275 This code must not be used on Linux and Windows, since it
2276 would disable buffer-related entries when hovering over the
2277 menu (see bug #9574).
2279 if (cmd.origin() == FuncRequest::MENU && !hasFocus()) {
2285 // Check whether we need a buffer
2286 if (!lyxaction.funcHasFlag(cmd.action(), LyXAction::NoBuffer) && !buf) {
2287 // no, exit directly
2288 flag.message(from_utf8(N_("Command not allowed with"
2289 "out any document open")));
2290 flag.setEnabled(false);
2294 if (cmd.origin() == FuncRequest::TOC) {
2295 GuiToc * toc = static_cast<GuiToc*>(findOrBuild("toc", false));
2296 if (!toc || !toc->getStatus(documentBufferView()->cursor(), cmd, flag))
2297 flag.setEnabled(false);
2301 switch(cmd.action()) {
2302 case LFUN_BUFFER_IMPORT:
2305 case LFUN_MASTER_BUFFER_EXPORT:
2307 && (doc_buffer->parent() != nullptr
2308 || doc_buffer->hasChildren())
2309 && !d.processing_thread_watcher_.isRunning()
2310 // this launches a dialog, which would be in the wrong Buffer
2311 && !(::lyx::operator==(cmd.argument(), "custom"));
2314 case LFUN_MASTER_BUFFER_UPDATE:
2315 case LFUN_MASTER_BUFFER_VIEW:
2317 && (doc_buffer->parent() != nullptr
2318 || doc_buffer->hasChildren())
2319 && !d.processing_thread_watcher_.isRunning();
2322 case LFUN_BUFFER_UPDATE:
2323 case LFUN_BUFFER_VIEW: {
2324 if (!doc_buffer || d.processing_thread_watcher_.isRunning()) {
2328 string format = to_utf8(cmd.argument());
2329 if (cmd.argument().empty())
2330 format = doc_buffer->params().getDefaultOutputFormat();
2331 enable = doc_buffer->params().isExportable(format, true);
2335 case LFUN_BUFFER_RELOAD:
2336 enable = doc_buffer && !doc_buffer->isUnnamed()
2337 && doc_buffer->fileName().exists()
2338 && (!doc_buffer->isClean() || doc_buffer->notifiesExternalModification());
2341 case LFUN_BUFFER_RESET_EXPORT:
2342 enable = doc_buffer != nullptr;
2345 case LFUN_BUFFER_CHILD_OPEN:
2346 enable = doc_buffer != nullptr;
2349 case LFUN_MASTER_BUFFER_FORALL: {
2350 if (doc_buffer == nullptr) {
2351 flag.message(from_utf8(N_("Command not allowed without a buffer open")));
2355 FuncRequest const cmdToPass = lyxaction.lookupFunc(cmd.getLongArg(0));
2356 if (cmdToPass.action() == LFUN_UNKNOWN_ACTION) {
2357 flag.message(from_utf8(N_("Invalid argument of master-buffer-forall")));
2362 for (Buffer * buf : doc_buffer->allRelatives()) {
2363 GuiWorkArea * wa = workArea(*buf);
2366 if (wa->bufferView().getStatus(cmdToPass, flag)) {
2367 enable = flag.enabled();
2374 case LFUN_BUFFER_WRITE:
2375 enable = doc_buffer && (doc_buffer->isUnnamed()
2376 || (!doc_buffer->isClean()
2377 || cmd.argument() == "force"));
2380 //FIXME: This LFUN should be moved to GuiApplication.
2381 case LFUN_BUFFER_WRITE_ALL: {
2382 // We enable the command only if there are some modified buffers
2383 Buffer * first = theBufferList().first();
2388 // We cannot use a for loop as the buffer list is a cycle.
2390 if (!b->isClean()) {
2394 b = theBufferList().next(b);
2395 } while (b != first);
2399 case LFUN_BUFFER_EXTERNAL_MODIFICATION_CLEAR:
2400 enable = doc_buffer && doc_buffer->notifiesExternalModification();
2403 case LFUN_BUFFER_EXPORT: {
2404 if (!doc_buffer || d.processing_thread_watcher_.isRunning()) {
2408 return doc_buffer->getStatus(cmd, flag);
2411 case LFUN_BUFFER_EXPORT_AS:
2412 if (!doc_buffer || d.processing_thread_watcher_.isRunning()) {
2417 case LFUN_BUFFER_WRITE_AS:
2418 case LFUN_BUFFER_WRITE_AS_TEMPLATE:
2419 enable = doc_buffer != nullptr;
2422 case LFUN_EXPORT_CANCEL:
2423 enable = d.processing_thread_watcher_.isRunning();
2426 case LFUN_BUFFER_CLOSE:
2427 case LFUN_VIEW_CLOSE:
2428 enable = doc_buffer != nullptr;
2431 case LFUN_BUFFER_CLOSE_ALL:
2432 enable = theBufferList().last() != theBufferList().first();
2435 case LFUN_BUFFER_CHKTEX: {
2436 // hide if we have no checktex command
2437 if (lyxrc.chktex_command.empty()) {
2438 flag.setUnknown(true);
2442 if (!doc_buffer || !doc_buffer->params().isLatex()
2443 || d.processing_thread_watcher_.isRunning()) {
2444 // grey out, don't hide
2452 case LFUN_VIEW_SPLIT:
2453 if (cmd.getArg(0) == "vertical")
2454 enable = doc_buffer && (d.splitter_->count() == 1 ||
2455 d.splitter_->orientation() == Qt::Vertical);
2457 enable = doc_buffer && (d.splitter_->count() == 1 ||
2458 d.splitter_->orientation() == Qt::Horizontal);
2461 case LFUN_TAB_GROUP_CLOSE:
2462 enable = d.tabWorkAreaCount() > 1;
2465 case LFUN_DEVEL_MODE_TOGGLE:
2466 flag.setOnOff(devel_mode_);
2469 case LFUN_TOOLBAR_SET: {
2470 string const name = cmd.getArg(0);
2471 string const state = cmd.getArg(1);
2472 if (name.empty() || state.empty()) {
2474 docstring const msg =
2475 _("Function toolbar-set requires two arguments!");
2479 if (state != "on" && state != "off" && state != "auto") {
2481 docstring const msg =
2482 bformat(_("Invalid argument \"%1$s\" to function toolbar-set!"),
2487 if (GuiToolbar * t = toolbar(name)) {
2488 bool const autovis = t->visibility() & Toolbars::AUTO;
2490 flag.setOnOff(t->isVisible() && !autovis);
2491 else if (state == "off")
2492 flag.setOnOff(!t->isVisible() && !autovis);
2493 else if (state == "auto")
2494 flag.setOnOff(autovis);
2497 docstring const msg =
2498 bformat(_("Unknown toolbar \"%1$s\""), from_utf8(name));
2504 case LFUN_TOOLBAR_TOGGLE: {
2505 string const name = cmd.getArg(0);
2506 if (GuiToolbar * t = toolbar(name))
2507 flag.setOnOff(t->isVisible());
2510 docstring const msg =
2511 bformat(_("Unknown toolbar \"%1$s\""), from_utf8(name));
2517 case LFUN_TOOLBAR_MOVABLE: {
2518 string const name = cmd.getArg(0);
2519 // use negation since locked == !movable
2521 // toolbar name * locks all toolbars
2522 flag.setOnOff(!toolbarsMovable_);
2523 else if (GuiToolbar * t = toolbar(name))
2524 flag.setOnOff(!(t->isMovable()));
2527 docstring const msg =
2528 bformat(_("Unknown toolbar \"%1$s\""), from_utf8(name));
2534 case LFUN_ICON_SIZE:
2535 flag.setOnOff(d.iconSize(cmd.argument()) == iconSize());
2538 case LFUN_DROP_LAYOUTS_CHOICE:
2539 enable = buf != nullptr;
2542 case LFUN_UI_TOGGLE:
2543 if (cmd.argument() == "zoomlevel") {
2544 flag.setOnOff(zoom_value_ ? zoom_value_->isVisible() : false);
2545 } else if (cmd.argument() == "zoomslider") {
2546 flag.setOnOff(zoom_slider_ ? zoom_slider_->isVisible() : false);
2547 } else if (cmd.argument() == "statistics-w") {
2548 flag.setOnOff(word_count_enabled_);
2549 } else if (cmd.argument() == "statistics-cb") {
2550 flag.setOnOff(char_count_enabled_);
2551 } else if (cmd.argument() == "statistics-c") {
2552 flag.setOnOff(char_nb_count_enabled_);
2554 flag.setOnOff(isFullScreen());
2557 case LFUN_DIALOG_DISCONNECT_INSET:
2560 case LFUN_DIALOG_HIDE:
2561 // FIXME: should we check if the dialog is shown?
2564 case LFUN_DIALOG_TOGGLE:
2565 flag.setOnOff(isDialogVisible(cmd.getArg(0)));
2568 case LFUN_DIALOG_SHOW: {
2569 string const name = cmd.getArg(0);
2571 enable = name == "aboutlyx"
2572 || name == "file" //FIXME: should be removed.
2573 || name == "lyxfiles"
2575 || name == "texinfo"
2576 || name == "progress"
2577 || name == "compare";
2578 else if (name == "character" || name == "symbols"
2579 || name == "mathdelimiter" || name == "mathmatrix") {
2580 if (!buf || buf->isReadonly())
2583 Cursor const & cur = currentBufferView()->cursor();
2584 enable = !(cur.inTexted() && cur.paragraph().isPassThru());
2587 else if (name == "latexlog")
2588 enable = FileName(doc_buffer->logName()).isReadableFile();
2589 else if (name == "spellchecker")
2590 enable = theSpellChecker()
2591 && !doc_buffer->text().empty();
2592 else if (name == "vclog")
2593 enable = doc_buffer->lyxvc().inUse();
2597 case LFUN_DIALOG_UPDATE: {
2598 string const name = cmd.getArg(0);
2600 enable = name == "prefs";
2604 case LFUN_COMMAND_EXECUTE:
2606 case LFUN_MENU_OPEN:
2607 // Nothing to check.
2610 case LFUN_COMPLETION_INLINE:
2611 if (!d.current_work_area_
2612 || !d.current_work_area_->completer().inlinePossible(
2613 currentBufferView()->cursor()))
2617 case LFUN_COMPLETION_POPUP:
2618 if (!d.current_work_area_
2619 || !d.current_work_area_->completer().popupPossible(
2620 currentBufferView()->cursor()))
2625 if (!d.current_work_area_
2626 || !d.current_work_area_->completer().inlinePossible(
2627 currentBufferView()->cursor()))
2631 case LFUN_COMPLETION_ACCEPT:
2632 if (!d.current_work_area_
2633 || (!d.current_work_area_->completer().popupVisible()
2634 && !d.current_work_area_->completer().inlineVisible()
2635 && !d.current_work_area_->completer().completionAvailable()))
2639 case LFUN_COMPLETION_CANCEL:
2640 if (!d.current_work_area_
2641 || (!d.current_work_area_->completer().popupVisible()
2642 && !d.current_work_area_->completer().inlineVisible()))
2646 case LFUN_BUFFER_ZOOM_OUT:
2647 case LFUN_BUFFER_ZOOM_IN:
2648 case LFUN_BUFFER_ZOOM: {
2649 int const zoom = (int)(lyxrc.defaultZoom * zoomRatio(cmd, zoom_ratio_));
2650 if (zoom < zoom_min_) {
2651 docstring const msg =
2652 bformat(_("Zoom level cannot be less than %1$d%."), zoom_min_);
2655 } else if (zoom > zoom_max_) {
2656 docstring const msg =
2657 bformat(_("Zoom level cannot be more than %1$d%."), zoom_max_);
2661 enable = doc_buffer;
2666 case LFUN_BUFFER_MOVE_NEXT:
2667 case LFUN_BUFFER_MOVE_PREVIOUS:
2668 // we do not cycle when moving
2669 case LFUN_BUFFER_NEXT:
2670 case LFUN_BUFFER_PREVIOUS:
2671 // because we cycle, it doesn't matter whether on first or last
2672 enable = (d.currentTabWorkArea()->count() > 1);
2674 case LFUN_BUFFER_SWITCH:
2675 // toggle on the current buffer, but do not toggle off
2676 // the other ones (is that a good idea?)
2678 && to_utf8(cmd.argument()) == doc_buffer->absFileName())
2679 flag.setOnOff(true);
2682 case LFUN_VC_REGISTER:
2683 enable = doc_buffer && !doc_buffer->lyxvc().inUse();
2685 case LFUN_VC_RENAME:
2686 enable = doc_buffer && doc_buffer->lyxvc().renameEnabled();
2689 enable = doc_buffer && doc_buffer->lyxvc().copyEnabled();
2691 case LFUN_VC_CHECK_IN:
2692 enable = doc_buffer && doc_buffer->lyxvc().checkInEnabled();
2694 case LFUN_VC_CHECK_OUT:
2695 enable = doc_buffer && doc_buffer->lyxvc().checkOutEnabled();
2697 case LFUN_VC_LOCKING_TOGGLE:
2698 enable = doc_buffer && !doc_buffer->hasReadonlyFlag()
2699 && doc_buffer->lyxvc().lockingToggleEnabled();
2700 flag.setOnOff(enable && doc_buffer->lyxvc().locking());
2702 case LFUN_VC_REVERT:
2703 enable = doc_buffer && doc_buffer->lyxvc().inUse()
2704 && !doc_buffer->hasReadonlyFlag();
2706 case LFUN_VC_UNDO_LAST:
2707 enable = doc_buffer && doc_buffer->lyxvc().undoLastEnabled();
2709 case LFUN_VC_REPO_UPDATE:
2710 enable = doc_buffer && doc_buffer->lyxvc().repoUpdateEnabled();
2712 case LFUN_VC_COMMAND: {
2713 if (cmd.argument().empty())
2715 if (!doc_buffer && contains(cmd.getArg(0), 'D'))
2719 case LFUN_VC_COMPARE:
2720 enable = doc_buffer && doc_buffer->lyxvc().prepareFileRevisionEnabled();
2723 case LFUN_SERVER_GOTO_FILE_ROW:
2724 case LFUN_LYX_ACTIVATE:
2725 case LFUN_WINDOW_RAISE:
2727 case LFUN_FORWARD_SEARCH:
2728 enable = !(lyxrc.forward_search_dvi.empty() && lyxrc.forward_search_pdf.empty()) &&
2729 doc_buffer && doc_buffer->isSyncTeXenabled();
2732 case LFUN_FILE_INSERT_PLAINTEXT:
2733 case LFUN_FILE_INSERT_PLAINTEXT_PARA:
2734 enable = documentBufferView() && documentBufferView()->cursor().inTexted();
2737 case LFUN_SPELLING_CONTINUOUSLY:
2738 flag.setOnOff(lyxrc.spellcheck_continuously);
2741 case LFUN_CITATION_OPEN:
2750 flag.setEnabled(false);
2756 static FileName selectTemplateFile()
2758 FileDialog dlg(qt_("Select template file"));
2759 dlg.setButton1(qt_("D&ocuments"), toqstr(lyxrc.document_path));
2760 dlg.setButton2(qt_("&Templates"), toqstr(lyxrc.template_path));
2762 FileDialog::Result result = dlg.open(toqstr(lyxrc.template_path),
2763 QStringList(qt_("LyX Documents (*.lyx)")));
2765 if (result.first == FileDialog::Later)
2767 if (result.second.isEmpty())
2769 return FileName(fromqstr(result.second));
2773 Buffer * GuiView::loadDocument(FileName const & filename, bool tolastfiles)
2777 Buffer * newBuffer = nullptr;
2779 newBuffer = checkAndLoadLyXFile(filename);
2780 } catch (ExceptionMessage const &) {
2787 message(_("Document not loaded."));
2791 setBuffer(newBuffer);
2792 newBuffer->errors("Parse");
2795 theSession().lastFiles().add(filename);
2796 theSession().writeFile();
2803 void GuiView::openDocument(string const & fname)
2805 string initpath = lyxrc.document_path;
2807 if (documentBufferView()) {
2808 string const trypath = documentBufferView()->buffer().filePath();
2809 // If directory is writeable, use this as default.
2810 if (FileName(trypath).isDirWritable())
2816 if (fname.empty()) {
2817 FileDialog dlg(qt_("Select document to open"));
2818 dlg.setButton1(qt_("D&ocuments"), toqstr(lyxrc.document_path));
2819 dlg.setButton2(qt_("&Examples"), toqstr(lyxrc.example_path));
2821 QStringList const filter({
2822 qt_("LyX Documents (*.lyx)"),
2823 qt_("LyX Document Backups (*.lyx~)"),
2824 qt_("All Files (*.*)")
2826 FileDialog::Result result =
2827 dlg.open(toqstr(initpath), filter);
2829 if (result.first == FileDialog::Later)
2832 filename = fromqstr(result.second);
2834 // check selected filename
2835 if (filename.empty()) {
2836 message(_("Canceled."));
2842 // get absolute path of file and add ".lyx" to the filename if
2844 FileName const fullname =
2845 fileSearch(string(), filename, "lyx", support::may_not_exist);
2846 if (!fullname.empty())
2847 filename = fullname.absFileName();
2849 if (!fullname.onlyPath().isDirectory()) {
2850 Alert::warning(_("Invalid filename"),
2851 bformat(_("The directory in the given path\n%1$s\ndoes not exist."),
2852 from_utf8(fullname.absFileName())));
2856 // if the file doesn't exist and isn't already open (bug 6645),
2857 // let the user create one
2858 if (!fullname.exists() && !theBufferList().exists(fullname) &&
2859 !LyXVC::file_not_found_hook(fullname)) {
2860 // the user specifically chose this name. Believe him.
2861 Buffer * const b = newFile(filename, string(), true);
2867 docstring const disp_fn = makeDisplayPath(filename);
2868 message(bformat(_("Opening document %1$s..."), disp_fn));
2871 Buffer * buf = loadDocument(fullname);
2873 str2 = bformat(_("Document %1$s opened."), disp_fn);
2874 if (buf->lyxvc().inUse())
2875 str2 += " " + from_utf8(buf->lyxvc().versionString()) +
2876 " " + _("Version control detected.");
2878 str2 = bformat(_("Could not open document %1$s"), disp_fn);
2883 // FIXME: clean that
2884 static bool import(GuiView * lv, FileName const & filename,
2885 string const & format, ErrorList & errorList)
2887 FileName const lyxfile(support::changeExtension(filename.absFileName(), ".lyx"));
2889 string loader_format;
2890 vector<string> loaders = theConverters().loaders();
2891 if (find(loaders.begin(), loaders.end(), format) == loaders.end()) {
2892 for (string const & loader : loaders) {
2893 if (!theConverters().isReachable(format, loader))
2896 string const tofile =
2897 support::changeExtension(filename.absFileName(),
2898 theFormats().extension(loader));
2899 if (theConverters().convert(nullptr, filename, FileName(tofile),
2900 filename, format, loader, errorList) != Converters::SUCCESS)
2902 loader_format = loader;
2905 if (loader_format.empty()) {
2906 frontend::Alert::error(_("Couldn't import file"),
2907 bformat(_("No information for importing the format %1$s."),
2908 translateIfPossible(theFormats().prettyName(format))));
2912 loader_format = format;
2914 if (loader_format == "lyx") {
2915 Buffer * buf = lv->loadDocument(lyxfile);
2919 Buffer * const b = newFile(lyxfile.absFileName(), string(), true);
2923 bool as_paragraphs = loader_format == "textparagraph";
2924 string filename2 = (loader_format == format) ? filename.absFileName()
2925 : support::changeExtension(filename.absFileName(),
2926 theFormats().extension(loader_format));
2927 lv->currentBufferView()->insertPlaintextFile(FileName(filename2),
2929 guiApp->setCurrentView(lv);
2930 lyx::dispatch(FuncRequest(LFUN_MARK_OFF));
2937 void GuiView::importDocument(string const & argument)
2940 string filename = split(argument, format, ' ');
2942 LYXERR(Debug::INFO, format << " file: " << filename);
2944 // need user interaction
2945 if (filename.empty()) {
2946 string initpath = lyxrc.document_path;
2947 if (documentBufferView()) {
2948 string const trypath = documentBufferView()->buffer().filePath();
2949 // If directory is writeable, use this as default.
2950 if (FileName(trypath).isDirWritable())
2954 docstring const text = bformat(_("Select %1$s file to import"),
2955 translateIfPossible(theFormats().prettyName(format)));
2957 FileDialog dlg(toqstr(text));
2958 dlg.setButton1(qt_("D&ocuments"), toqstr(lyxrc.document_path));
2959 dlg.setButton2(qt_("&Examples"), toqstr(lyxrc.example_path));
2961 docstring filter = translateIfPossible(theFormats().prettyName(format));
2964 filter += from_utf8(theFormats().extensions(format));
2967 FileDialog::Result result =
2968 dlg.open(toqstr(initpath), fileFilters(toqstr(filter)));
2970 if (result.first == FileDialog::Later)
2973 filename = fromqstr(result.second);
2975 // check selected filename
2976 if (filename.empty())
2977 message(_("Canceled."));
2980 if (filename.empty())
2983 // get absolute path of file
2984 FileName const fullname(support::makeAbsPath(filename));
2986 // Can happen if the user entered a path into the dialog
2988 if (fullname.onlyFileName().empty()) {
2989 docstring msg = bformat(_("The file name '%1$s' is invalid!\n"
2990 "Aborting import."),
2991 from_utf8(fullname.absFileName()));
2992 frontend::Alert::error(_("File name error"), msg);
2993 message(_("Canceled."));
2998 FileName const lyxfile(support::changeExtension(fullname.absFileName(), ".lyx"));
3000 // Check if the document already is open
3001 Buffer * buf = theBufferList().getBuffer(lyxfile);
3004 if (!closeBuffer()) {
3005 message(_("Canceled."));
3010 docstring const displaypath = makeDisplayPath(lyxfile.absFileName(), 30);
3012 // if the file exists already, and we didn't do
3013 // -i lyx thefile.lyx, warn
3014 if (lyxfile.exists() && fullname != lyxfile) {
3016 docstring text = bformat(_("The document %1$s already exists.\n\n"
3017 "Do you want to overwrite that document?"), displaypath);
3018 int const ret = Alert::prompt(_("Overwrite document?"),
3019 text, 0, 1, _("&Overwrite"), _("&Cancel"));
3022 message(_("Canceled."));
3027 message(bformat(_("Importing %1$s..."), displaypath));
3028 ErrorList errorList;
3029 if (import(this, fullname, format, errorList))
3030 message(_("imported."));
3032 message(_("file not imported!"));
3034 // FIXME (Abdel 12/08/06): Is there a need to display the error list here?
3038 void GuiView::newDocument(string const & filename, string templatefile,
3041 FileName initpath(lyxrc.document_path);
3042 if (documentBufferView()) {
3043 FileName const trypath(documentBufferView()->buffer().filePath());
3044 // If directory is writeable, use this as default.
3045 if (trypath.isDirWritable())
3049 if (from_template) {
3050 if (templatefile.empty())
3051 templatefile = selectTemplateFile().absFileName();
3052 if (templatefile.empty())
3057 if (filename.empty())
3058 b = newUnnamedFile(initpath, to_utf8(_("newfile")), templatefile);
3060 b = newFile(filename, templatefile, true);
3065 // If no new document could be created, it is unsure
3066 // whether there is a valid BufferView.
3067 if (currentBufferView())
3068 // Ensure the cursor is correctly positioned on screen.
3069 currentBufferView()->showCursor();
3073 bool GuiView::insertLyXFile(docstring const & fname, bool ignorelang)
3075 BufferView * bv = documentBufferView();
3080 FileName filename(to_utf8(fname));
3081 if (filename.empty()) {
3082 // Launch a file browser
3084 string initpath = lyxrc.document_path;
3085 string const trypath = bv->buffer().filePath();
3086 // If directory is writeable, use this as default.
3087 if (FileName(trypath).isDirWritable())
3091 FileDialog dlg(qt_("Select LyX document to insert"));
3092 dlg.setButton1(qt_("D&ocuments"), toqstr(lyxrc.document_path));
3093 dlg.setButton2(qt_("&Examples"), toqstr(lyxrc.example_path));
3095 FileDialog::Result result = dlg.open(toqstr(initpath),
3096 QStringList(qt_("LyX Documents (*.lyx)")));
3098 if (result.first == FileDialog::Later)
3102 filename.set(fromqstr(result.second));
3104 // check selected filename
3105 if (filename.empty()) {
3106 // emit message signal.
3107 message(_("Canceled."));
3112 bv->insertLyXFile(filename, ignorelang);
3113 bv->buffer().errors("Parse");
3118 string const GuiView::getTemplatesPath(Buffer & b)
3120 // We start off with the user's templates path
3121 string result = addPath(package().user_support().absFileName(), "templates");
3122 // Check for the document language
3123 string const langcode = b.params().language->code();
3124 string const shortcode = langcode.substr(0, 2);
3125 if (!langcode.empty() && shortcode != "en") {
3126 string subpath = addPath(result, shortcode);
3127 string subpath_long = addPath(result, langcode);
3128 // If we have a subdirectory for the language already,
3130 FileName sp = FileName(subpath);
3131 if (sp.isDirectory())
3133 else if (FileName(subpath_long).isDirectory())
3134 result = subpath_long;
3136 // Ask whether we should create such a subdirectory
3137 docstring const text =
3138 bformat(_("It is suggested to save the template in a subdirectory\n"
3139 "appropriate to the document language (%1$s).\n"
3140 "This subdirectory does not exists yet.\n"
3141 "Do you want to create it?"),
3142 _(b.params().language->display()));
3143 if (Alert::prompt(_("Create Language Directory?"),
3144 text, 0, 1, _("&Yes, Create"), _("&No, Save Template in Parent Directory")) == 0) {
3145 // If the user agreed, we try to create it and report if this failed.
3146 if (!sp.createDirectory(0777))
3147 Alert::error(_("Subdirectory creation failed!"),
3148 _("Could not create subdirectory.\n"
3149 "The template will be saved in the parent directory."));
3155 // Do we have a layout category?
3156 string const cat = b.params().baseClass() ?
3157 b.params().baseClass()->category()
3160 string subpath = addPath(result, cat);
3161 // If we have a subdirectory for the category already,
3163 FileName sp = FileName(subpath);
3164 if (sp.isDirectory())
3167 // Ask whether we should create such a subdirectory
3168 docstring const text =
3169 bformat(_("It is suggested to save the template in a subdirectory\n"
3170 "appropriate to the layout category (%1$s).\n"
3171 "This subdirectory does not exists yet.\n"
3172 "Do you want to create it?"),
3174 if (Alert::prompt(_("Create Category Directory?"),
3175 text, 0, 1, _("&Yes, Create"), _("&No, Save Template in Parent Directory")) == 0) {
3176 // If the user agreed, we try to create it and report if this failed.
3177 if (!sp.createDirectory(0777))
3178 Alert::error(_("Subdirectory creation failed!"),
3179 _("Could not create subdirectory.\n"
3180 "The template will be saved in the parent directory."));
3190 bool GuiView::renameBuffer(Buffer & b, docstring const & newname, RenameKind kind)
3192 FileName fname = b.fileName();
3193 FileName const oldname = fname;
3194 bool const as_template = (kind == LV_WRITE_AS_TEMPLATE);
3196 if (!newname.empty()) {
3199 fname = support::makeAbsPath(to_utf8(newname), getTemplatesPath(b));
3201 fname = support::makeAbsPath(to_utf8(newname),
3202 oldname.onlyPath().absFileName());
3204 // Switch to this Buffer.
3207 // No argument? Ask user through dialog.
3209 QString const title = as_template ? qt_("Choose a filename to save template as")
3210 : qt_("Choose a filename to save document as");
3211 FileDialog dlg(title);
3212 dlg.setButton1(qt_("D&ocuments"), toqstr(lyxrc.document_path));
3213 dlg.setButton2(qt_("&Templates"), toqstr(lyxrc.template_path));
3215 fname.ensureExtension(".lyx");
3217 string const path = as_template ?
3219 : fname.onlyPath().absFileName();
3220 FileDialog::Result result =
3221 dlg.save(toqstr(path),
3222 QStringList(qt_("LyX Documents (*.lyx)")),
3223 toqstr(fname.onlyFileName()));
3225 if (result.first == FileDialog::Later)
3228 fname.set(fromqstr(result.second));
3233 fname.ensureExtension(".lyx");
3236 // fname is now the new Buffer location.
3238 // if there is already a Buffer open with this name, we do not want
3239 // to have another one. (the second test makes sure we're not just
3240 // trying to overwrite ourselves, which is fine.)
3241 if (theBufferList().exists(fname) && fname != oldname
3242 && theBufferList().getBuffer(fname) != &b) {
3243 docstring const text =
3244 bformat(_("The file\n%1$s\nis already open in your current session.\n"
3245 "Please close it before attempting to overwrite it.\n"
3246 "Do you want to choose a new filename?"),
3247 from_utf8(fname.absFileName()));
3248 int const ret = Alert::prompt(_("Chosen File Already Open"),
3249 text, 0, 1, _("&Rename"), _("&Cancel"));
3251 case 0: return renameBuffer(b, docstring(), kind);
3252 case 1: return false;
3257 bool const existsLocal = fname.exists();
3258 bool const existsInVC = LyXVC::fileInVC(fname);
3259 if (existsLocal || existsInVC) {
3260 docstring const file = makeDisplayPath(fname.absFileName(), 30);
3261 if (kind != LV_WRITE_AS && existsInVC) {
3262 // renaming to a name that is already in VC
3264 docstring text = bformat(_("The document %1$s "
3265 "is already registered.\n\n"
3266 "Do you want to choose a new name?"),
3268 docstring const title = (kind == LV_VC_RENAME) ?
3269 _("Rename document?") : _("Copy document?");
3270 docstring const button = (kind == LV_VC_RENAME) ?
3271 _("&Rename") : _("&Copy");
3272 int const ret = Alert::prompt(title, text, 0, 1,
3273 button, _("&Cancel"));
3275 case 0: return renameBuffer(b, docstring(), kind);
3276 case 1: return false;
3281 docstring text = bformat(_("The document %1$s "
3282 "already exists.\n\n"
3283 "Do you want to overwrite that document?"),
3285 int const ret = Alert::prompt(_("Overwrite document?"),
3286 text, 0, 2, _("&Overwrite"),
3287 _("&Rename"), _("&Cancel"));
3290 case 1: return renameBuffer(b, docstring(), kind);
3291 case 2: return false;
3297 case LV_VC_RENAME: {
3298 string msg = b.lyxvc().rename(fname);
3301 message(from_utf8(msg));
3305 string msg = b.lyxvc().copy(fname);
3308 message(from_utf8(msg));
3312 case LV_WRITE_AS_TEMPLATE:
3315 // LyXVC created the file already in case of LV_VC_RENAME or
3316 // LV_VC_COPY, but call saveBuffer() nevertheless to get
3317 // relative paths of included stuff right if we moved e.g. from
3318 // /a/b.lyx to /a/c/b.lyx.
3320 bool const saved = saveBuffer(b, fname);
3327 bool GuiView::exportBufferAs(Buffer & b, docstring const & iformat)
3329 FileName fname = b.fileName();
3331 FileDialog dlg(qt_("Choose a filename to export the document as"));
3332 dlg.setButton1(qt_("D&ocuments"), toqstr(lyxrc.document_path));
3335 QString const anyformat = qt_("Guess from extension (*.*)");
3338 vector<Format const *> export_formats;
3339 for (Format const & f : theFormats())
3340 if (f.documentFormat())
3341 export_formats.push_back(&f);
3342 sort(export_formats.begin(), export_formats.end(), Format::formatSorter);
3343 map<QString, string> fmap;
3346 for (Format const * f : export_formats) {
3347 docstring const loc_prettyname = translateIfPossible(f->prettyname());
3348 QString const loc_filter = toqstr(bformat(from_ascii("%1$s (*.%2$s)"),
3350 from_ascii(f->extension())));
3351 types << loc_filter;
3352 fmap[loc_filter] = f->name();
3353 if (from_ascii(f->name()) == iformat) {
3354 filter = loc_filter;
3355 ext = f->extension();
3358 string ofname = fname.onlyFileName();
3360 ofname = support::changeExtension(ofname, ext);
3361 FileDialog::Result result =
3362 dlg.save(toqstr(fname.onlyPath().absFileName()),
3366 if (result.first != FileDialog::Chosen)
3370 fname.set(fromqstr(result.second));
3371 if (filter == anyformat)
3372 fmt_name = theFormats().getFormatFromExtension(fname.extension());
3374 fmt_name = fmap[filter];
3375 LYXERR(Debug::FILES, "filter=" << fromqstr(filter)
3376 << ", fmt_name=" << fmt_name << ", fname=" << fname.absFileName());
3378 if (fmt_name.empty() || fname.empty())
3381 fname.ensureExtension(theFormats().extension(fmt_name));
3383 // fname is now the new Buffer location.
3384 if (fname.exists()) {
3385 docstring const file = makeDisplayPath(fname.absFileName(), 30);
3386 docstring text = bformat(_("The document %1$s already "
3387 "exists.\n\nDo you want to "
3388 "overwrite that document?"),
3390 int const ret = Alert::prompt(_("Overwrite document?"),
3391 text, 0, 2, _("&Overwrite"), _("&Rename"), _("&Cancel"));
3394 case 1: return exportBufferAs(b, from_ascii(fmt_name));
3395 case 2: return false;
3399 FuncRequest cmd(LFUN_BUFFER_EXPORT, fmt_name + " " + fname.absFileName());
3402 return dr.dispatched();
3406 bool GuiView::saveBuffer(Buffer & b)
3408 return saveBuffer(b, FileName());
3412 bool GuiView::saveBuffer(Buffer & b, FileName const & fn)
3414 if (workArea(b) && workArea(b)->inDialogMode())
3417 if (fn.empty() && b.isUnnamed())
3418 return renameBuffer(b, docstring());
3420 bool const success = (fn.empty() ? b.save() : b.saveAs(fn));
3422 theSession().lastFiles().add(b.fileName());
3423 theSession().writeFile();
3427 // Switch to this Buffer.
3430 // FIXME: we don't tell the user *WHY* the save failed !!
3431 docstring const file = makeDisplayPath(b.absFileName(), 30);
3432 docstring text = bformat(_("The document %1$s could not be saved.\n\n"
3433 "Do you want to rename the document and "
3434 "try again?"), file);
3435 int const ret = Alert::prompt(_("Rename and save?"),
3436 text, 0, 2, _("&Rename"), _("&Retry"), _("&Cancel"));
3439 if (!renameBuffer(b, docstring()))
3448 return saveBuffer(b, fn);
3452 bool GuiView::hideWorkArea(GuiWorkArea * wa)
3454 return closeWorkArea(wa, false);
3458 // We only want to close the buffer if it is not visible in other workareas
3459 // of the same view, nor in other views, and if this is not a child
3460 bool GuiView::closeWorkArea(GuiWorkArea * wa)
3462 Buffer & buf = wa->bufferView().buffer();
3464 bool last_wa = d.countWorkAreasOf(buf) == 1
3465 && !inOtherView(buf) && !buf.parent();
3467 bool close_buffer = last_wa;
3470 if (lyxrc.close_buffer_with_last_view == "yes")
3472 else if (lyxrc.close_buffer_with_last_view == "no")
3473 close_buffer = false;
3476 if (buf.isUnnamed())
3477 file = from_utf8(buf.fileName().onlyFileName());
3479 file = buf.fileName().displayName(30);
3480 docstring const text = bformat(
3481 _("Last view on document %1$s is being closed.\n"
3482 "Would you like to close or hide the document?\n"
3484 "Hidden documents can be displayed back through\n"
3485 "the menu: View->Hidden->...\n"
3487 "To remove this question, set your preference in:\n"
3488 " Tools->Preferences->Look&Feel->UserInterface\n"
3490 int ret = Alert::prompt(_("Close or hide document?"),
3491 text, 0, 2, _("&Close"), _("&Hide"), _("&Cancel"));
3494 close_buffer = (ret == 0);
3498 return closeWorkArea(wa, close_buffer);
3502 bool GuiView::closeBuffer()
3504 GuiWorkArea * wa = currentMainWorkArea();
3505 // coverity complained about this
3506 // it seems unnecessary, but perhaps is worth the check
3507 LASSERT(wa, return false);
3509 setCurrentWorkArea(wa);
3510 Buffer & buf = wa->bufferView().buffer();
3511 return closeWorkArea(wa, !buf.parent());
3515 void GuiView::writeSession() const {
3516 GuiWorkArea const * active_wa = currentMainWorkArea();
3517 for (int i = 0; i < d.splitter_->count(); ++i) {
3518 TabWorkArea * twa = d.tabWorkArea(i);
3519 for (int j = 0; j < twa->count(); ++j) {
3520 GuiWorkArea * wa = twa->workArea(j);
3521 Buffer & buf = wa->bufferView().buffer();
3522 theSession().lastOpened().add(buf.fileName(), wa == active_wa);
3528 bool GuiView::closeBufferAll()
3531 for (auto & buf : theBufferList()) {
3532 if (!saveBufferIfNeeded(*buf, false)) {
3533 // Closing has been cancelled, so abort.
3538 // Close the workareas in all other views
3539 QList<int> const ids = guiApp->viewIds();
3540 for (int i = 0; i != ids.size(); ++i) {
3541 if (id_ != ids[i] && !guiApp->view(ids[i]).closeWorkAreaAll())
3545 // Close our own workareas
3546 if (!closeWorkAreaAll())
3553 bool GuiView::closeWorkAreaAll()
3555 setCurrentWorkArea(currentMainWorkArea());
3557 // We might be in a situation that there is still a tabWorkArea, but
3558 // there are no tabs anymore. This can happen when we get here after a
3559 // TabWorkArea::lastWorkAreaRemoved() signal. Therefore we count how
3560 // many TabWorkArea's have no documents anymore.
3563 // We have to call count() each time, because it can happen that
3564 // more than one splitter will disappear in one iteration (bug 5998).
3565 while (d.splitter_->count() > empty_twa) {
3566 TabWorkArea * twa = d.tabWorkArea(empty_twa);
3568 if (twa->count() == 0)
3571 setCurrentWorkArea(twa->currentWorkArea());
3572 if (!closeTabWorkArea(twa))
3580 bool GuiView::closeWorkArea(GuiWorkArea * wa, bool close_buffer)
3585 Buffer & buf = wa->bufferView().buffer();
3587 if (GuiViewPrivate::busyBuffers.contains(&buf)) {
3588 Alert::warning(_("Close document"),
3589 _("Document could not be closed because it is being processed by LyX."));
3594 return closeBuffer(buf);
3596 if (!inMultiTabs(wa))
3597 if (!saveBufferIfNeeded(buf, true))
3605 bool GuiView::closeBuffer(Buffer & buf)
3607 bool success = true;
3608 for (Buffer * child_buf : buf.getChildren()) {
3609 if (theBufferList().isOthersChild(&buf, child_buf)) {
3610 child_buf->setParent(nullptr);
3614 // FIXME: should we look in other tabworkareas?
3615 // ANSWER: I don't think so. I've tested, and if the child is
3616 // open in some other window, it closes without a problem.
3617 GuiWorkArea * child_wa = workArea(*child_buf);
3620 // If we are in a close_event all children will be closed in some time,
3621 // so no need to do it here. This will ensure that the children end up
3622 // in the session file in the correct order. If we close the master
3623 // buffer, we can close or release the child buffers here too.
3625 success = closeWorkArea(child_wa, true);
3629 // In this case the child buffer is open but hidden.
3630 // Even in this case, children can be dirty (e.g.,
3631 // after a label change in the master, see #11405).
3632 // Therefore, check this
3633 if (closing_ && (child_buf->isClean() || child_buf->paragraphs().empty())) {
3634 // If we are in a close_event all children will be closed in some time,
3635 // so no need to do it here. This will ensure that the children end up
3636 // in the session file in the correct order. If we close the master
3637 // buffer, we can close or release the child buffers here too.
3640 // Save dirty buffers also if closing_!
3641 if (saveBufferIfNeeded(*child_buf, false)) {
3642 child_buf->removeAutosaveFile();
3643 theBufferList().release(child_buf);
3645 // Saving of dirty children has been cancelled.
3646 // Cancel the whole process.
3653 // goto bookmark to update bookmark pit.
3654 // FIXME: we should update only the bookmarks related to this buffer!
3655 // FIXME: this is done also in LFUN_WINDOW_CLOSE!
3656 LYXERR(Debug::DEBUG, "GuiView::closeBuffer()");
3657 for (unsigned int i = 1; i < theSession().bookmarks().size(); ++i)
3658 guiApp->gotoBookmark(i, false, false);
3660 if (saveBufferIfNeeded(buf, false)) {
3661 buf.removeAutosaveFile();
3662 theBufferList().release(&buf);
3666 // open all children again to avoid a crash because of dangling
3667 // pointers (bug 6603)
3673 bool GuiView::closeTabWorkArea(TabWorkArea * twa)
3675 while (twa == d.currentTabWorkArea()) {
3676 twa->setCurrentIndex(twa->count() - 1);
3678 GuiWorkArea * wa = twa->currentWorkArea();
3679 Buffer & b = wa->bufferView().buffer();
3681 // We only want to close the buffer if the same buffer is not visible
3682 // in another view, and if this is not a child and if we are closing
3683 // a view (not a tabgroup).
3684 bool const close_buffer =
3685 !inOtherView(b) && !b.parent() && closing_;
3687 if (!closeWorkArea(wa, close_buffer))
3694 bool GuiView::saveBufferIfNeeded(Buffer & buf, bool hiding)
3696 if (buf.isClean() || buf.paragraphs().empty())
3699 // Switch to this Buffer.
3705 if (buf.isUnnamed()) {
3706 file = from_utf8(buf.fileName().onlyFileName());
3709 FileName filename = buf.fileName();
3711 file = filename.displayName(30);
3712 exists = filename.exists();
3715 // Bring this window to top before asking questions.
3720 if (hiding && buf.isUnnamed()) {
3721 docstring const text = bformat(_("The document %1$s has not been "
3722 "saved yet.\n\nDo you want to save "
3723 "the document?"), file);
3724 ret = Alert::prompt(_("Save new document?"),
3725 text, 0, 1, _("&Save"), _("&Cancel"));
3729 docstring const text = exists ?
3730 bformat(_("The document %1$s has unsaved changes."
3731 "\n\nDo you want to save the document or "
3732 "discard the changes?"), file) :
3733 bformat(_("The document %1$s has not been saved yet."
3734 "\n\nDo you want to save the document or "
3735 "discard it entirely?"), file);
3736 docstring const title = exists ?
3737 _("Save changed document?") : _("Save document?");
3738 ret = Alert::prompt(title, text, 0, 2,
3739 _("&Save"), _("&Discard"), _("&Cancel"));
3744 if (!saveBuffer(buf))
3748 // If we crash after this we could have no autosave file
3749 // but I guess this is really improbable (Jug).
3750 // Sometimes improbable things happen:
3751 // - see bug http://www.lyx.org/trac/ticket/6587 (ps)
3752 // buf.removeAutosaveFile();
3754 // revert all changes
3765 bool GuiView::inMultiTabs(GuiWorkArea * wa)
3767 Buffer & buf = wa->bufferView().buffer();
3769 for (int i = 0; i != d.splitter_->count(); ++i) {
3770 GuiWorkArea * wa_ = d.tabWorkArea(i)->workArea(buf);
3771 if (wa_ && wa_ != wa)
3774 return inOtherView(buf);
3778 bool GuiView::inOtherView(Buffer & buf)
3780 QList<int> const ids = guiApp->viewIds();
3782 for (int i = 0; i != ids.size(); ++i) {
3786 if (guiApp->view(ids[i]).workArea(buf))
3793 void GuiView::gotoNextOrPreviousBuffer(NextOrPrevious np, bool const move)
3795 if (!documentBufferView())
3798 if (TabWorkArea * twa = d.currentTabWorkArea()) {
3799 Buffer * const curbuf = &documentBufferView()->buffer();
3800 int nwa = twa->count();
3801 for (int i = 0; i < nwa; ++i) {
3802 if (&workArea(i)->bufferView().buffer() == curbuf) {
3804 if (np == NEXTBUFFER)
3805 next_index = (i == nwa - 1 ? 0 : i + 1);
3807 next_index = (i == 0 ? nwa - 1 : i - 1);
3809 twa->moveTab(i, next_index);
3811 setBuffer(&workArea(next_index)->bufferView().buffer());
3819 /// make sure the document is saved
3820 static bool ensureBufferClean(Buffer * buffer)
3822 LASSERT(buffer, return false);
3823 if (buffer->isClean() && !buffer->isUnnamed())
3826 docstring const file = buffer->fileName().displayName(30);
3829 if (!buffer->isUnnamed()) {
3830 text = bformat(_("The document %1$s has unsaved "
3831 "changes.\n\nDo you want to save "
3832 "the document?"), file);
3833 title = _("Save changed document?");
3836 text = bformat(_("The document %1$s has not been "
3837 "saved yet.\n\nDo you want to save "
3838 "the document?"), file);
3839 title = _("Save new document?");
3841 int const ret = Alert::prompt(title, text, 0, 1, _("&Save"), _("&Cancel"));
3844 dispatch(FuncRequest(LFUN_BUFFER_WRITE));
3846 return buffer->isClean() && !buffer->isUnnamed();
3850 bool GuiView::reloadBuffer(Buffer & buf)
3852 currentBufferView()->cursor().reset();
3853 Buffer::ReadStatus status = buf.reload();
3854 return status == Buffer::ReadSuccess;
3858 void GuiView::checkExternallyModifiedBuffers()
3860 for (Buffer * buf : theBufferList()) {
3861 if (buf->fileName().exists() && buf->isChecksumModified()) {
3862 docstring text = bformat(_("Document \n%1$s\n has been externally modified."
3863 " Reload now? Any local changes will be lost."),
3864 from_utf8(buf->absFileName()));
3865 int const ret = Alert::prompt(_("Reload externally changed document?"),
3866 text, 0, 1, _("&Reload"), _("&Cancel"));
3874 void GuiView::dispatchVC(FuncRequest const & cmd, DispatchResult & dr)
3876 Buffer * buffer = documentBufferView()
3877 ? &(documentBufferView()->buffer()) : nullptr;
3879 switch (cmd.action()) {
3880 case LFUN_VC_REGISTER:
3881 if (!buffer || !ensureBufferClean(buffer))
3883 if (!buffer->lyxvc().inUse()) {
3884 if (buffer->lyxvc().registrer()) {
3885 reloadBuffer(*buffer);
3886 dr.clearMessageUpdate();
3891 case LFUN_VC_RENAME:
3892 case LFUN_VC_COPY: {
3893 if (!buffer || !ensureBufferClean(buffer))
3895 if (buffer->lyxvc().inUse() && !buffer->hasReadonlyFlag()) {
3896 if (buffer->lyxvc().isCheckInWithConfirmation()) {
3897 // Some changes are not yet committed.
3898 // We test here and not in getStatus(), since
3899 // this test is expensive.
3901 LyXVC::CommandResult ret =
3902 buffer->lyxvc().checkIn(log);
3904 if (ret == LyXVC::ErrorCommand ||
3905 ret == LyXVC::VCSuccess)
3906 reloadBuffer(*buffer);
3907 if (buffer->lyxvc().isCheckInWithConfirmation()) {
3908 frontend::Alert::error(
3909 _("Revision control error."),
3910 _("Document could not be checked in."));
3914 RenameKind const kind = (cmd.action() == LFUN_VC_RENAME) ?
3915 LV_VC_RENAME : LV_VC_COPY;
3916 renameBuffer(*buffer, cmd.argument(), kind);
3921 case LFUN_VC_CHECK_IN:
3922 if (!buffer || !ensureBufferClean(buffer))
3924 if (buffer->lyxvc().inUse() && !buffer->hasReadonlyFlag()) {
3926 LyXVC::CommandResult ret = buffer->lyxvc().checkIn(log);
3928 // Only skip reloading if the checkin was cancelled or
3929 // an error occurred before the real checkin VCS command
3930 // was executed, since the VCS might have changed the
3931 // file even if it could not checkin successfully.
3932 if (ret == LyXVC::ErrorCommand || ret == LyXVC::VCSuccess)
3933 reloadBuffer(*buffer);
3937 case LFUN_VC_CHECK_OUT:
3938 if (!buffer || !ensureBufferClean(buffer))
3940 if (buffer->lyxvc().inUse()) {
3941 dr.setMessage(buffer->lyxvc().checkOut());
3942 reloadBuffer(*buffer);
3946 case LFUN_VC_LOCKING_TOGGLE:
3947 if (!buffer || !ensureBufferClean(buffer) || buffer->hasReadonlyFlag())
3949 if (buffer->lyxvc().inUse()) {
3950 string res = buffer->lyxvc().lockingToggle();
3952 frontend::Alert::error(_("Revision control error."),
3953 _("Error when setting the locking property."));
3956 reloadBuffer(*buffer);
3961 case LFUN_VC_REVERT:
3964 if (buffer->lyxvc().revert()) {
3965 reloadBuffer(*buffer);
3966 dr.clearMessageUpdate();
3970 case LFUN_VC_UNDO_LAST:
3973 buffer->lyxvc().undoLast();
3974 reloadBuffer(*buffer);
3975 dr.clearMessageUpdate();
3978 case LFUN_VC_REPO_UPDATE:
3981 if (ensureBufferClean(buffer)) {
3982 dr.setMessage(buffer->lyxvc().repoUpdate());
3983 checkExternallyModifiedBuffers();
3987 case LFUN_VC_COMMAND: {
3988 string flag = cmd.getArg(0);
3989 if (buffer && contains(flag, 'R') && !ensureBufferClean(buffer))
3992 if (contains(flag, 'M')) {
3993 if (!Alert::askForText(message, _("LyX VC: Log Message")))
3996 string path = cmd.getArg(1);
3997 if (contains(path, "$$p") && buffer)
3998 path = subst(path, "$$p", buffer->filePath());
3999 LYXERR(Debug::LYXVC, "Directory: " << path);
4001 if (!pp.isReadableDirectory()) {
4002 lyxerr << _("Directory is not accessible.") << endl;
4005 support::PathChanger p(pp);
4007 string command = cmd.getArg(2);
4008 if (command.empty())
4011 command = subst(command, "$$i", buffer->absFileName());
4012 command = subst(command, "$$p", buffer->filePath());
4014 command = subst(command, "$$m", to_utf8(message));
4015 LYXERR(Debug::LYXVC, "Command: " << command);
4017 one.startscript(Systemcall::Wait, command);
4021 if (contains(flag, 'I'))
4022 buffer->markDirty();
4023 if (contains(flag, 'R'))
4024 reloadBuffer(*buffer);
4029 case LFUN_VC_COMPARE: {
4030 if (cmd.argument().empty()) {
4031 lyx::dispatch(FuncRequest(LFUN_DIALOG_SHOW, "comparehistory"));
4037 string rev1 = cmd.getArg(0);
4041 if (!buffer->lyxvc().prepareFileRevision(rev1, f1))
4044 if (isStrInt(rev1) && convert<int>(rev1) <= 0) {
4045 f2 = buffer->absFileName();
4047 string rev2 = cmd.getArg(1);
4051 if (!buffer->lyxvc().prepareFileRevision(rev2, f2))
4055 LYXERR(Debug::LYXVC, "Launching comparison for fetched revisions:\n" <<
4056 f1 << "\n" << f2 << "\n" );
4057 string par = "compare run " + quoteName(f1) + " " + quoteName(f2);
4058 lyx::dispatch(FuncRequest(LFUN_DIALOG_SHOW, par));
4068 void GuiView::openChildDocument(string const & fname)
4070 LASSERT(documentBufferView(), return);
4071 Buffer & buffer = documentBufferView()->buffer();
4072 FileName const filename = support::makeAbsPath(fname, buffer.filePath());
4073 documentBufferView()->saveBookmark(false);
4074 Buffer * child = nullptr;
4075 if (theBufferList().exists(filename)) {
4076 child = theBufferList().getBuffer(filename);
4079 message(bformat(_("Opening child document %1$s..."),
4080 makeDisplayPath(filename.absFileName())));
4081 child = loadDocument(filename, false);
4083 // Set the parent name of the child document.
4084 // This makes insertion of citations and references in the child work,
4085 // when the target is in the parent or another child document.
4087 child->setParent(&buffer);
4091 bool GuiView::goToFileRow(string const & argument)
4095 size_t i = argument.find_last_of(' ');
4096 if (i != string::npos) {
4097 file_name = os::internal_path(FileName(trim(argument.substr(0, i))).realPath());
4098 istringstream is(argument.substr(i + 1));
4103 if (i == string::npos) {
4104 LYXERR0("Wrong argument: " << argument);
4107 Buffer * buf = nullptr;
4108 string const realtmp = package().temp_dir().realPath();
4109 // We have to use os::path_prefix_is() here, instead of
4110 // simply prefixIs(), because the file name comes from
4111 // an external application and may need case adjustment.
4112 if (os::path_prefix_is(file_name, realtmp, os::CASE_ADJUSTED)) {
4113 buf = theBufferList().getBufferFromTmp(file_name, true);
4114 LYXERR(Debug::FILES, "goToFileRow: buffer lookup for " << file_name
4115 << (buf ? " success" : " failed"));
4117 // Must replace extension of the file to be .lyx
4118 // and get full path
4119 FileName const s = fileSearch(string(),
4120 support::changeExtension(file_name, ".lyx"), "lyx");
4121 // Either change buffer or load the file
4122 if (theBufferList().exists(s))
4123 buf = theBufferList().getBuffer(s);
4124 else if (s.exists()) {
4125 buf = loadDocument(s);
4130 _("File does not exist: %1$s"),
4131 makeDisplayPath(file_name)));
4137 _("No buffer for file: %1$s."),
4138 makeDisplayPath(file_name))
4143 bool success = documentBufferView()->setCursorFromRow(row);
4145 LYXERR(Debug::OUTFILE,
4146 "setCursorFromRow: invalid position for row " << row);
4147 frontend::Alert::error(_("Inverse Search Failed"),
4148 _("Invalid position requested by inverse search.\n"
4149 "You may need to update the viewed document."));
4155 void GuiView::toolBarPopup(const QPoint & /*pos*/)
4157 QMenu * menu = guiApp->menus().menu(toqstr("context-toolbars"), * this);
4158 menu->exec(QCursor::pos());
4163 Buffer::ExportStatus GuiView::GuiViewPrivate::runAndDestroy(const T& func,
4164 Buffer const * orig, Buffer * clone, string const & format)
4166 Buffer::ExportStatus const status = func(format);
4168 // the cloning operation will have produced a clone of the entire set of
4169 // documents, starting from the master. so we must delete those.
4170 Buffer * mbuf = const_cast<Buffer *>(clone->masterBuffer());
4172 busyBuffers.remove(orig);
4177 Buffer::ExportStatus GuiView::GuiViewPrivate::compileAndDestroy(
4178 Buffer const * orig, Buffer * clone, string const & format)
4180 Buffer::ExportStatus (Buffer::* mem_func)(std::string const &, bool) const =
4182 return runAndDestroy(lyx::bind(mem_func, clone, _1, true), orig, clone, format);
4186 Buffer::ExportStatus GuiView::GuiViewPrivate::exportAndDestroy(
4187 Buffer const * orig, Buffer * clone, string const & format)
4189 Buffer::ExportStatus (Buffer::* mem_func)(std::string const &, bool) const =
4191 return runAndDestroy(lyx::bind(mem_func, clone, _1, false), orig, clone, format);
4195 Buffer::ExportStatus GuiView::GuiViewPrivate::previewAndDestroy(
4196 Buffer const * orig, Buffer * clone, string const & format)
4198 Buffer::ExportStatus (Buffer::* mem_func)(std::string const &) const =
4200 return runAndDestroy(lyx::bind(mem_func, clone, _1), orig, clone, format);
4204 bool GuiView::GuiViewPrivate::asyncBufferProcessing(string const & argument,
4205 Buffer const * used_buffer,
4206 docstring const & msg,
4207 Buffer::ExportStatus (*asyncFunc)(Buffer const *, Buffer *, string const &),
4208 Buffer::ExportStatus (Buffer::*syncFunc)(string const &, bool) const,
4209 Buffer::ExportStatus (Buffer::*previewFunc)(string const &) const,
4210 bool allow_async, bool use_tmpdir)
4215 string format = argument;
4217 format = used_buffer->params().getDefaultOutputFormat();
4218 processing_format = format;
4220 progress_->clearMessages();
4223 #if EXPORT_in_THREAD
4225 GuiViewPrivate::busyBuffers.insert(used_buffer);
4226 Buffer * cloned_buffer = used_buffer->cloneWithChildren();
4227 if (!cloned_buffer) {
4228 Alert::error(_("Export Error"),
4229 _("Error cloning the Buffer."));
4232 QFuture<Buffer::ExportStatus> f = QtConcurrent::run(
4237 setPreviewFuture(f);
4238 last_export_format = used_buffer->params().bufferFormat();
4241 // We are asynchronous, so we don't know here anything about the success
4244 Buffer::ExportStatus status;
4246 status = (used_buffer->*syncFunc)(format, use_tmpdir);
4247 } else if (previewFunc) {
4248 status = (used_buffer->*previewFunc)(format);
4251 handleExportStatus(gv_, status, format);
4253 return (status == Buffer::ExportSuccess
4254 || status == Buffer::PreviewSuccess);
4258 Buffer::ExportStatus status;
4260 status = (used_buffer->*syncFunc)(format, true);
4261 } else if (previewFunc) {
4262 status = (used_buffer->*previewFunc)(format);
4265 handleExportStatus(gv_, status, format);
4267 return (status == Buffer::ExportSuccess
4268 || status == Buffer::PreviewSuccess);
4272 void GuiView::dispatchToBufferView(FuncRequest const & cmd, DispatchResult & dr)
4274 BufferView * bv = currentBufferView();
4275 LASSERT(bv, return);
4277 // Let the current BufferView dispatch its own actions.
4278 bv->dispatch(cmd, dr);
4279 if (dr.dispatched()) {
4280 if (cmd.action() == LFUN_REDO || cmd.action() == LFUN_UNDO)
4281 updateDialog("document", "");
4285 // Try with the document BufferView dispatch if any.
4286 BufferView * doc_bv = documentBufferView();
4287 if (doc_bv && doc_bv != bv) {
4288 doc_bv->dispatch(cmd, dr);
4289 if (dr.dispatched()) {
4290 if (cmd.action() == LFUN_REDO || cmd.action() == LFUN_UNDO)
4291 updateDialog("document", "");
4296 // Then let the current Cursor dispatch its own actions.
4297 bv->cursor().dispatch(cmd);
4299 // update completion. We do it here and not in
4300 // processKeySym to avoid another redraw just for a
4301 // changed inline completion
4302 if (cmd.origin() == FuncRequest::KEYBOARD) {
4303 if (cmd.action() == LFUN_SELF_INSERT
4304 || (cmd.action() == LFUN_ERT_INSERT && bv->cursor().inMathed()))
4305 updateCompletion(bv->cursor(), true, true);
4306 else if (cmd.action() == LFUN_CHAR_DELETE_BACKWARD)
4307 updateCompletion(bv->cursor(), false, true);
4309 updateCompletion(bv->cursor(), false, false);
4312 dr = bv->cursor().result();
4316 void GuiView::dispatch(FuncRequest const & cmd, DispatchResult & dr)
4318 BufferView * bv = currentBufferView();
4319 // By default we won't need any update.
4320 dr.screenUpdate(Update::None);
4321 // assume cmd will be dispatched
4322 dr.dispatched(true);
4324 Buffer * doc_buffer = documentBufferView()
4325 ? &(documentBufferView()->buffer()) : nullptr;
4327 if (cmd.origin() == FuncRequest::TOC) {
4328 GuiToc * toc = static_cast<GuiToc*>(findOrBuild("toc", false));
4329 toc->doDispatch(bv->cursor(), cmd, dr);
4333 string const argument = to_utf8(cmd.argument());
4335 switch(cmd.action()) {
4336 case LFUN_BUFFER_CHILD_OPEN:
4337 openChildDocument(to_utf8(cmd.argument()));
4340 case LFUN_BUFFER_IMPORT:
4341 importDocument(to_utf8(cmd.argument()));
4344 case LFUN_MASTER_BUFFER_EXPORT:
4346 doc_buffer = const_cast<Buffer *>(doc_buffer->masterBuffer());
4348 case LFUN_BUFFER_EXPORT: {
4351 // GCC only sees strfwd.h when building merged
4352 if (::lyx::operator==(cmd.argument(), "custom")) {
4353 // LFUN_MASTER_BUFFER_EXPORT is not enabled for this case,
4354 // so the following test should not be needed.
4355 // In principle, we could try to switch to such a view...
4356 // if (cmd.action() == LFUN_BUFFER_EXPORT)
4357 dispatch(FuncRequest(LFUN_DIALOG_SHOW, "sendto"), dr);
4361 string const dest = cmd.getArg(1);
4362 FileName target_dir;
4363 if (!dest.empty() && FileName::isAbsolute(dest))
4364 target_dir = FileName(support::onlyPath(dest));
4366 target_dir = doc_buffer->fileName().onlyPath();
4368 string const format = (argument.empty() || argument == "default") ?
4369 doc_buffer->params().getDefaultOutputFormat() : argument;
4371 if ((dest.empty() && doc_buffer->isUnnamed())
4372 || !target_dir.isDirWritable()) {
4373 exportBufferAs(*doc_buffer, from_utf8(format));
4376 /* TODO/Review: Is it a problem to also export the children?
4377 See the update_unincluded flag */
4378 d.asyncBufferProcessing(format,
4381 &GuiViewPrivate::exportAndDestroy,
4383 nullptr, cmd.allowAsync());
4384 // TODO Inform user about success
4388 case LFUN_BUFFER_EXPORT_AS: {
4389 LASSERT(doc_buffer, break);
4390 docstring f = cmd.argument();
4392 f = from_ascii(doc_buffer->params().getDefaultOutputFormat());
4393 exportBufferAs(*doc_buffer, f);
4397 case LFUN_BUFFER_UPDATE: {
4398 d.asyncBufferProcessing(argument,
4401 &GuiViewPrivate::compileAndDestroy,
4403 nullptr, cmd.allowAsync(), true);
4406 case LFUN_BUFFER_VIEW: {
4407 d.asyncBufferProcessing(argument,
4409 _("Previewing ..."),
4410 &GuiViewPrivate::previewAndDestroy,
4412 &Buffer::preview, cmd.allowAsync());
4415 case LFUN_MASTER_BUFFER_UPDATE: {
4416 d.asyncBufferProcessing(argument,
4417 (doc_buffer ? doc_buffer->masterBuffer() : nullptr),
4419 &GuiViewPrivate::compileAndDestroy,
4421 nullptr, cmd.allowAsync(), true);
4424 case LFUN_MASTER_BUFFER_VIEW: {
4425 d.asyncBufferProcessing(argument,
4426 (doc_buffer ? doc_buffer->masterBuffer() : nullptr),
4428 &GuiViewPrivate::previewAndDestroy,
4429 nullptr, &Buffer::preview, cmd.allowAsync());
4432 case LFUN_EXPORT_CANCEL: {
4433 Systemcall::killscript();
4436 case LFUN_BUFFER_SWITCH: {
4437 string const file_name = to_utf8(cmd.argument());
4438 if (!FileName::isAbsolute(file_name)) {
4440 dr.setMessage(_("Absolute filename expected."));
4444 Buffer * buffer = theBufferList().getBuffer(FileName(file_name));
4447 dr.setMessage(_("Document not loaded"));
4451 // Do we open or switch to the buffer in this view ?
4452 if (workArea(*buffer)
4453 || lyxrc.open_buffers_in_tabs || !documentBufferView()) {
4458 // Look for the buffer in other views
4459 QList<int> const ids = guiApp->viewIds();
4461 for (; i != ids.size(); ++i) {
4462 GuiView & gv = guiApp->view(ids[i]);
4463 if (gv.workArea(*buffer)) {
4465 gv.activateWindow();
4467 gv.setBuffer(buffer);
4472 // If necessary, open a new window as a last resort
4473 if (i == ids.size()) {
4474 lyx::dispatch(FuncRequest(LFUN_WINDOW_NEW));
4480 case LFUN_BUFFER_NEXT:
4481 gotoNextOrPreviousBuffer(NEXTBUFFER, false);
4484 case LFUN_BUFFER_MOVE_NEXT:
4485 gotoNextOrPreviousBuffer(NEXTBUFFER, true);
4488 case LFUN_BUFFER_PREVIOUS:
4489 gotoNextOrPreviousBuffer(PREVBUFFER, false);
4492 case LFUN_BUFFER_MOVE_PREVIOUS:
4493 gotoNextOrPreviousBuffer(PREVBUFFER, true);
4496 case LFUN_BUFFER_CHKTEX:
4497 LASSERT(doc_buffer, break);
4498 doc_buffer->runChktex();
4501 case LFUN_COMMAND_EXECUTE: {
4502 command_execute_ = true;
4503 minibuffer_focus_ = true;
4506 case LFUN_DROP_LAYOUTS_CHOICE:
4507 d.layout_->showPopup();
4510 case LFUN_MENU_OPEN:
4511 if (QMenu * menu = guiApp->menus().menu(toqstr(cmd.argument()), *this))
4512 menu->exec(QCursor::pos());
4515 case LFUN_FILE_INSERT: {
4516 bool const ignore_lang = cmd.getArg(1) == "ignorelang";
4517 if (insertLyXFile(from_utf8(cmd.getArg(0)), ignore_lang)) {
4518 dr.forceBufferUpdate();
4519 dr.screenUpdate(Update::Force);
4524 case LFUN_FILE_INSERT_PLAINTEXT:
4525 case LFUN_FILE_INSERT_PLAINTEXT_PARA: {
4526 string const fname = to_utf8(cmd.argument());
4527 if (!fname.empty() && !FileName::isAbsolute(fname)) {
4528 dr.setMessage(_("Absolute filename expected."));
4532 FileName filename(fname);
4533 if (fname.empty()) {
4534 FileDialog dlg(qt_("Select file to insert"));
4536 FileDialog::Result result = dlg.open(toqstr(bv->buffer().filePath()),
4537 QStringList(qt_("All Files (*)")));
4539 if (result.first == FileDialog::Later || result.second.isEmpty()) {
4540 dr.setMessage(_("Canceled."));
4544 filename.set(fromqstr(result.second));
4548 FuncRequest const new_cmd(cmd, filename.absoluteFilePath());
4549 bv->dispatch(new_cmd, dr);
4554 case LFUN_BUFFER_RELOAD: {
4555 LASSERT(doc_buffer, break);
4558 bool drop = (cmd.argument() == "dump");
4561 if (!drop && !doc_buffer->isClean()) {
4562 docstring const file =
4563 makeDisplayPath(doc_buffer->absFileName(), 20);
4564 if (doc_buffer->notifiesExternalModification()) {
4565 docstring text = _("The current version will be lost. "
4566 "Are you sure you want to load the version on disk "
4567 "of the document %1$s?");
4568 ret = Alert::prompt(_("Reload saved document?"),
4569 bformat(text, file), 1, 1,
4570 _("&Reload"), _("&Cancel"));
4572 docstring text = _("Any changes will be lost. "
4573 "Are you sure you want to revert to the saved version "
4574 "of the document %1$s?");
4575 ret = Alert::prompt(_("Revert to saved document?"),
4576 bformat(text, file), 1, 1,
4577 _("&Revert"), _("&Cancel"));
4582 doc_buffer->markClean();
4583 reloadBuffer(*doc_buffer);
4584 dr.forceBufferUpdate();
4589 case LFUN_BUFFER_RESET_EXPORT:
4590 LASSERT(doc_buffer, break);
4591 doc_buffer->requireFreshStart(true);
4592 dr.setMessage(_("Buffer export reset."));
4595 case LFUN_BUFFER_WRITE:
4596 LASSERT(doc_buffer, break);
4597 saveBuffer(*doc_buffer);
4600 case LFUN_BUFFER_WRITE_AS:
4601 LASSERT(doc_buffer, break);
4602 renameBuffer(*doc_buffer, cmd.argument());
4605 case LFUN_BUFFER_WRITE_AS_TEMPLATE:
4606 LASSERT(doc_buffer, break);
4607 renameBuffer(*doc_buffer, cmd.argument(),
4608 LV_WRITE_AS_TEMPLATE);
4611 case LFUN_BUFFER_WRITE_ALL: {
4612 Buffer * first = theBufferList().first();
4615 message(_("Saving all documents..."));
4616 // We cannot use a for loop as the buffer list cycles.
4619 if (!b->isClean()) {
4621 LYXERR(Debug::ACTION, "Saved " << b->absFileName());
4623 b = theBufferList().next(b);
4624 } while (b != first);
4625 dr.setMessage(_("All documents saved."));
4629 case LFUN_MASTER_BUFFER_FORALL: {
4633 FuncRequest funcToRun = lyxaction.lookupFunc(cmd.getLongArg(0));
4634 funcToRun.allowAsync(false);
4636 for (Buffer const * buf : doc_buffer->allRelatives()) {
4637 // Switch to other buffer view and resend cmd
4638 lyx::dispatch(FuncRequest(
4639 LFUN_BUFFER_SWITCH, buf->absFileName()));
4640 lyx::dispatch(funcToRun);
4643 lyx::dispatch(FuncRequest(
4644 LFUN_BUFFER_SWITCH, doc_buffer->absFileName()));
4648 case LFUN_BUFFER_EXTERNAL_MODIFICATION_CLEAR:
4649 LASSERT(doc_buffer, break);
4650 doc_buffer->clearExternalModification();
4653 case LFUN_BUFFER_CLOSE:
4657 case LFUN_BUFFER_CLOSE_ALL:
4661 case LFUN_DEVEL_MODE_TOGGLE:
4662 devel_mode_ = !devel_mode_;
4664 dr.setMessage(_("Developer mode is now enabled."));
4666 dr.setMessage(_("Developer mode is now disabled."));
4669 case LFUN_TOOLBAR_SET: {
4670 string const name = cmd.getArg(0);
4671 string const state = cmd.getArg(1);
4672 if (GuiToolbar * t = toolbar(name))
4677 case LFUN_TOOLBAR_TOGGLE: {
4678 string const name = cmd.getArg(0);
4679 if (GuiToolbar * t = toolbar(name))
4684 case LFUN_TOOLBAR_MOVABLE: {
4685 string const name = cmd.getArg(0);
4687 // toggle (all) toolbars movablility
4688 toolbarsMovable_ = !toolbarsMovable_;
4689 for (ToolbarInfo const & ti : guiApp->toolbars()) {
4690 GuiToolbar * tb = toolbar(ti.name);
4691 if (tb && tb->isMovable() != toolbarsMovable_)
4692 // toggle toolbar movablity if it does not fit lock
4693 // (all) toolbars positions state silent = true, since
4694 // status bar notifications are slow
4697 if (toolbarsMovable_)
4698 dr.setMessage(_("Toolbars unlocked."));
4700 dr.setMessage(_("Toolbars locked."));
4701 } else if (GuiToolbar * tb = toolbar(name))
4702 // toggle current toolbar movablity
4704 // update lock (all) toolbars positions
4705 updateLockToolbars();
4709 case LFUN_ICON_SIZE: {
4710 QSize size = d.iconSize(cmd.argument());
4712 dr.setMessage(bformat(_("Icon size set to %1$dx%2$d."),
4713 size.width(), size.height()));
4717 case LFUN_DIALOG_UPDATE: {
4718 string const name = to_utf8(cmd.argument());
4719 if (name == "prefs" || name == "document")
4720 updateDialog(name, string());
4721 else if (name == "paragraph")
4722 lyx::dispatch(FuncRequest(LFUN_PARAGRAPH_UPDATE));
4723 else if (currentBufferView()) {
4724 Inset * inset = currentBufferView()->editedInset(name);
4725 // Can only update a dialog connected to an existing inset
4727 // FIXME: get rid of this indirection; GuiView ask the inset
4728 // if he is kind enough to update itself...
4729 FuncRequest fr(LFUN_INSET_DIALOG_UPDATE, cmd.argument());
4730 //FIXME: pass DispatchResult here?
4731 inset->dispatch(currentBufferView()->cursor(), fr);
4737 case LFUN_DIALOG_TOGGLE: {
4738 FuncCode const func_code = isDialogVisible(cmd.getArg(0))
4739 ? LFUN_DIALOG_HIDE : LFUN_DIALOG_SHOW;
4740 dispatch(FuncRequest(func_code, cmd.argument()), dr);
4744 case LFUN_DIALOG_DISCONNECT_INSET:
4745 disconnectDialog(to_utf8(cmd.argument()));
4748 case LFUN_DIALOG_HIDE: {
4749 guiApp->hideDialogs(to_utf8(cmd.argument()), nullptr);
4753 case LFUN_DIALOG_SHOW: {
4754 string const name = cmd.getArg(0);
4755 string sdata = trim(to_utf8(cmd.argument()).substr(name.size()));
4757 if (name == "latexlog") {
4758 // getStatus checks that
4759 LASSERT(doc_buffer, break);
4760 Buffer::LogType type;
4761 string const logfile = doc_buffer->logName(&type);
4763 case Buffer::latexlog:
4766 case Buffer::buildlog:
4767 sdata = "literate ";
4770 sdata += Lexer::quoteString(logfile);
4771 showDialog("log", sdata);
4772 } else if (name == "vclog") {
4773 // getStatus checks that
4774 LASSERT(doc_buffer, break);
4775 string const sdata2 = "vc " +
4776 Lexer::quoteString(doc_buffer->lyxvc().getLogFile());
4777 showDialog("log", sdata2);
4778 } else if (name == "symbols") {
4779 sdata = bv->cursor().getEncoding()->name();
4781 showDialog("symbols", sdata);
4782 } else if (name == "findreplace") {
4783 sdata = to_utf8(bv->cursor().selectionAsString(false));
4784 showDialog(name, sdata);
4786 } else if (name == "prefs" && isFullScreen()) {
4787 lfunUiToggle("fullscreen");
4788 showDialog("prefs", sdata);
4790 showDialog(name, sdata);
4795 dr.setMessage(cmd.argument());
4798 case LFUN_UI_TOGGLE: {
4799 string arg = cmd.getArg(0);
4800 if (!lfunUiToggle(arg)) {
4801 docstring const msg = "ui-toggle " + _("%1$s unknown command!");
4802 dr.setMessage(bformat(msg, from_utf8(arg)));
4804 // Make sure the keyboard focus stays in the work area.
4809 case LFUN_VIEW_SPLIT: {
4810 LASSERT(doc_buffer, break);
4811 string const orientation = cmd.getArg(0);
4812 d.splitter_->setOrientation(orientation == "vertical"
4813 ? Qt::Vertical : Qt::Horizontal);
4814 TabWorkArea * twa = addTabWorkArea();
4815 GuiWorkArea * wa = twa->addWorkArea(*doc_buffer, *this);
4816 setCurrentWorkArea(wa);
4819 case LFUN_TAB_GROUP_CLOSE:
4820 if (TabWorkArea * twa = d.currentTabWorkArea()) {
4821 closeTabWorkArea(twa);
4822 d.current_work_area_ = nullptr;
4823 twa = d.currentTabWorkArea();
4824 // Switch to the next GuiWorkArea in the found TabWorkArea.
4826 // Make sure the work area is up to date.
4827 setCurrentWorkArea(twa->currentWorkArea());
4829 setCurrentWorkArea(nullptr);
4834 case LFUN_VIEW_CLOSE:
4835 if (TabWorkArea * twa = d.currentTabWorkArea()) {
4836 closeWorkArea(twa->currentWorkArea());
4837 d.current_work_area_ = nullptr;
4838 twa = d.currentTabWorkArea();
4839 // Switch to the next GuiWorkArea in the found TabWorkArea.
4841 // Make sure the work area is up to date.
4842 setCurrentWorkArea(twa->currentWorkArea());
4844 setCurrentWorkArea(nullptr);
4849 case LFUN_COMPLETION_INLINE:
4850 if (d.current_work_area_)
4851 d.current_work_area_->completer().showInline();
4854 case LFUN_COMPLETION_POPUP:
4855 if (d.current_work_area_)
4856 d.current_work_area_->completer().showPopup();
4861 if (d.current_work_area_)
4862 d.current_work_area_->completer().tab();
4865 case LFUN_COMPLETION_CANCEL:
4866 if (d.current_work_area_) {
4867 if (d.current_work_area_->completer().popupVisible())
4868 d.current_work_area_->completer().hidePopup();
4870 d.current_work_area_->completer().hideInline();
4874 case LFUN_COMPLETION_ACCEPT:
4875 if (d.current_work_area_)
4876 d.current_work_area_->completer().activate();
4879 case LFUN_BUFFER_ZOOM_IN:
4880 case LFUN_BUFFER_ZOOM_OUT:
4881 case LFUN_BUFFER_ZOOM: {
4882 zoom_ratio_ = zoomRatio(cmd, zoom_ratio_);
4884 // Actual zoom value: default zoom + fractional extra value
4885 int zoom = (int)(lyxrc.defaultZoom * zoom_ratio_);
4886 zoom = min(max(zoom, zoom_min_), zoom_max_);
4888 setCurrentZoom(zoom);
4890 dr.setMessage(bformat(_("Zoom level is now %1$d% (default value: %2$d%)"),
4891 lyxrc.currentZoom, lyxrc.defaultZoom));
4893 guiApp->fontLoader().update();
4894 dr.screenUpdate(Update::ForceAll | Update::FitCursor);
4898 case LFUN_VC_REGISTER:
4899 case LFUN_VC_RENAME:
4901 case LFUN_VC_CHECK_IN:
4902 case LFUN_VC_CHECK_OUT:
4903 case LFUN_VC_REPO_UPDATE:
4904 case LFUN_VC_LOCKING_TOGGLE:
4905 case LFUN_VC_REVERT:
4906 case LFUN_VC_UNDO_LAST:
4907 case LFUN_VC_COMMAND:
4908 case LFUN_VC_COMPARE:
4909 dispatchVC(cmd, dr);
4912 case LFUN_SERVER_GOTO_FILE_ROW:
4913 if(goToFileRow(to_utf8(cmd.argument())))
4914 dr.screenUpdate(Update::Force | Update::FitCursor);
4917 case LFUN_LYX_ACTIVATE:
4921 case LFUN_WINDOW_RAISE:
4927 case LFUN_FORWARD_SEARCH: {
4928 // it seems safe to assume we have a document buffer, since
4929 // getStatus wants one.
4930 LASSERT(doc_buffer, break);
4931 Buffer const * doc_master = doc_buffer->masterBuffer();
4932 FileName const path(doc_master->temppath());
4933 string const texname = doc_master->isChild(doc_buffer)
4934 ? DocFileName(changeExtension(
4935 doc_buffer->absFileName(),
4936 "tex")).mangledFileName()
4937 : doc_buffer->latexName();
4938 string const fulltexname =
4939 support::makeAbsPath(texname, doc_master->temppath()).absFileName();
4940 string const mastername =
4941 removeExtension(doc_master->latexName());
4942 FileName const dviname(addName(path.absFileName(),
4943 addExtension(mastername, "dvi")));
4944 FileName const pdfname(addName(path.absFileName(),
4945 addExtension(mastername, "pdf")));
4946 bool const have_dvi = dviname.exists();
4947 bool const have_pdf = pdfname.exists();
4948 if (!have_dvi && !have_pdf) {
4949 dr.setMessage(_("Please, preview the document first."));
4952 bool const goto_dvi = have_dvi && !lyxrc.forward_search_dvi.empty();
4953 bool const goto_pdf = have_pdf && !lyxrc.forward_search_pdf.empty();
4954 string outname = dviname.onlyFileName();
4955 string command = lyxrc.forward_search_dvi;
4956 if ((!goto_dvi || goto_pdf) &&
4957 pdfname.lastModified() > dviname.lastModified()) {
4958 outname = pdfname.onlyFileName();
4959 command = lyxrc.forward_search_pdf;
4962 DocIterator cur = bv->cursor();
4963 int row = doc_buffer->texrow().rowFromDocIterator(cur).first;
4964 LYXERR(Debug::ACTION, "Forward search: row:" << row
4966 if (row == -1 || command.empty()) {
4967 dr.setMessage(_("Couldn't proceed."));
4970 string texrow = convert<string>(row);
4972 command = subst(command, "$$n", texrow);
4973 command = subst(command, "$$f", fulltexname);
4974 command = subst(command, "$$t", texname);
4975 command = subst(command, "$$o", outname);
4977 volatile PathChanger p(path);
4979 one.startscript(Systemcall::DontWait, command);
4983 case LFUN_SPELLING_CONTINUOUSLY:
4984 lyxrc.spellcheck_continuously = !lyxrc.spellcheck_continuously;
4985 dr.screenUpdate(Update::Force);
4988 case LFUN_CITATION_OPEN: {
4990 if (theFormats().getFormat("pdf"))
4991 pdfv = theFormats().getFormat("pdf")->viewer();
4992 if (theFormats().getFormat("ps"))
4993 psv = theFormats().getFormat("ps")->viewer();
4994 frontend::showTarget(argument, pdfv, psv);
4999 // The LFUN must be for one of BufferView, Buffer or Cursor;
5001 dispatchToBufferView(cmd, dr);
5005 // Need to update bv because many LFUNs here might have destroyed it
5006 bv = currentBufferView();
5008 // Clear non-empty selections
5009 // (e.g. from a "char-forward-select" followed by "char-backward-select")
5011 Cursor & cur = bv->cursor();
5012 if ((cur.selection() && cur.selBegin() == cur.selEnd())) {
5013 cur.clearSelection();
5019 bool GuiView::lfunUiToggle(string const & ui_component)
5021 if (ui_component == "scrollbar") {
5022 // hide() is of no help
5023 if (d.current_work_area_->verticalScrollBarPolicy() ==
5024 Qt::ScrollBarAlwaysOff)
5026 d.current_work_area_->setVerticalScrollBarPolicy(
5027 Qt::ScrollBarAsNeeded);
5029 d.current_work_area_->setVerticalScrollBarPolicy(
5030 Qt::ScrollBarAlwaysOff);
5031 } else if (ui_component == "statusbar") {
5032 statusBar()->setVisible(!statusBar()->isVisible());
5033 } else if (ui_component == "menubar") {
5034 menuBar()->setVisible(!menuBar()->isVisible());
5035 } else if (ui_component == "zoomlevel") {
5036 zoom_value_->setVisible(!zoom_value_->isVisible());
5037 } else if (ui_component == "zoomslider") {
5038 zoom_slider_->setVisible(!zoom_slider_->isVisible());
5039 zoom_in_->setVisible(zoom_slider_->isVisible());
5040 zoom_out_->setVisible(zoom_slider_->isVisible());
5041 } else if (ui_component == "statistics-w") {
5042 word_count_enabled_ = !word_count_enabled_;
5045 } else if (ui_component == "statistics-cb") {
5046 char_count_enabled_ = !char_count_enabled_;
5049 } else if (ui_component == "statistics-c") {
5050 char_nb_count_enabled_ = !char_nb_count_enabled_;
5053 } else if (ui_component == "frame") {
5054 int const l = contentsMargins().left();
5056 //are the frames in default state?
5057 d.current_work_area_->setFrameStyle(QFrame::NoFrame);
5059 #if QT_VERSION > 0x050903
5060 setAttribute(Qt::WA_ContentsMarginsRespectsSafeArea, false);
5062 setContentsMargins(-2, -2, -2, -2);
5064 #if QT_VERSION > 0x050903
5065 setAttribute(Qt::WA_ContentsMarginsRespectsSafeArea, true);
5067 setContentsMargins(0, 0, 0, 0);
5070 if (ui_component == "fullscreen") {
5074 stat_counts_->setVisible(statsEnabled());
5079 void GuiView::toggleFullScreen()
5081 setWindowState(windowState() ^ Qt::WindowFullScreen);
5085 Buffer const * GuiView::updateInset(Inset const * inset)
5090 Buffer const * inset_buffer = &(inset->buffer());
5092 for (int i = 0; i != d.splitter_->count(); ++i) {
5093 GuiWorkArea * wa = d.tabWorkArea(i)->currentWorkArea();
5096 Buffer const * buffer = &(wa->bufferView().buffer());
5097 if (inset_buffer == buffer)
5098 wa->scheduleRedraw(true);
5100 return inset_buffer;
5104 void GuiView::restartCaret()
5106 /* When we move around, or type, it's nice to be able to see
5107 * the caret immediately after the keypress.
5109 if (d.current_work_area_)
5110 d.current_work_area_->startBlinkingCaret();
5112 // Take this occasion to update the other GUI elements.
5118 void GuiView::updateCompletion(Cursor & cur, bool start, bool keep)
5120 if (d.current_work_area_)
5121 d.current_work_area_->completer().updateVisibility(cur, start, keep);
5126 // This list should be kept in sync with the list of insets in
5127 // src/insets/Inset.cpp. I.e., if a dialog goes with an inset, the
5128 // dialog should have the same name as the inset.
5129 // Changes should be also recorded in LFUN_DIALOG_SHOW doxygen
5130 // docs in LyXAction.cpp.
5132 char const * const dialognames[] = {
5134 "aboutlyx", "bibitem", "bibtex", "box", "branch", "changes", "character",
5135 "citation", "compare", "comparehistory", "counter", "document", "errorlist", "ert",
5136 "external", "file", "findreplace", "findreplaceadv", "float", "graphics",
5137 "href", "include", "index", "index_print", "info", "listings", "label", "line",
5138 "log", "lyxfiles", "mathdelimiter", "mathmatrix", "mathspace", "nomenclature",
5139 "nomencl_print", "note", "paragraph", "phantom", "prefs", "ref",
5140 "sendto", "space", "spellchecker", "symbols", "tabular", "tabularcreate",
5141 "thesaurus", "texinfo", "toc", "view-source", "vspace", "wrap", "progress"};
5143 char const * const * const end_dialognames =
5144 dialognames + (sizeof(dialognames) / sizeof(char *));
5148 cmpCStr(char const * name) : name_(name) {}
5149 bool operator()(char const * other) {
5150 return strcmp(other, name_) == 0;
5157 bool isValidName(string const & name)
5159 return find_if(dialognames, end_dialognames,
5160 cmpCStr(name.c_str())) != end_dialognames;
5166 void GuiView::resetDialogs()
5168 // Make sure that no LFUN uses any GuiView.
5169 guiApp->setCurrentView(nullptr);
5173 constructToolbars();
5174 guiApp->menus().fillMenuBar(menuBar(), this, false);
5175 d.layout_->updateContents(true);
5176 // Now update controls with current buffer.
5177 guiApp->setCurrentView(this);
5183 void GuiView::flatGroupBoxes(const QObject * widget, bool flag)
5185 for (QObject * child: widget->children()) {
5186 if (child->inherits("QGroupBox")) {
5187 QGroupBox * box = (QGroupBox*) child;
5190 flatGroupBoxes(child, flag);
5196 Dialog * GuiView::find(string const & name, bool hide_it) const
5198 if (!isValidName(name))
5201 map<string, DialogPtr>::iterator it = d.dialogs_.find(name);
5203 if (it != d.dialogs_.end()) {
5205 it->second->hideView();
5206 return it->second.get();
5212 Dialog * GuiView::findOrBuild(string const & name, bool hide_it)
5214 Dialog * dialog = find(name, hide_it);
5215 if (dialog != nullptr)
5218 dialog = build(name);
5219 d.dialogs_[name].reset(dialog);
5220 #if (QT_VERSION >= QT_VERSION_CHECK(5, 0, 0))
5221 // Force a uniform style for group boxes
5222 // On Mac non-flat works better, on Linux flat is standard
5223 flatGroupBoxes(dialog->asQWidget(), guiApp->platformName() != "cocoa");
5225 if (lyxrc.allow_geometry_session)
5226 dialog->restoreSession();
5233 void GuiView::showDialog(string const & name, string const & sdata,
5236 triggerShowDialog(toqstr(name), toqstr(sdata), inset);
5240 void GuiView::doShowDialog(QString const & qname, QString const & qdata,
5246 const string name = fromqstr(qname);
5247 const string sdata = fromqstr(qdata);
5251 Dialog * dialog = findOrBuild(name, false);
5253 bool const visible = dialog->isVisibleView();
5254 dialog->showData(sdata);
5255 if (currentBufferView())
5256 currentBufferView()->editInset(name, inset);
5257 // We only set the focus to the new dialog if it was not yet
5258 // visible in order not to change the existing previous behaviour
5260 // activateWindow is needed for floating dockviews
5261 dialog->asQWidget()->raise();
5262 dialog->asQWidget()->activateWindow();
5263 if (dialog->wantInitialFocus())
5264 dialog->asQWidget()->setFocus();
5268 catch (ExceptionMessage const &) {
5276 bool GuiView::isDialogVisible(string const & name) const
5278 map<string, DialogPtr>::const_iterator it = d.dialogs_.find(name);
5279 if (it == d.dialogs_.end())
5281 return it->second.get()->isVisibleView() && !it->second.get()->isClosing();
5285 void GuiView::hideDialog(string const & name, Inset * inset)
5287 map<string, DialogPtr>::const_iterator it = d.dialogs_.find(name);
5288 if (it == d.dialogs_.end())
5292 if (!currentBufferView())
5294 if (inset != currentBufferView()->editedInset(name))
5298 Dialog * const dialog = it->second.get();
5299 if (dialog->isVisibleView())
5301 if (currentBufferView())
5302 currentBufferView()->editInset(name, nullptr);
5306 void GuiView::disconnectDialog(string const & name)
5308 if (!isValidName(name))
5310 if (currentBufferView())
5311 currentBufferView()->editInset(name, nullptr);
5315 void GuiView::hideAll() const
5317 for(auto const & dlg_p : d.dialogs_)
5318 dlg_p.second->hideView();
5322 void GuiView::updateDialogs()
5324 for(auto const & dlg_p : d.dialogs_) {
5325 Dialog * dialog = dlg_p.second.get();
5327 if (dialog->needBufferOpen() && !documentBufferView())
5328 hideDialog(fromqstr(dialog->name()), nullptr);
5329 else if (dialog->isVisibleView())
5330 dialog->checkStatus();
5338 Dialog * GuiView::build(string const & name)
5340 return createDialog(*this, name);
5344 SEMenu::SEMenu(QWidget * parent)
5346 QAction * action = addAction(qt_("Disable Shell Escape"));
5347 connect(action, SIGNAL(triggered()),
5348 parent, SLOT(disableShellEscape()));
5352 void PressableSvgWidget::mousePressEvent(QMouseEvent * event)
5354 if (event->button() == Qt::LeftButton) {
5359 } // namespace frontend
5362 #include "moc_GuiView.cpp"