3 * This file is part of LyX, the document processor.
4 * Licence details can be found in the file COPYING.
6 * \author Lars Gullik Bjønnes
8 * \author Abdelrazak Younes
11 * Full author contact details are available in file CREDITS.
18 #include "DialogFactory.h"
19 #include "DispatchResult.h"
20 #include "FileDialog.h"
21 #include "FindAndReplace.h"
22 #include "FontLoader.h"
23 #include "GuiApplication.h"
24 #include "GuiClickableLabel.h"
25 #include "GuiCompleter.h"
26 #include "GuiFontMetrics.h"
27 #include "GuiKeySymbol.h"
29 #include "GuiToolbar.h"
30 #include "GuiWorkArea.h"
31 #include "GuiProgress.h"
32 #include "LayoutBox.h"
36 #include "qt_helpers.h"
37 #include "support/filetools.h"
39 #include "frontends/alert.h"
40 #include "frontends/KeySymbol.h"
42 #include "buffer_funcs.h"
44 #include "BufferList.h"
45 #include "BufferParams.h"
46 #include "BufferView.h"
48 #include "Converter.h"
50 #include "CutAndPaste.h"
52 #include "ErrorList.h"
54 #include "FuncStatus.h"
55 #include "FuncRequest.h"
56 #include "KeySymbol.h"
58 #include "LayoutFile.h"
60 #include "LyXAction.h"
64 #include "Paragraph.h"
65 #include "SpellChecker.h"
72 #include "graphics/PreviewLoader.h"
74 #include "support/convert.h"
75 #include "support/debug.h"
76 #include "support/ExceptionMessage.h"
77 #include "support/FileName.h"
78 #include "support/gettext.h"
79 #include "support/ForkedCalls.h"
80 #include "support/lassert.h"
81 #include "support/lstrings.h"
82 #include "support/os.h"
83 #include "support/Package.h"
84 #include "support/PathChanger.h"
85 #include "support/Systemcall.h"
86 #include "support/Timeout.h"
87 #include "support/ProgressInterface.h"
90 #include <QApplication>
91 #include <QCloseEvent>
92 #include <QDragEnterEvent>
95 #include <QFutureWatcher>
106 #include <QShowEvent>
109 #include <QStackedWidget>
110 #include <QStatusBar>
111 #include <QSvgRenderer>
112 #include <QtConcurrentRun>
115 #include <QWindowStateChangeEvent>
116 #include <QGestureEvent>
117 #include <QPinchGesture>
120 // sync with GuiAlert.cpp
121 #define EXPORT_in_THREAD 1
124 #include "support/bind.h"
128 #ifdef HAVE_SYS_TIME_H
129 # include <sys/time.h>
137 using namespace lyx::support;
141 using support::addExtension;
142 using support::changeExtension;
143 using support::removeExtension;
149 class BackgroundWidget : public QWidget
152 BackgroundWidget(int width, int height)
153 : width_(width), height_(height)
155 LYXERR(Debug::GUI, "show banner: " << lyxrc.show_banner);
156 if (!lyxrc.show_banner)
158 /// The text to be written on top of the pixmap
159 QString const htext = qt_("The Document\nProcessor[[welcome banner]]");
160 QString const htextsize = qt_("1.0[[possibly scale the welcome banner text size]]");
161 /// The text to be written on top of the pixmap
162 QString const text = lyx_version ?
163 qt_("version ") + lyx_version : qt_("unknown version");
164 #if QT_VERSION >= 0x050000
165 QString imagedir = "images/";
166 FileName fname = imageLibFileSearch(imagedir, "banner", "svgz");
167 QSvgRenderer svgRenderer(toqstr(fname.absFileName()));
168 if (svgRenderer.isValid()) {
169 splash_ = QPixmap(splashSize());
170 QPainter painter(&splash_);
171 svgRenderer.render(&painter);
172 splash_.setDevicePixelRatio(pixelRatio());
174 splash_ = getPixmap("images/", "banner", "png");
177 splash_ = getPixmap("images/", "banner", "svgz,png");
180 QPainter pain(&splash_);
181 pain.setPen(QColor(0, 0, 0));
182 qreal const fsize = fontSize();
185 qreal locscale = htextsize.toFloat(&ok);
188 QPointF const position = textPosition(false);
189 QPointF const hposition = textPosition(true);
190 QRectF const hrect(hposition, splashSize());
192 "widget pixel ratio: " << pixelRatio() <<
193 " splash pixel ratio: " << splashPixelRatio() <<
194 " version text size,position: " << fsize << "@" << position.x() << "+" << position.y());
196 // The font used to display the version info
197 font.setStyleHint(QFont::SansSerif);
198 font.setWeight(QFont::Bold);
199 font.setPointSizeF(fsize);
201 pain.drawText(position, text);
202 // The font used to display the version info
203 font.setStyleHint(QFont::SansSerif);
204 font.setWeight(QFont::Normal);
205 font.setPointSizeF(hfsize);
206 // Check how long the logo gets with the current font
207 // and adapt if the font is running wider than what
209 GuiFontMetrics fm(font);
210 // Split the title into lines to measure the longest line
211 // in the current l7n.
212 QStringList titlesegs = htext.split('\n');
214 int hline = fm.maxHeight();
215 for (QString const & seg : titlesegs) {
216 if (fm.width(seg) > wline)
217 wline = fm.width(seg);
219 // The longest line in the reference font (for English)
220 // is 180. Calculate scale factor from that.
221 double const wscale = wline > 0 ? (180.0 / wline) : 1;
222 // Now do the same for the height (necessary for condensed fonts)
223 double const hscale = (34.0 / hline);
224 // take the lower of the two scale factors.
225 double const scale = min(wscale, hscale);
226 // Now rescale. Also consider l7n's offset factor.
227 font.setPointSizeF(hfsize * scale * locscale);
230 pain.drawText(hrect, Qt::AlignLeft, htext);
231 setFocusPolicy(Qt::StrongFocus);
234 void paintEvent(QPaintEvent *) override
236 int const w = width_;
237 int const h = height_;
238 int const x = (width() - w) / 2;
239 int const y = (height() - h) / 2;
241 "widget pixel ratio: " << pixelRatio() <<
242 " splash pixel ratio: " << splashPixelRatio() <<
243 " paint pixmap: " << w << "x" << h << "@" << x << "+" << y);
245 pain.drawPixmap(x, y, w, h, splash_);
248 void keyPressEvent(QKeyEvent * ev) override
251 setKeySymbol(&sym, ev);
253 guiApp->processKeySym(sym, q_key_state(ev->modifiers()));
265 /// Current ratio between physical pixels and device-independent pixels
266 double pixelRatio() const {
267 #if QT_VERSION >= 0x050000
268 return qt_scale_factor * devicePixelRatio();
274 qreal fontSize() const {
275 return toqstr(lyxrc.font_sizes[NORMAL_SIZE]).toDouble();
278 QPointF textPosition(bool const heading) const {
279 return heading ? QPointF(width_/2 - 18, height_/2 - 45)
280 : QPointF(width_/2 - 18, height_/2 + 45);
283 QSize splashSize() const {
285 static_cast<unsigned int>(width_ * pixelRatio()),
286 static_cast<unsigned int>(height_ * pixelRatio()));
289 /// Ratio between physical pixels and device-independent pixels of splash image
290 double splashPixelRatio() const {
291 #if QT_VERSION >= 0x050000
292 return splash_.devicePixelRatio();
300 /// Toolbar store providing access to individual toolbars by name.
301 typedef map<string, GuiToolbar *> ToolbarMap;
303 typedef shared_ptr<Dialog> DialogPtr;
308 class GuiView::GuiViewPrivate
311 GuiViewPrivate(GuiViewPrivate const &);
312 void operator=(GuiViewPrivate const &);
314 GuiViewPrivate(GuiView * gv)
315 : gv_(gv), current_work_area_(nullptr), current_main_work_area_(nullptr),
316 layout_(nullptr), autosave_timeout_(5000),
319 // hardcode here the platform specific icon size
320 smallIconSize = 16; // scaling problems
321 normalIconSize = 20; // ok, default if iconsize.png is missing
322 bigIconSize = 26; // better for some math icons
323 hugeIconSize = 32; // better for hires displays
326 // if it exists, use width of iconsize.png as normal size
327 QString const dir = toqstr(addPath("images", lyxrc.icon_set));
328 FileName const fn = lyx::libFileSearch(dir, "iconsize.png");
330 QImage image(toqstr(fn.absFileName()));
331 if (image.width() < int(smallIconSize))
332 normalIconSize = smallIconSize;
333 else if (image.width() > int(giantIconSize))
334 normalIconSize = giantIconSize;
336 normalIconSize = image.width();
339 splitter_ = new QSplitter;
340 bg_widget_ = new BackgroundWidget(400, 250);
341 stack_widget_ = new QStackedWidget;
342 stack_widget_->addWidget(bg_widget_);
343 stack_widget_->addWidget(splitter_);
346 // TODO cleanup, remove the singleton, handle multiple Windows?
347 progress_ = ProgressInterface::instance();
348 if (!dynamic_cast<GuiProgress*>(progress_)) {
349 progress_ = new GuiProgress; // TODO who deletes it
350 ProgressInterface::setInstance(progress_);
353 dynamic_cast<GuiProgress*>(progress_),
354 SIGNAL(updateStatusBarMessage(QString const&)),
355 gv, SLOT(updateStatusBarMessage(QString const&)));
357 dynamic_cast<GuiProgress*>(progress_),
358 SIGNAL(clearMessageText()),
359 gv, SLOT(clearMessageText()));
366 delete stack_widget_;
371 stack_widget_->setCurrentWidget(bg_widget_);
372 bg_widget_->setUpdatesEnabled(true);
373 bg_widget_->setFocus();
376 int tabWorkAreaCount()
378 return splitter_->count();
381 TabWorkArea * tabWorkArea(int i)
383 return dynamic_cast<TabWorkArea *>(splitter_->widget(i));
386 TabWorkArea * currentTabWorkArea()
388 int areas = tabWorkAreaCount();
390 // The first TabWorkArea is always the first one, if any.
391 return tabWorkArea(0);
393 for (int i = 0; i != areas; ++i) {
394 TabWorkArea * twa = tabWorkArea(i);
395 if (current_main_work_area_ == twa->currentWorkArea())
399 // None has the focus so we just take the first one.
400 return tabWorkArea(0);
403 int countWorkAreasOf(Buffer & buf)
405 int areas = tabWorkAreaCount();
407 for (int i = 0; i != areas; ++i) {
408 TabWorkArea * twa = tabWorkArea(i);
409 if (twa->workArea(buf))
415 void setPreviewFuture(QFuture<Buffer::ExportStatus> const & f)
417 if (processing_thread_watcher_.isRunning()) {
418 // we prefer to cancel this preview in order to keep a snappy
422 processing_thread_watcher_.setFuture(f);
425 QSize iconSize(docstring const & icon_size)
428 if (icon_size == "small")
429 size = smallIconSize;
430 else if (icon_size == "normal")
431 size = normalIconSize;
432 else if (icon_size == "big")
434 else if (icon_size == "huge")
436 else if (icon_size == "giant")
437 size = giantIconSize;
439 size = icon_size.empty() ? normalIconSize : convert<int>(icon_size);
441 if (size < smallIconSize)
442 size = smallIconSize;
444 return QSize(size, size);
447 QSize iconSize(QString const & icon_size)
449 return iconSize(qstring_to_ucs4(icon_size));
452 string & iconSize(QSize const & qsize)
454 LATTEST(qsize.width() == qsize.height());
456 static string icon_size;
458 unsigned int size = qsize.width();
460 if (size < smallIconSize)
461 size = smallIconSize;
463 if (size == smallIconSize)
465 else if (size == normalIconSize)
466 icon_size = "normal";
467 else if (size == bigIconSize)
469 else if (size == hugeIconSize)
471 else if (size == giantIconSize)
474 icon_size = convert<string>(size);
479 static Buffer::ExportStatus previewAndDestroy(Buffer const * orig,
480 Buffer * buffer, string const & format);
481 static Buffer::ExportStatus exportAndDestroy(Buffer const * orig,
482 Buffer * buffer, string const & format);
483 static Buffer::ExportStatus compileAndDestroy(Buffer const * orig,
484 Buffer * buffer, string const & format);
485 static docstring autosaveAndDestroy(Buffer const * orig, Buffer * buffer);
488 static Buffer::ExportStatus runAndDestroy(const T& func,
489 Buffer const * orig, Buffer * buffer, string const & format);
491 // TODO syncFunc/previewFunc: use bind
492 bool asyncBufferProcessing(string const & argument,
493 Buffer const * used_buffer,
494 docstring const & msg,
495 Buffer::ExportStatus (*asyncFunc)(Buffer const *, Buffer *, string const &),
496 Buffer::ExportStatus (Buffer::*syncFunc)(string const &, bool) const,
497 Buffer::ExportStatus (Buffer::*previewFunc)(string const &) const,
498 bool allow_async, bool use_tmpdir = false);
500 QVector<GuiWorkArea*> guiWorkAreas();
504 GuiWorkArea * current_work_area_;
505 GuiWorkArea * current_main_work_area_;
506 QSplitter * splitter_;
507 QStackedWidget * stack_widget_;
508 BackgroundWidget * bg_widget_;
510 ToolbarMap toolbars_;
511 ProgressInterface* progress_;
512 /// The main layout box.
514 * \warning Don't Delete! The layout box is actually owned by
515 * whichever toolbar contains it. All the GuiView class needs is a
516 * means of accessing it.
518 * FIXME: replace that with a proper model so that we are not limited
519 * to only one dialog.
524 map<string, DialogPtr> dialogs_;
527 QTimer statusbar_timer_;
528 QTimer statusbar_stats_timer_;
529 /// auto-saving of buffers
530 Timeout autosave_timeout_;
533 TocModels toc_models_;
536 QFutureWatcher<docstring> autosave_watcher_;
537 QFutureWatcher<Buffer::ExportStatus> processing_thread_watcher_;
539 string last_export_format;
540 string processing_format;
542 static QSet<Buffer const *> busyBuffers;
544 unsigned int smallIconSize;
545 unsigned int normalIconSize;
546 unsigned int bigIconSize;
547 unsigned int hugeIconSize;
548 unsigned int giantIconSize;
550 /// flag against a race condition due to multiclicks, see bug #1119
553 // Timers for statistic updates in buffer
554 /// Current time left to the nearest info update
555 int time_to_update = 1000;
556 ///Basic step for timer in ms. Basically reaction time for short selections
557 int const timer_rate = 500;
558 /// Real stats updates infrequently. First they take long time for big buffers, second
559 /// they are visible for fast-repeat keyboards even for mid documents.
560 int const default_stats_rate = 5000;
561 /// Detection of new selection, so we can react fast
562 bool already_in_selection_ = false;
563 /// Maximum size of "short" selection for which we can update with faster timer_rate
564 int const max_sel_chars = 5000;
568 QSet<Buffer const *> GuiView::GuiViewPrivate::busyBuffers;
571 GuiView::GuiView(int id)
572 : d(*new GuiViewPrivate(this)), id_(id), closing_(false), busy_(0),
573 command_execute_(false), minibuffer_focus_(false), word_count_enabled_(true),
574 char_count_enabled_(true), char_nb_count_enabled_(false),
575 toolbarsMovable_(true), devel_mode_(false)
577 connect(this, SIGNAL(bufferViewChanged()),
578 this, SLOT(onBufferViewChanged()));
580 // GuiToolbars *must* be initialised before the menu bar.
581 setIconSize(QSize(d.normalIconSize, d.normalIconSize)); // at least on Mac the default is 32 otherwise, which is huge
584 // set ourself as the current view. This is needed for the menu bar
585 // filling, at least for the static special menu item on Mac. Otherwise
586 // they are greyed out.
587 guiApp->setCurrentView(this);
589 // Fill up the menu bar.
590 guiApp->menus().fillMenuBar(menuBar(), this, true);
592 setCentralWidget(d.stack_widget_);
594 // Start autosave timer
595 if (lyxrc.autosave) {
596 // The connection is closed when this is destroyed.
597 d.autosave_timeout_.timeout.connect([this](){ autoSave();});
598 d.autosave_timeout_.setTimeout(lyxrc.autosave * 1000);
599 d.autosave_timeout_.start();
601 connect(&d.statusbar_timer_, SIGNAL(timeout()),
602 this, SLOT(clearMessage()));
603 connect(&d.statusbar_stats_timer_, SIGNAL(timeout()),
604 this, SLOT(showStats()));
605 d.statusbar_stats_timer_.start(d.timer_rate);
607 // We don't want to keep the window in memory if it is closed.
608 setAttribute(Qt::WA_DeleteOnClose, true);
610 #if !(defined(Q_OS_WIN) || defined(Q_CYGWIN_WIN)) && !defined(Q_OS_MAC)
611 // QIcon::fromTheme was introduced in Qt 4.6
612 #if (QT_VERSION >= 0x040600)
613 // assign an icon to main form. We do not do it under Qt/Win or Qt/Mac,
614 // since the icon is provided in the application bundle. We use a themed
615 // version when available and use the bundled one as fallback.
616 setWindowIcon(QIcon::fromTheme("lyx", getPixmap("images/", "lyx", "svg,png")));
618 setWindowIcon(getPixmap("images/", "lyx", "svg,png"));
624 // use tabbed dock area for multiple docks
625 // (such as "source" and "messages")
626 setDockOptions(QMainWindow::ForceTabbedDocks);
629 // use document mode tabs on docks
630 setDocumentMode(true);
634 setAcceptDrops(true);
636 // add busy indicator to statusbar
637 search_mode mode = theGuiApp()->imageSearchMode();
638 QString fn = toqstr(lyx::libFileSearch("images", "busy", "svgz", mode).absFileName());
639 PressableSvgWidget * busySVG = new PressableSvgWidget(fn);
640 statusBar()->addPermanentWidget(busySVG);
641 // make busy indicator square with 5px margins
642 busySVG->setMaximumSize(busySVG->height() - 5, busySVG->height() - 5);
645 connect(&d.processing_thread_watcher_, SIGNAL(started()),
646 busySVG, SLOT(show()));
647 connect(&d.processing_thread_watcher_, SIGNAL(finished()),
648 busySVG, SLOT(hide()));
649 connect(busySVG, SIGNAL(pressed()), this, SLOT(checkCancelBackground()));
651 stat_counts_ = new GuiClickableLabel(statusBar());
652 stat_counts_->setAlignment(Qt::AlignCenter);
653 stat_counts_->setFrameStyle(QFrame::StyledPanel);
654 stat_counts_->hide();
655 statusBar()->addPermanentWidget(stat_counts_);
657 connect(stat_counts_, SIGNAL(clicked()), this, SLOT(statsPressed()));
660 QFontMetrics const fm(statusBar()->fontMetrics());
662 zoom_slider_ = new QSlider(Qt::Horizontal, statusBar());
663 // Small size slider for macOS to prevent the status bar from enlarging
664 zoom_slider_->setAttribute(Qt::WA_MacSmallSize);
665 #if (QT_VERSION >= QT_VERSION_CHECK(5, 11, 0))
666 zoom_slider_->setFixedWidth(fm.horizontalAdvance('x') * 15);
668 zoom_slider_->setFixedWidth(fm.width('x') * 15);
670 // Make the defaultZoom center
671 zoom_slider_->setRange(10, (lyxrc.defaultZoom * 2) - 10);
672 // Initialize proper zoom value
674 zoom_ratio_ = settings.value("zoom_ratio", 1.0).toDouble();
675 // Actual zoom value: default zoom + fractional offset
676 int zoom = (int)(lyxrc.defaultZoom * zoom_ratio_);
677 zoom = min(max(zoom, zoom_min_), zoom_max_);
678 zoom_slider_->setValue(zoom);
679 zoom_slider_->setToolTip(qt_("Workarea zoom level. Drag, use Ctrl-+/- or Shift-Mousewheel to adjust."));
681 // Buttons to change zoom stepwise
682 #if (QT_VERSION >= QT_VERSION_CHECK(5, 11, 0))
683 QSize s(fm.horizontalAdvance('+'), fm.height());
685 QSize s(fm.width('+'), fm.height());
687 zoom_in_ = new GuiClickableLabel(statusBar());
688 zoom_in_->setText("+");
689 zoom_in_->setFixedSize(s);
690 zoom_in_->setAlignment(Qt::AlignCenter);
691 zoom_out_ = new GuiClickableLabel(statusBar());
692 zoom_out_->setText(QString(QChar(0x2212)));
693 zoom_out_->setFixedSize(s);
694 zoom_out_->setAlignment(Qt::AlignCenter);
696 statusBar()->addPermanentWidget(zoom_out_);
697 zoom_out_->setEnabled(currentBufferView());
698 statusBar()->addPermanentWidget(zoom_slider_);
699 zoom_slider_->setEnabled(currentBufferView());
700 zoom_in_->setEnabled(currentBufferView());
701 statusBar()->addPermanentWidget(zoom_in_);
703 connect(zoom_slider_, SIGNAL(sliderMoved(int)), this, SLOT(zoomSliderMoved(int)));
704 connect(zoom_slider_, SIGNAL(valueChanged(int)), this, SLOT(zoomValueChanged(int)));
705 connect(this, SIGNAL(currentZoomChanged(int)), zoom_slider_, SLOT(setValue(int)));
706 connect(zoom_in_, SIGNAL(clicked()), this, SLOT(zoomInPressed()));
707 connect(zoom_out_, SIGNAL(clicked()), this, SLOT(zoomOutPressed()));
709 // QPalette palette = statusBar()->palette();
711 zoom_value_ = new GuiClickableLabel(statusBar());
712 connect(zoom_value_, SIGNAL(pressed()), this, SLOT(showZoomContextMenu()));
713 // zoom_value_->setPalette(palette);
714 zoom_value_->setForegroundRole(statusBar()->foregroundRole());
715 zoom_value_->setFixedHeight(fm.height());
716 #if (QT_VERSION >= QT_VERSION_CHECK(5, 11, 0))
717 zoom_value_->setMinimumWidth(fm.horizontalAdvance("444\%"));
719 zoom_value_->setMinimumWidth(fm.width("444\%"));
721 zoom_value_->setAlignment(Qt::AlignCenter);
722 zoom_value_->setText(toqstr(bformat(_("[[ZOOM]]%1$d%"), zoom)));
723 statusBar()->addPermanentWidget(zoom_value_);
724 zoom_value_->setEnabled(currentBufferView());
726 statusBar()->setContextMenuPolicy(Qt::CustomContextMenu);
727 connect(statusBar(), SIGNAL(customContextMenuRequested(QPoint)),
728 this, SLOT(showStatusBarContextMenu()));
730 // enable pinch to zoom
731 grabGesture(Qt::PinchGesture);
733 int const iconheight = max(int(d.normalIconSize), fm.height());
734 QSize const iconsize(iconheight, iconheight);
736 QPixmap shellescape = QIcon(getPixmap("images/", "emblem-shellescape", "svgz,png")).pixmap(iconsize);
737 shell_escape_ = new QLabel(statusBar());
738 shell_escape_->setPixmap(shellescape);
739 shell_escape_->setAlignment(Qt::AlignCenter);
740 shell_escape_->setContextMenuPolicy(Qt::CustomContextMenu);
741 shell_escape_->setToolTip(qt_("WARNING: LaTeX is allowed to execute "
742 "external commands for this document. "
743 "Right click to change."));
744 SEMenu * menu = new SEMenu(this);
745 connect(shell_escape_, SIGNAL(customContextMenuRequested(QPoint)),
746 menu, SLOT(showMenu(QPoint)));
747 shell_escape_->hide();
748 statusBar()->addPermanentWidget(shell_escape_);
750 QPixmap readonly = QIcon(getPixmap("images/", "emblem-readonly", "svgz,png")).pixmap(iconsize);
751 read_only_ = new QLabel(statusBar());
752 read_only_->setPixmap(readonly);
753 read_only_->setAlignment(Qt::AlignCenter);
755 statusBar()->addPermanentWidget(read_only_);
757 version_control_ = new QLabel(statusBar());
758 version_control_->setAlignment(Qt::AlignCenter);
759 version_control_->setFrameStyle(QFrame::StyledPanel);
760 version_control_->hide();
761 statusBar()->addPermanentWidget(version_control_);
763 statusBar()->setSizeGripEnabled(true);
766 connect(&d.autosave_watcher_, SIGNAL(finished()), this,
767 SLOT(autoSaveThreadFinished()));
769 connect(&d.processing_thread_watcher_, SIGNAL(started()), this,
770 SLOT(processingThreadStarted()));
771 connect(&d.processing_thread_watcher_, SIGNAL(finished()), this,
772 SLOT(processingThreadFinished()));
774 connect(this, SIGNAL(triggerShowDialog(QString const &, QString const &, Inset *)),
775 SLOT(doShowDialog(QString const &, QString const &, Inset *)));
777 // set custom application bars context menu, e.g. tool bar and menu bar
778 setContextMenuPolicy(Qt::CustomContextMenu);
779 connect(this, SIGNAL(customContextMenuRequested(const QPoint &)),
780 SLOT(toolBarPopup(const QPoint &)));
782 // Forbid too small unresizable window because it can happen
783 // with some window manager under X11.
784 setMinimumSize(300, 200);
786 if (lyxrc.allow_geometry_session) {
787 // Now take care of session management.
792 // no session handling, default to a sane size.
793 setGeometry(50, 50, 690, 510);
796 // clear session data if any.
797 settings.remove("views");
807 void GuiView::disableShellEscape()
809 BufferView * bv = documentBufferView();
812 theSession().shellescapeFiles().remove(bv->buffer().absFileName());
813 bv->buffer().params().shell_escape = false;
814 bv->processUpdateFlags(Update::Force);
818 void GuiView::checkCancelBackground()
820 docstring const ttl = _("Cancel Export?");
821 docstring const msg = _("Do you want to cancel the background export process?");
823 Alert::prompt(ttl, msg, 1, 1,
824 _("&Cancel export"), _("Co&ntinue"));
826 Systemcall::killscript();
829 void GuiView::statsPressed()
832 dispatch(FuncRequest(LFUN_STATISTICS), dr);
835 void GuiView::zoomSliderMoved(int value)
838 dispatch(FuncRequest(LFUN_BUFFER_ZOOM, convert<string>(value)), dr);
839 scheduleRedrawWorkAreas();
840 zoom_value_->setText(toqstr(bformat(_("[[ZOOM]]%1$d%"), value)));
844 void GuiView::zoomValueChanged(int value)
846 if (value != lyxrc.currentZoom)
847 zoomSliderMoved(value);
851 void GuiView::zoomInPressed()
854 dispatch(FuncRequest(LFUN_BUFFER_ZOOM_IN), dr);
855 scheduleRedrawWorkAreas();
859 void GuiView::zoomOutPressed()
862 dispatch(FuncRequest(LFUN_BUFFER_ZOOM_OUT), dr);
863 scheduleRedrawWorkAreas();
867 void GuiView::showZoomContextMenu()
869 QMenu * menu = guiApp->menus().menu(toqstr("context-zoom"), * this);
872 menu->exec(QCursor::pos());
876 void GuiView::showStatusBarContextMenu()
878 QMenu * menu = guiApp->menus().menu(toqstr("context-statusbar"), * this);
881 menu->exec(QCursor::pos());
885 void GuiView::scheduleRedrawWorkAreas()
887 for (int i = 0; i < d.tabWorkAreaCount(); i++) {
888 TabWorkArea* ta = d.tabWorkArea(i);
889 for (int u = 0; u < ta->count(); u++) {
890 ta->workArea(u)->scheduleRedraw(true);
896 QVector<GuiWorkArea*> GuiView::GuiViewPrivate::guiWorkAreas()
898 QVector<GuiWorkArea*> areas;
899 for (int i = 0; i < tabWorkAreaCount(); i++) {
900 TabWorkArea* ta = tabWorkArea(i);
901 for (int u = 0; u < ta->count(); u++) {
902 areas << ta->workArea(u);
908 static void handleExportStatus(GuiView * view, Buffer::ExportStatus status,
909 string const & format)
911 docstring const fmt = translateIfPossible(theFormats().prettyName(format));
914 case Buffer::ExportSuccess:
915 msg = bformat(_("Successful export to format: %1$s"), fmt);
917 case Buffer::ExportCancel:
918 msg = _("Document export cancelled.");
920 case Buffer::ExportError:
921 case Buffer::ExportNoPathToFormat:
922 case Buffer::ExportTexPathHasSpaces:
923 case Buffer::ExportConverterError:
924 msg = bformat(_("Error while exporting format: %1$s"), fmt);
926 case Buffer::PreviewSuccess:
927 msg = bformat(_("Successful preview of format: %1$s"), fmt);
929 case Buffer::PreviewError:
930 msg = bformat(_("Error while previewing format: %1$s"), fmt);
932 case Buffer::ExportKilled:
933 msg = bformat(_("Conversion cancelled while previewing format: %1$s"), fmt);
940 void GuiView::processingThreadStarted()
945 void GuiView::processingThreadFinished()
947 QFutureWatcher<Buffer::ExportStatus> const * watcher =
948 static_cast<QFutureWatcher<Buffer::ExportStatus> const *>(sender());
950 Buffer::ExportStatus const status = watcher->result();
951 handleExportStatus(this, status, d.processing_format);
954 BufferView const * const bv = currentBufferView();
955 if (bv && !bv->buffer().errorList("Export").empty()) {
960 bool const error = (status != Buffer::ExportSuccess &&
961 status != Buffer::PreviewSuccess &&
962 status != Buffer::ExportCancel);
964 ErrorList & el = bv->buffer().errorList(d.last_export_format);
965 // at this point, we do not know if buffer-view or
966 // master-buffer-view was called. If there was an export error,
967 // and the current buffer's error log is empty, we guess that
968 // it must be master-buffer-view that was called so we set
970 errors(d.last_export_format, el.empty());
975 void GuiView::autoSaveThreadFinished()
977 QFutureWatcher<docstring> const * watcher =
978 static_cast<QFutureWatcher<docstring> const *>(sender());
979 message(watcher->result());
984 void GuiView::saveLayout() const
987 settings.setValue("zoom_ratio", zoom_ratio_);
988 settings.setValue("devel_mode", devel_mode_);
989 settings.beginGroup("views");
990 settings.beginGroup(QString::number(id_));
991 if (guiApp->platformName() == "qt4x11" || guiApp->platformName() == "xcb") {
992 settings.setValue("pos", pos());
993 settings.setValue("size", size());
995 settings.setValue("geometry", saveGeometry());
996 settings.setValue("layout", saveState(0));
997 settings.setValue("icon_size", toqstr(d.iconSize(iconSize())));
998 settings.setValue("zoom_value_visible", zoom_value_->isVisible());
999 settings.setValue("zoom_slider_visible", zoom_slider_->isVisible());
1000 settings.setValue("word_count_enabled", word_count_enabled_);
1001 settings.setValue("char_count_enabled", char_count_enabled_);
1002 settings.setValue("char_nb_count_enabled", char_nb_count_enabled_);
1006 void GuiView::saveUISettings() const
1010 // Save the toolbar private states
1011 for (auto const & tb_p : d.toolbars_)
1012 tb_p.second->saveSession(settings);
1013 // Now take care of all other dialogs
1014 for (auto const & dlg_p : d.dialogs_)
1015 dlg_p.second->saveSession(settings);
1019 void GuiView::setCurrentZoom(const int v)
1021 lyxrc.currentZoom = v;
1022 zoom_value_->setText(toqstr(bformat(_("[[ZOOM]]%1$d%"), v)));
1023 Q_EMIT currentZoomChanged(v);
1027 bool GuiView::restoreLayout()
1030 zoom_ratio_ = settings.value("zoom_ratio", 1.0).toDouble();
1031 // Actual zoom value: default zoom + fractional offset
1032 int zoom = (int)(lyxrc.defaultZoom * zoom_ratio_);
1033 zoom = min(max(zoom, zoom_min_), zoom_max_);
1034 setCurrentZoom(zoom);
1035 devel_mode_ = settings.value("devel_mode", devel_mode_).toBool();
1036 settings.beginGroup("views");
1037 settings.beginGroup(QString::number(id_));
1038 QString const icon_key = "icon_size";
1039 if (!settings.contains(icon_key))
1042 //code below is skipped when when ~/.config/LyX is (re)created
1043 setIconSize(d.iconSize(settings.value(icon_key).toString()));
1045 zoom_value_->setVisible(settings.value("zoom_value_visible", true).toBool());
1047 bool const show_zoom_slider = settings.value("zoom_slider_visible", true).toBool();
1048 zoom_slider_->setVisible(show_zoom_slider);
1049 zoom_in_->setVisible(show_zoom_slider);
1050 zoom_out_->setVisible(show_zoom_slider);
1052 word_count_enabled_ = settings.value("word_count_enabled", true).toBool();
1053 char_count_enabled_ = settings.value("char_count_enabled", true).toBool();
1054 char_nb_count_enabled_ = settings.value("char_nb_count_enabled", true).toBool();
1055 stat_counts_->setVisible(word_count_enabled_ || char_count_enabled_ || char_nb_count_enabled_);
1057 if (guiApp->platformName() == "qt4x11" || guiApp->platformName() == "xcb") {
1058 QPoint pos = settings.value("pos", QPoint(50, 50)).toPoint();
1059 QSize size = settings.value("size", QSize(690, 510)).toSize();
1063 // Work-around for bug #6034: the window ends up in an undetermined
1064 // state when trying to restore a maximized window when it is
1065 // already maximized.
1066 if (!(windowState() & Qt::WindowMaximized))
1067 if (!restoreGeometry(settings.value("geometry").toByteArray()))
1068 setGeometry(50, 50, 690, 510);
1071 // Make sure layout is correctly oriented.
1072 setLayoutDirection(qApp->layoutDirection());
1074 // Allow the toc and view-source dock widget to be restored if needed.
1076 if ((dialog = findOrBuild("toc", true)))
1077 // see bug 5082. At least setup title and enabled state.
1078 // Visibility will be adjusted by restoreState below.
1079 dialog->prepareView();
1080 if ((dialog = findOrBuild("view-source", true)))
1081 dialog->prepareView();
1082 if ((dialog = findOrBuild("progress", true)))
1083 dialog->prepareView();
1085 if (!restoreState(settings.value("layout").toByteArray(), 0))
1088 // init the toolbars that have not been restored
1089 for (auto const & tb_p : guiApp->toolbars()) {
1090 GuiToolbar * tb = toolbar(tb_p.name);
1091 if (tb && !tb->isRestored())
1092 initToolbar(tb_p.name);
1095 // update lock (all) toolbars positions
1096 updateLockToolbars();
1103 GuiToolbar * GuiView::toolbar(string const & name)
1105 ToolbarMap::iterator it = d.toolbars_.find(name);
1106 if (it != d.toolbars_.end())
1109 LYXERR(Debug::GUI, "Toolbar::display: no toolbar named " << name);
1114 void GuiView::updateLockToolbars()
1116 toolbarsMovable_ = false;
1117 for (ToolbarInfo const & info : guiApp->toolbars()) {
1118 GuiToolbar * tb = toolbar(info.name);
1119 if (tb && tb->isMovable())
1120 toolbarsMovable_ = true;
1122 #if QT_VERSION >= 0x050200
1123 // set unified mac toolbars only when not movable as recommended:
1124 // https://doc.qt.io/qt-5/qmainwindow.html#unifiedTitleAndToolBarOnMac-prop
1125 setUnifiedTitleAndToolBarOnMac(!toolbarsMovable_);
1130 void GuiView::constructToolbars()
1132 for (auto const & tb_p : d.toolbars_)
1134 d.toolbars_.clear();
1136 // I don't like doing this here, but the standard toolbar
1137 // destroys this object when it's destroyed itself (vfr)
1138 d.layout_ = new LayoutBox(*this);
1139 d.stack_widget_->addWidget(d.layout_);
1140 d.layout_->move(0,0);
1142 // extracts the toolbars from the backend
1143 for (ToolbarInfo const & inf : guiApp->toolbars())
1144 d.toolbars_[inf.name] = new GuiToolbar(inf, *this);
1146 DynamicMenuButton::resetIconCache();
1150 void GuiView::initToolbars()
1152 // extracts the toolbars from the backend
1153 for (ToolbarInfo const & inf : guiApp->toolbars())
1154 initToolbar(inf.name);
1158 void GuiView::initToolbar(string const & name)
1160 GuiToolbar * tb = toolbar(name);
1163 int const visibility = guiApp->toolbars().defaultVisibility(name);
1164 bool newline = !(visibility & Toolbars::SAMEROW);
1165 tb->setVisible(false);
1166 tb->setVisibility(visibility);
1168 if (visibility & Toolbars::TOP) {
1170 addToolBarBreak(Qt::TopToolBarArea);
1171 addToolBar(Qt::TopToolBarArea, tb);
1174 if (visibility & Toolbars::BOTTOM) {
1176 addToolBarBreak(Qt::BottomToolBarArea);
1177 addToolBar(Qt::BottomToolBarArea, tb);
1180 if (visibility & Toolbars::LEFT) {
1182 addToolBarBreak(Qt::LeftToolBarArea);
1183 addToolBar(Qt::LeftToolBarArea, tb);
1186 if (visibility & Toolbars::RIGHT) {
1188 addToolBarBreak(Qt::RightToolBarArea);
1189 addToolBar(Qt::RightToolBarArea, tb);
1192 if (visibility & Toolbars::ON)
1193 tb->setVisible(true);
1195 tb->setMovable(true);
1199 TocModels & GuiView::tocModels()
1201 return d.toc_models_;
1205 void GuiView::setFocus()
1207 LYXERR(Debug::DEBUG, "GuiView::setFocus()" << this);
1208 QMainWindow::setFocus();
1212 bool GuiView::hasFocus() const
1214 if (currentWorkArea())
1215 return currentWorkArea()->hasFocus();
1216 if (currentMainWorkArea())
1217 return currentMainWorkArea()->hasFocus();
1218 return d.bg_widget_->hasFocus();
1222 void GuiView::focusInEvent(QFocusEvent * e)
1224 LYXERR(Debug::DEBUG, "GuiView::focusInEvent()" << this);
1225 QMainWindow::focusInEvent(e);
1226 // Make sure guiApp points to the correct view.
1227 guiApp->setCurrentView(this);
1228 if (currentWorkArea())
1229 currentWorkArea()->setFocus();
1230 else if (currentMainWorkArea())
1231 currentMainWorkArea()->setFocus();
1233 d.bg_widget_->setFocus();
1237 void GuiView::showEvent(QShowEvent * e)
1239 LYXERR(Debug::GUI, "Passed Geometry "
1240 << size().height() << "x" << size().width()
1241 << "+" << pos().x() << "+" << pos().y());
1243 if (d.splitter_->count() == 0)
1244 // No work area, switch to the background widget.
1248 QMainWindow::showEvent(e);
1252 bool GuiView::closeScheduled()
1259 bool GuiView::prepareAllBuffersForLogout()
1261 Buffer * first = theBufferList().first();
1265 // First, iterate over all buffers and ask the users if unsaved
1266 // changes should be saved.
1267 // We cannot use a for loop as the buffer list cycles.
1270 if (!saveBufferIfNeeded(*b, false))
1272 b = theBufferList().next(b);
1273 } while (b != first);
1275 // Next, save session state
1276 // When a view/window was closed before without quitting LyX, there
1277 // are already entries in the lastOpened list.
1278 theSession().lastOpened().clear();
1285 /** Destroy only all tabbed WorkAreas. Destruction of other WorkAreas
1286 ** is responsibility of the container (e.g., dialog)
1288 void GuiView::closeEvent(QCloseEvent * close_event)
1290 LYXERR(Debug::DEBUG, "GuiView::closeEvent()");
1292 if (!GuiViewPrivate::busyBuffers.isEmpty()) {
1293 Alert::warning(_("Exit LyX"),
1294 _("LyX could not be closed because documents are being processed by LyX."));
1295 close_event->setAccepted(false);
1299 // If the user pressed the x (so we didn't call closeView
1300 // programmatically), we want to clear all existing entries.
1302 theSession().lastOpened().clear();
1307 // it can happen that this event arrives without selecting the view,
1308 // e.g. when clicking the close button on a background window.
1310 if (!closeWorkAreaAll()) {
1312 close_event->ignore();
1316 // Make sure that nothing will use this to be closed View.
1317 guiApp->unregisterView(this);
1319 if (isFullScreen()) {
1320 // Switch off fullscreen before closing.
1325 // Make sure the timer time out will not trigger a statusbar update.
1326 d.statusbar_timer_.stop();
1327 d.statusbar_stats_timer_.stop();
1329 // Saving fullscreen requires additional tweaks in the toolbar code.
1330 // It wouldn't also work under linux natively.
1331 if (lyxrc.allow_geometry_session) {
1336 close_event->accept();
1340 void GuiView::dragEnterEvent(QDragEnterEvent * event)
1342 if (event->mimeData()->hasUrls())
1344 /// \todo Ask lyx-devel is this is enough:
1345 /// if (event->mimeData()->hasFormat("text/plain"))
1346 /// event->acceptProposedAction();
1350 void GuiView::dropEvent(QDropEvent * event)
1352 QList<QUrl> files = event->mimeData()->urls();
1353 if (files.isEmpty())
1356 LYXERR(Debug::GUI, "GuiView::dropEvent: got URLs!");
1357 for (int i = 0; i != files.size(); ++i) {
1358 string const file = os::internal_path(fromqstr(
1359 files.at(i).toLocalFile()));
1363 string const ext = support::getExtension(file);
1364 vector<const Format *> found_formats;
1366 // Find all formats that have the correct extension.
1367 for (const Format * fmt : theConverters().importableFormats())
1368 if (fmt->hasExtension(ext))
1369 found_formats.push_back(fmt);
1372 if (!found_formats.empty()) {
1373 if (found_formats.size() > 1) {
1374 //FIXME: show a dialog to choose the correct importable format
1375 LYXERR(Debug::FILES,
1376 "Multiple importable formats found, selecting first");
1378 string const arg = found_formats[0]->name() + " " + file;
1379 cmd = FuncRequest(LFUN_BUFFER_IMPORT, arg);
1382 //FIXME: do we have to explicitly check whether it's a lyx file?
1383 LYXERR(Debug::FILES,
1384 "No formats found, trying to open it as a lyx file");
1385 cmd = FuncRequest(LFUN_FILE_OPEN, file);
1387 // add the functions to the queue
1388 guiApp->addToFuncRequestQueue(cmd);
1391 // now process the collected functions. We perform the events
1392 // asynchronously. This prevents potential problems in case the
1393 // BufferView is closed within an event.
1394 guiApp->processFuncRequestQueueAsync();
1398 void GuiView::message(docstring const & str)
1400 if (ForkedProcess::iAmAChild())
1403 // call is moved to GUI-thread by GuiProgress
1404 d.progress_->appendMessage(toqstr(str));
1408 void GuiView::clearMessageText()
1410 message(docstring());
1414 void GuiView::updateStatusBarMessage(QString const & str)
1416 statusBar()->showMessage(str);
1417 d.statusbar_timer_.stop();
1418 d.statusbar_timer_.start(3000);
1422 void GuiView::clearMessage()
1424 // FIXME: This code was introduced in r19643 to fix bug #4123. However,
1425 // the hasFocus function mostly returns false, even if the focus is on
1426 // a workarea in this view.
1430 d.statusbar_timer_.stop();
1433 void GuiView::showStats()
1435 if (!statsEnabled())
1438 d.time_to_update -= d.timer_rate;
1440 BufferView * bv = currentBufferView();
1441 Buffer * buf = bv ? &bv->buffer() : nullptr;
1443 stat_counts_->hide();
1447 Cursor const & cur = bv->cursor();
1449 // we start new selection and need faster update
1450 if (!d.already_in_selection_ && cur.selection())
1451 d.time_to_update = 0;
1453 if (d.time_to_update > 0)
1456 DocIterator from, to;
1457 if (cur.selection()) {
1458 from = cur.selectionBegin();
1459 to = cur.selectionEnd();
1460 d.already_in_selection_ = true;
1462 from = doc_iterator_begin(buf);
1463 to = doc_iterator_end(buf);
1464 d.already_in_selection_ = false;
1467 buf->updateStatistics(from, to);
1470 if (word_count_enabled_) {
1471 int const words = buf->wordCount();
1473 stats << toqstr(bformat(_("%1$d Word"), words));
1475 stats << toqstr(bformat(_("%1$d Words"), words));
1477 int const chars_with_blanks = buf->charCount(true);
1478 if (char_count_enabled_) {
1479 if (chars_with_blanks == 1)
1480 stats << toqstr(bformat(_("%1$d Character"), chars_with_blanks));
1482 stats << toqstr(bformat(_("%1$d Characters"), chars_with_blanks));
1484 if (char_nb_count_enabled_) {
1485 int const chars = buf->charCount(false);
1487 stats << toqstr(bformat(_("%1$d Character (no Blanks)"), chars));
1489 stats << toqstr(bformat(_("%1$d Characters (no Blanks)"), chars));
1491 stat_counts_->setText(stats.join(qt_(", [[stats separator]]")));
1492 stat_counts_->show();
1494 d.time_to_update = d.default_stats_rate;
1495 // fast updates for small selections
1496 if (chars_with_blanks < d.max_sel_chars && cur.selection())
1497 d.time_to_update = d.timer_rate;
1501 void GuiView::updateWindowTitle(GuiWorkArea * wa)
1503 if (wa != d.current_work_area_
1504 || wa->bufferView().buffer().isInternal())
1506 Buffer const & buf = wa->bufferView().buffer();
1507 // Set the windows title
1508 docstring title = buf.fileName().displayName(130) + from_ascii("[*]");
1509 if (buf.notifiesExternalModification()) {
1510 title = bformat(_("%1$s (modified externally)"), title);
1511 // If the external modification status has changed, then maybe the status of
1512 // buffer-save has changed too.
1516 title += from_ascii(" - LyX");
1518 setWindowTitle(toqstr(title));
1519 // Sets the path for the window: this is used by OSX to
1520 // allow a context click on the title bar showing a menu
1521 // with the path up to the file
1522 setWindowFilePath(toqstr(buf.absFileName()));
1523 // Tell Qt whether the current document is changed
1524 setWindowModified(!buf.isClean());
1526 if (buf.params().shell_escape)
1527 shell_escape_->show();
1529 shell_escape_->hide();
1531 if (buf.hasReadonlyFlag())
1536 if (buf.lyxvc().inUse()) {
1537 version_control_->show();
1538 version_control_->setText(toqstr(buf.lyxvc().vcstatus()));
1540 version_control_->hide();
1544 void GuiView::on_currentWorkAreaChanged(GuiWorkArea * wa)
1546 if (d.current_work_area_)
1547 // disconnect the current work area from all slots
1548 QObject::disconnect(d.current_work_area_, nullptr, this, nullptr);
1550 disconnectBufferView();
1551 connectBufferView(wa->bufferView());
1552 connectBuffer(wa->bufferView().buffer());
1553 d.current_work_area_ = wa;
1554 QObject::connect(wa, SIGNAL(titleChanged(GuiWorkArea *)),
1555 this, SLOT(updateWindowTitle(GuiWorkArea *)));
1556 QObject::connect(wa, SIGNAL(busy(bool)),
1557 this, SLOT(setBusy(bool)));
1558 // connection of a signal to a signal
1559 QObject::connect(wa, SIGNAL(bufferViewChanged()),
1560 this, SIGNAL(bufferViewChanged()));
1561 Q_EMIT updateWindowTitle(wa);
1562 Q_EMIT bufferViewChanged();
1566 void GuiView::onBufferViewChanged()
1569 // Buffer-dependent dialogs must be updated. This is done here because
1570 // some dialogs require buffer()->text.
1572 zoom_slider_->setEnabled(currentBufferView());
1573 zoom_value_->setEnabled(currentBufferView());
1574 zoom_in_->setEnabled(currentBufferView());
1575 zoom_out_->setEnabled(currentBufferView());
1579 void GuiView::on_lastWorkAreaRemoved()
1582 // We already are in a close event. Nothing more to do.
1585 if (d.splitter_->count() > 1)
1586 // We have a splitter so don't close anything.
1589 // Reset and updates the dialogs.
1590 Q_EMIT bufferViewChanged();
1595 if (lyxrc.open_buffers_in_tabs)
1596 // Nothing more to do, the window should stay open.
1599 if (guiApp->viewIds().size() > 1) {
1605 // On Mac we also close the last window because the application stay
1606 // resident in memory. On other platforms we don't close the last
1607 // window because this would quit the application.
1613 void GuiView::updateStatusBar()
1615 // let the user see the explicit message
1616 if (d.statusbar_timer_.isActive())
1623 void GuiView::showMessage()
1627 QString msg = toqstr(theGuiApp()->viewStatusMessage());
1628 if (msg.isEmpty()) {
1629 BufferView const * bv = currentBufferView();
1631 msg = toqstr(bv->cursor().currentState(devel_mode_));
1633 msg = qt_("Welcome to LyX!");
1635 statusBar()->showMessage(msg);
1639 bool GuiView::statsEnabled() const
1641 return word_count_enabled_ || char_count_enabled_ || char_nb_count_enabled_;
1645 bool GuiView::event(QEvent * e)
1649 // Useful debug code:
1650 //case QEvent::ActivationChange:
1651 //case QEvent::WindowDeactivate:
1652 //case QEvent::Paint:
1653 //case QEvent::Enter:
1654 //case QEvent::Leave:
1655 //case QEvent::HoverEnter:
1656 //case QEvent::HoverLeave:
1657 //case QEvent::HoverMove:
1658 //case QEvent::StatusTip:
1659 //case QEvent::DragEnter:
1660 //case QEvent::DragLeave:
1661 //case QEvent::Drop:
1664 case QEvent::WindowStateChange: {
1665 QWindowStateChangeEvent * ev = (QWindowStateChangeEvent*)e;
1666 bool ofstate = (ev->oldState() & Qt::WindowFullScreen);
1667 bool result = QMainWindow::event(e);
1668 bool nfstate = (windowState() & Qt::WindowFullScreen);
1669 if (!ofstate && nfstate) {
1670 LYXERR(Debug::DEBUG, "GuiView: WindowStateChange(): full-screen " << nfstate);
1671 // switch to full-screen state
1672 if (lyxrc.full_screen_statusbar)
1673 statusBar()->hide();
1674 if (lyxrc.full_screen_menubar)
1676 if (lyxrc.full_screen_toolbars) {
1677 for (auto const & tb_p : d.toolbars_)
1678 if (tb_p.second->isVisibilityOn() && tb_p.second->isVisible())
1679 tb_p.second->hide();
1681 for (int i = 0; i != d.splitter_->count(); ++i)
1682 d.tabWorkArea(i)->setFullScreen(true);
1683 #if QT_VERSION > 0x050903
1684 //Qt's 5.9.4 ba44cdae38406c safe area measures won't allow us to go negative in margins
1685 setAttribute(Qt::WA_ContentsMarginsRespectsSafeArea, false);
1687 setContentsMargins(-2, -2, -2, -2);
1689 hideDialogs("prefs", nullptr);
1690 } else if (ofstate && !nfstate) {
1691 LYXERR(Debug::DEBUG, "GuiView: WindowStateChange(): full-screen " << nfstate);
1692 // switch back from full-screen state
1693 if (lyxrc.full_screen_statusbar && !statusBar()->isVisible())
1694 statusBar()->show();
1695 if (lyxrc.full_screen_menubar && !menuBar()->isVisible())
1697 if (lyxrc.full_screen_toolbars) {
1698 for (auto const & tb_p : d.toolbars_)
1699 if (tb_p.second->isVisibilityOn() && !tb_p.second->isVisible())
1700 tb_p.second->show();
1703 for (int i = 0; i != d.splitter_->count(); ++i)
1704 d.tabWorkArea(i)->setFullScreen(false);
1705 #if QT_VERSION > 0x050903
1706 setAttribute(Qt::WA_ContentsMarginsRespectsSafeArea, true);
1708 setContentsMargins(0, 0, 0, 0);
1713 case QEvent::WindowActivate: {
1714 GuiView * old_view = guiApp->currentView();
1715 if (this == old_view) {
1717 return QMainWindow::event(e);
1719 if (old_view && old_view->currentBufferView()) {
1720 // save current selection to the selection buffer to allow
1721 // middle-button paste in this window.
1722 cap::saveSelection(old_view->currentBufferView()->cursor());
1724 guiApp->setCurrentView(this);
1725 if (d.current_work_area_)
1726 on_currentWorkAreaChanged(d.current_work_area_);
1730 return QMainWindow::event(e);
1733 case QEvent::ShortcutOverride: {
1735 if (isFullScreen() && menuBar()->isHidden()) {
1736 QKeyEvent * ke = static_cast<QKeyEvent*>(e);
1737 // FIXME: we should also try to detect special LyX shortcut such as
1738 // Alt-P and Alt-M. Right now there is a hack in
1739 // GuiWorkArea::processKeySym() that hides again the menubar for
1741 if (ke->modifiers() & Qt::AltModifier && ke->key() != Qt::Key_Alt) {
1743 return QMainWindow::event(e);
1746 return QMainWindow::event(e);
1749 case QEvent::ApplicationPaletteChange: {
1750 // runtime switch from/to dark mode
1752 return QMainWindow::event(e);
1755 case QEvent::Gesture: {
1756 QGestureEvent *ge = static_cast<QGestureEvent*>(e);
1757 QGesture *gp = ge->gesture(Qt::PinchGesture);
1759 QPinchGesture *pinch = static_cast<QPinchGesture *>(gp);
1760 QPinchGesture::ChangeFlags changeFlags = pinch->changeFlags();
1761 qreal totalScaleFactor = pinch->totalScaleFactor();
1762 LYXERR(Debug::GUI, "totalScaleFactor: " << totalScaleFactor);
1763 if (pinch->state() == Qt::GestureStarted) {
1764 initialZoom_ = lyxrc.currentZoom;
1765 LYXERR(Debug::GUI, "initialZoom_: " << initialZoom_);
1767 if (changeFlags & QPinchGesture::ScaleFactorChanged) {
1768 qreal factor = initialZoom_ * totalScaleFactor;
1769 LYXERR(Debug::GUI, "scaleFactor: " << factor);
1770 zoomValueChanged(factor);
1773 return QMainWindow::event(e);
1777 return QMainWindow::event(e);
1781 void GuiView::resetWindowTitle()
1783 setWindowTitle(qt_("LyX"));
1786 bool GuiView::focusNextPrevChild(bool /*next*/)
1793 bool GuiView::busy() const
1799 void GuiView::setBusy(bool busy)
1801 bool const busy_before = busy_ > 0;
1802 busy ? ++busy_ : --busy_;
1803 if ((busy_ > 0) == busy_before)
1804 // busy state didn't change
1808 QApplication::setOverrideCursor(Qt::WaitCursor);
1811 QApplication::restoreOverrideCursor();
1816 void GuiView::resetCommandExecute()
1818 command_execute_ = false;
1823 double GuiView::pixelRatio() const
1825 #if QT_VERSION >= 0x050000
1826 return qt_scale_factor * devicePixelRatio();
1833 GuiWorkArea * GuiView::workArea(int index)
1835 if (TabWorkArea * twa = d.currentTabWorkArea())
1836 if (index < twa->count())
1837 return twa->workArea(index);
1842 GuiWorkArea * GuiView::workArea(Buffer & buffer)
1844 if (currentWorkArea()
1845 && ¤tWorkArea()->bufferView().buffer() == &buffer)
1846 return currentWorkArea();
1847 if (TabWorkArea * twa = d.currentTabWorkArea())
1848 return twa->workArea(buffer);
1853 GuiWorkArea * GuiView::addWorkArea(Buffer & buffer)
1855 // Automatically create a TabWorkArea if there are none yet.
1856 TabWorkArea * tab_widget = d.splitter_->count()
1857 ? d.currentTabWorkArea() : addTabWorkArea();
1858 return tab_widget->addWorkArea(buffer, *this);
1862 TabWorkArea * GuiView::addTabWorkArea()
1864 TabWorkArea * twa = new TabWorkArea;
1865 QObject::connect(twa, SIGNAL(currentWorkAreaChanged(GuiWorkArea *)),
1866 this, SLOT(on_currentWorkAreaChanged(GuiWorkArea *)));
1867 QObject::connect(twa, SIGNAL(lastWorkAreaRemoved()),
1868 this, SLOT(on_lastWorkAreaRemoved()));
1870 d.splitter_->addWidget(twa);
1871 d.stack_widget_->setCurrentWidget(d.splitter_);
1876 GuiWorkArea const * GuiView::currentWorkArea() const
1878 return d.current_work_area_;
1882 GuiWorkArea * GuiView::currentWorkArea()
1884 return d.current_work_area_;
1888 GuiWorkArea const * GuiView::currentMainWorkArea() const
1890 if (!d.currentTabWorkArea())
1892 return d.currentTabWorkArea()->currentWorkArea();
1896 GuiWorkArea * GuiView::currentMainWorkArea()
1898 if (!d.currentTabWorkArea())
1900 return d.currentTabWorkArea()->currentWorkArea();
1904 void GuiView::setCurrentWorkArea(GuiWorkArea * wa)
1906 LYXERR(Debug::DEBUG, "Setting current wa: " << wa << endl);
1908 d.current_work_area_ = nullptr;
1910 Q_EMIT bufferViewChanged();
1914 // FIXME: I've no clue why this is here and why it accesses
1915 // theGuiApp()->currentView, which might be 0 (bug 6464).
1916 // See also 27525 (vfr).
1917 if (theGuiApp()->currentView() == this
1918 && theGuiApp()->currentView()->currentWorkArea() == wa)
1921 if (currentBufferView())
1922 cap::saveSelection(currentBufferView()->cursor());
1924 theGuiApp()->setCurrentView(this);
1925 d.current_work_area_ = wa;
1927 // We need to reset this now, because it will need to be
1928 // right if the tabWorkArea gets reset in the for loop. We
1929 // will change it back if we aren't in that case.
1930 GuiWorkArea * const old_cmwa = d.current_main_work_area_;
1931 d.current_main_work_area_ = wa;
1933 for (int i = 0; i != d.splitter_->count(); ++i) {
1934 if (d.tabWorkArea(i)->setCurrentWorkArea(wa)) {
1935 LYXERR(Debug::DEBUG, "Current wa: " << currentWorkArea()
1936 << ", Current main wa: " << currentMainWorkArea());
1941 d.current_main_work_area_ = old_cmwa;
1943 LYXERR(Debug::DEBUG, "This is not a tabbed wa");
1944 on_currentWorkAreaChanged(wa);
1945 BufferView & bv = wa->bufferView();
1946 bv.cursor().fixIfBroken();
1948 wa->setUpdatesEnabled(true);
1949 LYXERR(Debug::DEBUG, "Current wa: " << currentWorkArea() << ", Current main wa: " << currentMainWorkArea());
1953 void GuiView::removeWorkArea(GuiWorkArea * wa)
1955 LASSERT(wa, return);
1956 if (wa == d.current_work_area_) {
1958 disconnectBufferView();
1959 d.current_work_area_ = nullptr;
1960 d.current_main_work_area_ = nullptr;
1963 bool found_twa = false;
1964 for (int i = 0; i != d.splitter_->count(); ++i) {
1965 TabWorkArea * twa = d.tabWorkArea(i);
1966 if (twa->removeWorkArea(wa)) {
1967 // Found in this tab group, and deleted the GuiWorkArea.
1969 if (twa->count() != 0) {
1970 if (d.current_work_area_ == nullptr)
1971 // This means that we are closing the current GuiWorkArea, so
1972 // switch to the next GuiWorkArea in the found TabWorkArea.
1973 setCurrentWorkArea(twa->currentWorkArea());
1975 // No more WorkAreas in this tab group, so delete it.
1982 // It is not a tabbed work area (i.e., the search work area), so it
1983 // should be deleted by other means.
1984 LASSERT(found_twa, return);
1986 if (d.current_work_area_ == nullptr) {
1987 if (d.splitter_->count() != 0) {
1988 TabWorkArea * twa = d.currentTabWorkArea();
1989 setCurrentWorkArea(twa->currentWorkArea());
1991 // No more work areas, switch to the background widget.
1992 setCurrentWorkArea(nullptr);
1998 bool GuiView::hasVisibleWorkArea(GuiWorkArea * wa) const
2000 for (int i = 0; i < d.splitter_->count(); ++i)
2001 if (d.tabWorkArea(i)->currentWorkArea() == wa)
2004 FindAndReplace * fr = static_cast<FindAndReplace*>(find("findreplaceadv", false));
2005 return fr->isVisible() && fr->hasWorkArea(wa);
2009 LayoutBox * GuiView::getLayoutDialog() const
2015 void GuiView::updateLayoutList()
2018 d.layout_->updateContents(false);
2022 void GuiView::updateToolbars()
2024 if (d.current_work_area_) {
2026 if (d.current_work_area_->bufferView().cursor().inMathed()
2027 && !d.current_work_area_->bufferView().cursor().inRegexped())
2028 context |= Toolbars::MATH;
2029 if (lyx::getStatus(FuncRequest(LFUN_LAYOUT_TABULAR)).enabled())
2030 context |= Toolbars::TABLE;
2031 if (currentBufferView()->buffer().areChangesPresent()
2032 || (lyx::getStatus(FuncRequest(LFUN_CHANGES_TRACK)).enabled()
2033 && lyx::getStatus(FuncRequest(LFUN_CHANGES_TRACK)).onOff(true))
2034 || (lyx::getStatus(FuncRequest(LFUN_CHANGES_OUTPUT)).enabled()
2035 && lyx::getStatus(FuncRequest(LFUN_CHANGES_OUTPUT)).onOff(true)))
2036 context |= Toolbars::REVIEW;
2037 if (lyx::getStatus(FuncRequest(LFUN_IN_MATHMACROTEMPLATE)).enabled())
2038 context |= Toolbars::MATHMACROTEMPLATE;
2039 if (lyx::getStatus(FuncRequest(LFUN_IN_IPA)).enabled())
2040 context |= Toolbars::IPA;
2041 if (command_execute_)
2042 context |= Toolbars::MINIBUFFER;
2043 if (minibuffer_focus_) {
2044 context |= Toolbars::MINIBUFFER_FOCUS;
2045 minibuffer_focus_ = false;
2048 for (auto const & tb_p : d.toolbars_)
2049 tb_p.second->update(context);
2051 for (auto const & tb_p : d.toolbars_)
2052 tb_p.second->update();
2056 void GuiView::refillToolbars()
2058 DynamicMenuButton::resetIconCache();
2059 for (auto const & tb_p : d.toolbars_)
2060 tb_p.second->refill();
2064 void GuiView::setBuffer(Buffer * newBuffer, bool switch_to)
2066 LYXERR(Debug::DEBUG, "Setting buffer: " << newBuffer << endl);
2067 LASSERT(newBuffer, return);
2069 GuiWorkArea * wa = workArea(*newBuffer);
2070 if (wa == nullptr) {
2072 newBuffer->masterBuffer()->updateBuffer();
2074 wa = addWorkArea(*newBuffer);
2075 // scroll to the position when the BufferView was last closed
2076 if (lyxrc.use_lastfilepos) {
2077 LastFilePosSection::FilePos filepos =
2078 theSession().lastFilePos().load(newBuffer->fileName());
2079 wa->bufferView().moveToPosition(filepos.pit, filepos.pos, 0, 0);
2082 //Disconnect the old buffer...there's no new one.
2085 connectBuffer(*newBuffer);
2086 connectBufferView(wa->bufferView());
2088 setCurrentWorkArea(wa);
2092 void GuiView::connectBuffer(Buffer & buf)
2094 buf.setGuiDelegate(this);
2098 void GuiView::disconnectBuffer()
2100 if (d.current_work_area_)
2101 d.current_work_area_->bufferView().buffer().setGuiDelegate(nullptr);
2105 void GuiView::connectBufferView(BufferView & bv)
2107 bv.setGuiDelegate(this);
2111 void GuiView::disconnectBufferView()
2113 if (d.current_work_area_)
2114 d.current_work_area_->bufferView().setGuiDelegate(nullptr);
2118 void GuiView::errors(string const & error_type, bool from_master)
2120 BufferView const * const bv = currentBufferView();
2124 ErrorList const & el = from_master ?
2125 bv->buffer().masterBuffer()->errorList(error_type) :
2126 bv->buffer().errorList(error_type);
2131 string err = error_type;
2133 err = "from_master|" + error_type;
2134 showDialog("errorlist", err);
2138 void GuiView::updateTocItem(string const & type, DocIterator const & dit)
2140 d.toc_models_.updateItem(toqstr(type), dit);
2144 void GuiView::structureChanged()
2146 // This is called from the Buffer, which has no way to ensure that cursors
2147 // in BufferView remain valid.
2148 if (documentBufferView())
2149 documentBufferView()->cursor().sanitize();
2150 // FIXME: This is slightly expensive, though less than the tocBackend update
2151 // (#9880). This also resets the view in the Toc Widget (#6675).
2152 d.toc_models_.reset(documentBufferView());
2153 // Navigator needs more than a simple update in this case. It needs to be
2155 updateDialog("toc", "");
2159 void GuiView::updateDialog(string const & name, string const & sdata)
2161 if (!isDialogVisible(name))
2164 map<string, DialogPtr>::const_iterator it = d.dialogs_.find(name);
2165 if (it == d.dialogs_.end())
2168 Dialog * const dialog = it->second.get();
2169 if (dialog->isVisibleView())
2170 dialog->initialiseParams(sdata);
2174 BufferView * GuiView::documentBufferView()
2176 return currentMainWorkArea()
2177 ? ¤tMainWorkArea()->bufferView()
2182 BufferView const * GuiView::documentBufferView() const
2184 return currentMainWorkArea()
2185 ? ¤tMainWorkArea()->bufferView()
2190 BufferView * GuiView::currentBufferView()
2192 return d.current_work_area_ ? &d.current_work_area_->bufferView() : nullptr;
2196 BufferView const * GuiView::currentBufferView() const
2198 return d.current_work_area_ ? &d.current_work_area_->bufferView() : nullptr;
2202 docstring GuiView::GuiViewPrivate::autosaveAndDestroy(
2203 Buffer const * orig, Buffer * clone)
2205 bool const success = clone->autoSave();
2207 busyBuffers.remove(orig);
2209 ? _("Automatic save done.")
2210 : _("Automatic save failed!");
2214 void GuiView::autoSave()
2216 LYXERR(Debug::INFO, "Running autoSave()");
2218 Buffer * buffer = documentBufferView()
2219 ? &documentBufferView()->buffer() : nullptr;
2221 resetAutosaveTimers();
2225 GuiViewPrivate::busyBuffers.insert(buffer);
2226 QFuture<docstring> f = QtConcurrent::run(GuiViewPrivate::autosaveAndDestroy,
2227 buffer, buffer->cloneBufferOnly());
2228 d.autosave_watcher_.setFuture(f);
2229 resetAutosaveTimers();
2233 void GuiView::resetAutosaveTimers()
2236 d.autosave_timeout_.restart();
2242 double zoomRatio(FuncRequest const & cmd, double const zr)
2244 if (cmd.argument().empty()) {
2245 if (cmd.action() == LFUN_BUFFER_ZOOM)
2247 else if (cmd.action() == LFUN_BUFFER_ZOOM_IN)
2249 else // cmd.action() == LFUN_BUFFER_ZOOM_OUT
2252 if (cmd.action() == LFUN_BUFFER_ZOOM)
2253 return convert<int>(cmd.argument()) / double(lyxrc.defaultZoom);
2254 else if (cmd.action() == LFUN_BUFFER_ZOOM_IN)
2255 return zr + convert<int>(cmd.argument()) / 100.0;
2256 else // cmd.action() == LFUN_BUFFER_ZOOM_OUT
2257 return zr - convert<int>(cmd.argument()) / 100.0;
2264 bool GuiView::getStatus(FuncRequest const & cmd, FuncStatus & flag)
2267 Buffer * buf = currentBufferView()
2268 ? ¤tBufferView()->buffer() : nullptr;
2269 Buffer * doc_buffer = documentBufferView()
2270 ? &(documentBufferView()->buffer()) : nullptr;
2273 /* In LyX/Mac, when a dialog is open, the menus of the
2274 application can still be accessed without giving focus to
2275 the main window. In this case, we want to disable the menu
2276 entries that are buffer-related.
2277 This code must not be used on Linux and Windows, since it
2278 would disable buffer-related entries when hovering over the
2279 menu (see bug #9574).
2281 if (cmd.origin() == FuncRequest::MENU && !hasFocus()) {
2287 // Check whether we need a buffer
2288 if (!lyxaction.funcHasFlag(cmd.action(), LyXAction::NoBuffer) && !buf) {
2289 // no, exit directly
2290 flag.message(from_utf8(N_("Command not allowed with"
2291 "out any document open")));
2292 flag.setEnabled(false);
2296 if (cmd.origin() == FuncRequest::TOC) {
2297 GuiToc * toc = static_cast<GuiToc*>(findOrBuild("toc", false));
2298 if (!toc || !toc->getStatus(documentBufferView()->cursor(), cmd, flag))
2299 flag.setEnabled(false);
2303 switch(cmd.action()) {
2304 case LFUN_BUFFER_IMPORT:
2307 case LFUN_MASTER_BUFFER_EXPORT:
2309 && (doc_buffer->parent() != nullptr
2310 || doc_buffer->hasChildren())
2311 && !d.processing_thread_watcher_.isRunning()
2312 // this launches a dialog, which would be in the wrong Buffer
2313 && !(::lyx::operator==(cmd.argument(), "custom"));
2316 case LFUN_MASTER_BUFFER_UPDATE:
2317 case LFUN_MASTER_BUFFER_VIEW:
2319 && (doc_buffer->parent() != nullptr
2320 || doc_buffer->hasChildren())
2321 && !d.processing_thread_watcher_.isRunning();
2324 case LFUN_BUFFER_UPDATE:
2325 case LFUN_BUFFER_VIEW: {
2326 if (!doc_buffer || d.processing_thread_watcher_.isRunning()) {
2330 string format = to_utf8(cmd.argument());
2331 if (cmd.argument().empty())
2332 format = doc_buffer->params().getDefaultOutputFormat();
2333 enable = doc_buffer->params().isExportable(format, true);
2337 case LFUN_BUFFER_RELOAD:
2338 enable = doc_buffer && !doc_buffer->isUnnamed()
2339 && doc_buffer->fileName().exists()
2340 && (!doc_buffer->isClean() || doc_buffer->notifiesExternalModification());
2343 case LFUN_BUFFER_RESET_EXPORT:
2344 enable = doc_buffer != nullptr;
2347 case LFUN_BUFFER_CHILD_OPEN:
2348 enable = doc_buffer != nullptr;
2351 case LFUN_MASTER_BUFFER_FORALL: {
2352 if (doc_buffer == nullptr) {
2353 flag.message(from_utf8(N_("Command not allowed without a buffer open")));
2357 FuncRequest const cmdToPass = lyxaction.lookupFunc(cmd.getLongArg(0));
2358 if (cmdToPass.action() == LFUN_UNKNOWN_ACTION) {
2359 flag.message(from_utf8(N_("Invalid argument of master-buffer-forall")));
2364 for (Buffer * buf : doc_buffer->allRelatives()) {
2365 GuiWorkArea * wa = workArea(*buf);
2368 if (wa->bufferView().getStatus(cmdToPass, flag)) {
2369 enable = flag.enabled();
2376 case LFUN_BUFFER_WRITE:
2377 enable = doc_buffer && (doc_buffer->isUnnamed()
2378 || (!doc_buffer->isClean()
2379 || cmd.argument() == "force"));
2382 //FIXME: This LFUN should be moved to GuiApplication.
2383 case LFUN_BUFFER_WRITE_ALL: {
2384 // We enable the command only if there are some modified buffers
2385 Buffer * first = theBufferList().first();
2390 // We cannot use a for loop as the buffer list is a cycle.
2392 if (!b->isClean()) {
2396 b = theBufferList().next(b);
2397 } while (b != first);
2401 case LFUN_BUFFER_EXTERNAL_MODIFICATION_CLEAR:
2402 enable = doc_buffer && doc_buffer->notifiesExternalModification();
2405 case LFUN_BUFFER_EXPORT: {
2406 if (!doc_buffer || d.processing_thread_watcher_.isRunning()) {
2410 return doc_buffer->getStatus(cmd, flag);
2413 case LFUN_BUFFER_EXPORT_AS:
2414 if (!doc_buffer || d.processing_thread_watcher_.isRunning()) {
2419 case LFUN_BUFFER_WRITE_AS:
2420 case LFUN_BUFFER_WRITE_AS_TEMPLATE:
2421 enable = doc_buffer != nullptr;
2424 case LFUN_EXPORT_CANCEL:
2425 enable = d.processing_thread_watcher_.isRunning();
2428 case LFUN_BUFFER_CLOSE:
2429 case LFUN_VIEW_CLOSE:
2430 enable = doc_buffer != nullptr;
2433 case LFUN_BUFFER_CLOSE_ALL:
2434 enable = theBufferList().last() != theBufferList().first();
2437 case LFUN_BUFFER_CHKTEX: {
2438 // hide if we have no checktex command
2439 if (lyxrc.chktex_command.empty()) {
2440 flag.setUnknown(true);
2444 if (!doc_buffer || !doc_buffer->params().isLatex()
2445 || d.processing_thread_watcher_.isRunning()) {
2446 // grey out, don't hide
2454 case LFUN_VIEW_SPLIT:
2455 if (cmd.getArg(0) == "vertical")
2456 enable = doc_buffer && (d.splitter_->count() == 1 ||
2457 d.splitter_->orientation() == Qt::Vertical);
2459 enable = doc_buffer && (d.splitter_->count() == 1 ||
2460 d.splitter_->orientation() == Qt::Horizontal);
2463 case LFUN_TAB_GROUP_CLOSE:
2464 enable = d.tabWorkAreaCount() > 1;
2467 case LFUN_DEVEL_MODE_TOGGLE:
2468 flag.setOnOff(devel_mode_);
2471 case LFUN_TOOLBAR_SET: {
2472 string const name = cmd.getArg(0);
2473 string const state = cmd.getArg(1);
2474 if (name.empty() || state.empty()) {
2476 docstring const msg =
2477 _("Function toolbar-set requires two arguments!");
2481 if (state != "on" && state != "off" && state != "auto") {
2483 docstring const msg =
2484 bformat(_("Invalid argument \"%1$s\" to function toolbar-set!"),
2489 if (GuiToolbar * t = toolbar(name)) {
2490 bool const autovis = t->visibility() & Toolbars::AUTO;
2492 flag.setOnOff(t->isVisible() && !autovis);
2493 else if (state == "off")
2494 flag.setOnOff(!t->isVisible() && !autovis);
2495 else if (state == "auto")
2496 flag.setOnOff(autovis);
2499 docstring const msg =
2500 bformat(_("Unknown toolbar \"%1$s\""), from_utf8(name));
2506 case LFUN_TOOLBAR_TOGGLE: {
2507 string const name = cmd.getArg(0);
2508 if (GuiToolbar * t = toolbar(name))
2509 flag.setOnOff(t->isVisible());
2512 docstring const msg =
2513 bformat(_("Unknown toolbar \"%1$s\""), from_utf8(name));
2519 case LFUN_TOOLBAR_MOVABLE: {
2520 string const name = cmd.getArg(0);
2521 // use negation since locked == !movable
2523 // toolbar name * locks all toolbars
2524 flag.setOnOff(!toolbarsMovable_);
2525 else if (GuiToolbar * t = toolbar(name))
2526 flag.setOnOff(!(t->isMovable()));
2529 docstring const msg =
2530 bformat(_("Unknown toolbar \"%1$s\""), from_utf8(name));
2536 case LFUN_ICON_SIZE:
2537 flag.setOnOff(d.iconSize(cmd.argument()) == iconSize());
2540 case LFUN_DROP_LAYOUTS_CHOICE:
2541 enable = buf != nullptr;
2544 case LFUN_UI_TOGGLE:
2545 if (cmd.argument() == "zoomlevel") {
2546 flag.setOnOff(zoom_value_ ? zoom_value_->isVisible() : false);
2547 } else if (cmd.argument() == "zoomslider") {
2548 flag.setOnOff(zoom_slider_ ? zoom_slider_->isVisible() : false);
2549 } else if (cmd.argument() == "statistics-w") {
2550 flag.setOnOff(word_count_enabled_);
2551 } else if (cmd.argument() == "statistics-cb") {
2552 flag.setOnOff(char_count_enabled_);
2553 } else if (cmd.argument() == "statistics-c") {
2554 flag.setOnOff(char_nb_count_enabled_);
2556 flag.setOnOff(isFullScreen());
2559 case LFUN_DIALOG_DISCONNECT_INSET:
2562 case LFUN_DIALOG_HIDE:
2563 // FIXME: should we check if the dialog is shown?
2566 case LFUN_DIALOG_TOGGLE:
2567 flag.setOnOff(isDialogVisible(cmd.getArg(0)));
2570 case LFUN_DIALOG_SHOW: {
2571 string const name = cmd.getArg(0);
2573 enable = name == "aboutlyx"
2574 || name == "file" //FIXME: should be removed.
2575 || name == "lyxfiles"
2577 || name == "texinfo"
2578 || name == "progress"
2579 || name == "compare";
2580 else if (name == "character" || name == "symbols"
2581 || name == "mathdelimiter" || name == "mathmatrix") {
2582 if (!buf || buf->isReadonly())
2585 Cursor const & cur = currentBufferView()->cursor();
2586 enable = !(cur.inTexted() && cur.paragraph().isPassThru());
2589 else if (name == "latexlog")
2590 enable = FileName(doc_buffer->logName()).isReadableFile();
2591 else if (name == "spellchecker")
2592 enable = theSpellChecker()
2593 && !doc_buffer->text().empty();
2594 else if (name == "vclog")
2595 enable = doc_buffer->lyxvc().inUse();
2599 case LFUN_DIALOG_UPDATE: {
2600 string const name = cmd.getArg(0);
2602 enable = name == "prefs";
2606 case LFUN_COMMAND_EXECUTE:
2608 case LFUN_MENU_OPEN:
2609 // Nothing to check.
2612 case LFUN_COMPLETION_INLINE:
2613 if (!d.current_work_area_
2614 || !d.current_work_area_->completer().inlinePossible(
2615 currentBufferView()->cursor()))
2619 case LFUN_COMPLETION_POPUP:
2620 if (!d.current_work_area_
2621 || !d.current_work_area_->completer().popupPossible(
2622 currentBufferView()->cursor()))
2627 if (!d.current_work_area_
2628 || !d.current_work_area_->completer().inlinePossible(
2629 currentBufferView()->cursor()))
2633 case LFUN_COMPLETION_ACCEPT:
2634 if (!d.current_work_area_
2635 || (!d.current_work_area_->completer().popupVisible()
2636 && !d.current_work_area_->completer().inlineVisible()
2637 && !d.current_work_area_->completer().completionAvailable()))
2641 case LFUN_COMPLETION_CANCEL:
2642 if (!d.current_work_area_
2643 || (!d.current_work_area_->completer().popupVisible()
2644 && !d.current_work_area_->completer().inlineVisible()))
2648 case LFUN_BUFFER_ZOOM_OUT:
2649 case LFUN_BUFFER_ZOOM_IN:
2650 case LFUN_BUFFER_ZOOM: {
2651 int const zoom = (int)(lyxrc.defaultZoom * zoomRatio(cmd, zoom_ratio_));
2652 if (zoom < zoom_min_) {
2653 docstring const msg =
2654 bformat(_("Zoom level cannot be less than %1$d%."), zoom_min_);
2657 } else if (zoom > zoom_max_) {
2658 docstring const msg =
2659 bformat(_("Zoom level cannot be more than %1$d%."), zoom_max_);
2663 enable = doc_buffer;
2668 case LFUN_BUFFER_MOVE_NEXT:
2669 case LFUN_BUFFER_MOVE_PREVIOUS:
2670 // we do not cycle when moving
2671 case LFUN_BUFFER_NEXT:
2672 case LFUN_BUFFER_PREVIOUS:
2673 // because we cycle, it doesn't matter whether on first or last
2674 enable = (d.currentTabWorkArea()->count() > 1);
2676 case LFUN_BUFFER_SWITCH:
2677 // toggle on the current buffer, but do not toggle off
2678 // the other ones (is that a good idea?)
2680 && to_utf8(cmd.argument()) == doc_buffer->absFileName())
2681 flag.setOnOff(true);
2684 case LFUN_VC_REGISTER:
2685 enable = doc_buffer && !doc_buffer->lyxvc().inUse();
2687 case LFUN_VC_RENAME:
2688 enable = doc_buffer && doc_buffer->lyxvc().renameEnabled();
2691 enable = doc_buffer && doc_buffer->lyxvc().copyEnabled();
2693 case LFUN_VC_CHECK_IN:
2694 enable = doc_buffer && doc_buffer->lyxvc().checkInEnabled();
2696 case LFUN_VC_CHECK_OUT:
2697 enable = doc_buffer && doc_buffer->lyxvc().checkOutEnabled();
2699 case LFUN_VC_LOCKING_TOGGLE:
2700 enable = doc_buffer && !doc_buffer->hasReadonlyFlag()
2701 && doc_buffer->lyxvc().lockingToggleEnabled();
2702 flag.setOnOff(enable && doc_buffer->lyxvc().locking());
2704 case LFUN_VC_REVERT:
2705 enable = doc_buffer && doc_buffer->lyxvc().inUse()
2706 && !doc_buffer->hasReadonlyFlag();
2708 case LFUN_VC_UNDO_LAST:
2709 enable = doc_buffer && doc_buffer->lyxvc().undoLastEnabled();
2711 case LFUN_VC_REPO_UPDATE:
2712 enable = doc_buffer && doc_buffer->lyxvc().repoUpdateEnabled();
2714 case LFUN_VC_COMMAND: {
2715 if (cmd.argument().empty())
2717 if (!doc_buffer && contains(cmd.getArg(0), 'D'))
2721 case LFUN_VC_COMPARE:
2722 enable = doc_buffer && doc_buffer->lyxvc().prepareFileRevisionEnabled();
2725 case LFUN_SERVER_GOTO_FILE_ROW:
2726 case LFUN_LYX_ACTIVATE:
2727 case LFUN_WINDOW_RAISE:
2729 case LFUN_FORWARD_SEARCH:
2730 enable = !(lyxrc.forward_search_dvi.empty() && lyxrc.forward_search_pdf.empty()) &&
2731 doc_buffer && doc_buffer->isSyncTeXenabled();
2734 case LFUN_FILE_INSERT_PLAINTEXT:
2735 case LFUN_FILE_INSERT_PLAINTEXT_PARA:
2736 enable = documentBufferView() && documentBufferView()->cursor().inTexted();
2739 case LFUN_SPELLING_CONTINUOUSLY:
2740 flag.setOnOff(lyxrc.spellcheck_continuously);
2743 case LFUN_CITATION_OPEN:
2752 flag.setEnabled(false);
2758 static FileName selectTemplateFile()
2760 FileDialog dlg(qt_("Select template file"));
2761 dlg.setButton1(qt_("D&ocuments"), toqstr(lyxrc.document_path));
2762 dlg.setButton2(qt_("&Templates"), toqstr(lyxrc.template_path));
2764 FileDialog::Result result = dlg.open(toqstr(lyxrc.template_path),
2765 QStringList(qt_("LyX Documents (*.lyx)")));
2767 if (result.first == FileDialog::Later)
2769 if (result.second.isEmpty())
2771 return FileName(fromqstr(result.second));
2775 Buffer * GuiView::loadDocument(FileName const & filename, bool tolastfiles)
2779 Buffer * newBuffer = nullptr;
2781 newBuffer = checkAndLoadLyXFile(filename);
2782 } catch (ExceptionMessage const &) {
2789 message(_("Document not loaded."));
2793 setBuffer(newBuffer);
2794 newBuffer->errors("Parse");
2797 theSession().lastFiles().add(filename);
2798 theSession().writeFile();
2805 void GuiView::openDocument(string const & fname)
2807 string initpath = lyxrc.document_path;
2809 if (documentBufferView()) {
2810 string const trypath = documentBufferView()->buffer().filePath();
2811 // If directory is writeable, use this as default.
2812 if (FileName(trypath).isDirWritable())
2818 if (fname.empty()) {
2819 FileDialog dlg(qt_("Select document to open"));
2820 dlg.setButton1(qt_("D&ocuments"), toqstr(lyxrc.document_path));
2821 dlg.setButton2(qt_("&Examples"), toqstr(lyxrc.example_path));
2823 QStringList const filter({
2824 qt_("LyX Documents (*.lyx)"),
2825 qt_("LyX Document Backups (*.lyx~)"),
2826 qt_("All Files (*.*)")
2828 FileDialog::Result result =
2829 dlg.open(toqstr(initpath), filter);
2831 if (result.first == FileDialog::Later)
2834 filename = fromqstr(result.second);
2836 // check selected filename
2837 if (filename.empty()) {
2838 message(_("Canceled."));
2844 // get absolute path of file and add ".lyx" to the filename if
2846 FileName const fullname =
2847 fileSearch(string(), filename, "lyx", support::may_not_exist);
2848 if (!fullname.empty())
2849 filename = fullname.absFileName();
2851 if (!fullname.onlyPath().isDirectory()) {
2852 Alert::warning(_("Invalid filename"),
2853 bformat(_("The directory in the given path\n%1$s\ndoes not exist."),
2854 from_utf8(fullname.absFileName())));
2858 // if the file doesn't exist and isn't already open (bug 6645),
2859 // let the user create one
2860 if (!fullname.exists() && !theBufferList().exists(fullname) &&
2861 !LyXVC::file_not_found_hook(fullname)) {
2862 // the user specifically chose this name. Believe him.
2863 Buffer * const b = newFile(filename, string(), true);
2869 docstring const disp_fn = makeDisplayPath(filename);
2870 message(bformat(_("Opening document %1$s..."), disp_fn));
2873 Buffer * buf = loadDocument(fullname);
2875 str2 = bformat(_("Document %1$s opened."), disp_fn);
2876 if (buf->lyxvc().inUse())
2877 str2 += " " + from_utf8(buf->lyxvc().versionString()) +
2878 " " + _("Version control detected.");
2880 str2 = bformat(_("Could not open document %1$s"), disp_fn);
2885 // FIXME: clean that
2886 static bool import(GuiView * lv, FileName const & filename,
2887 string const & format, ErrorList & errorList)
2889 FileName const lyxfile(support::changeExtension(filename.absFileName(), ".lyx"));
2891 string loader_format;
2892 vector<string> loaders = theConverters().loaders();
2893 if (find(loaders.begin(), loaders.end(), format) == loaders.end()) {
2894 for (string const & loader : loaders) {
2895 if (!theConverters().isReachable(format, loader))
2898 string const tofile =
2899 support::changeExtension(filename.absFileName(),
2900 theFormats().extension(loader));
2901 if (theConverters().convert(nullptr, filename, FileName(tofile),
2902 filename, format, loader, errorList) != Converters::SUCCESS)
2904 loader_format = loader;
2907 if (loader_format.empty()) {
2908 frontend::Alert::error(_("Couldn't import file"),
2909 bformat(_("No information for importing the format %1$s."),
2910 translateIfPossible(theFormats().prettyName(format))));
2914 loader_format = format;
2916 if (loader_format == "lyx") {
2917 Buffer * buf = lv->loadDocument(lyxfile);
2921 Buffer * const b = newFile(lyxfile.absFileName(), string(), true);
2925 bool as_paragraphs = loader_format == "textparagraph";
2926 string filename2 = (loader_format == format) ? filename.absFileName()
2927 : support::changeExtension(filename.absFileName(),
2928 theFormats().extension(loader_format));
2929 lv->currentBufferView()->insertPlaintextFile(FileName(filename2),
2931 guiApp->setCurrentView(lv);
2932 lyx::dispatch(FuncRequest(LFUN_MARK_OFF));
2939 void GuiView::importDocument(string const & argument)
2942 string filename = split(argument, format, ' ');
2944 LYXERR(Debug::INFO, format << " file: " << filename);
2946 // need user interaction
2947 if (filename.empty()) {
2948 string initpath = lyxrc.document_path;
2949 if (documentBufferView()) {
2950 string const trypath = documentBufferView()->buffer().filePath();
2951 // If directory is writeable, use this as default.
2952 if (FileName(trypath).isDirWritable())
2956 docstring const text = bformat(_("Select %1$s file to import"),
2957 translateIfPossible(theFormats().prettyName(format)));
2959 FileDialog dlg(toqstr(text));
2960 dlg.setButton1(qt_("D&ocuments"), toqstr(lyxrc.document_path));
2961 dlg.setButton2(qt_("&Examples"), toqstr(lyxrc.example_path));
2963 docstring filter = translateIfPossible(theFormats().prettyName(format));
2966 filter += from_utf8(theFormats().extensions(format));
2969 FileDialog::Result result =
2970 dlg.open(toqstr(initpath), fileFilters(toqstr(filter)));
2972 if (result.first == FileDialog::Later)
2975 filename = fromqstr(result.second);
2977 // check selected filename
2978 if (filename.empty())
2979 message(_("Canceled."));
2982 if (filename.empty())
2985 // get absolute path of file
2986 FileName const fullname(support::makeAbsPath(filename));
2988 // Can happen if the user entered a path into the dialog
2990 if (fullname.onlyFileName().empty()) {
2991 docstring msg = bformat(_("The file name '%1$s' is invalid!\n"
2992 "Aborting import."),
2993 from_utf8(fullname.absFileName()));
2994 frontend::Alert::error(_("File name error"), msg);
2995 message(_("Canceled."));
3000 FileName const lyxfile(support::changeExtension(fullname.absFileName(), ".lyx"));
3002 // Check if the document already is open
3003 Buffer * buf = theBufferList().getBuffer(lyxfile);
3006 if (!closeBuffer()) {
3007 message(_("Canceled."));
3012 docstring const displaypath = makeDisplayPath(lyxfile.absFileName(), 30);
3014 // if the file exists already, and we didn't do
3015 // -i lyx thefile.lyx, warn
3016 if (lyxfile.exists() && fullname != lyxfile) {
3018 docstring text = bformat(_("The document %1$s already exists.\n\n"
3019 "Do you want to overwrite that document?"), displaypath);
3020 int const ret = Alert::prompt(_("Overwrite document?"),
3021 text, 0, 1, _("&Overwrite"), _("&Cancel"));
3024 message(_("Canceled."));
3029 message(bformat(_("Importing %1$s..."), displaypath));
3030 ErrorList errorList;
3031 if (import(this, fullname, format, errorList))
3032 message(_("imported."));
3034 message(_("file not imported!"));
3036 // FIXME (Abdel 12/08/06): Is there a need to display the error list here?
3040 void GuiView::newDocument(string const & filename, string templatefile,
3043 FileName initpath(lyxrc.document_path);
3044 if (documentBufferView()) {
3045 FileName const trypath(documentBufferView()->buffer().filePath());
3046 // If directory is writeable, use this as default.
3047 if (trypath.isDirWritable())
3051 if (from_template) {
3052 if (templatefile.empty())
3053 templatefile = selectTemplateFile().absFileName();
3054 if (templatefile.empty())
3059 if (filename.empty())
3060 b = newUnnamedFile(initpath, to_utf8(_("newfile")), templatefile);
3062 b = newFile(filename, templatefile, true);
3067 // If no new document could be created, it is unsure
3068 // whether there is a valid BufferView.
3069 if (currentBufferView())
3070 // Ensure the cursor is correctly positioned on screen.
3071 currentBufferView()->showCursor();
3075 bool GuiView::insertLyXFile(docstring const & fname, bool ignorelang)
3077 BufferView * bv = documentBufferView();
3082 FileName filename(to_utf8(fname));
3083 if (filename.empty()) {
3084 // Launch a file browser
3086 string initpath = lyxrc.document_path;
3087 string const trypath = bv->buffer().filePath();
3088 // If directory is writeable, use this as default.
3089 if (FileName(trypath).isDirWritable())
3093 FileDialog dlg(qt_("Select LyX document to insert"));
3094 dlg.setButton1(qt_("D&ocuments"), toqstr(lyxrc.document_path));
3095 dlg.setButton2(qt_("&Examples"), toqstr(lyxrc.example_path));
3097 FileDialog::Result result = dlg.open(toqstr(initpath),
3098 QStringList(qt_("LyX Documents (*.lyx)")));
3100 if (result.first == FileDialog::Later)
3104 filename.set(fromqstr(result.second));
3106 // check selected filename
3107 if (filename.empty()) {
3108 // emit message signal.
3109 message(_("Canceled."));
3114 bv->insertLyXFile(filename, ignorelang);
3115 bv->buffer().errors("Parse");
3120 string const GuiView::getTemplatesPath(Buffer & b)
3122 // We start off with the user's templates path
3123 string result = addPath(package().user_support().absFileName(), "templates");
3124 // Check for the document language
3125 string const langcode = b.params().language->code();
3126 string const shortcode = langcode.substr(0, 2);
3127 if (!langcode.empty() && shortcode != "en") {
3128 string subpath = addPath(result, shortcode);
3129 string subpath_long = addPath(result, langcode);
3130 // If we have a subdirectory for the language already,
3132 FileName sp = FileName(subpath);
3133 if (sp.isDirectory())
3135 else if (FileName(subpath_long).isDirectory())
3136 result = subpath_long;
3138 // Ask whether we should create such a subdirectory
3139 docstring const text =
3140 bformat(_("It is suggested to save the template in a subdirectory\n"
3141 "appropriate to the document language (%1$s).\n"
3142 "This subdirectory does not exists yet.\n"
3143 "Do you want to create it?"),
3144 _(b.params().language->display()));
3145 if (Alert::prompt(_("Create Language Directory?"),
3146 text, 0, 1, _("&Yes, Create"), _("&No, Save Template in Parent Directory")) == 0) {
3147 // If the user agreed, we try to create it and report if this failed.
3148 if (!sp.createDirectory(0777))
3149 Alert::error(_("Subdirectory creation failed!"),
3150 _("Could not create subdirectory.\n"
3151 "The template will be saved in the parent directory."));
3157 // Do we have a layout category?
3158 string const cat = b.params().baseClass() ?
3159 b.params().baseClass()->category()
3162 string subpath = addPath(result, cat);
3163 // If we have a subdirectory for the category already,
3165 FileName sp = FileName(subpath);
3166 if (sp.isDirectory())
3169 // Ask whether we should create such a subdirectory
3170 docstring const text =
3171 bformat(_("It is suggested to save the template in a subdirectory\n"
3172 "appropriate to the layout category (%1$s).\n"
3173 "This subdirectory does not exists yet.\n"
3174 "Do you want to create it?"),
3176 if (Alert::prompt(_("Create Category Directory?"),
3177 text, 0, 1, _("&Yes, Create"), _("&No, Save Template in Parent Directory")) == 0) {
3178 // If the user agreed, we try to create it and report if this failed.
3179 if (!sp.createDirectory(0777))
3180 Alert::error(_("Subdirectory creation failed!"),
3181 _("Could not create subdirectory.\n"
3182 "The template will be saved in the parent directory."));
3192 bool GuiView::renameBuffer(Buffer & b, docstring const & newname, RenameKind kind)
3194 FileName fname = b.fileName();
3195 FileName const oldname = fname;
3196 bool const as_template = (kind == LV_WRITE_AS_TEMPLATE);
3198 if (!newname.empty()) {
3201 fname = support::makeAbsPath(to_utf8(newname), getTemplatesPath(b));
3203 fname = support::makeAbsPath(to_utf8(newname),
3204 oldname.onlyPath().absFileName());
3206 // Switch to this Buffer.
3209 // No argument? Ask user through dialog.
3211 QString const title = as_template ? qt_("Choose a filename to save template as")
3212 : qt_("Choose a filename to save document as");
3213 FileDialog dlg(title);
3214 dlg.setButton1(qt_("D&ocuments"), toqstr(lyxrc.document_path));
3215 dlg.setButton2(qt_("&Templates"), toqstr(lyxrc.template_path));
3217 fname.ensureExtension(".lyx");
3219 string const path = as_template ?
3221 : fname.onlyPath().absFileName();
3222 FileDialog::Result result =
3223 dlg.save(toqstr(path),
3224 QStringList(qt_("LyX Documents (*.lyx)")),
3225 toqstr(fname.onlyFileName()));
3227 if (result.first == FileDialog::Later)
3230 fname.set(fromqstr(result.second));
3235 fname.ensureExtension(".lyx");
3238 // fname is now the new Buffer location.
3240 // if there is already a Buffer open with this name, we do not want
3241 // to have another one. (the second test makes sure we're not just
3242 // trying to overwrite ourselves, which is fine.)
3243 if (theBufferList().exists(fname) && fname != oldname
3244 && theBufferList().getBuffer(fname) != &b) {
3245 docstring const text =
3246 bformat(_("The file\n%1$s\nis already open in your current session.\n"
3247 "Please close it before attempting to overwrite it.\n"
3248 "Do you want to choose a new filename?"),
3249 from_utf8(fname.absFileName()));
3250 int const ret = Alert::prompt(_("Chosen File Already Open"),
3251 text, 0, 1, _("&Rename"), _("&Cancel"));
3253 case 0: return renameBuffer(b, docstring(), kind);
3254 case 1: return false;
3259 bool const existsLocal = fname.exists();
3260 bool const existsInVC = LyXVC::fileInVC(fname);
3261 if (existsLocal || existsInVC) {
3262 docstring const file = makeDisplayPath(fname.absFileName(), 30);
3263 if (kind != LV_WRITE_AS && existsInVC) {
3264 // renaming to a name that is already in VC
3266 docstring text = bformat(_("The document %1$s "
3267 "is already registered.\n\n"
3268 "Do you want to choose a new name?"),
3270 docstring const title = (kind == LV_VC_RENAME) ?
3271 _("Rename document?") : _("Copy document?");
3272 docstring const button = (kind == LV_VC_RENAME) ?
3273 _("&Rename") : _("&Copy");
3274 int const ret = Alert::prompt(title, text, 0, 1,
3275 button, _("&Cancel"));
3277 case 0: return renameBuffer(b, docstring(), kind);
3278 case 1: return false;
3283 docstring text = bformat(_("The document %1$s "
3284 "already exists.\n\n"
3285 "Do you want to overwrite that document?"),
3287 int const ret = Alert::prompt(_("Overwrite document?"),
3288 text, 0, 2, _("&Overwrite"),
3289 _("&Rename"), _("&Cancel"));
3292 case 1: return renameBuffer(b, docstring(), kind);
3293 case 2: return false;
3299 case LV_VC_RENAME: {
3300 string msg = b.lyxvc().rename(fname);
3303 message(from_utf8(msg));
3307 string msg = b.lyxvc().copy(fname);
3310 message(from_utf8(msg));
3314 case LV_WRITE_AS_TEMPLATE:
3317 // LyXVC created the file already in case of LV_VC_RENAME or
3318 // LV_VC_COPY, but call saveBuffer() nevertheless to get
3319 // relative paths of included stuff right if we moved e.g. from
3320 // /a/b.lyx to /a/c/b.lyx.
3322 bool const saved = saveBuffer(b, fname);
3329 bool GuiView::exportBufferAs(Buffer & b, docstring const & iformat)
3331 FileName fname = b.fileName();
3333 FileDialog dlg(qt_("Choose a filename to export the document as"));
3334 dlg.setButton1(qt_("D&ocuments"), toqstr(lyxrc.document_path));
3337 QString const anyformat = qt_("Guess from extension (*.*)");
3340 vector<Format const *> export_formats;
3341 for (Format const & f : theFormats())
3342 if (f.documentFormat())
3343 export_formats.push_back(&f);
3344 sort(export_formats.begin(), export_formats.end(), Format::formatSorter);
3345 map<QString, string> fmap;
3348 for (Format const * f : export_formats) {
3349 docstring const loc_prettyname = translateIfPossible(f->prettyname());
3350 QString const loc_filter = toqstr(bformat(from_ascii("%1$s (*.%2$s)"),
3352 from_ascii(f->extension())));
3353 types << loc_filter;
3354 fmap[loc_filter] = f->name();
3355 if (from_ascii(f->name()) == iformat) {
3356 filter = loc_filter;
3357 ext = f->extension();
3360 string ofname = fname.onlyFileName();
3362 ofname = support::changeExtension(ofname, ext);
3363 FileDialog::Result result =
3364 dlg.save(toqstr(fname.onlyPath().absFileName()),
3368 if (result.first != FileDialog::Chosen)
3372 fname.set(fromqstr(result.second));
3373 if (filter == anyformat)
3374 fmt_name = theFormats().getFormatFromExtension(fname.extension());
3376 fmt_name = fmap[filter];
3377 LYXERR(Debug::FILES, "filter=" << fromqstr(filter)
3378 << ", fmt_name=" << fmt_name << ", fname=" << fname.absFileName());
3380 if (fmt_name.empty() || fname.empty())
3383 fname.ensureExtension(theFormats().extension(fmt_name));
3385 // fname is now the new Buffer location.
3386 if (fname.exists()) {
3387 docstring const file = makeDisplayPath(fname.absFileName(), 30);
3388 docstring text = bformat(_("The document %1$s already "
3389 "exists.\n\nDo you want to "
3390 "overwrite that document?"),
3392 int const ret = Alert::prompt(_("Overwrite document?"),
3393 text, 0, 2, _("&Overwrite"), _("&Rename"), _("&Cancel"));
3396 case 1: return exportBufferAs(b, from_ascii(fmt_name));
3397 case 2: return false;
3401 FuncRequest cmd(LFUN_BUFFER_EXPORT, fmt_name + " " + fname.absFileName());
3404 return dr.dispatched();
3408 bool GuiView::saveBuffer(Buffer & b)
3410 return saveBuffer(b, FileName());
3414 bool GuiView::saveBuffer(Buffer & b, FileName const & fn)
3416 if (workArea(b) && workArea(b)->inDialogMode())
3419 if (fn.empty() && b.isUnnamed())
3420 return renameBuffer(b, docstring());
3422 bool const success = (fn.empty() ? b.save() : b.saveAs(fn));
3424 theSession().lastFiles().add(b.fileName());
3425 theSession().writeFile();
3429 // Switch to this Buffer.
3432 // FIXME: we don't tell the user *WHY* the save failed !!
3433 docstring const file = makeDisplayPath(b.absFileName(), 30);
3434 docstring text = bformat(_("The document %1$s could not be saved.\n\n"
3435 "Do you want to rename the document and "
3436 "try again?"), file);
3437 int const ret = Alert::prompt(_("Rename and save?"),
3438 text, 0, 2, _("&Rename"), _("&Retry"), _("&Cancel"));
3441 if (!renameBuffer(b, docstring()))
3450 return saveBuffer(b, fn);
3454 bool GuiView::hideWorkArea(GuiWorkArea * wa)
3456 return closeWorkArea(wa, false);
3460 // We only want to close the buffer if it is not visible in other workareas
3461 // of the same view, nor in other views, and if this is not a child
3462 bool GuiView::closeWorkArea(GuiWorkArea * wa)
3464 Buffer & buf = wa->bufferView().buffer();
3466 bool last_wa = d.countWorkAreasOf(buf) == 1
3467 && !inOtherView(buf) && !buf.parent();
3469 bool close_buffer = last_wa;
3472 if (lyxrc.close_buffer_with_last_view == "yes")
3474 else if (lyxrc.close_buffer_with_last_view == "no")
3475 close_buffer = false;
3478 if (buf.isUnnamed())
3479 file = from_utf8(buf.fileName().onlyFileName());
3481 file = buf.fileName().displayName(30);
3482 docstring const text = bformat(
3483 _("Last view on document %1$s is being closed.\n"
3484 "Would you like to close or hide the document?\n"
3486 "Hidden documents can be displayed back through\n"
3487 "the menu: View->Hidden->...\n"
3489 "To remove this question, set your preference in:\n"
3490 " Tools->Preferences->Look&Feel->UserInterface\n"
3492 int ret = Alert::prompt(_("Close or hide document?"),
3493 text, 0, 2, _("&Close"), _("&Hide"), _("&Cancel"));
3496 close_buffer = (ret == 0);
3500 return closeWorkArea(wa, close_buffer);
3504 bool GuiView::closeBuffer()
3506 GuiWorkArea * wa = currentMainWorkArea();
3507 // coverity complained about this
3508 // it seems unnecessary, but perhaps is worth the check
3509 LASSERT(wa, return false);
3511 setCurrentWorkArea(wa);
3512 Buffer & buf = wa->bufferView().buffer();
3513 return closeWorkArea(wa, !buf.parent());
3517 void GuiView::writeSession() const {
3518 GuiWorkArea const * active_wa = currentMainWorkArea();
3519 for (int i = 0; i < d.splitter_->count(); ++i) {
3520 TabWorkArea * twa = d.tabWorkArea(i);
3521 for (int j = 0; j < twa->count(); ++j) {
3522 GuiWorkArea * wa = twa->workArea(j);
3523 Buffer & buf = wa->bufferView().buffer();
3524 theSession().lastOpened().add(buf.fileName(), wa == active_wa);
3530 bool GuiView::closeBufferAll()
3533 for (auto & buf : theBufferList()) {
3534 if (!saveBufferIfNeeded(*buf, false)) {
3535 // Closing has been cancelled, so abort.
3540 // Close the workareas in all other views
3541 QList<int> const ids = guiApp->viewIds();
3542 for (int i = 0; i != ids.size(); ++i) {
3543 if (id_ != ids[i] && !guiApp->view(ids[i]).closeWorkAreaAll())
3547 // Close our own workareas
3548 if (!closeWorkAreaAll())
3555 bool GuiView::closeWorkAreaAll()
3557 setCurrentWorkArea(currentMainWorkArea());
3559 // We might be in a situation that there is still a tabWorkArea, but
3560 // there are no tabs anymore. This can happen when we get here after a
3561 // TabWorkArea::lastWorkAreaRemoved() signal. Therefore we count how
3562 // many TabWorkArea's have no documents anymore.
3565 // We have to call count() each time, because it can happen that
3566 // more than one splitter will disappear in one iteration (bug 5998).
3567 while (d.splitter_->count() > empty_twa) {
3568 TabWorkArea * twa = d.tabWorkArea(empty_twa);
3570 if (twa->count() == 0)
3573 setCurrentWorkArea(twa->currentWorkArea());
3574 if (!closeTabWorkArea(twa))
3582 bool GuiView::closeWorkArea(GuiWorkArea * wa, bool close_buffer)
3587 Buffer & buf = wa->bufferView().buffer();
3589 if (GuiViewPrivate::busyBuffers.contains(&buf)) {
3590 Alert::warning(_("Close document"),
3591 _("Document could not be closed because it is being processed by LyX."));
3596 return closeBuffer(buf);
3598 if (!inMultiTabs(wa))
3599 if (!saveBufferIfNeeded(buf, true))
3607 bool GuiView::closeBuffer(Buffer & buf)
3609 bool success = true;
3610 for (Buffer * child_buf : buf.getChildren()) {
3611 if (theBufferList().isOthersChild(&buf, child_buf)) {
3612 child_buf->setParent(nullptr);
3616 // FIXME: should we look in other tabworkareas?
3617 // ANSWER: I don't think so. I've tested, and if the child is
3618 // open in some other window, it closes without a problem.
3619 GuiWorkArea * child_wa = workArea(*child_buf);
3622 // If we are in a close_event all children will be closed in some time,
3623 // so no need to do it here. This will ensure that the children end up
3624 // in the session file in the correct order. If we close the master
3625 // buffer, we can close or release the child buffers here too.
3627 success = closeWorkArea(child_wa, true);
3631 // In this case the child buffer is open but hidden.
3632 // Even in this case, children can be dirty (e.g.,
3633 // after a label change in the master, see #11405).
3634 // Therefore, check this
3635 if (closing_ && (child_buf->isClean() || child_buf->paragraphs().empty())) {
3636 // If we are in a close_event all children will be closed in some time,
3637 // so no need to do it here. This will ensure that the children end up
3638 // in the session file in the correct order. If we close the master
3639 // buffer, we can close or release the child buffers here too.
3642 // Save dirty buffers also if closing_!
3643 if (saveBufferIfNeeded(*child_buf, false)) {
3644 child_buf->removeAutosaveFile();
3645 theBufferList().release(child_buf);
3647 // Saving of dirty children has been cancelled.
3648 // Cancel the whole process.
3655 // goto bookmark to update bookmark pit.
3656 // FIXME: we should update only the bookmarks related to this buffer!
3657 // FIXME: this is done also in LFUN_WINDOW_CLOSE!
3658 LYXERR(Debug::DEBUG, "GuiView::closeBuffer()");
3659 for (unsigned int i = 1; i < theSession().bookmarks().size(); ++i)
3660 guiApp->gotoBookmark(i, false, false);
3662 if (saveBufferIfNeeded(buf, false)) {
3663 buf.removeAutosaveFile();
3664 theBufferList().release(&buf);
3668 // open all children again to avoid a crash because of dangling
3669 // pointers (bug 6603)
3675 bool GuiView::closeTabWorkArea(TabWorkArea * twa)
3677 while (twa == d.currentTabWorkArea()) {
3678 twa->setCurrentIndex(twa->count() - 1);
3680 GuiWorkArea * wa = twa->currentWorkArea();
3681 Buffer & b = wa->bufferView().buffer();
3683 // We only want to close the buffer if the same buffer is not visible
3684 // in another view, and if this is not a child and if we are closing
3685 // a view (not a tabgroup).
3686 bool const close_buffer =
3687 !inOtherView(b) && !b.parent() && closing_;
3689 if (!closeWorkArea(wa, close_buffer))
3696 bool GuiView::saveBufferIfNeeded(Buffer & buf, bool hiding)
3698 if (buf.isClean() || buf.paragraphs().empty())
3701 // Switch to this Buffer.
3707 if (buf.isUnnamed()) {
3708 file = from_utf8(buf.fileName().onlyFileName());
3711 FileName filename = buf.fileName();
3713 file = filename.displayName(30);
3714 exists = filename.exists();
3717 // Bring this window to top before asking questions.
3722 if (hiding && buf.isUnnamed()) {
3723 docstring const text = bformat(_("The document %1$s has not been "
3724 "saved yet.\n\nDo you want to save "
3725 "the document?"), file);
3726 ret = Alert::prompt(_("Save new document?"),
3727 text, 0, 1, _("&Save"), _("&Cancel"));
3731 docstring const text = exists ?
3732 bformat(_("The document %1$s has unsaved changes."
3733 "\n\nDo you want to save the document or "
3734 "discard the changes?"), file) :
3735 bformat(_("The document %1$s has not been saved yet."
3736 "\n\nDo you want to save the document or "
3737 "discard it entirely?"), file);
3738 docstring const title = exists ?
3739 _("Save changed document?") : _("Save document?");
3740 ret = Alert::prompt(title, text, 0, 2,
3741 _("&Save"), _("&Discard"), _("&Cancel"));
3746 if (!saveBuffer(buf))
3750 // If we crash after this we could have no autosave file
3751 // but I guess this is really improbable (Jug).
3752 // Sometimes improbable things happen:
3753 // - see bug http://www.lyx.org/trac/ticket/6587 (ps)
3754 // buf.removeAutosaveFile();
3756 // revert all changes
3767 bool GuiView::inMultiTabs(GuiWorkArea * wa)
3769 Buffer & buf = wa->bufferView().buffer();
3771 for (int i = 0; i != d.splitter_->count(); ++i) {
3772 GuiWorkArea * wa_ = d.tabWorkArea(i)->workArea(buf);
3773 if (wa_ && wa_ != wa)
3776 return inOtherView(buf);
3780 bool GuiView::inOtherView(Buffer & buf)
3782 QList<int> const ids = guiApp->viewIds();
3784 for (int i = 0; i != ids.size(); ++i) {
3788 if (guiApp->view(ids[i]).workArea(buf))
3795 void GuiView::gotoNextOrPreviousBuffer(NextOrPrevious np, bool const move)
3797 if (!documentBufferView())
3800 if (TabWorkArea * twa = d.currentTabWorkArea()) {
3801 Buffer * const curbuf = &documentBufferView()->buffer();
3802 int nwa = twa->count();
3803 for (int i = 0; i < nwa; ++i) {
3804 if (&workArea(i)->bufferView().buffer() == curbuf) {
3806 if (np == NEXTBUFFER)
3807 next_index = (i == nwa - 1 ? 0 : i + 1);
3809 next_index = (i == 0 ? nwa - 1 : i - 1);
3811 twa->moveTab(i, next_index);
3813 setBuffer(&workArea(next_index)->bufferView().buffer());
3821 /// make sure the document is saved
3822 static bool ensureBufferClean(Buffer * buffer)
3824 LASSERT(buffer, return false);
3825 if (buffer->isClean() && !buffer->isUnnamed())
3828 docstring const file = buffer->fileName().displayName(30);
3831 if (!buffer->isUnnamed()) {
3832 text = bformat(_("The document %1$s has unsaved "
3833 "changes.\n\nDo you want to save "
3834 "the document?"), file);
3835 title = _("Save changed document?");
3838 text = bformat(_("The document %1$s has not been "
3839 "saved yet.\n\nDo you want to save "
3840 "the document?"), file);
3841 title = _("Save new document?");
3843 int const ret = Alert::prompt(title, text, 0, 1, _("&Save"), _("&Cancel"));
3846 dispatch(FuncRequest(LFUN_BUFFER_WRITE));
3848 return buffer->isClean() && !buffer->isUnnamed();
3852 bool GuiView::reloadBuffer(Buffer & buf)
3854 currentBufferView()->cursor().reset();
3855 Buffer::ReadStatus status = buf.reload();
3856 return status == Buffer::ReadSuccess;
3860 void GuiView::checkExternallyModifiedBuffers()
3862 for (Buffer * buf : theBufferList()) {
3863 if (buf->fileName().exists() && buf->isChecksumModified()) {
3864 docstring text = bformat(_("Document \n%1$s\n has been externally modified."
3865 " Reload now? Any local changes will be lost."),
3866 from_utf8(buf->absFileName()));
3867 int const ret = Alert::prompt(_("Reload externally changed document?"),
3868 text, 0, 1, _("&Reload"), _("&Cancel"));
3876 void GuiView::dispatchVC(FuncRequest const & cmd, DispatchResult & dr)
3878 Buffer * buffer = documentBufferView()
3879 ? &(documentBufferView()->buffer()) : nullptr;
3881 switch (cmd.action()) {
3882 case LFUN_VC_REGISTER:
3883 if (!buffer || !ensureBufferClean(buffer))
3885 if (!buffer->lyxvc().inUse()) {
3886 if (buffer->lyxvc().registrer()) {
3887 reloadBuffer(*buffer);
3888 dr.clearMessageUpdate();
3893 case LFUN_VC_RENAME:
3894 case LFUN_VC_COPY: {
3895 if (!buffer || !ensureBufferClean(buffer))
3897 if (buffer->lyxvc().inUse() && !buffer->hasReadonlyFlag()) {
3898 if (buffer->lyxvc().isCheckInWithConfirmation()) {
3899 // Some changes are not yet committed.
3900 // We test here and not in getStatus(), since
3901 // this test is expensive.
3903 LyXVC::CommandResult ret =
3904 buffer->lyxvc().checkIn(log);
3906 if (ret == LyXVC::ErrorCommand ||
3907 ret == LyXVC::VCSuccess)
3908 reloadBuffer(*buffer);
3909 if (buffer->lyxvc().isCheckInWithConfirmation()) {
3910 frontend::Alert::error(
3911 _("Revision control error."),
3912 _("Document could not be checked in."));
3916 RenameKind const kind = (cmd.action() == LFUN_VC_RENAME) ?
3917 LV_VC_RENAME : LV_VC_COPY;
3918 renameBuffer(*buffer, cmd.argument(), kind);
3923 case LFUN_VC_CHECK_IN:
3924 if (!buffer || !ensureBufferClean(buffer))
3926 if (buffer->lyxvc().inUse() && !buffer->hasReadonlyFlag()) {
3928 LyXVC::CommandResult ret = buffer->lyxvc().checkIn(log);
3930 // Only skip reloading if the checkin was cancelled or
3931 // an error occurred before the real checkin VCS command
3932 // was executed, since the VCS might have changed the
3933 // file even if it could not checkin successfully.
3934 if (ret == LyXVC::ErrorCommand || ret == LyXVC::VCSuccess)
3935 reloadBuffer(*buffer);
3939 case LFUN_VC_CHECK_OUT:
3940 if (!buffer || !ensureBufferClean(buffer))
3942 if (buffer->lyxvc().inUse()) {
3943 dr.setMessage(buffer->lyxvc().checkOut());
3944 reloadBuffer(*buffer);
3948 case LFUN_VC_LOCKING_TOGGLE:
3949 if (!buffer || !ensureBufferClean(buffer) || buffer->hasReadonlyFlag())
3951 if (buffer->lyxvc().inUse()) {
3952 string res = buffer->lyxvc().lockingToggle();
3954 frontend::Alert::error(_("Revision control error."),
3955 _("Error when setting the locking property."));
3958 reloadBuffer(*buffer);
3963 case LFUN_VC_REVERT:
3966 if (buffer->lyxvc().revert()) {
3967 reloadBuffer(*buffer);
3968 dr.clearMessageUpdate();
3972 case LFUN_VC_UNDO_LAST:
3975 buffer->lyxvc().undoLast();
3976 reloadBuffer(*buffer);
3977 dr.clearMessageUpdate();
3980 case LFUN_VC_REPO_UPDATE:
3983 if (ensureBufferClean(buffer)) {
3984 dr.setMessage(buffer->lyxvc().repoUpdate());
3985 checkExternallyModifiedBuffers();
3989 case LFUN_VC_COMMAND: {
3990 string flag = cmd.getArg(0);
3991 if (buffer && contains(flag, 'R') && !ensureBufferClean(buffer))
3994 if (contains(flag, 'M')) {
3995 if (!Alert::askForText(message, _("LyX VC: Log Message")))
3998 string path = cmd.getArg(1);
3999 if (contains(path, "$$p") && buffer)
4000 path = subst(path, "$$p", buffer->filePath());
4001 LYXERR(Debug::LYXVC, "Directory: " << path);
4003 if (!pp.isReadableDirectory()) {
4004 lyxerr << _("Directory is not accessible.") << endl;
4007 support::PathChanger p(pp);
4009 string command = cmd.getArg(2);
4010 if (command.empty())
4013 command = subst(command, "$$i", buffer->absFileName());
4014 command = subst(command, "$$p", buffer->filePath());
4016 command = subst(command, "$$m", to_utf8(message));
4017 LYXERR(Debug::LYXVC, "Command: " << command);
4019 one.startscript(Systemcall::Wait, command);
4023 if (contains(flag, 'I'))
4024 buffer->markDirty();
4025 if (contains(flag, 'R'))
4026 reloadBuffer(*buffer);
4031 case LFUN_VC_COMPARE: {
4032 if (cmd.argument().empty()) {
4033 lyx::dispatch(FuncRequest(LFUN_DIALOG_SHOW, "comparehistory"));
4039 string rev1 = cmd.getArg(0);
4043 if (!buffer->lyxvc().prepareFileRevision(rev1, f1))
4046 if (isStrInt(rev1) && convert<int>(rev1) <= 0) {
4047 f2 = buffer->absFileName();
4049 string rev2 = cmd.getArg(1);
4053 if (!buffer->lyxvc().prepareFileRevision(rev2, f2))
4057 LYXERR(Debug::LYXVC, "Launching comparison for fetched revisions:\n" <<
4058 f1 << "\n" << f2 << "\n" );
4059 string par = "compare run " + quoteName(f1) + " " + quoteName(f2);
4060 lyx::dispatch(FuncRequest(LFUN_DIALOG_SHOW, par));
4070 void GuiView::openChildDocument(string const & fname)
4072 LASSERT(documentBufferView(), return);
4073 Buffer & buffer = documentBufferView()->buffer();
4074 FileName const filename = support::makeAbsPath(fname, buffer.filePath());
4075 documentBufferView()->saveBookmark(false);
4076 Buffer * child = nullptr;
4077 if (theBufferList().exists(filename)) {
4078 child = theBufferList().getBuffer(filename);
4081 message(bformat(_("Opening child document %1$s..."),
4082 makeDisplayPath(filename.absFileName())));
4083 child = loadDocument(filename, false);
4085 // Set the parent name of the child document.
4086 // This makes insertion of citations and references in the child work,
4087 // when the target is in the parent or another child document.
4089 child->setParent(&buffer);
4093 bool GuiView::goToFileRow(string const & argument)
4097 size_t i = argument.find_last_of(' ');
4098 if (i != string::npos) {
4099 file_name = os::internal_path(FileName(trim(argument.substr(0, i))).realPath());
4100 istringstream is(argument.substr(i + 1));
4105 if (i == string::npos) {
4106 LYXERR0("Wrong argument: " << argument);
4109 Buffer * buf = nullptr;
4110 string const realtmp = package().temp_dir().realPath();
4111 // We have to use os::path_prefix_is() here, instead of
4112 // simply prefixIs(), because the file name comes from
4113 // an external application and may need case adjustment.
4114 if (os::path_prefix_is(file_name, realtmp, os::CASE_ADJUSTED)) {
4115 buf = theBufferList().getBufferFromTmp(file_name, true);
4116 LYXERR(Debug::FILES, "goToFileRow: buffer lookup for " << file_name
4117 << (buf ? " success" : " failed"));
4119 // Must replace extension of the file to be .lyx
4120 // and get full path
4121 FileName const s = fileSearch(string(),
4122 support::changeExtension(file_name, ".lyx"), "lyx");
4123 // Either change buffer or load the file
4124 if (theBufferList().exists(s))
4125 buf = theBufferList().getBuffer(s);
4126 else if (s.exists()) {
4127 buf = loadDocument(s);
4132 _("File does not exist: %1$s"),
4133 makeDisplayPath(file_name)));
4139 _("No buffer for file: %1$s."),
4140 makeDisplayPath(file_name))
4145 bool success = documentBufferView()->setCursorFromRow(row);
4147 LYXERR(Debug::OUTFILE,
4148 "setCursorFromRow: invalid position for row " << row);
4149 frontend::Alert::error(_("Inverse Search Failed"),
4150 _("Invalid position requested by inverse search.\n"
4151 "You may need to update the viewed document."));
4157 void GuiView::toolBarPopup(const QPoint & /*pos*/)
4159 QMenu * menu = guiApp->menus().menu(toqstr("context-toolbars"), * this);
4160 menu->exec(QCursor::pos());
4165 Buffer::ExportStatus GuiView::GuiViewPrivate::runAndDestroy(const T& func,
4166 Buffer const * orig, Buffer * clone, string const & format)
4168 Buffer::ExportStatus const status = func(format);
4170 // the cloning operation will have produced a clone of the entire set of
4171 // documents, starting from the master. so we must delete those.
4172 Buffer * mbuf = const_cast<Buffer *>(clone->masterBuffer());
4174 busyBuffers.remove(orig);
4179 Buffer::ExportStatus GuiView::GuiViewPrivate::compileAndDestroy(
4180 Buffer const * orig, Buffer * clone, string const & format)
4182 Buffer::ExportStatus (Buffer::* mem_func)(std::string const &, bool) const =
4184 return runAndDestroy(lyx::bind(mem_func, clone, _1, true), orig, clone, format);
4188 Buffer::ExportStatus GuiView::GuiViewPrivate::exportAndDestroy(
4189 Buffer const * orig, Buffer * clone, string const & format)
4191 Buffer::ExportStatus (Buffer::* mem_func)(std::string const &, bool) const =
4193 return runAndDestroy(lyx::bind(mem_func, clone, _1, false), orig, clone, format);
4197 Buffer::ExportStatus GuiView::GuiViewPrivate::previewAndDestroy(
4198 Buffer const * orig, Buffer * clone, string const & format)
4200 Buffer::ExportStatus (Buffer::* mem_func)(std::string const &) const =
4202 return runAndDestroy(lyx::bind(mem_func, clone, _1), orig, clone, format);
4206 bool GuiView::GuiViewPrivate::asyncBufferProcessing(string const & argument,
4207 Buffer const * used_buffer,
4208 docstring const & msg,
4209 Buffer::ExportStatus (*asyncFunc)(Buffer const *, Buffer *, string const &),
4210 Buffer::ExportStatus (Buffer::*syncFunc)(string const &, bool) const,
4211 Buffer::ExportStatus (Buffer::*previewFunc)(string const &) const,
4212 bool allow_async, bool use_tmpdir)
4217 string format = argument;
4219 format = used_buffer->params().getDefaultOutputFormat();
4220 processing_format = format;
4222 progress_->clearMessages();
4225 #if EXPORT_in_THREAD
4227 GuiViewPrivate::busyBuffers.insert(used_buffer);
4228 Buffer * cloned_buffer = used_buffer->cloneWithChildren();
4229 if (!cloned_buffer) {
4230 Alert::error(_("Export Error"),
4231 _("Error cloning the Buffer."));
4234 QFuture<Buffer::ExportStatus> f = QtConcurrent::run(
4239 setPreviewFuture(f);
4240 last_export_format = used_buffer->params().bufferFormat();
4243 // We are asynchronous, so we don't know here anything about the success
4246 Buffer::ExportStatus status;
4248 status = (used_buffer->*syncFunc)(format, use_tmpdir);
4249 } else if (previewFunc) {
4250 status = (used_buffer->*previewFunc)(format);
4253 handleExportStatus(gv_, status, format);
4255 return (status == Buffer::ExportSuccess
4256 || status == Buffer::PreviewSuccess);
4260 Buffer::ExportStatus status;
4262 status = (used_buffer->*syncFunc)(format, true);
4263 } else if (previewFunc) {
4264 status = (used_buffer->*previewFunc)(format);
4267 handleExportStatus(gv_, status, format);
4269 return (status == Buffer::ExportSuccess
4270 || status == Buffer::PreviewSuccess);
4274 void GuiView::dispatchToBufferView(FuncRequest const & cmd, DispatchResult & dr)
4276 BufferView * bv = currentBufferView();
4277 LASSERT(bv, return);
4279 // Let the current BufferView dispatch its own actions.
4280 bv->dispatch(cmd, dr);
4281 if (dr.dispatched()) {
4282 if (cmd.action() == LFUN_REDO || cmd.action() == LFUN_UNDO)
4283 updateDialog("document", "");
4287 // Try with the document BufferView dispatch if any.
4288 BufferView * doc_bv = documentBufferView();
4289 if (doc_bv && doc_bv != bv) {
4290 doc_bv->dispatch(cmd, dr);
4291 if (dr.dispatched()) {
4292 if (cmd.action() == LFUN_REDO || cmd.action() == LFUN_UNDO)
4293 updateDialog("document", "");
4298 // Then let the current Cursor dispatch its own actions.
4299 bv->cursor().dispatch(cmd);
4301 // update completion. We do it here and not in
4302 // processKeySym to avoid another redraw just for a
4303 // changed inline completion
4304 if (cmd.origin() == FuncRequest::KEYBOARD) {
4305 if (cmd.action() == LFUN_SELF_INSERT
4306 || (cmd.action() == LFUN_ERT_INSERT && bv->cursor().inMathed()))
4307 updateCompletion(bv->cursor(), true, true);
4308 else if (cmd.action() == LFUN_CHAR_DELETE_BACKWARD)
4309 updateCompletion(bv->cursor(), false, true);
4311 updateCompletion(bv->cursor(), false, false);
4314 dr = bv->cursor().result();
4318 void GuiView::dispatch(FuncRequest const & cmd, DispatchResult & dr)
4320 BufferView * bv = currentBufferView();
4321 // By default we won't need any update.
4322 dr.screenUpdate(Update::None);
4323 // assume cmd will be dispatched
4324 dr.dispatched(true);
4326 Buffer * doc_buffer = documentBufferView()
4327 ? &(documentBufferView()->buffer()) : nullptr;
4329 if (cmd.origin() == FuncRequest::TOC) {
4330 GuiToc * toc = static_cast<GuiToc*>(findOrBuild("toc", false));
4331 toc->doDispatch(bv->cursor(), cmd, dr);
4335 string const argument = to_utf8(cmd.argument());
4337 switch(cmd.action()) {
4338 case LFUN_BUFFER_CHILD_OPEN:
4339 openChildDocument(to_utf8(cmd.argument()));
4342 case LFUN_BUFFER_IMPORT:
4343 importDocument(to_utf8(cmd.argument()));
4346 case LFUN_MASTER_BUFFER_EXPORT:
4348 doc_buffer = const_cast<Buffer *>(doc_buffer->masterBuffer());
4350 case LFUN_BUFFER_EXPORT: {
4353 // GCC only sees strfwd.h when building merged
4354 if (::lyx::operator==(cmd.argument(), "custom")) {
4355 // LFUN_MASTER_BUFFER_EXPORT is not enabled for this case,
4356 // so the following test should not be needed.
4357 // In principle, we could try to switch to such a view...
4358 // if (cmd.action() == LFUN_BUFFER_EXPORT)
4359 dispatch(FuncRequest(LFUN_DIALOG_SHOW, "sendto"), dr);
4363 string const dest = cmd.getArg(1);
4364 FileName target_dir;
4365 if (!dest.empty() && FileName::isAbsolute(dest))
4366 target_dir = FileName(support::onlyPath(dest));
4368 target_dir = doc_buffer->fileName().onlyPath();
4370 string const format = (argument.empty() || argument == "default") ?
4371 doc_buffer->params().getDefaultOutputFormat() : argument;
4373 if ((dest.empty() && doc_buffer->isUnnamed())
4374 || !target_dir.isDirWritable()) {
4375 exportBufferAs(*doc_buffer, from_utf8(format));
4378 /* TODO/Review: Is it a problem to also export the children?
4379 See the update_unincluded flag */
4380 d.asyncBufferProcessing(format,
4383 &GuiViewPrivate::exportAndDestroy,
4385 nullptr, cmd.allowAsync());
4386 // TODO Inform user about success
4390 case LFUN_BUFFER_EXPORT_AS: {
4391 LASSERT(doc_buffer, break);
4392 docstring f = cmd.argument();
4394 f = from_ascii(doc_buffer->params().getDefaultOutputFormat());
4395 exportBufferAs(*doc_buffer, f);
4399 case LFUN_BUFFER_UPDATE: {
4400 d.asyncBufferProcessing(argument,
4403 &GuiViewPrivate::compileAndDestroy,
4405 nullptr, cmd.allowAsync(), true);
4408 case LFUN_BUFFER_VIEW: {
4409 d.asyncBufferProcessing(argument,
4411 _("Previewing ..."),
4412 &GuiViewPrivate::previewAndDestroy,
4414 &Buffer::preview, cmd.allowAsync());
4417 case LFUN_MASTER_BUFFER_UPDATE: {
4418 d.asyncBufferProcessing(argument,
4419 (doc_buffer ? doc_buffer->masterBuffer() : nullptr),
4421 &GuiViewPrivate::compileAndDestroy,
4423 nullptr, cmd.allowAsync(), true);
4426 case LFUN_MASTER_BUFFER_VIEW: {
4427 d.asyncBufferProcessing(argument,
4428 (doc_buffer ? doc_buffer->masterBuffer() : nullptr),
4430 &GuiViewPrivate::previewAndDestroy,
4431 nullptr, &Buffer::preview, cmd.allowAsync());
4434 case LFUN_EXPORT_CANCEL: {
4435 Systemcall::killscript();
4438 case LFUN_BUFFER_SWITCH: {
4439 string const file_name = to_utf8(cmd.argument());
4440 if (!FileName::isAbsolute(file_name)) {
4442 dr.setMessage(_("Absolute filename expected."));
4446 Buffer * buffer = theBufferList().getBuffer(FileName(file_name));
4449 dr.setMessage(_("Document not loaded"));
4453 // Do we open or switch to the buffer in this view ?
4454 if (workArea(*buffer)
4455 || lyxrc.open_buffers_in_tabs || !documentBufferView()) {
4460 // Look for the buffer in other views
4461 QList<int> const ids = guiApp->viewIds();
4463 for (; i != ids.size(); ++i) {
4464 GuiView & gv = guiApp->view(ids[i]);
4465 if (gv.workArea(*buffer)) {
4467 gv.activateWindow();
4469 gv.setBuffer(buffer);
4474 // If necessary, open a new window as a last resort
4475 if (i == ids.size()) {
4476 lyx::dispatch(FuncRequest(LFUN_WINDOW_NEW));
4482 case LFUN_BUFFER_NEXT:
4483 gotoNextOrPreviousBuffer(NEXTBUFFER, false);
4486 case LFUN_BUFFER_MOVE_NEXT:
4487 gotoNextOrPreviousBuffer(NEXTBUFFER, true);
4490 case LFUN_BUFFER_PREVIOUS:
4491 gotoNextOrPreviousBuffer(PREVBUFFER, false);
4494 case LFUN_BUFFER_MOVE_PREVIOUS:
4495 gotoNextOrPreviousBuffer(PREVBUFFER, true);
4498 case LFUN_BUFFER_CHKTEX:
4499 LASSERT(doc_buffer, break);
4500 doc_buffer->runChktex();
4503 case LFUN_COMMAND_EXECUTE: {
4504 command_execute_ = true;
4505 minibuffer_focus_ = true;
4508 case LFUN_DROP_LAYOUTS_CHOICE:
4509 d.layout_->showPopup();
4512 case LFUN_MENU_OPEN:
4513 if (QMenu * menu = guiApp->menus().menu(toqstr(cmd.argument()), *this))
4514 menu->exec(QCursor::pos());
4517 case LFUN_FILE_INSERT: {
4518 bool const ignore_lang = cmd.getArg(1) == "ignorelang";
4519 if (insertLyXFile(from_utf8(cmd.getArg(0)), ignore_lang)) {
4520 dr.forceBufferUpdate();
4521 dr.screenUpdate(Update::Force);
4526 case LFUN_FILE_INSERT_PLAINTEXT:
4527 case LFUN_FILE_INSERT_PLAINTEXT_PARA: {
4528 string const fname = to_utf8(cmd.argument());
4529 if (!fname.empty() && !FileName::isAbsolute(fname)) {
4530 dr.setMessage(_("Absolute filename expected."));
4534 FileName filename(fname);
4535 if (fname.empty()) {
4536 FileDialog dlg(qt_("Select file to insert"));
4538 FileDialog::Result result = dlg.open(toqstr(bv->buffer().filePath()),
4539 QStringList(qt_("All Files (*)")));
4541 if (result.first == FileDialog::Later || result.second.isEmpty()) {
4542 dr.setMessage(_("Canceled."));
4546 filename.set(fromqstr(result.second));
4550 FuncRequest const new_cmd(cmd, filename.absoluteFilePath());
4551 bv->dispatch(new_cmd, dr);
4556 case LFUN_BUFFER_RELOAD: {
4557 LASSERT(doc_buffer, break);
4560 bool drop = (cmd.argument() == "dump");
4563 if (!drop && !doc_buffer->isClean()) {
4564 docstring const file =
4565 makeDisplayPath(doc_buffer->absFileName(), 20);
4566 if (doc_buffer->notifiesExternalModification()) {
4567 docstring text = _("The current version will be lost. "
4568 "Are you sure you want to load the version on disk "
4569 "of the document %1$s?");
4570 ret = Alert::prompt(_("Reload saved document?"),
4571 bformat(text, file), 1, 1,
4572 _("&Reload"), _("&Cancel"));
4574 docstring text = _("Any changes will be lost. "
4575 "Are you sure you want to revert to the saved version "
4576 "of the document %1$s?");
4577 ret = Alert::prompt(_("Revert to saved document?"),
4578 bformat(text, file), 1, 1,
4579 _("&Revert"), _("&Cancel"));
4584 doc_buffer->markClean();
4585 reloadBuffer(*doc_buffer);
4586 dr.forceBufferUpdate();
4591 case LFUN_BUFFER_RESET_EXPORT:
4592 LASSERT(doc_buffer, break);
4593 doc_buffer->requireFreshStart(true);
4594 dr.setMessage(_("Buffer export reset."));
4597 case LFUN_BUFFER_WRITE:
4598 LASSERT(doc_buffer, break);
4599 saveBuffer(*doc_buffer);
4602 case LFUN_BUFFER_WRITE_AS:
4603 LASSERT(doc_buffer, break);
4604 renameBuffer(*doc_buffer, cmd.argument());
4607 case LFUN_BUFFER_WRITE_AS_TEMPLATE:
4608 LASSERT(doc_buffer, break);
4609 renameBuffer(*doc_buffer, cmd.argument(),
4610 LV_WRITE_AS_TEMPLATE);
4613 case LFUN_BUFFER_WRITE_ALL: {
4614 Buffer * first = theBufferList().first();
4617 message(_("Saving all documents..."));
4618 // We cannot use a for loop as the buffer list cycles.
4621 if (!b->isClean()) {
4623 LYXERR(Debug::ACTION, "Saved " << b->absFileName());
4625 b = theBufferList().next(b);
4626 } while (b != first);
4627 dr.setMessage(_("All documents saved."));
4631 case LFUN_MASTER_BUFFER_FORALL: {
4635 FuncRequest funcToRun = lyxaction.lookupFunc(cmd.getLongArg(0));
4636 funcToRun.allowAsync(false);
4638 for (Buffer const * buf : doc_buffer->allRelatives()) {
4639 // Switch to other buffer view and resend cmd
4640 lyx::dispatch(FuncRequest(
4641 LFUN_BUFFER_SWITCH, buf->absFileName()));
4642 lyx::dispatch(funcToRun);
4645 lyx::dispatch(FuncRequest(
4646 LFUN_BUFFER_SWITCH, doc_buffer->absFileName()));
4650 case LFUN_BUFFER_EXTERNAL_MODIFICATION_CLEAR:
4651 LASSERT(doc_buffer, break);
4652 doc_buffer->clearExternalModification();
4655 case LFUN_BUFFER_CLOSE:
4659 case LFUN_BUFFER_CLOSE_ALL:
4663 case LFUN_DEVEL_MODE_TOGGLE:
4664 devel_mode_ = !devel_mode_;
4666 dr.setMessage(_("Developer mode is now enabled."));
4668 dr.setMessage(_("Developer mode is now disabled."));
4671 case LFUN_TOOLBAR_SET: {
4672 string const name = cmd.getArg(0);
4673 string const state = cmd.getArg(1);
4674 if (GuiToolbar * t = toolbar(name))
4679 case LFUN_TOOLBAR_TOGGLE: {
4680 string const name = cmd.getArg(0);
4681 if (GuiToolbar * t = toolbar(name))
4686 case LFUN_TOOLBAR_MOVABLE: {
4687 string const name = cmd.getArg(0);
4689 // toggle (all) toolbars movablility
4690 toolbarsMovable_ = !toolbarsMovable_;
4691 for (ToolbarInfo const & ti : guiApp->toolbars()) {
4692 GuiToolbar * tb = toolbar(ti.name);
4693 if (tb && tb->isMovable() != toolbarsMovable_)
4694 // toggle toolbar movablity if it does not fit lock
4695 // (all) toolbars positions state silent = true, since
4696 // status bar notifications are slow
4699 if (toolbarsMovable_)
4700 dr.setMessage(_("Toolbars unlocked."));
4702 dr.setMessage(_("Toolbars locked."));
4703 } else if (GuiToolbar * tb = toolbar(name))
4704 // toggle current toolbar movablity
4706 // update lock (all) toolbars positions
4707 updateLockToolbars();
4711 case LFUN_ICON_SIZE: {
4712 QSize size = d.iconSize(cmd.argument());
4714 dr.setMessage(bformat(_("Icon size set to %1$dx%2$d."),
4715 size.width(), size.height()));
4719 case LFUN_DIALOG_UPDATE: {
4720 string const name = to_utf8(cmd.argument());
4721 if (name == "prefs" || name == "document")
4722 updateDialog(name, string());
4723 else if (name == "paragraph")
4724 lyx::dispatch(FuncRequest(LFUN_PARAGRAPH_UPDATE));
4725 else if (currentBufferView()) {
4726 Inset * inset = currentBufferView()->editedInset(name);
4727 // Can only update a dialog connected to an existing inset
4729 // FIXME: get rid of this indirection; GuiView ask the inset
4730 // if he is kind enough to update itself...
4731 FuncRequest fr(LFUN_INSET_DIALOG_UPDATE, cmd.argument());
4732 //FIXME: pass DispatchResult here?
4733 inset->dispatch(currentBufferView()->cursor(), fr);
4739 case LFUN_DIALOG_TOGGLE: {
4740 FuncCode const func_code = isDialogVisible(cmd.getArg(0))
4741 ? LFUN_DIALOG_HIDE : LFUN_DIALOG_SHOW;
4742 dispatch(FuncRequest(func_code, cmd.argument()), dr);
4746 case LFUN_DIALOG_DISCONNECT_INSET:
4747 disconnectDialog(to_utf8(cmd.argument()));
4750 case LFUN_DIALOG_HIDE: {
4751 guiApp->hideDialogs(to_utf8(cmd.argument()), nullptr);
4755 case LFUN_DIALOG_SHOW: {
4756 string const name = cmd.getArg(0);
4757 string sdata = trim(to_utf8(cmd.argument()).substr(name.size()));
4759 if (name == "latexlog") {
4760 // getStatus checks that
4761 LASSERT(doc_buffer, break);
4762 Buffer::LogType type;
4763 string const logfile = doc_buffer->logName(&type);
4765 case Buffer::latexlog:
4768 case Buffer::buildlog:
4769 sdata = "literate ";
4772 sdata += Lexer::quoteString(logfile);
4773 showDialog("log", sdata);
4774 } else if (name == "vclog") {
4775 // getStatus checks that
4776 LASSERT(doc_buffer, break);
4777 string const sdata2 = "vc " +
4778 Lexer::quoteString(doc_buffer->lyxvc().getLogFile());
4779 showDialog("log", sdata2);
4780 } else if (name == "symbols") {
4781 sdata = bv->cursor().getEncoding()->name();
4783 showDialog("symbols", sdata);
4784 } else if (name == "findreplace") {
4785 sdata = to_utf8(bv->cursor().selectionAsString(false));
4786 showDialog(name, sdata);
4788 } else if (name == "prefs" && isFullScreen()) {
4789 lfunUiToggle("fullscreen");
4790 showDialog("prefs", sdata);
4792 showDialog(name, sdata);
4797 dr.setMessage(cmd.argument());
4800 case LFUN_UI_TOGGLE: {
4801 string arg = cmd.getArg(0);
4802 if (!lfunUiToggle(arg)) {
4803 docstring const msg = "ui-toggle " + _("%1$s unknown command!");
4804 dr.setMessage(bformat(msg, from_utf8(arg)));
4806 // Make sure the keyboard focus stays in the work area.
4811 case LFUN_VIEW_SPLIT: {
4812 LASSERT(doc_buffer, break);
4813 string const orientation = cmd.getArg(0);
4814 d.splitter_->setOrientation(orientation == "vertical"
4815 ? Qt::Vertical : Qt::Horizontal);
4816 TabWorkArea * twa = addTabWorkArea();
4817 GuiWorkArea * wa = twa->addWorkArea(*doc_buffer, *this);
4818 setCurrentWorkArea(wa);
4821 case LFUN_TAB_GROUP_CLOSE:
4822 if (TabWorkArea * twa = d.currentTabWorkArea()) {
4823 closeTabWorkArea(twa);
4824 d.current_work_area_ = nullptr;
4825 twa = d.currentTabWorkArea();
4826 // Switch to the next GuiWorkArea in the found TabWorkArea.
4828 // Make sure the work area is up to date.
4829 setCurrentWorkArea(twa->currentWorkArea());
4831 setCurrentWorkArea(nullptr);
4836 case LFUN_VIEW_CLOSE:
4837 if (TabWorkArea * twa = d.currentTabWorkArea()) {
4838 closeWorkArea(twa->currentWorkArea());
4839 d.current_work_area_ = nullptr;
4840 twa = d.currentTabWorkArea();
4841 // Switch to the next GuiWorkArea in the found TabWorkArea.
4843 // Make sure the work area is up to date.
4844 setCurrentWorkArea(twa->currentWorkArea());
4846 setCurrentWorkArea(nullptr);
4851 case LFUN_COMPLETION_INLINE:
4852 if (d.current_work_area_)
4853 d.current_work_area_->completer().showInline();
4856 case LFUN_COMPLETION_POPUP:
4857 if (d.current_work_area_)
4858 d.current_work_area_->completer().showPopup();
4863 if (d.current_work_area_)
4864 d.current_work_area_->completer().tab();
4867 case LFUN_COMPLETION_CANCEL:
4868 if (d.current_work_area_) {
4869 if (d.current_work_area_->completer().popupVisible())
4870 d.current_work_area_->completer().hidePopup();
4872 d.current_work_area_->completer().hideInline();
4876 case LFUN_COMPLETION_ACCEPT:
4877 if (d.current_work_area_)
4878 d.current_work_area_->completer().activate();
4881 case LFUN_BUFFER_ZOOM_IN:
4882 case LFUN_BUFFER_ZOOM_OUT:
4883 case LFUN_BUFFER_ZOOM: {
4884 zoom_ratio_ = zoomRatio(cmd, zoom_ratio_);
4886 // Actual zoom value: default zoom + fractional extra value
4887 int zoom = (int)(lyxrc.defaultZoom * zoom_ratio_);
4888 zoom = min(max(zoom, zoom_min_), zoom_max_);
4890 setCurrentZoom(zoom);
4892 dr.setMessage(bformat(_("Zoom level is now %1$d% (default value: %2$d%)"),
4893 lyxrc.currentZoom, lyxrc.defaultZoom));
4895 guiApp->fontLoader().update();
4896 // Regenerate instant previews
4897 if (lyxrc.preview != LyXRC::PREVIEW_OFF
4898 && doc_buffer && doc_buffer->loader())
4899 doc_buffer->loader()->refreshPreviews();
4900 dr.screenUpdate(Update::ForceAll | Update::FitCursor);
4904 case LFUN_VC_REGISTER:
4905 case LFUN_VC_RENAME:
4907 case LFUN_VC_CHECK_IN:
4908 case LFUN_VC_CHECK_OUT:
4909 case LFUN_VC_REPO_UPDATE:
4910 case LFUN_VC_LOCKING_TOGGLE:
4911 case LFUN_VC_REVERT:
4912 case LFUN_VC_UNDO_LAST:
4913 case LFUN_VC_COMMAND:
4914 case LFUN_VC_COMPARE:
4915 dispatchVC(cmd, dr);
4918 case LFUN_SERVER_GOTO_FILE_ROW:
4919 if(goToFileRow(to_utf8(cmd.argument())))
4920 dr.screenUpdate(Update::Force | Update::FitCursor);
4923 case LFUN_LYX_ACTIVATE:
4927 case LFUN_WINDOW_RAISE:
4933 case LFUN_FORWARD_SEARCH: {
4934 // it seems safe to assume we have a document buffer, since
4935 // getStatus wants one.
4936 LASSERT(doc_buffer, break);
4937 Buffer const * doc_master = doc_buffer->masterBuffer();
4938 FileName const path(doc_master->temppath());
4939 string const texname = doc_master->isChild(doc_buffer)
4940 ? DocFileName(changeExtension(
4941 doc_buffer->absFileName(),
4942 "tex")).mangledFileName()
4943 : doc_buffer->latexName();
4944 string const fulltexname =
4945 support::makeAbsPath(texname, doc_master->temppath()).absFileName();
4946 string const mastername =
4947 removeExtension(doc_master->latexName());
4948 FileName const dviname(addName(path.absFileName(),
4949 addExtension(mastername, "dvi")));
4950 FileName const pdfname(addName(path.absFileName(),
4951 addExtension(mastername, "pdf")));
4952 bool const have_dvi = dviname.exists();
4953 bool const have_pdf = pdfname.exists();
4954 if (!have_dvi && !have_pdf) {
4955 dr.setMessage(_("Please, preview the document first."));
4958 bool const goto_dvi = have_dvi && !lyxrc.forward_search_dvi.empty();
4959 bool const goto_pdf = have_pdf && !lyxrc.forward_search_pdf.empty();
4960 string outname = dviname.onlyFileName();
4961 string command = lyxrc.forward_search_dvi;
4962 if ((!goto_dvi || goto_pdf) &&
4963 pdfname.lastModified() > dviname.lastModified()) {
4964 outname = pdfname.onlyFileName();
4965 command = lyxrc.forward_search_pdf;
4968 DocIterator cur = bv->cursor();
4969 int row = doc_buffer->texrow().rowFromDocIterator(cur).first;
4970 LYXERR(Debug::ACTION, "Forward search: row:" << row
4972 if (row == -1 || command.empty()) {
4973 dr.setMessage(_("Couldn't proceed."));
4976 string texrow = convert<string>(row);
4978 command = subst(command, "$$n", texrow);
4979 command = subst(command, "$$f", fulltexname);
4980 command = subst(command, "$$t", texname);
4981 command = subst(command, "$$o", outname);
4983 volatile PathChanger p(path);
4985 one.startscript(Systemcall::DontWait, command);
4989 case LFUN_SPELLING_CONTINUOUSLY:
4990 lyxrc.spellcheck_continuously = !lyxrc.spellcheck_continuously;
4991 dr.screenUpdate(Update::Force);
4994 case LFUN_CITATION_OPEN: {
4996 if (theFormats().getFormat("pdf"))
4997 pdfv = theFormats().getFormat("pdf")->viewer();
4998 if (theFormats().getFormat("ps"))
4999 psv = theFormats().getFormat("ps")->viewer();
5000 frontend::showTarget(argument, pdfv, psv);
5005 // The LFUN must be for one of BufferView, Buffer or Cursor;
5007 dispatchToBufferView(cmd, dr);
5011 // Need to update bv because many LFUNs here might have destroyed it
5012 bv = currentBufferView();
5014 // Clear non-empty selections
5015 // (e.g. from a "char-forward-select" followed by "char-backward-select")
5017 Cursor & cur = bv->cursor();
5018 if ((cur.selection() && cur.selBegin() == cur.selEnd())) {
5019 cur.clearSelection();
5025 bool GuiView::lfunUiToggle(string const & ui_component)
5027 if (ui_component == "scrollbar") {
5028 // hide() is of no help
5029 if (d.current_work_area_->verticalScrollBarPolicy() ==
5030 Qt::ScrollBarAlwaysOff)
5032 d.current_work_area_->setVerticalScrollBarPolicy(
5033 Qt::ScrollBarAsNeeded);
5035 d.current_work_area_->setVerticalScrollBarPolicy(
5036 Qt::ScrollBarAlwaysOff);
5037 } else if (ui_component == "statusbar") {
5038 statusBar()->setVisible(!statusBar()->isVisible());
5039 } else if (ui_component == "menubar") {
5040 menuBar()->setVisible(!menuBar()->isVisible());
5041 } else if (ui_component == "zoomlevel") {
5042 zoom_value_->setVisible(!zoom_value_->isVisible());
5043 } else if (ui_component == "zoomslider") {
5044 zoom_slider_->setVisible(!zoom_slider_->isVisible());
5045 zoom_in_->setVisible(zoom_slider_->isVisible());
5046 zoom_out_->setVisible(zoom_slider_->isVisible());
5047 } else if (ui_component == "statistics-w") {
5048 word_count_enabled_ = !word_count_enabled_;
5051 } else if (ui_component == "statistics-cb") {
5052 char_count_enabled_ = !char_count_enabled_;
5055 } else if (ui_component == "statistics-c") {
5056 char_nb_count_enabled_ = !char_nb_count_enabled_;
5059 } else if (ui_component == "frame") {
5060 int const l = contentsMargins().left();
5062 //are the frames in default state?
5063 d.current_work_area_->setFrameStyle(QFrame::NoFrame);
5065 #if QT_VERSION > 0x050903
5066 setAttribute(Qt::WA_ContentsMarginsRespectsSafeArea, false);
5068 setContentsMargins(-2, -2, -2, -2);
5070 #if QT_VERSION > 0x050903
5071 setAttribute(Qt::WA_ContentsMarginsRespectsSafeArea, true);
5073 setContentsMargins(0, 0, 0, 0);
5076 if (ui_component == "fullscreen") {
5080 stat_counts_->setVisible(statsEnabled());
5085 void GuiView::toggleFullScreen()
5087 setWindowState(windowState() ^ Qt::WindowFullScreen);
5091 Buffer const * GuiView::updateInset(Inset const * inset)
5096 Buffer const * inset_buffer = &(inset->buffer());
5098 for (int i = 0; i != d.splitter_->count(); ++i) {
5099 GuiWorkArea * wa = d.tabWorkArea(i)->currentWorkArea();
5102 Buffer const * buffer = &(wa->bufferView().buffer());
5103 if (inset_buffer == buffer)
5104 wa->scheduleRedraw(true);
5106 return inset_buffer;
5110 void GuiView::restartCaret()
5112 /* When we move around, or type, it's nice to be able to see
5113 * the caret immediately after the keypress.
5115 if (d.current_work_area_)
5116 d.current_work_area_->startBlinkingCaret();
5118 // Take this occasion to update the other GUI elements.
5124 void GuiView::updateCompletion(Cursor & cur, bool start, bool keep)
5126 if (d.current_work_area_)
5127 d.current_work_area_->completer().updateVisibility(cur, start, keep);
5132 // This list should be kept in sync with the list of insets in
5133 // src/insets/Inset.cpp. I.e., if a dialog goes with an inset, the
5134 // dialog should have the same name as the inset.
5135 // Changes should be also recorded in LFUN_DIALOG_SHOW doxygen
5136 // docs in LyXAction.cpp.
5138 char const * const dialognames[] = {
5140 "aboutlyx", "bibitem", "bibtex", "box", "branch", "changes", "character",
5141 "citation", "compare", "comparehistory", "counter", "document", "errorlist", "ert",
5142 "external", "file", "findreplace", "findreplaceadv", "float", "graphics",
5143 "href", "include", "index", "index_print", "info", "listings", "label", "line",
5144 "log", "lyxfiles", "mathdelimiter", "mathmatrix", "mathspace", "nomenclature",
5145 "nomencl_print", "note", "paragraph", "phantom", "prefs", "ref",
5146 "sendto", "space", "spellchecker", "symbols", "tabular", "tabularcreate",
5147 "thesaurus", "texinfo", "toc", "view-source", "vspace", "wrap", "progress"};
5149 char const * const * const end_dialognames =
5150 dialognames + (sizeof(dialognames) / sizeof(char *));
5154 cmpCStr(char const * name) : name_(name) {}
5155 bool operator()(char const * other) {
5156 return strcmp(other, name_) == 0;
5163 bool isValidName(string const & name)
5165 return find_if(dialognames, end_dialognames,
5166 cmpCStr(name.c_str())) != end_dialognames;
5172 void GuiView::resetDialogs()
5174 // Make sure that no LFUN uses any GuiView.
5175 guiApp->setCurrentView(nullptr);
5179 constructToolbars();
5180 guiApp->menus().fillMenuBar(menuBar(), this, false);
5181 d.layout_->updateContents(true);
5182 // Now update controls with current buffer.
5183 guiApp->setCurrentView(this);
5189 void GuiView::flatGroupBoxes(const QObject * widget, bool flag)
5191 for (QObject * child: widget->children()) {
5192 if (child->inherits("QGroupBox")) {
5193 QGroupBox * box = (QGroupBox*) child;
5196 flatGroupBoxes(child, flag);
5202 Dialog * GuiView::find(string const & name, bool hide_it) const
5204 if (!isValidName(name))
5207 map<string, DialogPtr>::iterator it = d.dialogs_.find(name);
5209 if (it != d.dialogs_.end()) {
5211 it->second->hideView();
5212 return it->second.get();
5218 Dialog * GuiView::findOrBuild(string const & name, bool hide_it)
5220 Dialog * dialog = find(name, hide_it);
5221 if (dialog != nullptr)
5224 dialog = build(name);
5225 d.dialogs_[name].reset(dialog);
5226 #if (QT_VERSION >= QT_VERSION_CHECK(5, 0, 0))
5227 // Force a uniform style for group boxes
5228 // On Mac non-flat works better, on Linux flat is standard
5229 flatGroupBoxes(dialog->asQWidget(), guiApp->platformName() != "cocoa");
5231 if (lyxrc.allow_geometry_session)
5232 dialog->restoreSession();
5239 void GuiView::showDialog(string const & name, string const & sdata,
5242 triggerShowDialog(toqstr(name), toqstr(sdata), inset);
5246 void GuiView::doShowDialog(QString const & qname, QString const & qdata,
5252 const string name = fromqstr(qname);
5253 const string sdata = fromqstr(qdata);
5257 Dialog * dialog = findOrBuild(name, false);
5259 bool const visible = dialog->isVisibleView();
5260 dialog->showData(sdata);
5261 if (currentBufferView())
5262 currentBufferView()->editInset(name, inset);
5263 // We only set the focus to the new dialog if it was not yet
5264 // visible in order not to change the existing previous behaviour
5266 // activateWindow is needed for floating dockviews
5267 dialog->asQWidget()->raise();
5268 dialog->asQWidget()->activateWindow();
5269 if (dialog->wantInitialFocus())
5270 dialog->asQWidget()->setFocus();
5274 catch (ExceptionMessage const &) {
5282 bool GuiView::isDialogVisible(string const & name) const
5284 map<string, DialogPtr>::const_iterator it = d.dialogs_.find(name);
5285 if (it == d.dialogs_.end())
5287 return it->second.get()->isVisibleView() && !it->second.get()->isClosing();
5291 void GuiView::hideDialog(string const & name, Inset * inset)
5293 map<string, DialogPtr>::const_iterator it = d.dialogs_.find(name);
5294 if (it == d.dialogs_.end())
5298 if (!currentBufferView())
5300 if (inset != currentBufferView()->editedInset(name))
5304 Dialog * const dialog = it->second.get();
5305 if (dialog->isVisibleView())
5307 if (currentBufferView())
5308 currentBufferView()->editInset(name, nullptr);
5312 void GuiView::disconnectDialog(string const & name)
5314 if (!isValidName(name))
5316 if (currentBufferView())
5317 currentBufferView()->editInset(name, nullptr);
5321 void GuiView::hideAll() const
5323 for(auto const & dlg_p : d.dialogs_)
5324 dlg_p.second->hideView();
5328 void GuiView::updateDialogs()
5330 for(auto const & dlg_p : d.dialogs_) {
5331 Dialog * dialog = dlg_p.second.get();
5333 if (dialog->needBufferOpen() && !documentBufferView())
5334 hideDialog(fromqstr(dialog->name()), nullptr);
5335 else if (dialog->isVisibleView())
5336 dialog->checkStatus();
5344 Dialog * GuiView::build(string const & name)
5346 return createDialog(*this, name);
5350 SEMenu::SEMenu(QWidget * parent)
5352 QAction * action = addAction(qt_("Disable Shell Escape"));
5353 connect(action, SIGNAL(triggered()),
5354 parent, SLOT(disableShellEscape()));
5358 void PressableSvgWidget::mousePressEvent(QMouseEvent * event)
5360 if (event->button() == Qt::LeftButton) {
5365 } // namespace frontend
5368 #include "moc_GuiView.cpp"