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 QFontMetrics const fm(statusBar()->fontMetrics());
637 int const iconheight = max(int(d.normalIconSize), fm.height());
638 QSize const iconsize(iconheight, iconheight);
640 // add busy indicator to statusbar
641 search_mode mode = theGuiApp()->imageSearchMode();
642 QString fn = toqstr(lyx::libFileSearch("images", "busy", "svgz", mode).absFileName());
643 PressableSvgWidget * busySVG = new PressableSvgWidget(fn);
644 statusBar()->addPermanentWidget(busySVG);
645 // make busy indicator square with 5px margins
646 busySVG->setMaximumSize(busySVG->height() - 5, busySVG->height() - 5);
649 QPixmap ps = QIcon(getPixmap("images/", "process-stop", "svgz")).pixmap(iconsize);
650 GuiClickableLabel * processStop = new GuiClickableLabel(statusBar());
651 processStop->setPixmap(ps);
652 processStop->setToolTip(qt_("Click here to stop export/output process"));
654 statusBar()->addPermanentWidget(processStop);
656 connect(&d.processing_thread_watcher_, SIGNAL(started()),
657 busySVG, SLOT(show()));
658 connect(&d.processing_thread_watcher_, SIGNAL(finished()),
659 busySVG, SLOT(hide()));
660 connect(&d.processing_thread_watcher_, SIGNAL(started()),
661 processStop, SLOT(show()));
662 connect(&d.processing_thread_watcher_, SIGNAL(finished()),
663 processStop, SLOT(hide()));
664 connect(processStop, SIGNAL(pressed()), this, SLOT(checkCancelBackground()));
666 stat_counts_ = new GuiClickableLabel(statusBar());
667 stat_counts_->setAlignment(Qt::AlignCenter);
668 stat_counts_->setFrameStyle(QFrame::StyledPanel);
669 stat_counts_->hide();
670 statusBar()->addPermanentWidget(stat_counts_);
672 connect(stat_counts_, SIGNAL(clicked()), this, SLOT(statsPressed()));
674 zoom_slider_ = new QSlider(Qt::Horizontal, statusBar());
675 // Small size slider for macOS to prevent the status bar from enlarging
676 zoom_slider_->setAttribute(Qt::WA_MacSmallSize);
677 #if (QT_VERSION >= QT_VERSION_CHECK(5, 11, 0))
678 zoom_slider_->setFixedWidth(fm.horizontalAdvance('x') * 15);
680 zoom_slider_->setFixedWidth(fm.width('x') * 15);
682 // Make the defaultZoom center
683 zoom_slider_->setRange(10, (lyxrc.defaultZoom * 2) - 10);
684 // Initialize proper zoom value
686 zoom_ratio_ = settings.value("zoom_ratio", 1.0).toDouble();
687 // Actual zoom value: default zoom + fractional offset
688 int zoom = (int)(lyxrc.defaultZoom * zoom_ratio_);
689 zoom = min(max(zoom, zoom_min_), zoom_max_);
690 zoom_slider_->setValue(zoom);
691 zoom_slider_->setToolTip(qt_("Workarea zoom level. Drag, use Ctrl-+/- or Shift-Mousewheel to adjust."));
693 // Buttons to change zoom stepwise
694 #if (QT_VERSION >= QT_VERSION_CHECK(5, 11, 0))
695 QSize s(fm.horizontalAdvance('+'), fm.height());
697 QSize s(fm.width('+'), fm.height());
699 zoom_in_ = new GuiClickableLabel(statusBar());
700 zoom_in_->setText("+");
701 zoom_in_->setFixedSize(s);
702 zoom_in_->setAlignment(Qt::AlignCenter);
703 zoom_out_ = new GuiClickableLabel(statusBar());
704 zoom_out_->setText(QString(QChar(0x2212)));
705 zoom_out_->setFixedSize(s);
706 zoom_out_->setAlignment(Qt::AlignCenter);
708 statusBar()->addPermanentWidget(zoom_out_);
709 zoom_out_->setEnabled(currentBufferView());
710 statusBar()->addPermanentWidget(zoom_slider_);
711 zoom_slider_->setEnabled(currentBufferView());
712 zoom_in_->setEnabled(currentBufferView());
713 statusBar()->addPermanentWidget(zoom_in_);
715 connect(zoom_slider_, SIGNAL(sliderMoved(int)), this, SLOT(zoomSliderMoved(int)));
716 connect(zoom_slider_, SIGNAL(valueChanged(int)), this, SLOT(zoomValueChanged(int)));
717 connect(this, SIGNAL(currentZoomChanged(int)), zoom_slider_, SLOT(setValue(int)));
718 connect(zoom_in_, SIGNAL(clicked()), this, SLOT(zoomInPressed()));
719 connect(zoom_out_, SIGNAL(clicked()), this, SLOT(zoomOutPressed()));
721 // QPalette palette = statusBar()->palette();
723 zoom_value_ = new GuiClickableLabel(statusBar());
724 connect(zoom_value_, SIGNAL(pressed()), this, SLOT(showZoomContextMenu()));
725 // zoom_value_->setPalette(palette);
726 zoom_value_->setForegroundRole(statusBar()->foregroundRole());
727 zoom_value_->setFixedHeight(fm.height());
728 #if (QT_VERSION >= QT_VERSION_CHECK(5, 11, 0))
729 zoom_value_->setMinimumWidth(fm.horizontalAdvance("444\%"));
731 zoom_value_->setMinimumWidth(fm.width("444\%"));
733 zoom_value_->setAlignment(Qt::AlignCenter);
734 zoom_value_->setText(toqstr(bformat(_("[[ZOOM]]%1$d%"), zoom)));
735 statusBar()->addPermanentWidget(zoom_value_);
736 zoom_value_->setEnabled(currentBufferView());
738 statusBar()->setContextMenuPolicy(Qt::CustomContextMenu);
739 connect(statusBar(), SIGNAL(customContextMenuRequested(QPoint)),
740 this, SLOT(showStatusBarContextMenu()));
742 // enable pinch to zoom
743 grabGesture(Qt::PinchGesture);
745 QPixmap shellescape = QIcon(getPixmap("images/", "emblem-shellescape", "svgz,png")).pixmap(iconsize);
746 shell_escape_ = new QLabel(statusBar());
747 shell_escape_->setPixmap(shellescape);
748 shell_escape_->setAlignment(Qt::AlignCenter);
749 shell_escape_->setContextMenuPolicy(Qt::CustomContextMenu);
750 shell_escape_->setToolTip(qt_("WARNING: LaTeX is allowed to execute "
751 "external commands for this document. "
752 "Right click to change."));
753 SEMenu * menu = new SEMenu(this);
754 connect(shell_escape_, SIGNAL(customContextMenuRequested(QPoint)),
755 menu, SLOT(showMenu(QPoint)));
756 shell_escape_->hide();
757 statusBar()->addPermanentWidget(shell_escape_);
759 QPixmap readonly = QIcon(getPixmap("images/", "emblem-readonly", "svgz,png")).pixmap(iconsize);
760 read_only_ = new QLabel(statusBar());
761 read_only_->setPixmap(readonly);
762 read_only_->setAlignment(Qt::AlignCenter);
764 statusBar()->addPermanentWidget(read_only_);
766 version_control_ = new QLabel(statusBar());
767 version_control_->setAlignment(Qt::AlignCenter);
768 version_control_->setFrameStyle(QFrame::StyledPanel);
769 version_control_->hide();
770 statusBar()->addPermanentWidget(version_control_);
772 statusBar()->setSizeGripEnabled(true);
775 connect(&d.autosave_watcher_, SIGNAL(finished()), this,
776 SLOT(autoSaveThreadFinished()));
778 connect(&d.processing_thread_watcher_, SIGNAL(started()), this,
779 SLOT(processingThreadStarted()));
780 connect(&d.processing_thread_watcher_, SIGNAL(finished()), this,
781 SLOT(processingThreadFinished()));
783 connect(this, SIGNAL(triggerShowDialog(QString const &, QString const &, Inset *)),
784 SLOT(doShowDialog(QString const &, QString const &, Inset *)));
786 // set custom application bars context menu, e.g. tool bar and menu bar
787 setContextMenuPolicy(Qt::CustomContextMenu);
788 connect(this, SIGNAL(customContextMenuRequested(const QPoint &)),
789 SLOT(toolBarPopup(const QPoint &)));
791 // Forbid too small unresizable window because it can happen
792 // with some window manager under X11.
793 setMinimumSize(300, 200);
795 if (lyxrc.allow_geometry_session) {
796 // Now take care of session management.
801 // no session handling, default to a sane size.
802 setGeometry(50, 50, 690, 510);
805 // clear session data if any.
806 settings.remove("views");
816 void GuiView::disableShellEscape()
818 BufferView * bv = documentBufferView();
821 theSession().shellescapeFiles().remove(bv->buffer().absFileName());
822 bv->buffer().params().shell_escape = false;
823 bv->processUpdateFlags(Update::Force);
827 void GuiView::checkCancelBackground()
829 docstring const ttl = _("Cancel Export?");
830 docstring const msg = _("Do you want to cancel the background export process?");
832 Alert::prompt(ttl, msg, 1, 1,
833 _("&Cancel export"), _("Co&ntinue"));
835 Systemcall::killscript();
838 void GuiView::statsPressed()
841 dispatch(FuncRequest(LFUN_STATISTICS), dr);
844 void GuiView::zoomSliderMoved(int value)
847 dispatch(FuncRequest(LFUN_BUFFER_ZOOM, convert<string>(value)), dr);
848 scheduleRedrawWorkAreas();
849 zoom_value_->setText(toqstr(bformat(_("[[ZOOM]]%1$d%"), value)));
853 void GuiView::zoomValueChanged(int value)
855 if (value != lyxrc.currentZoom)
856 zoomSliderMoved(value);
860 void GuiView::zoomInPressed()
863 dispatch(FuncRequest(LFUN_BUFFER_ZOOM_IN), dr);
864 scheduleRedrawWorkAreas();
868 void GuiView::zoomOutPressed()
871 dispatch(FuncRequest(LFUN_BUFFER_ZOOM_OUT), dr);
872 scheduleRedrawWorkAreas();
876 void GuiView::showZoomContextMenu()
878 QMenu * menu = guiApp->menus().menu(toqstr("context-zoom"), * this);
881 menu->exec(QCursor::pos());
885 void GuiView::showStatusBarContextMenu()
887 QMenu * menu = guiApp->menus().menu(toqstr("context-statusbar"), * this);
890 menu->exec(QCursor::pos());
894 void GuiView::scheduleRedrawWorkAreas()
896 for (int i = 0; i < d.tabWorkAreaCount(); i++) {
897 TabWorkArea* ta = d.tabWorkArea(i);
898 for (int u = 0; u < ta->count(); u++) {
899 ta->workArea(u)->scheduleRedraw(true);
905 QVector<GuiWorkArea*> GuiView::GuiViewPrivate::guiWorkAreas()
907 QVector<GuiWorkArea*> areas;
908 for (int i = 0; i < tabWorkAreaCount(); i++) {
909 TabWorkArea* ta = tabWorkArea(i);
910 for (int u = 0; u < ta->count(); u++) {
911 areas << ta->workArea(u);
917 static void handleExportStatus(GuiView * view, Buffer::ExportStatus status,
918 string const & format)
920 docstring const fmt = translateIfPossible(theFormats().prettyName(format));
923 case Buffer::ExportSuccess:
924 msg = bformat(_("Successful export to format: %1$s"), fmt);
926 case Buffer::ExportCancel:
927 msg = _("Document export cancelled.");
929 case Buffer::ExportError:
930 case Buffer::ExportNoPathToFormat:
931 case Buffer::ExportTexPathHasSpaces:
932 case Buffer::ExportConverterError:
933 msg = bformat(_("Error while exporting format: %1$s"), fmt);
935 case Buffer::PreviewSuccess:
936 msg = bformat(_("Successful preview of format: %1$s"), fmt);
938 case Buffer::PreviewError:
939 msg = bformat(_("Error while previewing format: %1$s"), fmt);
941 case Buffer::ExportKilled:
942 msg = bformat(_("Conversion cancelled while previewing format: %1$s"), fmt);
949 void GuiView::processingThreadStarted()
954 void GuiView::processingThreadFinished()
956 QFutureWatcher<Buffer::ExportStatus> const * watcher =
957 static_cast<QFutureWatcher<Buffer::ExportStatus> const *>(sender());
959 Buffer::ExportStatus const status = watcher->result();
960 handleExportStatus(this, status, d.processing_format);
963 BufferView const * const bv = currentBufferView();
964 if (bv && !bv->buffer().errorList("Export").empty()) {
969 bool const error = (status != Buffer::ExportSuccess &&
970 status != Buffer::PreviewSuccess &&
971 status != Buffer::ExportCancel);
973 ErrorList & el = bv->buffer().errorList(d.last_export_format);
974 // at this point, we do not know if buffer-view or
975 // master-buffer-view was called. If there was an export error,
976 // and the current buffer's error log is empty, we guess that
977 // it must be master-buffer-view that was called so we set
979 errors(d.last_export_format, el.empty());
984 void GuiView::autoSaveThreadFinished()
986 QFutureWatcher<docstring> const * watcher =
987 static_cast<QFutureWatcher<docstring> const *>(sender());
988 message(watcher->result());
993 void GuiView::saveLayout() const
996 settings.setValue("zoom_ratio", zoom_ratio_);
997 settings.setValue("devel_mode", devel_mode_);
998 settings.beginGroup("views");
999 settings.beginGroup(QString::number(id_));
1000 if (guiApp->platformName() == "qt4x11" || guiApp->platformName() == "xcb") {
1001 settings.setValue("pos", pos());
1002 settings.setValue("size", size());
1004 settings.setValue("geometry", saveGeometry());
1005 settings.setValue("layout", saveState(0));
1006 settings.setValue("icon_size", toqstr(d.iconSize(iconSize())));
1007 settings.setValue("zoom_value_visible", zoom_value_->isVisible());
1008 settings.setValue("zoom_slider_visible", zoom_slider_->isVisible());
1009 settings.setValue("word_count_enabled", word_count_enabled_);
1010 settings.setValue("char_count_enabled", char_count_enabled_);
1011 settings.setValue("char_nb_count_enabled", char_nb_count_enabled_);
1015 void GuiView::saveUISettings() const
1019 // Save the toolbar private states
1020 for (auto const & tb_p : d.toolbars_)
1021 tb_p.second->saveSession(settings);
1022 // Now take care of all other dialogs
1023 for (auto const & dlg_p : d.dialogs_)
1024 dlg_p.second->saveSession(settings);
1028 void GuiView::setCurrentZoom(const int v)
1030 lyxrc.currentZoom = v;
1031 zoom_value_->setText(toqstr(bformat(_("[[ZOOM]]%1$d%"), v)));
1032 Q_EMIT currentZoomChanged(v);
1036 bool GuiView::restoreLayout()
1039 zoom_ratio_ = settings.value("zoom_ratio", 1.0).toDouble();
1040 // Actual zoom value: default zoom + fractional offset
1041 int zoom = (int)(lyxrc.defaultZoom * zoom_ratio_);
1042 zoom = min(max(zoom, zoom_min_), zoom_max_);
1043 setCurrentZoom(zoom);
1044 devel_mode_ = settings.value("devel_mode", devel_mode_).toBool();
1045 settings.beginGroup("views");
1046 settings.beginGroup(QString::number(id_));
1047 QString const icon_key = "icon_size";
1048 if (!settings.contains(icon_key))
1051 //code below is skipped when when ~/.config/LyX is (re)created
1052 setIconSize(d.iconSize(settings.value(icon_key).toString()));
1054 zoom_value_->setVisible(settings.value("zoom_value_visible", true).toBool());
1056 bool const show_zoom_slider = settings.value("zoom_slider_visible", true).toBool();
1057 zoom_slider_->setVisible(show_zoom_slider);
1058 zoom_in_->setVisible(show_zoom_slider);
1059 zoom_out_->setVisible(show_zoom_slider);
1061 word_count_enabled_ = settings.value("word_count_enabled", true).toBool();
1062 char_count_enabled_ = settings.value("char_count_enabled", true).toBool();
1063 char_nb_count_enabled_ = settings.value("char_nb_count_enabled", true).toBool();
1064 stat_counts_->setVisible(word_count_enabled_ || char_count_enabled_ || char_nb_count_enabled_);
1066 if (guiApp->platformName() == "qt4x11" || guiApp->platformName() == "xcb") {
1067 QPoint pos = settings.value("pos", QPoint(50, 50)).toPoint();
1068 QSize size = settings.value("size", QSize(690, 510)).toSize();
1072 // Work-around for bug #6034: the window ends up in an undetermined
1073 // state when trying to restore a maximized window when it is
1074 // already maximized.
1075 if (!(windowState() & Qt::WindowMaximized))
1076 if (!restoreGeometry(settings.value("geometry").toByteArray()))
1077 setGeometry(50, 50, 690, 510);
1080 // Make sure layout is correctly oriented.
1081 setLayoutDirection(qApp->layoutDirection());
1083 // Allow the toc and view-source dock widget to be restored if needed.
1085 if ((dialog = findOrBuild("toc", true)))
1086 // see bug 5082. At least setup title and enabled state.
1087 // Visibility will be adjusted by restoreState below.
1088 dialog->prepareView();
1089 if ((dialog = findOrBuild("view-source", true)))
1090 dialog->prepareView();
1091 if ((dialog = findOrBuild("progress", true)))
1092 dialog->prepareView();
1094 if (!restoreState(settings.value("layout").toByteArray(), 0))
1097 // init the toolbars that have not been restored
1098 for (auto const & tb_p : guiApp->toolbars()) {
1099 GuiToolbar * tb = toolbar(tb_p.name);
1100 if (tb && !tb->isRestored())
1101 initToolbar(tb_p.name);
1104 // update lock (all) toolbars positions
1105 updateLockToolbars();
1112 GuiToolbar * GuiView::toolbar(string const & name)
1114 ToolbarMap::iterator it = d.toolbars_.find(name);
1115 if (it != d.toolbars_.end())
1118 LYXERR(Debug::GUI, "Toolbar::display: no toolbar named " << name);
1123 void GuiView::updateLockToolbars()
1125 toolbarsMovable_ = false;
1126 for (ToolbarInfo const & info : guiApp->toolbars()) {
1127 GuiToolbar * tb = toolbar(info.name);
1128 if (tb && tb->isMovable())
1129 toolbarsMovable_ = true;
1131 #if QT_VERSION >= 0x050200
1132 // set unified mac toolbars only when not movable as recommended:
1133 // https://doc.qt.io/qt-5/qmainwindow.html#unifiedTitleAndToolBarOnMac-prop
1134 setUnifiedTitleAndToolBarOnMac(!toolbarsMovable_);
1139 void GuiView::constructToolbars()
1141 for (auto const & tb_p : d.toolbars_)
1143 d.toolbars_.clear();
1145 // I don't like doing this here, but the standard toolbar
1146 // destroys this object when it's destroyed itself (vfr)
1147 d.layout_ = new LayoutBox(*this);
1148 d.stack_widget_->addWidget(d.layout_);
1149 d.layout_->move(0,0);
1151 // extracts the toolbars from the backend
1152 for (ToolbarInfo const & inf : guiApp->toolbars())
1153 d.toolbars_[inf.name] = new GuiToolbar(inf, *this);
1155 DynamicMenuButton::resetIconCache();
1159 void GuiView::initToolbars()
1161 // extracts the toolbars from the backend
1162 for (ToolbarInfo const & inf : guiApp->toolbars())
1163 initToolbar(inf.name);
1167 void GuiView::initToolbar(string const & name)
1169 GuiToolbar * tb = toolbar(name);
1172 int const visibility = guiApp->toolbars().defaultVisibility(name);
1173 bool newline = !(visibility & Toolbars::SAMEROW);
1174 tb->setVisible(false);
1175 tb->setVisibility(visibility);
1177 if (visibility & Toolbars::TOP) {
1179 addToolBarBreak(Qt::TopToolBarArea);
1180 addToolBar(Qt::TopToolBarArea, tb);
1183 if (visibility & Toolbars::BOTTOM) {
1185 addToolBarBreak(Qt::BottomToolBarArea);
1186 addToolBar(Qt::BottomToolBarArea, tb);
1189 if (visibility & Toolbars::LEFT) {
1191 addToolBarBreak(Qt::LeftToolBarArea);
1192 addToolBar(Qt::LeftToolBarArea, tb);
1195 if (visibility & Toolbars::RIGHT) {
1197 addToolBarBreak(Qt::RightToolBarArea);
1198 addToolBar(Qt::RightToolBarArea, tb);
1201 if (visibility & Toolbars::ON)
1202 tb->setVisible(true);
1204 tb->setMovable(true);
1208 TocModels & GuiView::tocModels()
1210 return d.toc_models_;
1214 void GuiView::setFocus()
1216 LYXERR(Debug::DEBUG, "GuiView::setFocus()" << this);
1217 QMainWindow::setFocus();
1221 bool GuiView::hasFocus() const
1223 if (currentWorkArea())
1224 return currentWorkArea()->hasFocus();
1225 if (currentMainWorkArea())
1226 return currentMainWorkArea()->hasFocus();
1227 return d.bg_widget_->hasFocus();
1231 void GuiView::focusInEvent(QFocusEvent * e)
1233 LYXERR(Debug::DEBUG, "GuiView::focusInEvent()" << this);
1234 QMainWindow::focusInEvent(e);
1235 // Make sure guiApp points to the correct view.
1236 guiApp->setCurrentView(this);
1237 if (currentWorkArea())
1238 currentWorkArea()->setFocus();
1239 else if (currentMainWorkArea())
1240 currentMainWorkArea()->setFocus();
1242 d.bg_widget_->setFocus();
1246 void GuiView::showEvent(QShowEvent * e)
1248 LYXERR(Debug::GUI, "Passed Geometry "
1249 << size().height() << "x" << size().width()
1250 << "+" << pos().x() << "+" << pos().y());
1252 if (d.splitter_->count() == 0)
1253 // No work area, switch to the background widget.
1257 QMainWindow::showEvent(e);
1261 bool GuiView::closeScheduled()
1268 bool GuiView::prepareAllBuffersForLogout()
1270 Buffer * first = theBufferList().first();
1274 // First, iterate over all buffers and ask the users if unsaved
1275 // changes should be saved.
1276 // We cannot use a for loop as the buffer list cycles.
1279 if (!saveBufferIfNeeded(*b, false))
1281 b = theBufferList().next(b);
1282 } while (b != first);
1284 // Next, save session state
1285 // When a view/window was closed before without quitting LyX, there
1286 // are already entries in the lastOpened list.
1287 theSession().lastOpened().clear();
1294 /** Destroy only all tabbed WorkAreas. Destruction of other WorkAreas
1295 ** is responsibility of the container (e.g., dialog)
1297 void GuiView::closeEvent(QCloseEvent * close_event)
1299 LYXERR(Debug::DEBUG, "GuiView::closeEvent()");
1301 if (!GuiViewPrivate::busyBuffers.isEmpty()) {
1302 Alert::warning(_("Exit LyX"),
1303 _("LyX could not be closed because documents are being processed by LyX."));
1304 close_event->setAccepted(false);
1308 // If the user pressed the x (so we didn't call closeView
1309 // programmatically), we want to clear all existing entries.
1311 theSession().lastOpened().clear();
1316 // it can happen that this event arrives without selecting the view,
1317 // e.g. when clicking the close button on a background window.
1319 if (!closeWorkAreaAll()) {
1321 close_event->ignore();
1325 // Make sure that nothing will use this to be closed View.
1326 guiApp->unregisterView(this);
1328 if (isFullScreen()) {
1329 // Switch off fullscreen before closing.
1334 // Make sure the timer time out will not trigger a statusbar update.
1335 d.statusbar_timer_.stop();
1336 d.statusbar_stats_timer_.stop();
1338 // Saving fullscreen requires additional tweaks in the toolbar code.
1339 // It wouldn't also work under linux natively.
1340 if (lyxrc.allow_geometry_session) {
1345 close_event->accept();
1349 void GuiView::dragEnterEvent(QDragEnterEvent * event)
1351 if (event->mimeData()->hasUrls())
1353 /// \todo Ask lyx-devel is this is enough:
1354 /// if (event->mimeData()->hasFormat("text/plain"))
1355 /// event->acceptProposedAction();
1359 void GuiView::dropEvent(QDropEvent * event)
1361 QList<QUrl> files = event->mimeData()->urls();
1362 if (files.isEmpty())
1365 LYXERR(Debug::GUI, "GuiView::dropEvent: got URLs!");
1366 for (int i = 0; i != files.size(); ++i) {
1367 string const file = os::internal_path(fromqstr(
1368 files.at(i).toLocalFile()));
1372 string const ext = support::getExtension(file);
1373 vector<const Format *> found_formats;
1375 // Find all formats that have the correct extension.
1376 for (const Format * fmt : theConverters().importableFormats())
1377 if (fmt->hasExtension(ext))
1378 found_formats.push_back(fmt);
1381 if (!found_formats.empty()) {
1382 if (found_formats.size() > 1) {
1383 //FIXME: show a dialog to choose the correct importable format
1384 LYXERR(Debug::FILES,
1385 "Multiple importable formats found, selecting first");
1387 string const arg = found_formats[0]->name() + " " + file;
1388 cmd = FuncRequest(LFUN_BUFFER_IMPORT, arg);
1391 //FIXME: do we have to explicitly check whether it's a lyx file?
1392 LYXERR(Debug::FILES,
1393 "No formats found, trying to open it as a lyx file");
1394 cmd = FuncRequest(LFUN_FILE_OPEN, file);
1396 // add the functions to the queue
1397 guiApp->addToFuncRequestQueue(cmd);
1400 // now process the collected functions. We perform the events
1401 // asynchronously. This prevents potential problems in case the
1402 // BufferView is closed within an event.
1403 guiApp->processFuncRequestQueueAsync();
1407 void GuiView::message(docstring const & str)
1409 if (ForkedProcess::iAmAChild())
1412 // call is moved to GUI-thread by GuiProgress
1413 d.progress_->appendMessage(toqstr(str));
1417 void GuiView::clearMessageText()
1419 message(docstring());
1423 void GuiView::updateStatusBarMessage(QString const & str)
1425 statusBar()->showMessage(str);
1426 d.statusbar_timer_.stop();
1427 d.statusbar_timer_.start(3000);
1431 void GuiView::clearMessage()
1433 // FIXME: This code was introduced in r19643 to fix bug #4123. However,
1434 // the hasFocus function mostly returns false, even if the focus is on
1435 // a workarea in this view.
1439 d.statusbar_timer_.stop();
1442 void GuiView::showStats()
1444 if (!statsEnabled())
1447 d.time_to_update -= d.timer_rate;
1449 BufferView * bv = currentBufferView();
1450 Buffer * buf = bv ? &bv->buffer() : nullptr;
1452 stat_counts_->hide();
1456 Cursor const & cur = bv->cursor();
1458 // we start new selection and need faster update
1459 if (!d.already_in_selection_ && cur.selection())
1460 d.time_to_update = 0;
1462 if (d.time_to_update > 0)
1465 DocIterator from, to;
1466 if (cur.selection()) {
1467 from = cur.selectionBegin();
1468 to = cur.selectionEnd();
1469 d.already_in_selection_ = true;
1471 from = doc_iterator_begin(buf);
1472 to = doc_iterator_end(buf);
1473 d.already_in_selection_ = false;
1476 buf->updateStatistics(from, to);
1479 if (word_count_enabled_) {
1480 int const words = buf->wordCount();
1482 stats << toqstr(bformat(_("%1$d Word"), words));
1484 stats << toqstr(bformat(_("%1$d Words"), words));
1486 int const chars_with_blanks = buf->charCount(true);
1487 if (char_count_enabled_) {
1488 if (chars_with_blanks == 1)
1489 stats << toqstr(bformat(_("%1$d Character"), chars_with_blanks));
1491 stats << toqstr(bformat(_("%1$d Characters"), chars_with_blanks));
1493 if (char_nb_count_enabled_) {
1494 int const chars = buf->charCount(false);
1496 stats << toqstr(bformat(_("%1$d Character (no Blanks)"), chars));
1498 stats << toqstr(bformat(_("%1$d Characters (no Blanks)"), chars));
1500 stat_counts_->setText(stats.join(qt_(", [[stats separator]]")));
1501 stat_counts_->show();
1503 d.time_to_update = d.default_stats_rate;
1504 // fast updates for small selections
1505 if (chars_with_blanks < d.max_sel_chars && cur.selection())
1506 d.time_to_update = d.timer_rate;
1510 void GuiView::updateWindowTitle(GuiWorkArea * wa)
1512 if (wa != d.current_work_area_
1513 || wa->bufferView().buffer().isInternal())
1515 Buffer const & buf = wa->bufferView().buffer();
1516 // Set the windows title
1517 docstring title = buf.fileName().displayName(130) + from_ascii("[*]");
1518 if (buf.notifiesExternalModification()) {
1519 title = bformat(_("%1$s (modified externally)"), title);
1520 // If the external modification status has changed, then maybe the status of
1521 // buffer-save has changed too.
1525 title += from_ascii(" - LyX");
1527 setWindowTitle(toqstr(title));
1528 // Sets the path for the window: this is used by OSX to
1529 // allow a context click on the title bar showing a menu
1530 // with the path up to the file
1531 setWindowFilePath(toqstr(buf.absFileName()));
1532 // Tell Qt whether the current document is changed
1533 setWindowModified(!buf.isClean());
1535 if (buf.params().shell_escape)
1536 shell_escape_->show();
1538 shell_escape_->hide();
1540 if (buf.hasReadonlyFlag())
1545 if (buf.lyxvc().inUse()) {
1546 version_control_->show();
1547 version_control_->setText(toqstr(buf.lyxvc().vcstatus()));
1549 version_control_->hide();
1553 void GuiView::on_currentWorkAreaChanged(GuiWorkArea * wa)
1555 if (d.current_work_area_)
1556 // disconnect the current work area from all slots
1557 QObject::disconnect(d.current_work_area_, nullptr, this, nullptr);
1559 disconnectBufferView();
1560 connectBufferView(wa->bufferView());
1561 connectBuffer(wa->bufferView().buffer());
1562 d.current_work_area_ = wa;
1563 QObject::connect(wa, SIGNAL(titleChanged(GuiWorkArea *)),
1564 this, SLOT(updateWindowTitle(GuiWorkArea *)));
1565 QObject::connect(wa, SIGNAL(busy(bool)),
1566 this, SLOT(setBusy(bool)));
1567 // connection of a signal to a signal
1568 QObject::connect(wa, SIGNAL(bufferViewChanged()),
1569 this, SIGNAL(bufferViewChanged()));
1570 Q_EMIT updateWindowTitle(wa);
1571 Q_EMIT bufferViewChanged();
1575 void GuiView::onBufferViewChanged()
1578 // Buffer-dependent dialogs must be updated. This is done here because
1579 // some dialogs require buffer()->text.
1581 zoom_slider_->setEnabled(currentBufferView());
1582 zoom_value_->setEnabled(currentBufferView());
1583 zoom_in_->setEnabled(currentBufferView());
1584 zoom_out_->setEnabled(currentBufferView());
1588 void GuiView::on_lastWorkAreaRemoved()
1591 // We already are in a close event. Nothing more to do.
1594 if (d.splitter_->count() > 1)
1595 // We have a splitter so don't close anything.
1598 // Reset and updates the dialogs.
1599 Q_EMIT bufferViewChanged();
1604 if (lyxrc.open_buffers_in_tabs)
1605 // Nothing more to do, the window should stay open.
1608 if (guiApp->viewIds().size() > 1) {
1614 // On Mac we also close the last window because the application stay
1615 // resident in memory. On other platforms we don't close the last
1616 // window because this would quit the application.
1622 void GuiView::updateStatusBar()
1624 // let the user see the explicit message
1625 if (d.statusbar_timer_.isActive())
1632 void GuiView::showMessage()
1636 QString msg = toqstr(theGuiApp()->viewStatusMessage());
1637 if (msg.isEmpty()) {
1638 BufferView const * bv = currentBufferView();
1640 msg = toqstr(bv->cursor().currentState(devel_mode_));
1642 msg = qt_("Welcome to LyX!");
1644 statusBar()->showMessage(msg);
1648 bool GuiView::statsEnabled() const
1650 return word_count_enabled_ || char_count_enabled_ || char_nb_count_enabled_;
1654 bool GuiView::event(QEvent * e)
1658 // Useful debug code:
1659 //case QEvent::ActivationChange:
1660 //case QEvent::WindowDeactivate:
1661 //case QEvent::Paint:
1662 //case QEvent::Enter:
1663 //case QEvent::Leave:
1664 //case QEvent::HoverEnter:
1665 //case QEvent::HoverLeave:
1666 //case QEvent::HoverMove:
1667 //case QEvent::StatusTip:
1668 //case QEvent::DragEnter:
1669 //case QEvent::DragLeave:
1670 //case QEvent::Drop:
1673 case QEvent::WindowStateChange: {
1674 QWindowStateChangeEvent * ev = (QWindowStateChangeEvent*)e;
1675 bool ofstate = (ev->oldState() & Qt::WindowFullScreen);
1676 bool result = QMainWindow::event(e);
1677 bool nfstate = (windowState() & Qt::WindowFullScreen);
1678 if (!ofstate && nfstate) {
1679 LYXERR(Debug::DEBUG, "GuiView: WindowStateChange(): full-screen " << nfstate);
1680 // switch to full-screen state
1681 if (lyxrc.full_screen_statusbar)
1682 statusBar()->hide();
1683 if (lyxrc.full_screen_menubar)
1685 if (lyxrc.full_screen_toolbars) {
1686 for (auto const & tb_p : d.toolbars_)
1687 if (tb_p.second->isVisibilityOn() && tb_p.second->isVisible())
1688 tb_p.second->hide();
1690 for (int i = 0; i != d.splitter_->count(); ++i)
1691 d.tabWorkArea(i)->setFullScreen(true);
1692 #if QT_VERSION > 0x050903
1693 //Qt's 5.9.4 ba44cdae38406c safe area measures won't allow us to go negative in margins
1694 setAttribute(Qt::WA_ContentsMarginsRespectsSafeArea, false);
1696 setContentsMargins(-2, -2, -2, -2);
1698 hideDialogs("prefs", nullptr);
1699 } else if (ofstate && !nfstate) {
1700 LYXERR(Debug::DEBUG, "GuiView: WindowStateChange(): full-screen " << nfstate);
1701 // switch back from full-screen state
1702 if (lyxrc.full_screen_statusbar && !statusBar()->isVisible())
1703 statusBar()->show();
1704 if (lyxrc.full_screen_menubar && !menuBar()->isVisible())
1706 if (lyxrc.full_screen_toolbars) {
1707 for (auto const & tb_p : d.toolbars_)
1708 if (tb_p.second->isVisibilityOn() && !tb_p.second->isVisible())
1709 tb_p.second->show();
1712 for (int i = 0; i != d.splitter_->count(); ++i)
1713 d.tabWorkArea(i)->setFullScreen(false);
1714 #if QT_VERSION > 0x050903
1715 setAttribute(Qt::WA_ContentsMarginsRespectsSafeArea, true);
1717 setContentsMargins(0, 0, 0, 0);
1722 case QEvent::WindowActivate: {
1723 GuiView * old_view = guiApp->currentView();
1724 if (this == old_view) {
1726 return QMainWindow::event(e);
1728 if (old_view && old_view->currentBufferView()) {
1729 // save current selection to the selection buffer to allow
1730 // middle-button paste in this window.
1731 cap::saveSelection(old_view->currentBufferView()->cursor());
1733 guiApp->setCurrentView(this);
1734 if (d.current_work_area_)
1735 on_currentWorkAreaChanged(d.current_work_area_);
1739 return QMainWindow::event(e);
1742 case QEvent::ShortcutOverride: {
1744 if (isFullScreen() && menuBar()->isHidden()) {
1745 QKeyEvent * ke = static_cast<QKeyEvent*>(e);
1746 // FIXME: we should also try to detect special LyX shortcut such as
1747 // Alt-P and Alt-M. Right now there is a hack in
1748 // GuiWorkArea::processKeySym() that hides again the menubar for
1750 if (ke->modifiers() & Qt::AltModifier && ke->key() != Qt::Key_Alt) {
1752 return QMainWindow::event(e);
1755 return QMainWindow::event(e);
1758 case QEvent::ApplicationPaletteChange: {
1759 // runtime switch from/to dark mode
1761 return QMainWindow::event(e);
1764 case QEvent::Gesture: {
1765 QGestureEvent *ge = static_cast<QGestureEvent*>(e);
1766 QGesture *gp = ge->gesture(Qt::PinchGesture);
1768 QPinchGesture *pinch = static_cast<QPinchGesture *>(gp);
1769 QPinchGesture::ChangeFlags changeFlags = pinch->changeFlags();
1770 qreal totalScaleFactor = pinch->totalScaleFactor();
1771 LYXERR(Debug::GUI, "totalScaleFactor: " << totalScaleFactor);
1772 if (pinch->state() == Qt::GestureStarted) {
1773 initialZoom_ = lyxrc.currentZoom;
1774 LYXERR(Debug::GUI, "initialZoom_: " << initialZoom_);
1776 if (changeFlags & QPinchGesture::ScaleFactorChanged) {
1777 qreal factor = initialZoom_ * totalScaleFactor;
1778 LYXERR(Debug::GUI, "scaleFactor: " << factor);
1779 zoomValueChanged(factor);
1782 return QMainWindow::event(e);
1786 return QMainWindow::event(e);
1790 void GuiView::resetWindowTitle()
1792 setWindowTitle(qt_("LyX"));
1795 bool GuiView::focusNextPrevChild(bool /*next*/)
1802 bool GuiView::busy() const
1808 void GuiView::setBusy(bool busy)
1810 bool const busy_before = busy_ > 0;
1811 busy ? ++busy_ : --busy_;
1812 if ((busy_ > 0) == busy_before)
1813 // busy state didn't change
1817 QApplication::setOverrideCursor(Qt::WaitCursor);
1820 QApplication::restoreOverrideCursor();
1825 void GuiView::resetCommandExecute()
1827 command_execute_ = false;
1832 double GuiView::pixelRatio() const
1834 #if QT_VERSION >= 0x050000
1835 return qt_scale_factor * devicePixelRatio();
1842 GuiWorkArea * GuiView::workArea(int index)
1844 if (TabWorkArea * twa = d.currentTabWorkArea())
1845 if (index < twa->count())
1846 return twa->workArea(index);
1851 GuiWorkArea * GuiView::workArea(Buffer & buffer)
1853 if (currentWorkArea()
1854 && ¤tWorkArea()->bufferView().buffer() == &buffer)
1855 return currentWorkArea();
1856 if (TabWorkArea * twa = d.currentTabWorkArea())
1857 return twa->workArea(buffer);
1862 GuiWorkArea * GuiView::addWorkArea(Buffer & buffer)
1864 // Automatically create a TabWorkArea if there are none yet.
1865 TabWorkArea * tab_widget = d.splitter_->count()
1866 ? d.currentTabWorkArea() : addTabWorkArea();
1867 return tab_widget->addWorkArea(buffer, *this);
1871 TabWorkArea * GuiView::addTabWorkArea()
1873 TabWorkArea * twa = new TabWorkArea;
1874 QObject::connect(twa, SIGNAL(currentWorkAreaChanged(GuiWorkArea *)),
1875 this, SLOT(on_currentWorkAreaChanged(GuiWorkArea *)));
1876 QObject::connect(twa, SIGNAL(lastWorkAreaRemoved()),
1877 this, SLOT(on_lastWorkAreaRemoved()));
1879 d.splitter_->addWidget(twa);
1880 d.stack_widget_->setCurrentWidget(d.splitter_);
1885 GuiWorkArea const * GuiView::currentWorkArea() const
1887 return d.current_work_area_;
1891 GuiWorkArea * GuiView::currentWorkArea()
1893 return d.current_work_area_;
1897 GuiWorkArea const * GuiView::currentMainWorkArea() const
1899 if (!d.currentTabWorkArea())
1901 return d.currentTabWorkArea()->currentWorkArea();
1905 GuiWorkArea * GuiView::currentMainWorkArea()
1907 if (!d.currentTabWorkArea())
1909 return d.currentTabWorkArea()->currentWorkArea();
1913 void GuiView::setCurrentWorkArea(GuiWorkArea * wa)
1915 LYXERR(Debug::DEBUG, "Setting current wa: " << wa << endl);
1917 d.current_work_area_ = nullptr;
1919 Q_EMIT bufferViewChanged();
1923 // FIXME: I've no clue why this is here and why it accesses
1924 // theGuiApp()->currentView, which might be 0 (bug 6464).
1925 // See also 27525 (vfr).
1926 if (theGuiApp()->currentView() == this
1927 && theGuiApp()->currentView()->currentWorkArea() == wa)
1930 if (currentBufferView())
1931 cap::saveSelection(currentBufferView()->cursor());
1933 theGuiApp()->setCurrentView(this);
1934 d.current_work_area_ = wa;
1936 // We need to reset this now, because it will need to be
1937 // right if the tabWorkArea gets reset in the for loop. We
1938 // will change it back if we aren't in that case.
1939 GuiWorkArea * const old_cmwa = d.current_main_work_area_;
1940 d.current_main_work_area_ = wa;
1942 for (int i = 0; i != d.splitter_->count(); ++i) {
1943 if (d.tabWorkArea(i)->setCurrentWorkArea(wa)) {
1944 LYXERR(Debug::DEBUG, "Current wa: " << currentWorkArea()
1945 << ", Current main wa: " << currentMainWorkArea());
1950 d.current_main_work_area_ = old_cmwa;
1952 LYXERR(Debug::DEBUG, "This is not a tabbed wa");
1953 on_currentWorkAreaChanged(wa);
1954 BufferView & bv = wa->bufferView();
1955 bv.cursor().fixIfBroken();
1957 wa->setUpdatesEnabled(true);
1958 LYXERR(Debug::DEBUG, "Current wa: " << currentWorkArea() << ", Current main wa: " << currentMainWorkArea());
1962 void GuiView::removeWorkArea(GuiWorkArea * wa)
1964 LASSERT(wa, return);
1965 if (wa == d.current_work_area_) {
1967 disconnectBufferView();
1968 d.current_work_area_ = nullptr;
1969 d.current_main_work_area_ = nullptr;
1972 bool found_twa = false;
1973 for (int i = 0; i != d.splitter_->count(); ++i) {
1974 TabWorkArea * twa = d.tabWorkArea(i);
1975 if (twa->removeWorkArea(wa)) {
1976 // Found in this tab group, and deleted the GuiWorkArea.
1978 if (twa->count() != 0) {
1979 if (d.current_work_area_ == nullptr)
1980 // This means that we are closing the current GuiWorkArea, so
1981 // switch to the next GuiWorkArea in the found TabWorkArea.
1982 setCurrentWorkArea(twa->currentWorkArea());
1984 // No more WorkAreas in this tab group, so delete it.
1991 // It is not a tabbed work area (i.e., the search work area), so it
1992 // should be deleted by other means.
1993 LASSERT(found_twa, return);
1995 if (d.current_work_area_ == nullptr) {
1996 if (d.splitter_->count() != 0) {
1997 TabWorkArea * twa = d.currentTabWorkArea();
1998 setCurrentWorkArea(twa->currentWorkArea());
2000 // No more work areas, switch to the background widget.
2001 setCurrentWorkArea(nullptr);
2007 bool GuiView::hasVisibleWorkArea(GuiWorkArea * wa) const
2009 for (int i = 0; i < d.splitter_->count(); ++i)
2010 if (d.tabWorkArea(i)->currentWorkArea() == wa)
2013 FindAndReplace * fr = static_cast<FindAndReplace*>(find("findreplaceadv", false));
2014 return fr->isVisible() && fr->hasWorkArea(wa);
2018 LayoutBox * GuiView::getLayoutDialog() const
2024 void GuiView::updateLayoutList()
2027 d.layout_->updateContents(false);
2031 void GuiView::updateToolbars()
2033 if (d.current_work_area_) {
2035 if (d.current_work_area_->bufferView().cursor().inMathed()
2036 && !d.current_work_area_->bufferView().cursor().inRegexped())
2037 context |= Toolbars::MATH;
2038 if (lyx::getStatus(FuncRequest(LFUN_LAYOUT_TABULAR)).enabled())
2039 context |= Toolbars::TABLE;
2040 if (currentBufferView()->buffer().areChangesPresent()
2041 || (lyx::getStatus(FuncRequest(LFUN_CHANGES_TRACK)).enabled()
2042 && lyx::getStatus(FuncRequest(LFUN_CHANGES_TRACK)).onOff(true))
2043 || (lyx::getStatus(FuncRequest(LFUN_CHANGES_OUTPUT)).enabled()
2044 && lyx::getStatus(FuncRequest(LFUN_CHANGES_OUTPUT)).onOff(true)))
2045 context |= Toolbars::REVIEW;
2046 if (lyx::getStatus(FuncRequest(LFUN_IN_MATHMACROTEMPLATE)).enabled())
2047 context |= Toolbars::MATHMACROTEMPLATE;
2048 if (lyx::getStatus(FuncRequest(LFUN_IN_IPA)).enabled())
2049 context |= Toolbars::IPA;
2050 if (command_execute_)
2051 context |= Toolbars::MINIBUFFER;
2052 if (minibuffer_focus_) {
2053 context |= Toolbars::MINIBUFFER_FOCUS;
2054 minibuffer_focus_ = false;
2057 for (auto const & tb_p : d.toolbars_)
2058 tb_p.second->update(context);
2060 for (auto const & tb_p : d.toolbars_)
2061 tb_p.second->update();
2065 void GuiView::refillToolbars()
2067 DynamicMenuButton::resetIconCache();
2068 for (auto const & tb_p : d.toolbars_)
2069 tb_p.second->refill();
2073 void GuiView::setBuffer(Buffer * newBuffer, bool switch_to)
2075 LYXERR(Debug::DEBUG, "Setting buffer: " << newBuffer << endl);
2076 LASSERT(newBuffer, return);
2078 GuiWorkArea * wa = workArea(*newBuffer);
2079 if (wa == nullptr) {
2081 newBuffer->masterBuffer()->updateBuffer();
2083 wa = addWorkArea(*newBuffer);
2084 // scroll to the position when the BufferView was last closed
2085 if (lyxrc.use_lastfilepos) {
2086 LastFilePosSection::FilePos filepos =
2087 theSession().lastFilePos().load(newBuffer->fileName());
2088 wa->bufferView().moveToPosition(filepos.pit, filepos.pos, 0, 0);
2091 //Disconnect the old buffer...there's no new one.
2094 connectBuffer(*newBuffer);
2095 connectBufferView(wa->bufferView());
2097 setCurrentWorkArea(wa);
2101 void GuiView::connectBuffer(Buffer & buf)
2103 buf.setGuiDelegate(this);
2107 void GuiView::disconnectBuffer()
2109 if (d.current_work_area_)
2110 d.current_work_area_->bufferView().buffer().setGuiDelegate(nullptr);
2114 void GuiView::connectBufferView(BufferView & bv)
2116 bv.setGuiDelegate(this);
2120 void GuiView::disconnectBufferView()
2122 if (d.current_work_area_)
2123 d.current_work_area_->bufferView().setGuiDelegate(nullptr);
2127 void GuiView::errors(string const & error_type, bool from_master)
2129 BufferView const * const bv = currentBufferView();
2133 ErrorList const & el = from_master ?
2134 bv->buffer().masterBuffer()->errorList(error_type) :
2135 bv->buffer().errorList(error_type);
2140 string err = error_type;
2142 err = "from_master|" + error_type;
2143 showDialog("errorlist", err);
2147 void GuiView::updateTocItem(string const & type, DocIterator const & dit)
2149 d.toc_models_.updateItem(toqstr(type), dit);
2153 void GuiView::structureChanged()
2155 // This is called from the Buffer, which has no way to ensure that cursors
2156 // in BufferView remain valid.
2157 if (documentBufferView())
2158 documentBufferView()->cursor().sanitize();
2159 // FIXME: This is slightly expensive, though less than the tocBackend update
2160 // (#9880). This also resets the view in the Toc Widget (#6675).
2161 d.toc_models_.reset(documentBufferView());
2162 // Navigator needs more than a simple update in this case. It needs to be
2164 updateDialog("toc", "");
2168 void GuiView::updateDialog(string const & name, string const & sdata)
2170 if (!isDialogVisible(name))
2173 map<string, DialogPtr>::const_iterator it = d.dialogs_.find(name);
2174 if (it == d.dialogs_.end())
2177 Dialog * const dialog = it->second.get();
2178 if (dialog->isVisibleView())
2179 dialog->initialiseParams(sdata);
2183 BufferView * GuiView::documentBufferView()
2185 return currentMainWorkArea()
2186 ? ¤tMainWorkArea()->bufferView()
2191 BufferView const * GuiView::documentBufferView() const
2193 return currentMainWorkArea()
2194 ? ¤tMainWorkArea()->bufferView()
2199 BufferView * GuiView::currentBufferView()
2201 return d.current_work_area_ ? &d.current_work_area_->bufferView() : nullptr;
2205 BufferView const * GuiView::currentBufferView() const
2207 return d.current_work_area_ ? &d.current_work_area_->bufferView() : nullptr;
2211 docstring GuiView::GuiViewPrivate::autosaveAndDestroy(
2212 Buffer const * orig, Buffer * clone)
2214 bool const success = clone->autoSave();
2216 busyBuffers.remove(orig);
2218 ? _("Automatic save done.")
2219 : _("Automatic save failed!");
2223 void GuiView::autoSave()
2225 LYXERR(Debug::INFO, "Running autoSave()");
2227 Buffer * buffer = documentBufferView()
2228 ? &documentBufferView()->buffer() : nullptr;
2230 resetAutosaveTimers();
2234 GuiViewPrivate::busyBuffers.insert(buffer);
2235 QFuture<docstring> f = QtConcurrent::run(GuiViewPrivate::autosaveAndDestroy,
2236 buffer, buffer->cloneBufferOnly());
2237 d.autosave_watcher_.setFuture(f);
2238 resetAutosaveTimers();
2242 void GuiView::resetAutosaveTimers()
2245 d.autosave_timeout_.restart();
2251 double zoomRatio(FuncRequest const & cmd, double const zr)
2253 if (cmd.argument().empty()) {
2254 if (cmd.action() == LFUN_BUFFER_ZOOM)
2256 else if (cmd.action() == LFUN_BUFFER_ZOOM_IN)
2258 else // cmd.action() == LFUN_BUFFER_ZOOM_OUT
2261 if (cmd.action() == LFUN_BUFFER_ZOOM)
2262 return convert<int>(cmd.argument()) / double(lyxrc.defaultZoom);
2263 else if (cmd.action() == LFUN_BUFFER_ZOOM_IN)
2264 return zr + convert<int>(cmd.argument()) / 100.0;
2265 else // cmd.action() == LFUN_BUFFER_ZOOM_OUT
2266 return zr - convert<int>(cmd.argument()) / 100.0;
2273 bool GuiView::getStatus(FuncRequest const & cmd, FuncStatus & flag)
2276 Buffer * buf = currentBufferView()
2277 ? ¤tBufferView()->buffer() : nullptr;
2278 Buffer * doc_buffer = documentBufferView()
2279 ? &(documentBufferView()->buffer()) : nullptr;
2282 /* In LyX/Mac, when a dialog is open, the menus of the
2283 application can still be accessed without giving focus to
2284 the main window. In this case, we want to disable the menu
2285 entries that are buffer-related.
2286 This code must not be used on Linux and Windows, since it
2287 would disable buffer-related entries when hovering over the
2288 menu (see bug #9574).
2290 if (cmd.origin() == FuncRequest::MENU && !hasFocus()) {
2296 // Check whether we need a buffer
2297 if (!lyxaction.funcHasFlag(cmd.action(), LyXAction::NoBuffer) && !buf) {
2298 // no, exit directly
2299 flag.message(from_utf8(N_("Command not allowed with"
2300 "out any document open")));
2301 flag.setEnabled(false);
2305 if (cmd.origin() == FuncRequest::TOC) {
2306 GuiToc * toc = static_cast<GuiToc*>(findOrBuild("toc", false));
2307 if (!toc || !toc->getStatus(documentBufferView()->cursor(), cmd, flag))
2308 flag.setEnabled(false);
2312 switch(cmd.action()) {
2313 case LFUN_BUFFER_IMPORT:
2316 case LFUN_MASTER_BUFFER_EXPORT:
2318 && (doc_buffer->parent() != nullptr
2319 || doc_buffer->hasChildren())
2320 && !d.processing_thread_watcher_.isRunning()
2321 // this launches a dialog, which would be in the wrong Buffer
2322 && !(::lyx::operator==(cmd.argument(), "custom"));
2325 case LFUN_MASTER_BUFFER_UPDATE:
2326 case LFUN_MASTER_BUFFER_VIEW:
2328 && (doc_buffer->parent() != nullptr
2329 || doc_buffer->hasChildren())
2330 && !d.processing_thread_watcher_.isRunning();
2333 case LFUN_BUFFER_UPDATE:
2334 case LFUN_BUFFER_VIEW: {
2335 if (!doc_buffer || d.processing_thread_watcher_.isRunning()) {
2339 string format = to_utf8(cmd.argument());
2340 if (cmd.argument().empty())
2341 format = doc_buffer->params().getDefaultOutputFormat();
2342 enable = doc_buffer->params().isExportable(format, true);
2346 case LFUN_BUFFER_RELOAD:
2347 enable = doc_buffer && !doc_buffer->isUnnamed()
2348 && doc_buffer->fileName().exists()
2349 && (!doc_buffer->isClean() || doc_buffer->notifiesExternalModification());
2352 case LFUN_BUFFER_RESET_EXPORT:
2353 enable = doc_buffer != nullptr;
2356 case LFUN_BUFFER_CHILD_OPEN:
2357 enable = doc_buffer != nullptr;
2360 case LFUN_MASTER_BUFFER_FORALL: {
2361 if (doc_buffer == nullptr) {
2362 flag.message(from_utf8(N_("Command not allowed without a buffer open")));
2366 FuncRequest const cmdToPass = lyxaction.lookupFunc(cmd.getLongArg(0));
2367 if (cmdToPass.action() == LFUN_UNKNOWN_ACTION) {
2368 flag.message(from_utf8(N_("Invalid argument of master-buffer-forall")));
2373 for (Buffer * buf : doc_buffer->allRelatives()) {
2374 GuiWorkArea * wa = workArea(*buf);
2377 if (wa->bufferView().getStatus(cmdToPass, flag)) {
2378 enable = flag.enabled();
2385 case LFUN_BUFFER_WRITE:
2386 enable = doc_buffer && (doc_buffer->isUnnamed()
2387 || (!doc_buffer->isClean()
2388 || cmd.argument() == "force"));
2391 //FIXME: This LFUN should be moved to GuiApplication.
2392 case LFUN_BUFFER_WRITE_ALL: {
2393 // We enable the command only if there are some modified buffers
2394 Buffer * first = theBufferList().first();
2399 // We cannot use a for loop as the buffer list is a cycle.
2401 if (!b->isClean()) {
2405 b = theBufferList().next(b);
2406 } while (b != first);
2410 case LFUN_BUFFER_EXTERNAL_MODIFICATION_CLEAR:
2411 enable = doc_buffer && doc_buffer->notifiesExternalModification();
2414 case LFUN_BUFFER_EXPORT: {
2415 if (!doc_buffer || d.processing_thread_watcher_.isRunning()) {
2419 return doc_buffer->getStatus(cmd, flag);
2422 case LFUN_BUFFER_EXPORT_AS:
2423 if (!doc_buffer || d.processing_thread_watcher_.isRunning()) {
2428 case LFUN_BUFFER_WRITE_AS:
2429 case LFUN_BUFFER_WRITE_AS_TEMPLATE:
2430 enable = doc_buffer != nullptr;
2433 case LFUN_EXPORT_CANCEL:
2434 enable = d.processing_thread_watcher_.isRunning();
2437 case LFUN_BUFFER_CLOSE:
2438 case LFUN_VIEW_CLOSE:
2439 enable = doc_buffer != nullptr;
2442 case LFUN_BUFFER_CLOSE_ALL:
2443 enable = theBufferList().last() != theBufferList().first();
2446 case LFUN_BUFFER_CHKTEX: {
2447 // hide if we have no checktex command
2448 if (lyxrc.chktex_command.empty()) {
2449 flag.setUnknown(true);
2453 if (!doc_buffer || !doc_buffer->params().isLatex()
2454 || d.processing_thread_watcher_.isRunning()) {
2455 // grey out, don't hide
2463 case LFUN_VIEW_SPLIT:
2464 if (cmd.getArg(0) == "vertical")
2465 enable = doc_buffer && (d.splitter_->count() == 1 ||
2466 d.splitter_->orientation() == Qt::Vertical);
2468 enable = doc_buffer && (d.splitter_->count() == 1 ||
2469 d.splitter_->orientation() == Qt::Horizontal);
2472 case LFUN_TAB_GROUP_CLOSE:
2473 enable = d.tabWorkAreaCount() > 1;
2476 case LFUN_DEVEL_MODE_TOGGLE:
2477 flag.setOnOff(devel_mode_);
2480 case LFUN_TOOLBAR_SET: {
2481 string const name = cmd.getArg(0);
2482 string const state = cmd.getArg(1);
2483 if (name.empty() || state.empty()) {
2485 docstring const msg =
2486 _("Function toolbar-set requires two arguments!");
2490 if (state != "on" && state != "off" && state != "auto") {
2492 docstring const msg =
2493 bformat(_("Invalid argument \"%1$s\" to function toolbar-set!"),
2498 if (GuiToolbar * t = toolbar(name)) {
2499 bool const autovis = t->visibility() & Toolbars::AUTO;
2501 flag.setOnOff(t->isVisible() && !autovis);
2502 else if (state == "off")
2503 flag.setOnOff(!t->isVisible() && !autovis);
2504 else if (state == "auto")
2505 flag.setOnOff(autovis);
2508 docstring const msg =
2509 bformat(_("Unknown toolbar \"%1$s\""), from_utf8(name));
2515 case LFUN_TOOLBAR_TOGGLE: {
2516 string const name = cmd.getArg(0);
2517 if (GuiToolbar * t = toolbar(name))
2518 flag.setOnOff(t->isVisible());
2521 docstring const msg =
2522 bformat(_("Unknown toolbar \"%1$s\""), from_utf8(name));
2528 case LFUN_TOOLBAR_MOVABLE: {
2529 string const name = cmd.getArg(0);
2530 // use negation since locked == !movable
2532 // toolbar name * locks all toolbars
2533 flag.setOnOff(!toolbarsMovable_);
2534 else if (GuiToolbar * t = toolbar(name))
2535 flag.setOnOff(!(t->isMovable()));
2538 docstring const msg =
2539 bformat(_("Unknown toolbar \"%1$s\""), from_utf8(name));
2545 case LFUN_ICON_SIZE:
2546 flag.setOnOff(d.iconSize(cmd.argument()) == iconSize());
2549 case LFUN_DROP_LAYOUTS_CHOICE:
2550 enable = buf != nullptr;
2553 case LFUN_UI_TOGGLE:
2554 if (cmd.argument() == "zoomlevel") {
2555 flag.setOnOff(zoom_value_ ? zoom_value_->isVisible() : false);
2556 } else if (cmd.argument() == "zoomslider") {
2557 flag.setOnOff(zoom_slider_ ? zoom_slider_->isVisible() : false);
2558 } else if (cmd.argument() == "statistics-w") {
2559 flag.setOnOff(word_count_enabled_);
2560 } else if (cmd.argument() == "statistics-cb") {
2561 flag.setOnOff(char_count_enabled_);
2562 } else if (cmd.argument() == "statistics-c") {
2563 flag.setOnOff(char_nb_count_enabled_);
2565 flag.setOnOff(isFullScreen());
2568 case LFUN_DIALOG_DISCONNECT_INSET:
2571 case LFUN_DIALOG_HIDE:
2572 // FIXME: should we check if the dialog is shown?
2575 case LFUN_DIALOG_TOGGLE:
2576 flag.setOnOff(isDialogVisible(cmd.getArg(0)));
2579 case LFUN_DIALOG_SHOW: {
2580 string const name = cmd.getArg(0);
2582 enable = name == "aboutlyx"
2583 || name == "file" //FIXME: should be removed.
2584 || name == "lyxfiles"
2586 || name == "texinfo"
2587 || name == "progress"
2588 || name == "compare";
2589 else if (name == "character" || name == "symbols"
2590 || name == "mathdelimiter" || name == "mathmatrix") {
2591 if (!buf || buf->isReadonly())
2594 Cursor const & cur = currentBufferView()->cursor();
2595 enable = !(cur.inTexted() && cur.paragraph().isPassThru());
2598 else if (name == "latexlog")
2599 enable = FileName(doc_buffer->logName()).isReadableFile();
2600 else if (name == "spellchecker")
2601 enable = theSpellChecker()
2602 && !doc_buffer->text().empty();
2603 else if (name == "vclog")
2604 enable = doc_buffer->lyxvc().inUse();
2608 case LFUN_DIALOG_UPDATE: {
2609 string const name = cmd.getArg(0);
2611 enable = name == "prefs";
2615 case LFUN_COMMAND_EXECUTE:
2617 case LFUN_MENU_OPEN:
2618 // Nothing to check.
2621 case LFUN_COMPLETION_INLINE:
2622 if (!d.current_work_area_
2623 || !d.current_work_area_->completer().inlinePossible(
2624 currentBufferView()->cursor()))
2628 case LFUN_COMPLETION_POPUP:
2629 if (!d.current_work_area_
2630 || !d.current_work_area_->completer().popupPossible(
2631 currentBufferView()->cursor()))
2636 if (!d.current_work_area_
2637 || !d.current_work_area_->completer().inlinePossible(
2638 currentBufferView()->cursor()))
2642 case LFUN_COMPLETION_ACCEPT:
2643 if (!d.current_work_area_
2644 || (!d.current_work_area_->completer().popupVisible()
2645 && !d.current_work_area_->completer().inlineVisible()
2646 && !d.current_work_area_->completer().completionAvailable()))
2650 case LFUN_COMPLETION_CANCEL:
2651 if (!d.current_work_area_
2652 || (!d.current_work_area_->completer().popupVisible()
2653 && !d.current_work_area_->completer().inlineVisible()))
2657 case LFUN_BUFFER_ZOOM_OUT:
2658 case LFUN_BUFFER_ZOOM_IN:
2659 case LFUN_BUFFER_ZOOM: {
2660 int const zoom = (int)(lyxrc.defaultZoom * zoomRatio(cmd, zoom_ratio_));
2661 if (zoom < zoom_min_) {
2662 docstring const msg =
2663 bformat(_("Zoom level cannot be less than %1$d%."), zoom_min_);
2666 } else if (zoom > zoom_max_) {
2667 docstring const msg =
2668 bformat(_("Zoom level cannot be more than %1$d%."), zoom_max_);
2672 enable = doc_buffer;
2677 case LFUN_BUFFER_MOVE_NEXT:
2678 case LFUN_BUFFER_MOVE_PREVIOUS:
2679 // we do not cycle when moving
2680 case LFUN_BUFFER_NEXT:
2681 case LFUN_BUFFER_PREVIOUS:
2682 // because we cycle, it doesn't matter whether on first or last
2683 enable = (d.currentTabWorkArea()->count() > 1);
2685 case LFUN_BUFFER_SWITCH:
2686 // toggle on the current buffer, but do not toggle off
2687 // the other ones (is that a good idea?)
2689 && to_utf8(cmd.argument()) == doc_buffer->absFileName())
2690 flag.setOnOff(true);
2693 case LFUN_VC_REGISTER:
2694 enable = doc_buffer && !doc_buffer->lyxvc().inUse();
2696 case LFUN_VC_RENAME:
2697 enable = doc_buffer && doc_buffer->lyxvc().renameEnabled();
2700 enable = doc_buffer && doc_buffer->lyxvc().copyEnabled();
2702 case LFUN_VC_CHECK_IN:
2703 enable = doc_buffer && doc_buffer->lyxvc().checkInEnabled();
2705 case LFUN_VC_CHECK_OUT:
2706 enable = doc_buffer && doc_buffer->lyxvc().checkOutEnabled();
2708 case LFUN_VC_LOCKING_TOGGLE:
2709 enable = doc_buffer && !doc_buffer->hasReadonlyFlag()
2710 && doc_buffer->lyxvc().lockingToggleEnabled();
2711 flag.setOnOff(enable && doc_buffer->lyxvc().locking());
2713 case LFUN_VC_REVERT:
2714 enable = doc_buffer && doc_buffer->lyxvc().inUse()
2715 && !doc_buffer->hasReadonlyFlag();
2717 case LFUN_VC_UNDO_LAST:
2718 enable = doc_buffer && doc_buffer->lyxvc().undoLastEnabled();
2720 case LFUN_VC_REPO_UPDATE:
2721 enable = doc_buffer && doc_buffer->lyxvc().repoUpdateEnabled();
2723 case LFUN_VC_COMMAND: {
2724 if (cmd.argument().empty())
2726 if (!doc_buffer && contains(cmd.getArg(0), 'D'))
2730 case LFUN_VC_COMPARE:
2731 enable = doc_buffer && doc_buffer->lyxvc().prepareFileRevisionEnabled();
2734 case LFUN_SERVER_GOTO_FILE_ROW:
2735 case LFUN_LYX_ACTIVATE:
2736 case LFUN_WINDOW_RAISE:
2738 case LFUN_FORWARD_SEARCH:
2739 enable = !(lyxrc.forward_search_dvi.empty() && lyxrc.forward_search_pdf.empty()) &&
2740 doc_buffer && doc_buffer->isSyncTeXenabled();
2743 case LFUN_FILE_INSERT_PLAINTEXT:
2744 case LFUN_FILE_INSERT_PLAINTEXT_PARA:
2745 enable = documentBufferView() && documentBufferView()->cursor().inTexted();
2748 case LFUN_SPELLING_CONTINUOUSLY:
2749 flag.setOnOff(lyxrc.spellcheck_continuously);
2752 case LFUN_CITATION_OPEN:
2761 flag.setEnabled(false);
2767 static FileName selectTemplateFile()
2769 FileDialog dlg(qt_("Select template file"));
2770 dlg.setButton1(qt_("D&ocuments"), toqstr(lyxrc.document_path));
2771 dlg.setButton2(qt_("&Templates"), toqstr(lyxrc.template_path));
2773 FileDialog::Result result = dlg.open(toqstr(lyxrc.template_path),
2774 QStringList(qt_("LyX Documents (*.lyx)")));
2776 if (result.first == FileDialog::Later)
2778 if (result.second.isEmpty())
2780 return FileName(fromqstr(result.second));
2784 Buffer * GuiView::loadDocument(FileName const & filename, bool tolastfiles)
2788 Buffer * newBuffer = nullptr;
2790 newBuffer = checkAndLoadLyXFile(filename);
2791 } catch (ExceptionMessage const &) {
2798 message(_("Document not loaded."));
2802 setBuffer(newBuffer);
2803 newBuffer->errors("Parse");
2806 theSession().lastFiles().add(filename);
2807 theSession().writeFile();
2814 void GuiView::openDocument(string const & fname)
2816 string initpath = lyxrc.document_path;
2818 if (documentBufferView()) {
2819 string const trypath = documentBufferView()->buffer().filePath();
2820 // If directory is writeable, use this as default.
2821 if (FileName(trypath).isDirWritable())
2827 if (fname.empty()) {
2828 FileDialog dlg(qt_("Select document to open"));
2829 dlg.setButton1(qt_("D&ocuments"), toqstr(lyxrc.document_path));
2830 dlg.setButton2(qt_("&Examples"), toqstr(lyxrc.example_path));
2832 QStringList const filter({
2833 qt_("LyX Documents (*.lyx)"),
2834 qt_("LyX Document Backups (*.lyx~)"),
2835 qt_("All Files (*.*)")
2837 FileDialog::Result result =
2838 dlg.open(toqstr(initpath), filter);
2840 if (result.first == FileDialog::Later)
2843 filename = fromqstr(result.second);
2845 // check selected filename
2846 if (filename.empty()) {
2847 message(_("Canceled."));
2853 // get absolute path of file and add ".lyx" to the filename if
2855 FileName const fullname =
2856 fileSearch(string(), filename, "lyx", support::may_not_exist);
2857 if (!fullname.empty())
2858 filename = fullname.absFileName();
2860 if (!fullname.onlyPath().isDirectory()) {
2861 Alert::warning(_("Invalid filename"),
2862 bformat(_("The directory in the given path\n%1$s\ndoes not exist."),
2863 from_utf8(fullname.absFileName())));
2867 // if the file doesn't exist and isn't already open (bug 6645),
2868 // let the user create one
2869 if (!fullname.exists() && !theBufferList().exists(fullname) &&
2870 !LyXVC::file_not_found_hook(fullname)) {
2871 // the user specifically chose this name. Believe him.
2872 Buffer * const b = newFile(filename, string(), true);
2878 docstring const disp_fn = makeDisplayPath(filename);
2879 message(bformat(_("Opening document %1$s..."), disp_fn));
2882 Buffer * buf = loadDocument(fullname);
2884 str2 = bformat(_("Document %1$s opened."), disp_fn);
2885 if (buf->lyxvc().inUse())
2886 str2 += " " + from_utf8(buf->lyxvc().versionString()) +
2887 " " + _("Version control detected.");
2889 str2 = bformat(_("Could not open document %1$s"), disp_fn);
2894 // FIXME: clean that
2895 static bool import(GuiView * lv, FileName const & filename,
2896 string const & format, ErrorList & errorList)
2898 FileName const lyxfile(support::changeExtension(filename.absFileName(), ".lyx"));
2900 string loader_format;
2901 vector<string> loaders = theConverters().loaders();
2902 if (find(loaders.begin(), loaders.end(), format) == loaders.end()) {
2903 for (string const & loader : loaders) {
2904 if (!theConverters().isReachable(format, loader))
2907 string const tofile =
2908 support::changeExtension(filename.absFileName(),
2909 theFormats().extension(loader));
2910 if (theConverters().convert(nullptr, filename, FileName(tofile),
2911 filename, format, loader, errorList) != Converters::SUCCESS)
2913 loader_format = loader;
2916 if (loader_format.empty()) {
2917 frontend::Alert::error(_("Couldn't import file"),
2918 bformat(_("No information for importing the format %1$s."),
2919 translateIfPossible(theFormats().prettyName(format))));
2923 loader_format = format;
2925 if (loader_format == "lyx") {
2926 Buffer * buf = lv->loadDocument(lyxfile);
2930 Buffer * const b = newFile(lyxfile.absFileName(), string(), true);
2934 bool as_paragraphs = loader_format == "textparagraph";
2935 string filename2 = (loader_format == format) ? filename.absFileName()
2936 : support::changeExtension(filename.absFileName(),
2937 theFormats().extension(loader_format));
2938 lv->currentBufferView()->insertPlaintextFile(FileName(filename2),
2940 guiApp->setCurrentView(lv);
2941 lyx::dispatch(FuncRequest(LFUN_MARK_OFF));
2948 void GuiView::importDocument(string const & argument)
2951 string filename = split(argument, format, ' ');
2953 LYXERR(Debug::INFO, format << " file: " << filename);
2955 // need user interaction
2956 if (filename.empty()) {
2957 string initpath = lyxrc.document_path;
2958 if (documentBufferView()) {
2959 string const trypath = documentBufferView()->buffer().filePath();
2960 // If directory is writeable, use this as default.
2961 if (FileName(trypath).isDirWritable())
2965 docstring const text = bformat(_("Select %1$s file to import"),
2966 translateIfPossible(theFormats().prettyName(format)));
2968 FileDialog dlg(toqstr(text));
2969 dlg.setButton1(qt_("D&ocuments"), toqstr(lyxrc.document_path));
2970 dlg.setButton2(qt_("&Examples"), toqstr(lyxrc.example_path));
2972 docstring filter = translateIfPossible(theFormats().prettyName(format));
2975 filter += from_utf8(theFormats().extensions(format));
2978 FileDialog::Result result =
2979 dlg.open(toqstr(initpath), fileFilters(toqstr(filter)));
2981 if (result.first == FileDialog::Later)
2984 filename = fromqstr(result.second);
2986 // check selected filename
2987 if (filename.empty())
2988 message(_("Canceled."));
2991 if (filename.empty())
2994 // get absolute path of file
2995 FileName const fullname(support::makeAbsPath(filename));
2997 // Can happen if the user entered a path into the dialog
2999 if (fullname.onlyFileName().empty()) {
3000 docstring msg = bformat(_("The file name '%1$s' is invalid!\n"
3001 "Aborting import."),
3002 from_utf8(fullname.absFileName()));
3003 frontend::Alert::error(_("File name error"), msg);
3004 message(_("Canceled."));
3009 FileName const lyxfile(support::changeExtension(fullname.absFileName(), ".lyx"));
3011 // Check if the document already is open
3012 Buffer * buf = theBufferList().getBuffer(lyxfile);
3015 if (!closeBuffer()) {
3016 message(_("Canceled."));
3021 docstring const displaypath = makeDisplayPath(lyxfile.absFileName(), 30);
3023 // if the file exists already, and we didn't do
3024 // -i lyx thefile.lyx, warn
3025 if (lyxfile.exists() && fullname != lyxfile) {
3027 docstring text = bformat(_("The document %1$s already exists.\n\n"
3028 "Do you want to overwrite that document?"), displaypath);
3029 int const ret = Alert::prompt(_("Overwrite document?"),
3030 text, 0, 1, _("&Overwrite"), _("&Cancel"));
3033 message(_("Canceled."));
3038 message(bformat(_("Importing %1$s..."), displaypath));
3039 ErrorList errorList;
3040 if (import(this, fullname, format, errorList))
3041 message(_("imported."));
3043 message(_("file not imported!"));
3045 // FIXME (Abdel 12/08/06): Is there a need to display the error list here?
3049 void GuiView::newDocument(string const & filename, string templatefile,
3052 FileName initpath(lyxrc.document_path);
3053 if (documentBufferView()) {
3054 FileName const trypath(documentBufferView()->buffer().filePath());
3055 // If directory is writeable, use this as default.
3056 if (trypath.isDirWritable())
3060 if (from_template) {
3061 if (templatefile.empty())
3062 templatefile = selectTemplateFile().absFileName();
3063 if (templatefile.empty())
3068 if (filename.empty())
3069 b = newUnnamedFile(initpath, to_utf8(_("newfile")), templatefile);
3071 b = newFile(filename, templatefile, true);
3076 // If no new document could be created, it is unsure
3077 // whether there is a valid BufferView.
3078 if (currentBufferView())
3079 // Ensure the cursor is correctly positioned on screen.
3080 currentBufferView()->showCursor();
3084 bool GuiView::insertLyXFile(docstring const & fname, bool ignorelang)
3086 BufferView * bv = documentBufferView();
3091 FileName filename(to_utf8(fname));
3092 if (filename.empty()) {
3093 // Launch a file browser
3095 string initpath = lyxrc.document_path;
3096 string const trypath = bv->buffer().filePath();
3097 // If directory is writeable, use this as default.
3098 if (FileName(trypath).isDirWritable())
3102 FileDialog dlg(qt_("Select LyX document to insert"));
3103 dlg.setButton1(qt_("D&ocuments"), toqstr(lyxrc.document_path));
3104 dlg.setButton2(qt_("&Examples"), toqstr(lyxrc.example_path));
3106 FileDialog::Result result = dlg.open(toqstr(initpath),
3107 QStringList(qt_("LyX Documents (*.lyx)")));
3109 if (result.first == FileDialog::Later)
3113 filename.set(fromqstr(result.second));
3115 // check selected filename
3116 if (filename.empty()) {
3117 // emit message signal.
3118 message(_("Canceled."));
3123 bv->insertLyXFile(filename, ignorelang);
3124 bv->buffer().errors("Parse");
3129 string const GuiView::getTemplatesPath(Buffer & b)
3131 // We start off with the user's templates path
3132 string result = addPath(package().user_support().absFileName(), "templates");
3133 // Check for the document language
3134 string const langcode = b.params().language->code();
3135 string const shortcode = langcode.substr(0, 2);
3136 if (!langcode.empty() && shortcode != "en") {
3137 string subpath = addPath(result, shortcode);
3138 string subpath_long = addPath(result, langcode);
3139 // If we have a subdirectory for the language already,
3141 FileName sp = FileName(subpath);
3142 if (sp.isDirectory())
3144 else if (FileName(subpath_long).isDirectory())
3145 result = subpath_long;
3147 // Ask whether we should create such a subdirectory
3148 docstring const text =
3149 bformat(_("It is suggested to save the template in a subdirectory\n"
3150 "appropriate to the document language (%1$s).\n"
3151 "This subdirectory does not exists yet.\n"
3152 "Do you want to create it?"),
3153 _(b.params().language->display()));
3154 if (Alert::prompt(_("Create Language Directory?"),
3155 text, 0, 1, _("&Yes, Create"), _("&No, Save Template in Parent Directory")) == 0) {
3156 // If the user agreed, we try to create it and report if this failed.
3157 if (!sp.createDirectory(0777))
3158 Alert::error(_("Subdirectory creation failed!"),
3159 _("Could not create subdirectory.\n"
3160 "The template will be saved in the parent directory."));
3166 // Do we have a layout category?
3167 string const cat = b.params().baseClass() ?
3168 b.params().baseClass()->category()
3171 string subpath = addPath(result, cat);
3172 // If we have a subdirectory for the category already,
3174 FileName sp = FileName(subpath);
3175 if (sp.isDirectory())
3178 // Ask whether we should create such a subdirectory
3179 docstring const text =
3180 bformat(_("It is suggested to save the template in a subdirectory\n"
3181 "appropriate to the layout category (%1$s).\n"
3182 "This subdirectory does not exists yet.\n"
3183 "Do you want to create it?"),
3185 if (Alert::prompt(_("Create Category Directory?"),
3186 text, 0, 1, _("&Yes, Create"), _("&No, Save Template in Parent Directory")) == 0) {
3187 // If the user agreed, we try to create it and report if this failed.
3188 if (!sp.createDirectory(0777))
3189 Alert::error(_("Subdirectory creation failed!"),
3190 _("Could not create subdirectory.\n"
3191 "The template will be saved in the parent directory."));
3201 bool GuiView::renameBuffer(Buffer & b, docstring const & newname, RenameKind kind)
3203 FileName fname = b.fileName();
3204 FileName const oldname = fname;
3205 bool const as_template = (kind == LV_WRITE_AS_TEMPLATE);
3207 if (!newname.empty()) {
3210 fname = support::makeAbsPath(to_utf8(newname), getTemplatesPath(b));
3212 fname = support::makeAbsPath(to_utf8(newname),
3213 oldname.onlyPath().absFileName());
3215 // Switch to this Buffer.
3218 // No argument? Ask user through dialog.
3220 QString const title = as_template ? qt_("Choose a filename to save template as")
3221 : qt_("Choose a filename to save document as");
3222 FileDialog dlg(title);
3223 dlg.setButton1(qt_("D&ocuments"), toqstr(lyxrc.document_path));
3224 dlg.setButton2(qt_("&Templates"), toqstr(lyxrc.template_path));
3226 fname.ensureExtension(".lyx");
3228 string const path = as_template ?
3230 : fname.onlyPath().absFileName();
3231 FileDialog::Result result =
3232 dlg.save(toqstr(path),
3233 QStringList(qt_("LyX Documents (*.lyx)")),
3234 toqstr(fname.onlyFileName()));
3236 if (result.first == FileDialog::Later)
3239 fname.set(fromqstr(result.second));
3244 fname.ensureExtension(".lyx");
3247 // fname is now the new Buffer location.
3249 // if there is already a Buffer open with this name, we do not want
3250 // to have another one. (the second test makes sure we're not just
3251 // trying to overwrite ourselves, which is fine.)
3252 if (theBufferList().exists(fname) && fname != oldname
3253 && theBufferList().getBuffer(fname) != &b) {
3254 docstring const text =
3255 bformat(_("The file\n%1$s\nis already open in your current session.\n"
3256 "Please close it before attempting to overwrite it.\n"
3257 "Do you want to choose a new filename?"),
3258 from_utf8(fname.absFileName()));
3259 int const ret = Alert::prompt(_("Chosen File Already Open"),
3260 text, 0, 1, _("&Rename"), _("&Cancel"));
3262 case 0: return renameBuffer(b, docstring(), kind);
3263 case 1: return false;
3268 bool const existsLocal = fname.exists();
3269 bool const existsInVC = LyXVC::fileInVC(fname);
3270 if (existsLocal || existsInVC) {
3271 docstring const file = makeDisplayPath(fname.absFileName(), 30);
3272 if (kind != LV_WRITE_AS && existsInVC) {
3273 // renaming to a name that is already in VC
3275 docstring text = bformat(_("The document %1$s "
3276 "is already registered.\n\n"
3277 "Do you want to choose a new name?"),
3279 docstring const title = (kind == LV_VC_RENAME) ?
3280 _("Rename document?") : _("Copy document?");
3281 docstring const button = (kind == LV_VC_RENAME) ?
3282 _("&Rename") : _("&Copy");
3283 int const ret = Alert::prompt(title, text, 0, 1,
3284 button, _("&Cancel"));
3286 case 0: return renameBuffer(b, docstring(), kind);
3287 case 1: return false;
3292 docstring text = bformat(_("The document %1$s "
3293 "already exists.\n\n"
3294 "Do you want to overwrite that document?"),
3296 int const ret = Alert::prompt(_("Overwrite document?"),
3297 text, 0, 2, _("&Overwrite"),
3298 _("&Rename"), _("&Cancel"));
3301 case 1: return renameBuffer(b, docstring(), kind);
3302 case 2: return false;
3308 case LV_VC_RENAME: {
3309 string msg = b.lyxvc().rename(fname);
3312 message(from_utf8(msg));
3316 string msg = b.lyxvc().copy(fname);
3319 message(from_utf8(msg));
3323 case LV_WRITE_AS_TEMPLATE:
3326 // LyXVC created the file already in case of LV_VC_RENAME or
3327 // LV_VC_COPY, but call saveBuffer() nevertheless to get
3328 // relative paths of included stuff right if we moved e.g. from
3329 // /a/b.lyx to /a/c/b.lyx.
3331 bool const saved = saveBuffer(b, fname);
3338 bool GuiView::exportBufferAs(Buffer & b, docstring const & iformat)
3340 FileName fname = b.fileName();
3342 FileDialog dlg(qt_("Choose a filename to export the document as"));
3343 dlg.setButton1(qt_("D&ocuments"), toqstr(lyxrc.document_path));
3346 QString const anyformat = qt_("Guess from extension (*.*)");
3349 vector<Format const *> export_formats;
3350 for (Format const & f : theFormats())
3351 if (f.documentFormat())
3352 export_formats.push_back(&f);
3353 sort(export_formats.begin(), export_formats.end(), Format::formatSorter);
3354 map<QString, string> fmap;
3357 for (Format const * f : export_formats) {
3358 docstring const loc_prettyname = translateIfPossible(f->prettyname());
3359 QString const loc_filter = toqstr(bformat(from_ascii("%1$s (*.%2$s)"),
3361 from_ascii(f->extension())));
3362 types << loc_filter;
3363 fmap[loc_filter] = f->name();
3364 if (from_ascii(f->name()) == iformat) {
3365 filter = loc_filter;
3366 ext = f->extension();
3369 string ofname = fname.onlyFileName();
3371 ofname = support::changeExtension(ofname, ext);
3372 FileDialog::Result result =
3373 dlg.save(toqstr(fname.onlyPath().absFileName()),
3377 if (result.first != FileDialog::Chosen)
3381 fname.set(fromqstr(result.second));
3382 if (filter == anyformat)
3383 fmt_name = theFormats().getFormatFromExtension(fname.extension());
3385 fmt_name = fmap[filter];
3386 LYXERR(Debug::FILES, "filter=" << fromqstr(filter)
3387 << ", fmt_name=" << fmt_name << ", fname=" << fname.absFileName());
3389 if (fmt_name.empty() || fname.empty())
3392 fname.ensureExtension(theFormats().extension(fmt_name));
3394 // fname is now the new Buffer location.
3395 if (fname.exists()) {
3396 docstring const file = makeDisplayPath(fname.absFileName(), 30);
3397 docstring text = bformat(_("The document %1$s already "
3398 "exists.\n\nDo you want to "
3399 "overwrite that document?"),
3401 int const ret = Alert::prompt(_("Overwrite document?"),
3402 text, 0, 2, _("&Overwrite"), _("&Rename"), _("&Cancel"));
3405 case 1: return exportBufferAs(b, from_ascii(fmt_name));
3406 case 2: return false;
3410 FuncRequest cmd(LFUN_BUFFER_EXPORT, fmt_name + " " + fname.absFileName());
3413 return dr.dispatched();
3417 bool GuiView::saveBuffer(Buffer & b)
3419 return saveBuffer(b, FileName());
3423 bool GuiView::saveBuffer(Buffer & b, FileName const & fn)
3425 if (workArea(b) && workArea(b)->inDialogMode())
3428 if (fn.empty() && b.isUnnamed())
3429 return renameBuffer(b, docstring());
3431 bool const success = (fn.empty() ? b.save() : b.saveAs(fn));
3433 theSession().lastFiles().add(b.fileName());
3434 theSession().writeFile();
3438 // Switch to this Buffer.
3441 // FIXME: we don't tell the user *WHY* the save failed !!
3442 docstring const file = makeDisplayPath(b.absFileName(), 30);
3443 docstring text = bformat(_("The document %1$s could not be saved.\n\n"
3444 "Do you want to rename the document and "
3445 "try again?"), file);
3446 int const ret = Alert::prompt(_("Rename and save?"),
3447 text, 0, 2, _("&Rename"), _("&Retry"), _("&Cancel"));
3450 if (!renameBuffer(b, docstring()))
3459 return saveBuffer(b, fn);
3463 bool GuiView::hideWorkArea(GuiWorkArea * wa)
3465 return closeWorkArea(wa, false);
3469 // We only want to close the buffer if it is not visible in other workareas
3470 // of the same view, nor in other views, and if this is not a child
3471 bool GuiView::closeWorkArea(GuiWorkArea * wa)
3473 Buffer & buf = wa->bufferView().buffer();
3475 bool last_wa = d.countWorkAreasOf(buf) == 1
3476 && !inOtherView(buf) && !buf.parent();
3478 bool close_buffer = last_wa;
3481 if (lyxrc.close_buffer_with_last_view == "yes")
3483 else if (lyxrc.close_buffer_with_last_view == "no")
3484 close_buffer = false;
3487 if (buf.isUnnamed())
3488 file = from_utf8(buf.fileName().onlyFileName());
3490 file = buf.fileName().displayName(30);
3491 docstring const text = bformat(
3492 _("Last view on document %1$s is being closed.\n"
3493 "Would you like to close or hide the document?\n"
3495 "Hidden documents can be displayed back through\n"
3496 "the menu: View->Hidden->...\n"
3498 "To remove this question, set your preference in:\n"
3499 " Tools->Preferences->Look&Feel->UserInterface\n"
3501 int ret = Alert::prompt(_("Close or hide document?"),
3502 text, 0, 2, _("&Close"), _("&Hide"), _("&Cancel"));
3505 close_buffer = (ret == 0);
3509 return closeWorkArea(wa, close_buffer);
3513 bool GuiView::closeBuffer()
3515 GuiWorkArea * wa = currentMainWorkArea();
3516 // coverity complained about this
3517 // it seems unnecessary, but perhaps is worth the check
3518 LASSERT(wa, return false);
3520 setCurrentWorkArea(wa);
3521 Buffer & buf = wa->bufferView().buffer();
3522 return closeWorkArea(wa, !buf.parent());
3526 void GuiView::writeSession() const {
3527 GuiWorkArea const * active_wa = currentMainWorkArea();
3528 for (int i = 0; i < d.splitter_->count(); ++i) {
3529 TabWorkArea * twa = d.tabWorkArea(i);
3530 for (int j = 0; j < twa->count(); ++j) {
3531 GuiWorkArea * wa = twa->workArea(j);
3532 Buffer & buf = wa->bufferView().buffer();
3533 theSession().lastOpened().add(buf.fileName(), wa == active_wa);
3539 bool GuiView::closeBufferAll()
3542 for (auto & buf : theBufferList()) {
3543 if (!saveBufferIfNeeded(*buf, false)) {
3544 // Closing has been cancelled, so abort.
3549 // Close the workareas in all other views
3550 QList<int> const ids = guiApp->viewIds();
3551 for (int i = 0; i != ids.size(); ++i) {
3552 if (id_ != ids[i] && !guiApp->view(ids[i]).closeWorkAreaAll())
3556 // Close our own workareas
3557 if (!closeWorkAreaAll())
3564 bool GuiView::closeWorkAreaAll()
3566 setCurrentWorkArea(currentMainWorkArea());
3568 // We might be in a situation that there is still a tabWorkArea, but
3569 // there are no tabs anymore. This can happen when we get here after a
3570 // TabWorkArea::lastWorkAreaRemoved() signal. Therefore we count how
3571 // many TabWorkArea's have no documents anymore.
3574 // We have to call count() each time, because it can happen that
3575 // more than one splitter will disappear in one iteration (bug 5998).
3576 while (d.splitter_->count() > empty_twa) {
3577 TabWorkArea * twa = d.tabWorkArea(empty_twa);
3579 if (twa->count() == 0)
3582 setCurrentWorkArea(twa->currentWorkArea());
3583 if (!closeTabWorkArea(twa))
3591 bool GuiView::closeWorkArea(GuiWorkArea * wa, bool close_buffer)
3596 Buffer & buf = wa->bufferView().buffer();
3598 if (GuiViewPrivate::busyBuffers.contains(&buf)) {
3599 Alert::warning(_("Close document"),
3600 _("Document could not be closed because it is being processed by LyX."));
3605 return closeBuffer(buf);
3607 if (!inMultiTabs(wa))
3608 if (!saveBufferIfNeeded(buf, true))
3616 bool GuiView::closeBuffer(Buffer & buf)
3618 bool success = true;
3619 for (Buffer * child_buf : buf.getChildren()) {
3620 if (theBufferList().isOthersChild(&buf, child_buf)) {
3621 child_buf->setParent(nullptr);
3625 // FIXME: should we look in other tabworkareas?
3626 // ANSWER: I don't think so. I've tested, and if the child is
3627 // open in some other window, it closes without a problem.
3628 GuiWorkArea * child_wa = workArea(*child_buf);
3631 // If we are in a close_event all children will be closed in some time,
3632 // so no need to do it here. This will ensure that the children end up
3633 // in the session file in the correct order. If we close the master
3634 // buffer, we can close or release the child buffers here too.
3636 success = closeWorkArea(child_wa, true);
3640 // In this case the child buffer is open but hidden.
3641 // Even in this case, children can be dirty (e.g.,
3642 // after a label change in the master, see #11405).
3643 // Therefore, check this
3644 if (closing_ && (child_buf->isClean() || child_buf->paragraphs().empty())) {
3645 // If we are in a close_event all children will be closed in some time,
3646 // so no need to do it here. This will ensure that the children end up
3647 // in the session file in the correct order. If we close the master
3648 // buffer, we can close or release the child buffers here too.
3651 // Save dirty buffers also if closing_!
3652 if (saveBufferIfNeeded(*child_buf, false)) {
3653 child_buf->removeAutosaveFile();
3654 theBufferList().release(child_buf);
3656 // Saving of dirty children has been cancelled.
3657 // Cancel the whole process.
3664 // goto bookmark to update bookmark pit.
3665 // FIXME: we should update only the bookmarks related to this buffer!
3666 // FIXME: this is done also in LFUN_WINDOW_CLOSE!
3667 LYXERR(Debug::DEBUG, "GuiView::closeBuffer()");
3668 for (unsigned int i = 1; i < theSession().bookmarks().size(); ++i)
3669 guiApp->gotoBookmark(i, false, false);
3671 if (saveBufferIfNeeded(buf, false)) {
3672 buf.removeAutosaveFile();
3673 theBufferList().release(&buf);
3677 // open all children again to avoid a crash because of dangling
3678 // pointers (bug 6603)
3684 bool GuiView::closeTabWorkArea(TabWorkArea * twa)
3686 while (twa == d.currentTabWorkArea()) {
3687 twa->setCurrentIndex(twa->count() - 1);
3689 GuiWorkArea * wa = twa->currentWorkArea();
3690 Buffer & b = wa->bufferView().buffer();
3692 // We only want to close the buffer if the same buffer is not visible
3693 // in another view, and if this is not a child and if we are closing
3694 // a view (not a tabgroup).
3695 bool const close_buffer =
3696 !inOtherView(b) && !b.parent() && closing_;
3698 if (!closeWorkArea(wa, close_buffer))
3705 bool GuiView::saveBufferIfNeeded(Buffer & buf, bool hiding)
3707 if (buf.isClean() || buf.paragraphs().empty())
3710 // Switch to this Buffer.
3716 if (buf.isUnnamed()) {
3717 file = from_utf8(buf.fileName().onlyFileName());
3720 FileName filename = buf.fileName();
3722 file = filename.displayName(30);
3723 exists = filename.exists();
3726 // Bring this window to top before asking questions.
3731 if (hiding && buf.isUnnamed()) {
3732 docstring const text = bformat(_("The document %1$s has not been "
3733 "saved yet.\n\nDo you want to save "
3734 "the document?"), file);
3735 ret = Alert::prompt(_("Save new document?"),
3736 text, 0, 1, _("&Save"), _("&Cancel"));
3740 docstring const text = exists ?
3741 bformat(_("The document %1$s has unsaved changes."
3742 "\n\nDo you want to save the document or "
3743 "discard the changes?"), file) :
3744 bformat(_("The document %1$s has not been saved yet."
3745 "\n\nDo you want to save the document or "
3746 "discard it entirely?"), file);
3747 docstring const title = exists ?
3748 _("Save changed document?") : _("Save document?");
3749 ret = Alert::prompt(title, text, 0, 2,
3750 _("&Save"), _("&Discard"), _("&Cancel"));
3755 if (!saveBuffer(buf))
3759 // If we crash after this we could have no autosave file
3760 // but I guess this is really improbable (Jug).
3761 // Sometimes improbable things happen:
3762 // - see bug http://www.lyx.org/trac/ticket/6587 (ps)
3763 // buf.removeAutosaveFile();
3765 // revert all changes
3776 bool GuiView::inMultiTabs(GuiWorkArea * wa)
3778 Buffer & buf = wa->bufferView().buffer();
3780 for (int i = 0; i != d.splitter_->count(); ++i) {
3781 GuiWorkArea * wa_ = d.tabWorkArea(i)->workArea(buf);
3782 if (wa_ && wa_ != wa)
3785 return inOtherView(buf);
3789 bool GuiView::inOtherView(Buffer & buf)
3791 QList<int> const ids = guiApp->viewIds();
3793 for (int i = 0; i != ids.size(); ++i) {
3797 if (guiApp->view(ids[i]).workArea(buf))
3804 void GuiView::gotoNextOrPreviousBuffer(NextOrPrevious np, bool const move)
3806 if (!documentBufferView())
3809 if (TabWorkArea * twa = d.currentTabWorkArea()) {
3810 Buffer * const curbuf = &documentBufferView()->buffer();
3811 int nwa = twa->count();
3812 for (int i = 0; i < nwa; ++i) {
3813 if (&workArea(i)->bufferView().buffer() == curbuf) {
3815 if (np == NEXTBUFFER)
3816 next_index = (i == nwa - 1 ? 0 : i + 1);
3818 next_index = (i == 0 ? nwa - 1 : i - 1);
3820 twa->moveTab(i, next_index);
3822 setBuffer(&workArea(next_index)->bufferView().buffer());
3830 /// make sure the document is saved
3831 static bool ensureBufferClean(Buffer * buffer)
3833 LASSERT(buffer, return false);
3834 if (buffer->isClean() && !buffer->isUnnamed())
3837 docstring const file = buffer->fileName().displayName(30);
3840 if (!buffer->isUnnamed()) {
3841 text = bformat(_("The document %1$s has unsaved "
3842 "changes.\n\nDo you want to save "
3843 "the document?"), file);
3844 title = _("Save changed document?");
3847 text = bformat(_("The document %1$s has not been "
3848 "saved yet.\n\nDo you want to save "
3849 "the document?"), file);
3850 title = _("Save new document?");
3852 int const ret = Alert::prompt(title, text, 0, 1, _("&Save"), _("&Cancel"));
3855 dispatch(FuncRequest(LFUN_BUFFER_WRITE));
3857 return buffer->isClean() && !buffer->isUnnamed();
3861 bool GuiView::reloadBuffer(Buffer & buf)
3863 currentBufferView()->cursor().reset();
3864 Buffer::ReadStatus status = buf.reload();
3865 return status == Buffer::ReadSuccess;
3869 void GuiView::checkExternallyModifiedBuffers()
3871 for (Buffer * buf : theBufferList()) {
3872 if (buf->fileName().exists() && buf->isChecksumModified()) {
3873 docstring text = bformat(_("Document \n%1$s\n has been externally modified."
3874 " Reload now? Any local changes will be lost."),
3875 from_utf8(buf->absFileName()));
3876 int const ret = Alert::prompt(_("Reload externally changed document?"),
3877 text, 0, 1, _("&Reload"), _("&Cancel"));
3885 void GuiView::dispatchVC(FuncRequest const & cmd, DispatchResult & dr)
3887 Buffer * buffer = documentBufferView()
3888 ? &(documentBufferView()->buffer()) : nullptr;
3890 switch (cmd.action()) {
3891 case LFUN_VC_REGISTER:
3892 if (!buffer || !ensureBufferClean(buffer))
3894 if (!buffer->lyxvc().inUse()) {
3895 if (buffer->lyxvc().registrer()) {
3896 reloadBuffer(*buffer);
3897 dr.clearMessageUpdate();
3902 case LFUN_VC_RENAME:
3903 case LFUN_VC_COPY: {
3904 if (!buffer || !ensureBufferClean(buffer))
3906 if (buffer->lyxvc().inUse() && !buffer->hasReadonlyFlag()) {
3907 if (buffer->lyxvc().isCheckInWithConfirmation()) {
3908 // Some changes are not yet committed.
3909 // We test here and not in getStatus(), since
3910 // this test is expensive.
3912 LyXVC::CommandResult ret =
3913 buffer->lyxvc().checkIn(log);
3915 if (ret == LyXVC::ErrorCommand ||
3916 ret == LyXVC::VCSuccess)
3917 reloadBuffer(*buffer);
3918 if (buffer->lyxvc().isCheckInWithConfirmation()) {
3919 frontend::Alert::error(
3920 _("Revision control error."),
3921 _("Document could not be checked in."));
3925 RenameKind const kind = (cmd.action() == LFUN_VC_RENAME) ?
3926 LV_VC_RENAME : LV_VC_COPY;
3927 renameBuffer(*buffer, cmd.argument(), kind);
3932 case LFUN_VC_CHECK_IN:
3933 if (!buffer || !ensureBufferClean(buffer))
3935 if (buffer->lyxvc().inUse() && !buffer->hasReadonlyFlag()) {
3937 LyXVC::CommandResult ret = buffer->lyxvc().checkIn(log);
3939 // Only skip reloading if the checkin was cancelled or
3940 // an error occurred before the real checkin VCS command
3941 // was executed, since the VCS might have changed the
3942 // file even if it could not checkin successfully.
3943 if (ret == LyXVC::ErrorCommand || ret == LyXVC::VCSuccess)
3944 reloadBuffer(*buffer);
3948 case LFUN_VC_CHECK_OUT:
3949 if (!buffer || !ensureBufferClean(buffer))
3951 if (buffer->lyxvc().inUse()) {
3952 dr.setMessage(buffer->lyxvc().checkOut());
3953 reloadBuffer(*buffer);
3957 case LFUN_VC_LOCKING_TOGGLE:
3958 if (!buffer || !ensureBufferClean(buffer) || buffer->hasReadonlyFlag())
3960 if (buffer->lyxvc().inUse()) {
3961 string res = buffer->lyxvc().lockingToggle();
3963 frontend::Alert::error(_("Revision control error."),
3964 _("Error when setting the locking property."));
3967 reloadBuffer(*buffer);
3972 case LFUN_VC_REVERT:
3975 if (buffer->lyxvc().revert()) {
3976 reloadBuffer(*buffer);
3977 dr.clearMessageUpdate();
3981 case LFUN_VC_UNDO_LAST:
3984 buffer->lyxvc().undoLast();
3985 reloadBuffer(*buffer);
3986 dr.clearMessageUpdate();
3989 case LFUN_VC_REPO_UPDATE:
3992 if (ensureBufferClean(buffer)) {
3993 dr.setMessage(buffer->lyxvc().repoUpdate());
3994 checkExternallyModifiedBuffers();
3998 case LFUN_VC_COMMAND: {
3999 string flag = cmd.getArg(0);
4000 if (buffer && contains(flag, 'R') && !ensureBufferClean(buffer))
4003 if (contains(flag, 'M')) {
4004 if (!Alert::askForText(message, _("LyX VC: Log Message")))
4007 string path = cmd.getArg(1);
4008 if (contains(path, "$$p") && buffer)
4009 path = subst(path, "$$p", buffer->filePath());
4010 LYXERR(Debug::LYXVC, "Directory: " << path);
4012 if (!pp.isReadableDirectory()) {
4013 lyxerr << _("Directory is not accessible.") << endl;
4016 support::PathChanger p(pp);
4018 string command = cmd.getArg(2);
4019 if (command.empty())
4022 command = subst(command, "$$i", buffer->absFileName());
4023 command = subst(command, "$$p", buffer->filePath());
4025 command = subst(command, "$$m", to_utf8(message));
4026 LYXERR(Debug::LYXVC, "Command: " << command);
4028 one.startscript(Systemcall::Wait, command);
4032 if (contains(flag, 'I'))
4033 buffer->markDirty();
4034 if (contains(flag, 'R'))
4035 reloadBuffer(*buffer);
4040 case LFUN_VC_COMPARE: {
4041 if (cmd.argument().empty()) {
4042 lyx::dispatch(FuncRequest(LFUN_DIALOG_SHOW, "comparehistory"));
4048 string rev1 = cmd.getArg(0);
4052 if (!buffer->lyxvc().prepareFileRevision(rev1, f1))
4055 if (isStrInt(rev1) && convert<int>(rev1) <= 0) {
4056 f2 = buffer->absFileName();
4058 string rev2 = cmd.getArg(1);
4062 if (!buffer->lyxvc().prepareFileRevision(rev2, f2))
4066 LYXERR(Debug::LYXVC, "Launching comparison for fetched revisions:\n" <<
4067 f1 << "\n" << f2 << "\n" );
4068 string par = "compare run " + quoteName(f1) + " " + quoteName(f2);
4069 lyx::dispatch(FuncRequest(LFUN_DIALOG_SHOW, par));
4079 void GuiView::openChildDocument(string const & fname)
4081 LASSERT(documentBufferView(), return);
4082 Buffer & buffer = documentBufferView()->buffer();
4083 FileName const filename = support::makeAbsPath(fname, buffer.filePath());
4084 documentBufferView()->saveBookmark(false);
4085 Buffer * child = nullptr;
4086 if (theBufferList().exists(filename)) {
4087 child = theBufferList().getBuffer(filename);
4090 message(bformat(_("Opening child document %1$s..."),
4091 makeDisplayPath(filename.absFileName())));
4092 child = loadDocument(filename, false);
4094 // Set the parent name of the child document.
4095 // This makes insertion of citations and references in the child work,
4096 // when the target is in the parent or another child document.
4098 child->setParent(&buffer);
4102 bool GuiView::goToFileRow(string const & argument)
4106 size_t i = argument.find_last_of(' ');
4107 if (i != string::npos) {
4108 file_name = os::internal_path(FileName(trim(argument.substr(0, i))).realPath());
4109 istringstream is(argument.substr(i + 1));
4114 if (i == string::npos) {
4115 LYXERR0("Wrong argument: " << argument);
4118 Buffer * buf = nullptr;
4119 string const realtmp = package().temp_dir().realPath();
4120 // We have to use os::path_prefix_is() here, instead of
4121 // simply prefixIs(), because the file name comes from
4122 // an external application and may need case adjustment.
4123 if (os::path_prefix_is(file_name, realtmp, os::CASE_ADJUSTED)) {
4124 buf = theBufferList().getBufferFromTmp(file_name, true);
4125 LYXERR(Debug::FILES, "goToFileRow: buffer lookup for " << file_name
4126 << (buf ? " success" : " failed"));
4128 // Must replace extension of the file to be .lyx
4129 // and get full path
4130 FileName const s = fileSearch(string(),
4131 support::changeExtension(file_name, ".lyx"), "lyx");
4132 // Either change buffer or load the file
4133 if (theBufferList().exists(s))
4134 buf = theBufferList().getBuffer(s);
4135 else if (s.exists()) {
4136 buf = loadDocument(s);
4141 _("File does not exist: %1$s"),
4142 makeDisplayPath(file_name)));
4148 _("No buffer for file: %1$s."),
4149 makeDisplayPath(file_name))
4154 bool success = documentBufferView()->setCursorFromRow(row);
4156 LYXERR(Debug::OUTFILE,
4157 "setCursorFromRow: invalid position for row " << row);
4158 frontend::Alert::error(_("Inverse Search Failed"),
4159 _("Invalid position requested by inverse search.\n"
4160 "You may need to update the viewed document."));
4166 void GuiView::toolBarPopup(const QPoint & /*pos*/)
4168 QMenu * menu = guiApp->menus().menu(toqstr("context-toolbars"), * this);
4169 menu->exec(QCursor::pos());
4174 Buffer::ExportStatus GuiView::GuiViewPrivate::runAndDestroy(const T& func,
4175 Buffer const * orig, Buffer * clone, string const & format)
4177 Buffer::ExportStatus const status = func(format);
4179 // the cloning operation will have produced a clone of the entire set of
4180 // documents, starting from the master. so we must delete those.
4181 Buffer * mbuf = const_cast<Buffer *>(clone->masterBuffer());
4183 busyBuffers.remove(orig);
4188 Buffer::ExportStatus GuiView::GuiViewPrivate::compileAndDestroy(
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, true), orig, clone, format);
4197 Buffer::ExportStatus GuiView::GuiViewPrivate::exportAndDestroy(
4198 Buffer const * orig, Buffer * clone, string const & format)
4200 Buffer::ExportStatus (Buffer::* mem_func)(std::string const &, bool) const =
4202 return runAndDestroy(lyx::bind(mem_func, clone, _1, false), orig, clone, format);
4206 Buffer::ExportStatus GuiView::GuiViewPrivate::previewAndDestroy(
4207 Buffer const * orig, Buffer * clone, string const & format)
4209 Buffer::ExportStatus (Buffer::* mem_func)(std::string const &) const =
4211 return runAndDestroy(lyx::bind(mem_func, clone, _1), orig, clone, format);
4215 bool GuiView::GuiViewPrivate::asyncBufferProcessing(string const & argument,
4216 Buffer const * used_buffer,
4217 docstring const & msg,
4218 Buffer::ExportStatus (*asyncFunc)(Buffer const *, Buffer *, string const &),
4219 Buffer::ExportStatus (Buffer::*syncFunc)(string const &, bool) const,
4220 Buffer::ExportStatus (Buffer::*previewFunc)(string const &) const,
4221 bool allow_async, bool use_tmpdir)
4226 string format = argument;
4228 format = used_buffer->params().getDefaultOutputFormat();
4229 processing_format = format;
4231 progress_->clearMessages();
4234 #if EXPORT_in_THREAD
4236 GuiViewPrivate::busyBuffers.insert(used_buffer);
4237 Buffer * cloned_buffer = used_buffer->cloneWithChildren();
4238 if (!cloned_buffer) {
4239 Alert::error(_("Export Error"),
4240 _("Error cloning the Buffer."));
4243 QFuture<Buffer::ExportStatus> f = QtConcurrent::run(
4248 setPreviewFuture(f);
4249 last_export_format = used_buffer->params().bufferFormat();
4252 // We are asynchronous, so we don't know here anything about the success
4255 Buffer::ExportStatus status;
4257 status = (used_buffer->*syncFunc)(format, use_tmpdir);
4258 } else if (previewFunc) {
4259 status = (used_buffer->*previewFunc)(format);
4262 handleExportStatus(gv_, status, format);
4264 return (status == Buffer::ExportSuccess
4265 || status == Buffer::PreviewSuccess);
4269 Buffer::ExportStatus status;
4271 status = (used_buffer->*syncFunc)(format, true);
4272 } else if (previewFunc) {
4273 status = (used_buffer->*previewFunc)(format);
4276 handleExportStatus(gv_, status, format);
4278 return (status == Buffer::ExportSuccess
4279 || status == Buffer::PreviewSuccess);
4283 void GuiView::dispatchToBufferView(FuncRequest const & cmd, DispatchResult & dr)
4285 BufferView * bv = currentBufferView();
4286 LASSERT(bv, return);
4288 // Let the current BufferView dispatch its own actions.
4289 bv->dispatch(cmd, dr);
4290 if (dr.dispatched()) {
4291 if (cmd.action() == LFUN_REDO || cmd.action() == LFUN_UNDO)
4292 updateDialog("document", "");
4296 // Try with the document BufferView dispatch if any.
4297 BufferView * doc_bv = documentBufferView();
4298 if (doc_bv && doc_bv != bv) {
4299 doc_bv->dispatch(cmd, dr);
4300 if (dr.dispatched()) {
4301 if (cmd.action() == LFUN_REDO || cmd.action() == LFUN_UNDO)
4302 updateDialog("document", "");
4307 // Then let the current Cursor dispatch its own actions.
4308 bv->cursor().dispatch(cmd);
4310 // update completion. We do it here and not in
4311 // processKeySym to avoid another redraw just for a
4312 // changed inline completion
4313 if (cmd.origin() == FuncRequest::KEYBOARD) {
4314 if (cmd.action() == LFUN_SELF_INSERT
4315 || (cmd.action() == LFUN_ERT_INSERT && bv->cursor().inMathed()))
4316 updateCompletion(bv->cursor(), true, true);
4317 else if (cmd.action() == LFUN_CHAR_DELETE_BACKWARD)
4318 updateCompletion(bv->cursor(), false, true);
4320 updateCompletion(bv->cursor(), false, false);
4323 dr = bv->cursor().result();
4327 void GuiView::dispatch(FuncRequest const & cmd, DispatchResult & dr)
4329 BufferView * bv = currentBufferView();
4330 // By default we won't need any update.
4331 dr.screenUpdate(Update::None);
4332 // assume cmd will be dispatched
4333 dr.dispatched(true);
4335 Buffer * doc_buffer = documentBufferView()
4336 ? &(documentBufferView()->buffer()) : nullptr;
4338 if (cmd.origin() == FuncRequest::TOC) {
4339 GuiToc * toc = static_cast<GuiToc*>(findOrBuild("toc", false));
4340 toc->doDispatch(bv->cursor(), cmd, dr);
4344 string const argument = to_utf8(cmd.argument());
4346 switch(cmd.action()) {
4347 case LFUN_BUFFER_CHILD_OPEN:
4348 openChildDocument(to_utf8(cmd.argument()));
4351 case LFUN_BUFFER_IMPORT:
4352 importDocument(to_utf8(cmd.argument()));
4355 case LFUN_MASTER_BUFFER_EXPORT:
4357 doc_buffer = const_cast<Buffer *>(doc_buffer->masterBuffer());
4359 case LFUN_BUFFER_EXPORT: {
4362 // GCC only sees strfwd.h when building merged
4363 if (::lyx::operator==(cmd.argument(), "custom")) {
4364 // LFUN_MASTER_BUFFER_EXPORT is not enabled for this case,
4365 // so the following test should not be needed.
4366 // In principle, we could try to switch to such a view...
4367 // if (cmd.action() == LFUN_BUFFER_EXPORT)
4368 dispatch(FuncRequest(LFUN_DIALOG_SHOW, "sendto"), dr);
4372 string const dest = cmd.getArg(1);
4373 FileName target_dir;
4374 if (!dest.empty() && FileName::isAbsolute(dest))
4375 target_dir = FileName(support::onlyPath(dest));
4377 target_dir = doc_buffer->fileName().onlyPath();
4379 string const format = (argument.empty() || argument == "default") ?
4380 doc_buffer->params().getDefaultOutputFormat() : argument;
4382 if ((dest.empty() && doc_buffer->isUnnamed())
4383 || !target_dir.isDirWritable()) {
4384 exportBufferAs(*doc_buffer, from_utf8(format));
4387 /* TODO/Review: Is it a problem to also export the children?
4388 See the update_unincluded flag */
4389 d.asyncBufferProcessing(format,
4392 &GuiViewPrivate::exportAndDestroy,
4394 nullptr, cmd.allowAsync());
4395 // TODO Inform user about success
4399 case LFUN_BUFFER_EXPORT_AS: {
4400 LASSERT(doc_buffer, break);
4401 docstring f = cmd.argument();
4403 f = from_ascii(doc_buffer->params().getDefaultOutputFormat());
4404 exportBufferAs(*doc_buffer, f);
4408 case LFUN_BUFFER_UPDATE: {
4409 d.asyncBufferProcessing(argument,
4412 &GuiViewPrivate::compileAndDestroy,
4414 nullptr, cmd.allowAsync(), true);
4417 case LFUN_BUFFER_VIEW: {
4418 d.asyncBufferProcessing(argument,
4420 _("Previewing ..."),
4421 &GuiViewPrivate::previewAndDestroy,
4423 &Buffer::preview, cmd.allowAsync());
4426 case LFUN_MASTER_BUFFER_UPDATE: {
4427 d.asyncBufferProcessing(argument,
4428 (doc_buffer ? doc_buffer->masterBuffer() : nullptr),
4430 &GuiViewPrivate::compileAndDestroy,
4432 nullptr, cmd.allowAsync(), true);
4435 case LFUN_MASTER_BUFFER_VIEW: {
4436 d.asyncBufferProcessing(argument,
4437 (doc_buffer ? doc_buffer->masterBuffer() : nullptr),
4439 &GuiViewPrivate::previewAndDestroy,
4440 nullptr, &Buffer::preview, cmd.allowAsync());
4443 case LFUN_EXPORT_CANCEL: {
4444 Systemcall::killscript();
4447 case LFUN_BUFFER_SWITCH: {
4448 string const file_name = to_utf8(cmd.argument());
4449 if (!FileName::isAbsolute(file_name)) {
4451 dr.setMessage(_("Absolute filename expected."));
4455 Buffer * buffer = theBufferList().getBuffer(FileName(file_name));
4458 dr.setMessage(_("Document not loaded"));
4462 // Do we open or switch to the buffer in this view ?
4463 if (workArea(*buffer)
4464 || lyxrc.open_buffers_in_tabs || !documentBufferView()) {
4469 // Look for the buffer in other views
4470 QList<int> const ids = guiApp->viewIds();
4472 for (; i != ids.size(); ++i) {
4473 GuiView & gv = guiApp->view(ids[i]);
4474 if (gv.workArea(*buffer)) {
4476 gv.activateWindow();
4478 gv.setBuffer(buffer);
4483 // If necessary, open a new window as a last resort
4484 if (i == ids.size()) {
4485 lyx::dispatch(FuncRequest(LFUN_WINDOW_NEW));
4491 case LFUN_BUFFER_NEXT:
4492 gotoNextOrPreviousBuffer(NEXTBUFFER, false);
4495 case LFUN_BUFFER_MOVE_NEXT:
4496 gotoNextOrPreviousBuffer(NEXTBUFFER, true);
4499 case LFUN_BUFFER_PREVIOUS:
4500 gotoNextOrPreviousBuffer(PREVBUFFER, false);
4503 case LFUN_BUFFER_MOVE_PREVIOUS:
4504 gotoNextOrPreviousBuffer(PREVBUFFER, true);
4507 case LFUN_BUFFER_CHKTEX:
4508 LASSERT(doc_buffer, break);
4509 doc_buffer->runChktex();
4512 case LFUN_COMMAND_EXECUTE: {
4513 command_execute_ = true;
4514 minibuffer_focus_ = true;
4517 case LFUN_DROP_LAYOUTS_CHOICE:
4518 d.layout_->showPopup();
4521 case LFUN_MENU_OPEN:
4522 if (QMenu * menu = guiApp->menus().menu(toqstr(cmd.argument()), *this))
4523 menu->exec(QCursor::pos());
4526 case LFUN_FILE_INSERT: {
4527 bool const ignore_lang = cmd.getArg(1) == "ignorelang";
4528 if (insertLyXFile(from_utf8(cmd.getArg(0)), ignore_lang)) {
4529 dr.forceBufferUpdate();
4530 dr.screenUpdate(Update::Force);
4535 case LFUN_FILE_INSERT_PLAINTEXT:
4536 case LFUN_FILE_INSERT_PLAINTEXT_PARA: {
4537 string const fname = to_utf8(cmd.argument());
4538 if (!fname.empty() && !FileName::isAbsolute(fname)) {
4539 dr.setMessage(_("Absolute filename expected."));
4543 FileName filename(fname);
4544 if (fname.empty()) {
4545 FileDialog dlg(qt_("Select file to insert"));
4547 FileDialog::Result result = dlg.open(toqstr(bv->buffer().filePath()),
4548 QStringList(qt_("All Files (*)")));
4550 if (result.first == FileDialog::Later || result.second.isEmpty()) {
4551 dr.setMessage(_("Canceled."));
4555 filename.set(fromqstr(result.second));
4559 FuncRequest const new_cmd(cmd, filename.absoluteFilePath());
4560 bv->dispatch(new_cmd, dr);
4565 case LFUN_BUFFER_RELOAD: {
4566 LASSERT(doc_buffer, break);
4569 bool drop = (cmd.argument() == "dump");
4572 if (!drop && !doc_buffer->isClean()) {
4573 docstring const file =
4574 makeDisplayPath(doc_buffer->absFileName(), 20);
4575 if (doc_buffer->notifiesExternalModification()) {
4576 docstring text = _("The current version will be lost. "
4577 "Are you sure you want to load the version on disk "
4578 "of the document %1$s?");
4579 ret = Alert::prompt(_("Reload saved document?"),
4580 bformat(text, file), 1, 1,
4581 _("&Reload"), _("&Cancel"));
4583 docstring text = _("Any changes will be lost. "
4584 "Are you sure you want to revert to the saved version "
4585 "of the document %1$s?");
4586 ret = Alert::prompt(_("Revert to saved document?"),
4587 bformat(text, file), 1, 1,
4588 _("&Revert"), _("&Cancel"));
4593 doc_buffer->markClean();
4594 reloadBuffer(*doc_buffer);
4595 dr.forceBufferUpdate();
4600 case LFUN_BUFFER_RESET_EXPORT:
4601 LASSERT(doc_buffer, break);
4602 doc_buffer->requireFreshStart(true);
4603 dr.setMessage(_("Buffer export reset."));
4606 case LFUN_BUFFER_WRITE:
4607 LASSERT(doc_buffer, break);
4608 saveBuffer(*doc_buffer);
4611 case LFUN_BUFFER_WRITE_AS:
4612 LASSERT(doc_buffer, break);
4613 renameBuffer(*doc_buffer, cmd.argument());
4616 case LFUN_BUFFER_WRITE_AS_TEMPLATE:
4617 LASSERT(doc_buffer, break);
4618 renameBuffer(*doc_buffer, cmd.argument(),
4619 LV_WRITE_AS_TEMPLATE);
4622 case LFUN_BUFFER_WRITE_ALL: {
4623 Buffer * first = theBufferList().first();
4626 message(_("Saving all documents..."));
4627 // We cannot use a for loop as the buffer list cycles.
4630 if (!b->isClean()) {
4632 LYXERR(Debug::ACTION, "Saved " << b->absFileName());
4634 b = theBufferList().next(b);
4635 } while (b != first);
4636 dr.setMessage(_("All documents saved."));
4640 case LFUN_MASTER_BUFFER_FORALL: {
4644 FuncRequest funcToRun = lyxaction.lookupFunc(cmd.getLongArg(0));
4645 funcToRun.allowAsync(false);
4647 for (Buffer const * buf : doc_buffer->allRelatives()) {
4648 // Switch to other buffer view and resend cmd
4649 lyx::dispatch(FuncRequest(
4650 LFUN_BUFFER_SWITCH, buf->absFileName()));
4651 lyx::dispatch(funcToRun);
4654 lyx::dispatch(FuncRequest(
4655 LFUN_BUFFER_SWITCH, doc_buffer->absFileName()));
4659 case LFUN_BUFFER_EXTERNAL_MODIFICATION_CLEAR:
4660 LASSERT(doc_buffer, break);
4661 doc_buffer->clearExternalModification();
4664 case LFUN_BUFFER_CLOSE:
4668 case LFUN_BUFFER_CLOSE_ALL:
4672 case LFUN_DEVEL_MODE_TOGGLE:
4673 devel_mode_ = !devel_mode_;
4675 dr.setMessage(_("Developer mode is now enabled."));
4677 dr.setMessage(_("Developer mode is now disabled."));
4680 case LFUN_TOOLBAR_SET: {
4681 string const name = cmd.getArg(0);
4682 string const state = cmd.getArg(1);
4683 if (GuiToolbar * t = toolbar(name))
4688 case LFUN_TOOLBAR_TOGGLE: {
4689 string const name = cmd.getArg(0);
4690 if (GuiToolbar * t = toolbar(name))
4695 case LFUN_TOOLBAR_MOVABLE: {
4696 string const name = cmd.getArg(0);
4698 // toggle (all) toolbars movablility
4699 toolbarsMovable_ = !toolbarsMovable_;
4700 for (ToolbarInfo const & ti : guiApp->toolbars()) {
4701 GuiToolbar * tb = toolbar(ti.name);
4702 if (tb && tb->isMovable() != toolbarsMovable_)
4703 // toggle toolbar movablity if it does not fit lock
4704 // (all) toolbars positions state silent = true, since
4705 // status bar notifications are slow
4708 if (toolbarsMovable_)
4709 dr.setMessage(_("Toolbars unlocked."));
4711 dr.setMessage(_("Toolbars locked."));
4712 } else if (GuiToolbar * tb = toolbar(name))
4713 // toggle current toolbar movablity
4715 // update lock (all) toolbars positions
4716 updateLockToolbars();
4720 case LFUN_ICON_SIZE: {
4721 QSize size = d.iconSize(cmd.argument());
4723 dr.setMessage(bformat(_("Icon size set to %1$dx%2$d."),
4724 size.width(), size.height()));
4728 case LFUN_DIALOG_UPDATE: {
4729 string const name = to_utf8(cmd.argument());
4730 if (name == "prefs" || name == "document")
4731 updateDialog(name, string());
4732 else if (name == "paragraph")
4733 lyx::dispatch(FuncRequest(LFUN_PARAGRAPH_UPDATE));
4734 else if (currentBufferView()) {
4735 Inset * inset = currentBufferView()->editedInset(name);
4736 // Can only update a dialog connected to an existing inset
4738 // FIXME: get rid of this indirection; GuiView ask the inset
4739 // if he is kind enough to update itself...
4740 FuncRequest fr(LFUN_INSET_DIALOG_UPDATE, cmd.argument());
4741 //FIXME: pass DispatchResult here?
4742 inset->dispatch(currentBufferView()->cursor(), fr);
4748 case LFUN_DIALOG_TOGGLE: {
4749 FuncCode const func_code = isDialogVisible(cmd.getArg(0))
4750 ? LFUN_DIALOG_HIDE : LFUN_DIALOG_SHOW;
4751 dispatch(FuncRequest(func_code, cmd.argument()), dr);
4755 case LFUN_DIALOG_DISCONNECT_INSET:
4756 disconnectDialog(to_utf8(cmd.argument()));
4759 case LFUN_DIALOG_HIDE: {
4760 guiApp->hideDialogs(to_utf8(cmd.argument()), nullptr);
4764 case LFUN_DIALOG_SHOW: {
4765 string const name = cmd.getArg(0);
4766 string sdata = trim(to_utf8(cmd.argument()).substr(name.size()));
4768 if (name == "latexlog") {
4769 // getStatus checks that
4770 LASSERT(doc_buffer, break);
4771 Buffer::LogType type;
4772 string const logfile = doc_buffer->logName(&type);
4774 case Buffer::latexlog:
4777 case Buffer::buildlog:
4778 sdata = "literate ";
4781 sdata += Lexer::quoteString(logfile);
4782 showDialog("log", sdata);
4783 } else if (name == "vclog") {
4784 // getStatus checks that
4785 LASSERT(doc_buffer, break);
4786 string const sdata2 = "vc " +
4787 Lexer::quoteString(doc_buffer->lyxvc().getLogFile());
4788 showDialog("log", sdata2);
4789 } else if (name == "symbols") {
4790 sdata = bv->cursor().getEncoding()->name();
4792 showDialog("symbols", sdata);
4793 } else if (name == "findreplace") {
4794 sdata = to_utf8(bv->cursor().selectionAsString(false));
4795 showDialog(name, sdata);
4797 } else if (name == "prefs" && isFullScreen()) {
4798 lfunUiToggle("fullscreen");
4799 showDialog("prefs", sdata);
4801 showDialog(name, sdata);
4806 dr.setMessage(cmd.argument());
4809 case LFUN_UI_TOGGLE: {
4810 string arg = cmd.getArg(0);
4811 if (!lfunUiToggle(arg)) {
4812 docstring const msg = "ui-toggle " + _("%1$s unknown command!");
4813 dr.setMessage(bformat(msg, from_utf8(arg)));
4815 // Make sure the keyboard focus stays in the work area.
4820 case LFUN_VIEW_SPLIT: {
4821 LASSERT(doc_buffer, break);
4822 string const orientation = cmd.getArg(0);
4823 d.splitter_->setOrientation(orientation == "vertical"
4824 ? Qt::Vertical : Qt::Horizontal);
4825 TabWorkArea * twa = addTabWorkArea();
4826 GuiWorkArea * wa = twa->addWorkArea(*doc_buffer, *this);
4827 setCurrentWorkArea(wa);
4830 case LFUN_TAB_GROUP_CLOSE:
4831 if (TabWorkArea * twa = d.currentTabWorkArea()) {
4832 closeTabWorkArea(twa);
4833 d.current_work_area_ = nullptr;
4834 twa = d.currentTabWorkArea();
4835 // Switch to the next GuiWorkArea in the found TabWorkArea.
4837 // Make sure the work area is up to date.
4838 setCurrentWorkArea(twa->currentWorkArea());
4840 setCurrentWorkArea(nullptr);
4845 case LFUN_VIEW_CLOSE:
4846 if (TabWorkArea * twa = d.currentTabWorkArea()) {
4847 closeWorkArea(twa->currentWorkArea());
4848 d.current_work_area_ = nullptr;
4849 twa = d.currentTabWorkArea();
4850 // Switch to the next GuiWorkArea in the found TabWorkArea.
4852 // Make sure the work area is up to date.
4853 setCurrentWorkArea(twa->currentWorkArea());
4855 setCurrentWorkArea(nullptr);
4860 case LFUN_COMPLETION_INLINE:
4861 if (d.current_work_area_)
4862 d.current_work_area_->completer().showInline();
4865 case LFUN_COMPLETION_POPUP:
4866 if (d.current_work_area_)
4867 d.current_work_area_->completer().showPopup();
4872 if (d.current_work_area_)
4873 d.current_work_area_->completer().tab();
4876 case LFUN_COMPLETION_CANCEL:
4877 if (d.current_work_area_) {
4878 if (d.current_work_area_->completer().popupVisible())
4879 d.current_work_area_->completer().hidePopup();
4881 d.current_work_area_->completer().hideInline();
4885 case LFUN_COMPLETION_ACCEPT:
4886 if (d.current_work_area_)
4887 d.current_work_area_->completer().activate();
4890 case LFUN_BUFFER_ZOOM_IN:
4891 case LFUN_BUFFER_ZOOM_OUT:
4892 case LFUN_BUFFER_ZOOM: {
4893 zoom_ratio_ = zoomRatio(cmd, zoom_ratio_);
4895 // Actual zoom value: default zoom + fractional extra value
4896 int zoom = (int)(lyxrc.defaultZoom * zoom_ratio_);
4897 zoom = min(max(zoom, zoom_min_), zoom_max_);
4899 setCurrentZoom(zoom);
4901 dr.setMessage(bformat(_("Zoom level is now %1$d% (default value: %2$d%)"),
4902 lyxrc.currentZoom, lyxrc.defaultZoom));
4904 guiApp->fontLoader().update();
4905 // Regenerate instant previews
4906 if (lyxrc.preview != LyXRC::PREVIEW_OFF
4907 && doc_buffer && doc_buffer->loader())
4908 doc_buffer->loader()->refreshPreviews();
4909 dr.screenUpdate(Update::ForceAll | Update::FitCursor);
4913 case LFUN_VC_REGISTER:
4914 case LFUN_VC_RENAME:
4916 case LFUN_VC_CHECK_IN:
4917 case LFUN_VC_CHECK_OUT:
4918 case LFUN_VC_REPO_UPDATE:
4919 case LFUN_VC_LOCKING_TOGGLE:
4920 case LFUN_VC_REVERT:
4921 case LFUN_VC_UNDO_LAST:
4922 case LFUN_VC_COMMAND:
4923 case LFUN_VC_COMPARE:
4924 dispatchVC(cmd, dr);
4927 case LFUN_SERVER_GOTO_FILE_ROW:
4928 if(goToFileRow(to_utf8(cmd.argument())))
4929 dr.screenUpdate(Update::Force | Update::FitCursor);
4932 case LFUN_LYX_ACTIVATE:
4936 case LFUN_WINDOW_RAISE:
4942 case LFUN_FORWARD_SEARCH: {
4943 // it seems safe to assume we have a document buffer, since
4944 // getStatus wants one.
4945 LASSERT(doc_buffer, break);
4946 Buffer const * doc_master = doc_buffer->masterBuffer();
4947 FileName const path(doc_master->temppath());
4948 string const texname = doc_master->isChild(doc_buffer)
4949 ? DocFileName(changeExtension(
4950 doc_buffer->absFileName(),
4951 "tex")).mangledFileName()
4952 : doc_buffer->latexName();
4953 string const fulltexname =
4954 support::makeAbsPath(texname, doc_master->temppath()).absFileName();
4955 string const mastername =
4956 removeExtension(doc_master->latexName());
4957 FileName const dviname(addName(path.absFileName(),
4958 addExtension(mastername, "dvi")));
4959 FileName const pdfname(addName(path.absFileName(),
4960 addExtension(mastername, "pdf")));
4961 bool const have_dvi = dviname.exists();
4962 bool const have_pdf = pdfname.exists();
4963 if (!have_dvi && !have_pdf) {
4964 dr.setMessage(_("Please, preview the document first."));
4967 bool const goto_dvi = have_dvi && !lyxrc.forward_search_dvi.empty();
4968 bool const goto_pdf = have_pdf && !lyxrc.forward_search_pdf.empty();
4969 string outname = dviname.onlyFileName();
4970 string command = lyxrc.forward_search_dvi;
4971 if ((!goto_dvi || goto_pdf) &&
4972 pdfname.lastModified() > dviname.lastModified()) {
4973 outname = pdfname.onlyFileName();
4974 command = lyxrc.forward_search_pdf;
4977 DocIterator cur = bv->cursor();
4978 int row = doc_buffer->texrow().rowFromDocIterator(cur).first;
4979 LYXERR(Debug::ACTION, "Forward search: row:" << row
4981 if (row == -1 || command.empty()) {
4982 dr.setMessage(_("Couldn't proceed."));
4985 string texrow = convert<string>(row);
4987 command = subst(command, "$$n", texrow);
4988 command = subst(command, "$$f", fulltexname);
4989 command = subst(command, "$$t", texname);
4990 command = subst(command, "$$o", outname);
4992 volatile PathChanger p(path);
4994 one.startscript(Systemcall::DontWait, command);
4998 case LFUN_SPELLING_CONTINUOUSLY:
4999 lyxrc.spellcheck_continuously = !lyxrc.spellcheck_continuously;
5000 dr.screenUpdate(Update::Force);
5003 case LFUN_CITATION_OPEN: {
5005 if (theFormats().getFormat("pdf"))
5006 pdfv = theFormats().getFormat("pdf")->viewer();
5007 if (theFormats().getFormat("ps"))
5008 psv = theFormats().getFormat("ps")->viewer();
5009 frontend::showTarget(argument, pdfv, psv);
5014 // The LFUN must be for one of BufferView, Buffer or Cursor;
5016 dispatchToBufferView(cmd, dr);
5020 // Need to update bv because many LFUNs here might have destroyed it
5021 bv = currentBufferView();
5023 // Clear non-empty selections
5024 // (e.g. from a "char-forward-select" followed by "char-backward-select")
5026 Cursor & cur = bv->cursor();
5027 if ((cur.selection() && cur.selBegin() == cur.selEnd())) {
5028 cur.clearSelection();
5034 bool GuiView::lfunUiToggle(string const & ui_component)
5036 if (ui_component == "scrollbar") {
5037 // hide() is of no help
5038 if (d.current_work_area_->verticalScrollBarPolicy() ==
5039 Qt::ScrollBarAlwaysOff)
5041 d.current_work_area_->setVerticalScrollBarPolicy(
5042 Qt::ScrollBarAsNeeded);
5044 d.current_work_area_->setVerticalScrollBarPolicy(
5045 Qt::ScrollBarAlwaysOff);
5046 } else if (ui_component == "statusbar") {
5047 statusBar()->setVisible(!statusBar()->isVisible());
5048 } else if (ui_component == "menubar") {
5049 menuBar()->setVisible(!menuBar()->isVisible());
5050 } else if (ui_component == "zoomlevel") {
5051 zoom_value_->setVisible(!zoom_value_->isVisible());
5052 } else if (ui_component == "zoomslider") {
5053 zoom_slider_->setVisible(!zoom_slider_->isVisible());
5054 zoom_in_->setVisible(zoom_slider_->isVisible());
5055 zoom_out_->setVisible(zoom_slider_->isVisible());
5056 } else if (ui_component == "statistics-w") {
5057 word_count_enabled_ = !word_count_enabled_;
5060 } else if (ui_component == "statistics-cb") {
5061 char_count_enabled_ = !char_count_enabled_;
5064 } else if (ui_component == "statistics-c") {
5065 char_nb_count_enabled_ = !char_nb_count_enabled_;
5068 } else if (ui_component == "frame") {
5069 int const l = contentsMargins().left();
5071 //are the frames in default state?
5072 d.current_work_area_->setFrameStyle(QFrame::NoFrame);
5074 #if QT_VERSION > 0x050903
5075 setAttribute(Qt::WA_ContentsMarginsRespectsSafeArea, false);
5077 setContentsMargins(-2, -2, -2, -2);
5079 #if QT_VERSION > 0x050903
5080 setAttribute(Qt::WA_ContentsMarginsRespectsSafeArea, true);
5082 setContentsMargins(0, 0, 0, 0);
5085 if (ui_component == "fullscreen") {
5089 stat_counts_->setVisible(statsEnabled());
5094 void GuiView::toggleFullScreen()
5096 setWindowState(windowState() ^ Qt::WindowFullScreen);
5100 Buffer const * GuiView::updateInset(Inset const * inset)
5105 Buffer const * inset_buffer = &(inset->buffer());
5107 for (int i = 0; i != d.splitter_->count(); ++i) {
5108 GuiWorkArea * wa = d.tabWorkArea(i)->currentWorkArea();
5111 Buffer const * buffer = &(wa->bufferView().buffer());
5112 if (inset_buffer == buffer)
5113 wa->scheduleRedraw(true);
5115 return inset_buffer;
5119 void GuiView::restartCaret()
5121 /* When we move around, or type, it's nice to be able to see
5122 * the caret immediately after the keypress.
5124 if (d.current_work_area_)
5125 d.current_work_area_->startBlinkingCaret();
5127 // Take this occasion to update the other GUI elements.
5133 void GuiView::updateCompletion(Cursor & cur, bool start, bool keep)
5135 if (d.current_work_area_)
5136 d.current_work_area_->completer().updateVisibility(cur, start, keep);
5141 // This list should be kept in sync with the list of insets in
5142 // src/insets/Inset.cpp. I.e., if a dialog goes with an inset, the
5143 // dialog should have the same name as the inset.
5144 // Changes should be also recorded in LFUN_DIALOG_SHOW doxygen
5145 // docs in LyXAction.cpp.
5147 char const * const dialognames[] = {
5149 "aboutlyx", "bibitem", "bibtex", "box", "branch", "changes", "character",
5150 "citation", "compare", "comparehistory", "counter", "document", "errorlist", "ert",
5151 "external", "file", "findreplace", "findreplaceadv", "float", "graphics",
5152 "href", "include", "index", "index_print", "info", "listings", "label", "line",
5153 "log", "lyxfiles", "mathdelimiter", "mathmatrix", "mathspace", "nomenclature",
5154 "nomencl_print", "note", "paragraph", "phantom", "prefs", "ref",
5155 "sendto", "space", "spellchecker", "symbols", "tabular", "tabularcreate",
5156 "thesaurus", "texinfo", "toc", "view-source", "vspace", "wrap", "progress"};
5158 char const * const * const end_dialognames =
5159 dialognames + (sizeof(dialognames) / sizeof(char *));
5163 cmpCStr(char const * name) : name_(name) {}
5164 bool operator()(char const * other) {
5165 return strcmp(other, name_) == 0;
5172 bool isValidName(string const & name)
5174 return find_if(dialognames, end_dialognames,
5175 cmpCStr(name.c_str())) != end_dialognames;
5181 void GuiView::resetDialogs()
5183 // Make sure that no LFUN uses any GuiView.
5184 guiApp->setCurrentView(nullptr);
5188 constructToolbars();
5189 guiApp->menus().fillMenuBar(menuBar(), this, false);
5190 d.layout_->updateContents(true);
5191 // Now update controls with current buffer.
5192 guiApp->setCurrentView(this);
5198 void GuiView::flatGroupBoxes(const QObject * widget, bool flag)
5200 for (QObject * child: widget->children()) {
5201 if (child->inherits("QGroupBox")) {
5202 QGroupBox * box = (QGroupBox*) child;
5205 flatGroupBoxes(child, flag);
5211 Dialog * GuiView::find(string const & name, bool hide_it) const
5213 if (!isValidName(name))
5216 map<string, DialogPtr>::iterator it = d.dialogs_.find(name);
5218 if (it != d.dialogs_.end()) {
5220 it->second->hideView();
5221 return it->second.get();
5227 Dialog * GuiView::findOrBuild(string const & name, bool hide_it)
5229 Dialog * dialog = find(name, hide_it);
5230 if (dialog != nullptr)
5233 dialog = build(name);
5234 d.dialogs_[name].reset(dialog);
5235 #if (QT_VERSION >= QT_VERSION_CHECK(5, 0, 0))
5236 // Force a uniform style for group boxes
5237 // On Mac non-flat works better, on Linux flat is standard
5238 flatGroupBoxes(dialog->asQWidget(), guiApp->platformName() != "cocoa");
5240 if (lyxrc.allow_geometry_session)
5241 dialog->restoreSession();
5248 void GuiView::showDialog(string const & name, string const & sdata,
5251 triggerShowDialog(toqstr(name), toqstr(sdata), inset);
5255 void GuiView::doShowDialog(QString const & qname, QString const & qdata,
5261 const string name = fromqstr(qname);
5262 const string sdata = fromqstr(qdata);
5266 Dialog * dialog = findOrBuild(name, false);
5268 bool const visible = dialog->isVisibleView();
5269 dialog->showData(sdata);
5270 if (currentBufferView())
5271 currentBufferView()->editInset(name, inset);
5272 // We only set the focus to the new dialog if it was not yet
5273 // visible in order not to change the existing previous behaviour
5275 // activateWindow is needed for floating dockviews
5276 dialog->asQWidget()->raise();
5277 dialog->asQWidget()->activateWindow();
5278 if (dialog->wantInitialFocus())
5279 dialog->asQWidget()->setFocus();
5283 catch (ExceptionMessage const &) {
5291 bool GuiView::isDialogVisible(string const & name) const
5293 map<string, DialogPtr>::const_iterator it = d.dialogs_.find(name);
5294 if (it == d.dialogs_.end())
5296 return it->second.get()->isVisibleView() && !it->second.get()->isClosing();
5300 void GuiView::hideDialog(string const & name, Inset * inset)
5302 map<string, DialogPtr>::const_iterator it = d.dialogs_.find(name);
5303 if (it == d.dialogs_.end())
5307 if (!currentBufferView())
5309 if (inset != currentBufferView()->editedInset(name))
5313 Dialog * const dialog = it->second.get();
5314 if (dialog->isVisibleView())
5316 if (currentBufferView())
5317 currentBufferView()->editInset(name, nullptr);
5321 void GuiView::disconnectDialog(string const & name)
5323 if (!isValidName(name))
5325 if (currentBufferView())
5326 currentBufferView()->editInset(name, nullptr);
5330 void GuiView::hideAll() const
5332 for(auto const & dlg_p : d.dialogs_)
5333 dlg_p.second->hideView();
5337 void GuiView::updateDialogs()
5339 for(auto const & dlg_p : d.dialogs_) {
5340 Dialog * dialog = dlg_p.second.get();
5342 if (dialog->needBufferOpen() && !documentBufferView())
5343 hideDialog(fromqstr(dialog->name()), nullptr);
5344 else if (dialog->isVisibleView())
5345 dialog->checkStatus();
5353 Dialog * GuiView::build(string const & name)
5355 return createDialog(*this, name);
5359 SEMenu::SEMenu(QWidget * parent)
5361 QAction * action = addAction(qt_("Disable Shell Escape"));
5362 connect(action, SIGNAL(triggered()),
5363 parent, SLOT(disableShellEscape()));
5367 void PressableSvgWidget::mousePressEvent(QMouseEvent * event)
5369 if (event->button() == Qt::LeftButton) {
5374 } // namespace frontend
5377 #include "moc_GuiView.cpp"