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 connect(this, SIGNAL(scriptKilled()), busySVG, SLOT(hide()));
667 connect(this, SIGNAL(scriptKilled()), processStop, SLOT(hide()));
669 stat_counts_ = new GuiClickableLabel(statusBar());
670 stat_counts_->setAlignment(Qt::AlignCenter);
671 stat_counts_->setFrameStyle(QFrame::StyledPanel);
672 stat_counts_->hide();
673 statusBar()->addPermanentWidget(stat_counts_);
675 connect(stat_counts_, SIGNAL(clicked()), this, SLOT(statsPressed()));
677 zoom_slider_ = new QSlider(Qt::Horizontal, statusBar());
678 // Small size slider for macOS to prevent the status bar from enlarging
679 zoom_slider_->setAttribute(Qt::WA_MacSmallSize);
680 #if (QT_VERSION >= QT_VERSION_CHECK(5, 11, 0))
681 zoom_slider_->setFixedWidth(fm.horizontalAdvance('x') * 15);
683 zoom_slider_->setFixedWidth(fm.width('x') * 15);
685 // Make the defaultZoom center
686 zoom_slider_->setRange(10, (lyxrc.defaultZoom * 2) - 10);
687 // Initialize proper zoom value
689 zoom_ratio_ = settings.value("zoom_ratio", 1.0).toDouble();
690 // Actual zoom value: default zoom + fractional offset
691 int zoom = (int)(lyxrc.defaultZoom * zoom_ratio_);
692 zoom = min(max(zoom, zoom_min_), zoom_max_);
693 zoom_slider_->setValue(zoom);
694 zoom_slider_->setToolTip(qt_("Workarea zoom level. Drag, use Ctrl-+/- or Shift-Mousewheel to adjust."));
696 // Buttons to change zoom stepwise
697 #if (QT_VERSION >= QT_VERSION_CHECK(5, 11, 0))
698 QSize s(fm.horizontalAdvance('+'), fm.height());
700 QSize s(fm.width('+'), fm.height());
702 zoom_in_ = new GuiClickableLabel(statusBar());
703 zoom_in_->setText("+");
704 zoom_in_->setFixedSize(s);
705 zoom_in_->setAlignment(Qt::AlignCenter);
706 zoom_out_ = new GuiClickableLabel(statusBar());
707 zoom_out_->setText(QString(QChar(0x2212)));
708 zoom_out_->setFixedSize(s);
709 zoom_out_->setAlignment(Qt::AlignCenter);
711 statusBar()->addPermanentWidget(zoom_out_);
712 zoom_out_->setEnabled(currentBufferView());
713 statusBar()->addPermanentWidget(zoom_slider_);
714 zoom_slider_->setEnabled(currentBufferView());
715 zoom_in_->setEnabled(currentBufferView());
716 statusBar()->addPermanentWidget(zoom_in_);
718 connect(zoom_slider_, SIGNAL(sliderMoved(int)), this, SLOT(zoomSliderMoved(int)));
719 connect(zoom_slider_, SIGNAL(valueChanged(int)), this, SLOT(zoomValueChanged(int)));
720 connect(this, SIGNAL(currentZoomChanged(int)), zoom_slider_, SLOT(setValue(int)));
721 connect(zoom_in_, SIGNAL(clicked()), this, SLOT(zoomInPressed()));
722 connect(zoom_out_, SIGNAL(clicked()), this, SLOT(zoomOutPressed()));
724 // QPalette palette = statusBar()->palette();
726 zoom_value_ = new GuiClickableLabel(statusBar());
727 connect(zoom_value_, SIGNAL(pressed()), this, SLOT(showZoomContextMenu()));
728 // zoom_value_->setPalette(palette);
729 zoom_value_->setForegroundRole(statusBar()->foregroundRole());
730 zoom_value_->setFixedHeight(fm.height());
731 #if (QT_VERSION >= QT_VERSION_CHECK(5, 11, 0))
732 zoom_value_->setMinimumWidth(fm.horizontalAdvance("444\%"));
734 zoom_value_->setMinimumWidth(fm.width("444\%"));
736 zoom_value_->setAlignment(Qt::AlignCenter);
737 zoom_value_->setText(toqstr(bformat(_("[[ZOOM]]%1$d%"), zoom)));
738 statusBar()->addPermanentWidget(zoom_value_);
739 zoom_value_->setEnabled(currentBufferView());
741 statusBar()->setContextMenuPolicy(Qt::CustomContextMenu);
742 connect(statusBar(), SIGNAL(customContextMenuRequested(QPoint)),
743 this, SLOT(showStatusBarContextMenu()));
745 // enable pinch to zoom
746 grabGesture(Qt::PinchGesture);
748 QPixmap shellescape = QIcon(getPixmap("images/", "emblem-shellescape", "svgz,png")).pixmap(iconsize);
749 shell_escape_ = new QLabel(statusBar());
750 shell_escape_->setPixmap(shellescape);
751 shell_escape_->setAlignment(Qt::AlignCenter);
752 shell_escape_->setContextMenuPolicy(Qt::CustomContextMenu);
753 shell_escape_->setToolTip(qt_("WARNING: LaTeX is allowed to execute "
754 "external commands for this document. "
755 "Right click to change."));
756 SEMenu * menu = new SEMenu(this);
757 connect(shell_escape_, SIGNAL(customContextMenuRequested(QPoint)),
758 menu, SLOT(showMenu(QPoint)));
759 shell_escape_->hide();
760 statusBar()->addPermanentWidget(shell_escape_);
762 QPixmap readonly = QIcon(getPixmap("images/", "emblem-readonly", "svgz,png")).pixmap(iconsize);
763 read_only_ = new QLabel(statusBar());
764 read_only_->setPixmap(readonly);
765 read_only_->setAlignment(Qt::AlignCenter);
767 statusBar()->addPermanentWidget(read_only_);
769 version_control_ = new QLabel(statusBar());
770 version_control_->setAlignment(Qt::AlignCenter);
771 version_control_->setFrameStyle(QFrame::StyledPanel);
772 version_control_->hide();
773 statusBar()->addPermanentWidget(version_control_);
775 statusBar()->setSizeGripEnabled(true);
778 connect(&d.autosave_watcher_, SIGNAL(finished()), this,
779 SLOT(autoSaveThreadFinished()));
781 connect(&d.processing_thread_watcher_, SIGNAL(started()), this,
782 SLOT(processingThreadStarted()));
783 connect(&d.processing_thread_watcher_, SIGNAL(finished()), this,
784 SLOT(processingThreadFinished()));
786 connect(this, SIGNAL(triggerShowDialog(QString const &, QString const &, Inset *)),
787 SLOT(doShowDialog(QString const &, QString const &, Inset *)));
789 // set custom application bars context menu, e.g. tool bar and menu bar
790 setContextMenuPolicy(Qt::CustomContextMenu);
791 connect(this, SIGNAL(customContextMenuRequested(const QPoint &)),
792 SLOT(toolBarPopup(const QPoint &)));
794 // Forbid too small unresizable window because it can happen
795 // with some window manager under X11.
796 setMinimumSize(300, 200);
798 if (lyxrc.allow_geometry_session) {
799 // Now take care of session management.
804 // no session handling, default to a sane size.
805 setGeometry(50, 50, 690, 510);
808 // clear session data if any.
809 settings.remove("views");
819 void GuiView::disableShellEscape()
821 BufferView * bv = documentBufferView();
824 theSession().shellescapeFiles().remove(bv->buffer().absFileName());
825 bv->buffer().params().shell_escape = false;
826 bv->processUpdateFlags(Update::Force);
830 void GuiView::checkCancelBackground()
832 docstring const ttl = _("Cancel Export?");
833 docstring const msg = _("Do you want to cancel the background export process?");
835 Alert::prompt(ttl, msg, 1, 1,
836 _("&Cancel export"), _("Co&ntinue"));
842 void GuiView::statsPressed()
845 dispatch(FuncRequest(LFUN_STATISTICS), dr);
848 void GuiView::zoomSliderMoved(int value)
851 dispatch(FuncRequest(LFUN_BUFFER_ZOOM, convert<string>(value)), dr);
852 scheduleRedrawWorkAreas();
853 zoom_value_->setText(toqstr(bformat(_("[[ZOOM]]%1$d%"), value)));
857 void GuiView::zoomValueChanged(int value)
859 if (value != lyxrc.currentZoom)
860 zoomSliderMoved(value);
864 void GuiView::zoomInPressed()
867 dispatch(FuncRequest(LFUN_BUFFER_ZOOM_IN), dr);
868 scheduleRedrawWorkAreas();
872 void GuiView::zoomOutPressed()
875 dispatch(FuncRequest(LFUN_BUFFER_ZOOM_OUT), dr);
876 scheduleRedrawWorkAreas();
880 void GuiView::showZoomContextMenu()
882 QMenu * menu = guiApp->menus().menu(toqstr("context-zoom"), * this);
885 menu->exec(QCursor::pos());
889 void GuiView::showStatusBarContextMenu()
891 QMenu * menu = guiApp->menus().menu(toqstr("context-statusbar"), * this);
894 menu->exec(QCursor::pos());
898 void GuiView::scheduleRedrawWorkAreas()
900 for (int i = 0; i < d.tabWorkAreaCount(); i++) {
901 TabWorkArea* ta = d.tabWorkArea(i);
902 for (int u = 0; u < ta->count(); u++) {
903 ta->workArea(u)->scheduleRedraw(true);
909 QVector<GuiWorkArea*> GuiView::GuiViewPrivate::guiWorkAreas()
911 QVector<GuiWorkArea*> areas;
912 for (int i = 0; i < tabWorkAreaCount(); i++) {
913 TabWorkArea* ta = tabWorkArea(i);
914 for (int u = 0; u < ta->count(); u++) {
915 areas << ta->workArea(u);
921 static void handleExportStatus(GuiView * view, Buffer::ExportStatus status,
922 string const & format)
924 docstring const fmt = translateIfPossible(theFormats().prettyName(format));
927 case Buffer::ExportSuccess:
928 msg = bformat(_("Successful export to format: %1$s"), fmt);
930 case Buffer::ExportCancel:
931 msg = _("Document export cancelled.");
933 case Buffer::ExportError:
934 case Buffer::ExportNoPathToFormat:
935 case Buffer::ExportTexPathHasSpaces:
936 case Buffer::ExportConverterError:
937 msg = bformat(_("Error while exporting format: %1$s"), fmt);
939 case Buffer::PreviewSuccess:
940 msg = bformat(_("Successful preview of format: %1$s"), fmt);
942 case Buffer::PreviewError:
943 msg = bformat(_("Error while previewing format: %1$s"), fmt);
945 case Buffer::ExportKilled:
946 msg = bformat(_("Conversion cancelled while previewing format: %1$s"), fmt);
953 void GuiView::processingThreadStarted()
958 void GuiView::processingThreadFinished()
960 QFutureWatcher<Buffer::ExportStatus> const * watcher =
961 static_cast<QFutureWatcher<Buffer::ExportStatus> const *>(sender());
963 Buffer::ExportStatus const status = watcher->result();
964 handleExportStatus(this, status, d.processing_format);
967 BufferView const * const bv = currentBufferView();
968 if (bv && !bv->buffer().errorList("Export").empty()) {
973 bool const error = (status != Buffer::ExportSuccess &&
974 status != Buffer::PreviewSuccess &&
975 status != Buffer::ExportCancel);
977 ErrorList & el = bv->buffer().errorList(d.last_export_format);
978 // at this point, we do not know if buffer-view or
979 // master-buffer-view was called. If there was an export error,
980 // and the current buffer's error log is empty, we guess that
981 // it must be master-buffer-view that was called so we set
983 errors(d.last_export_format, el.empty());
988 void GuiView::autoSaveThreadFinished()
990 QFutureWatcher<docstring> const * watcher =
991 static_cast<QFutureWatcher<docstring> const *>(sender());
992 message(watcher->result());
997 void GuiView::saveLayout() const
1000 settings.setValue("zoom_ratio", zoom_ratio_);
1001 settings.setValue("devel_mode", devel_mode_);
1002 settings.beginGroup("views");
1003 settings.beginGroup(QString::number(id_));
1004 if (guiApp->platformName() == "qt4x11" || guiApp->platformName() == "xcb") {
1005 settings.setValue("pos", pos());
1006 settings.setValue("size", size());
1008 settings.setValue("geometry", saveGeometry());
1009 settings.setValue("layout", saveState(0));
1010 settings.setValue("icon_size", toqstr(d.iconSize(iconSize())));
1011 settings.setValue("zoom_value_visible", zoom_value_->isVisible());
1012 settings.setValue("zoom_slider_visible", zoom_slider_->isVisible());
1013 settings.setValue("word_count_enabled", word_count_enabled_);
1014 settings.setValue("char_count_enabled", char_count_enabled_);
1015 settings.setValue("char_nb_count_enabled", char_nb_count_enabled_);
1019 void GuiView::saveUISettings() const
1023 // Save the toolbar private states
1024 for (auto const & tb_p : d.toolbars_)
1025 tb_p.second->saveSession(settings);
1026 // Now take care of all other dialogs
1027 for (auto const & dlg_p : d.dialogs_)
1028 dlg_p.second->saveSession(settings);
1032 void GuiView::setCurrentZoom(const int v)
1034 lyxrc.currentZoom = v;
1035 zoom_value_->setText(toqstr(bformat(_("[[ZOOM]]%1$d%"), v)));
1036 Q_EMIT currentZoomChanged(v);
1040 bool GuiView::restoreLayout()
1043 zoom_ratio_ = settings.value("zoom_ratio", 1.0).toDouble();
1044 // Actual zoom value: default zoom + fractional offset
1045 int zoom = (int)(lyxrc.defaultZoom * zoom_ratio_);
1046 zoom = min(max(zoom, zoom_min_), zoom_max_);
1047 setCurrentZoom(zoom);
1048 devel_mode_ = settings.value("devel_mode", devel_mode_).toBool();
1049 settings.beginGroup("views");
1050 settings.beginGroup(QString::number(id_));
1051 QString const icon_key = "icon_size";
1052 if (!settings.contains(icon_key))
1055 //code below is skipped when when ~/.config/LyX is (re)created
1056 setIconSize(d.iconSize(settings.value(icon_key).toString()));
1058 zoom_value_->setVisible(settings.value("zoom_value_visible", true).toBool());
1060 bool const show_zoom_slider = settings.value("zoom_slider_visible", true).toBool();
1061 zoom_slider_->setVisible(show_zoom_slider);
1062 zoom_in_->setVisible(show_zoom_slider);
1063 zoom_out_->setVisible(show_zoom_slider);
1065 word_count_enabled_ = settings.value("word_count_enabled", true).toBool();
1066 char_count_enabled_ = settings.value("char_count_enabled", true).toBool();
1067 char_nb_count_enabled_ = settings.value("char_nb_count_enabled", true).toBool();
1068 stat_counts_->setVisible(word_count_enabled_ || char_count_enabled_ || char_nb_count_enabled_);
1070 if (guiApp->platformName() == "qt4x11" || guiApp->platformName() == "xcb") {
1071 QPoint pos = settings.value("pos", QPoint(50, 50)).toPoint();
1072 QSize size = settings.value("size", QSize(690, 510)).toSize();
1076 // Work-around for bug #6034: the window ends up in an undetermined
1077 // state when trying to restore a maximized window when it is
1078 // already maximized.
1079 if (!(windowState() & Qt::WindowMaximized))
1080 if (!restoreGeometry(settings.value("geometry").toByteArray()))
1081 setGeometry(50, 50, 690, 510);
1084 // Make sure layout is correctly oriented.
1085 setLayoutDirection(qApp->layoutDirection());
1087 // Allow the toc and view-source dock widget to be restored if needed.
1089 if ((dialog = findOrBuild("toc", true)))
1090 // see bug 5082. At least setup title and enabled state.
1091 // Visibility will be adjusted by restoreState below.
1092 dialog->prepareView();
1093 if ((dialog = findOrBuild("view-source", true)))
1094 dialog->prepareView();
1095 if ((dialog = findOrBuild("progress", true)))
1096 dialog->prepareView();
1098 if (!restoreState(settings.value("layout").toByteArray(), 0))
1101 // init the toolbars that have not been restored
1102 for (auto const & tb_p : guiApp->toolbars()) {
1103 GuiToolbar * tb = toolbar(tb_p.name);
1104 if (tb && !tb->isRestored())
1105 initToolbar(tb_p.name);
1108 // update lock (all) toolbars positions
1109 updateLockToolbars();
1116 GuiToolbar * GuiView::toolbar(string const & name)
1118 ToolbarMap::iterator it = d.toolbars_.find(name);
1119 if (it != d.toolbars_.end())
1122 LYXERR(Debug::GUI, "Toolbar::display: no toolbar named " << name);
1127 void GuiView::updateLockToolbars()
1129 toolbarsMovable_ = false;
1130 for (ToolbarInfo const & info : guiApp->toolbars()) {
1131 GuiToolbar * tb = toolbar(info.name);
1132 if (tb && tb->isMovable())
1133 toolbarsMovable_ = true;
1135 #if QT_VERSION >= 0x050200
1136 // set unified mac toolbars only when not movable as recommended:
1137 // https://doc.qt.io/qt-5/qmainwindow.html#unifiedTitleAndToolBarOnMac-prop
1138 setUnifiedTitleAndToolBarOnMac(!toolbarsMovable_);
1143 void GuiView::constructToolbars()
1145 for (auto const & tb_p : d.toolbars_)
1147 d.toolbars_.clear();
1149 // I don't like doing this here, but the standard toolbar
1150 // destroys this object when it's destroyed itself (vfr)
1151 d.layout_ = new LayoutBox(*this);
1152 d.stack_widget_->addWidget(d.layout_);
1153 d.layout_->move(0,0);
1155 // extracts the toolbars from the backend
1156 for (ToolbarInfo const & inf : guiApp->toolbars())
1157 d.toolbars_[inf.name] = new GuiToolbar(inf, *this);
1159 DynamicMenuButton::resetIconCache();
1163 void GuiView::initToolbars()
1165 // extracts the toolbars from the backend
1166 for (ToolbarInfo const & inf : guiApp->toolbars())
1167 initToolbar(inf.name);
1171 void GuiView::initToolbar(string const & name)
1173 GuiToolbar * tb = toolbar(name);
1176 int const visibility = guiApp->toolbars().defaultVisibility(name);
1177 bool newline = !(visibility & Toolbars::SAMEROW);
1178 tb->setVisible(false);
1179 tb->setVisibility(visibility);
1181 if (visibility & Toolbars::TOP) {
1183 addToolBarBreak(Qt::TopToolBarArea);
1184 addToolBar(Qt::TopToolBarArea, tb);
1187 if (visibility & Toolbars::BOTTOM) {
1189 addToolBarBreak(Qt::BottomToolBarArea);
1190 addToolBar(Qt::BottomToolBarArea, tb);
1193 if (visibility & Toolbars::LEFT) {
1195 addToolBarBreak(Qt::LeftToolBarArea);
1196 addToolBar(Qt::LeftToolBarArea, tb);
1199 if (visibility & Toolbars::RIGHT) {
1201 addToolBarBreak(Qt::RightToolBarArea);
1202 addToolBar(Qt::RightToolBarArea, tb);
1205 if (visibility & Toolbars::ON)
1206 tb->setVisible(true);
1208 tb->setMovable(true);
1212 TocModels & GuiView::tocModels()
1214 return d.toc_models_;
1218 void GuiView::setFocus()
1220 LYXERR(Debug::DEBUG, "GuiView::setFocus()" << this);
1221 QMainWindow::setFocus();
1225 bool GuiView::hasFocus() const
1227 if (currentWorkArea())
1228 return currentWorkArea()->hasFocus();
1229 if (currentMainWorkArea())
1230 return currentMainWorkArea()->hasFocus();
1231 return d.bg_widget_->hasFocus();
1235 void GuiView::focusInEvent(QFocusEvent * e)
1237 LYXERR(Debug::DEBUG, "GuiView::focusInEvent()" << this);
1238 QMainWindow::focusInEvent(e);
1239 // Make sure guiApp points to the correct view.
1240 guiApp->setCurrentView(this);
1241 if (currentWorkArea())
1242 currentWorkArea()->setFocus();
1243 else if (currentMainWorkArea())
1244 currentMainWorkArea()->setFocus();
1246 d.bg_widget_->setFocus();
1250 void GuiView::showEvent(QShowEvent * e)
1252 LYXERR(Debug::GUI, "Passed Geometry "
1253 << size().height() << "x" << size().width()
1254 << "+" << pos().x() << "+" << pos().y());
1256 if (d.splitter_->count() == 0)
1257 // No work area, switch to the background widget.
1261 QMainWindow::showEvent(e);
1265 bool GuiView::closeScheduled()
1272 bool GuiView::prepareAllBuffersForLogout()
1274 Buffer * first = theBufferList().first();
1278 // First, iterate over all buffers and ask the users if unsaved
1279 // changes should be saved.
1280 // We cannot use a for loop as the buffer list cycles.
1283 if (!saveBufferIfNeeded(*b, false))
1285 b = theBufferList().next(b);
1286 } while (b != first);
1288 // Next, save session state
1289 // When a view/window was closed before without quitting LyX, there
1290 // are already entries in the lastOpened list.
1291 theSession().lastOpened().clear();
1298 /** Destroy only all tabbed WorkAreas. Destruction of other WorkAreas
1299 ** is responsibility of the container (e.g., dialog)
1301 void GuiView::closeEvent(QCloseEvent * close_event)
1303 LYXERR(Debug::DEBUG, "GuiView::closeEvent()");
1305 if (!GuiViewPrivate::busyBuffers.isEmpty()) {
1306 Alert::warning(_("Exit LyX"),
1307 _("LyX could not be closed because documents are being processed by LyX."));
1308 close_event->setAccepted(false);
1312 // If the user pressed the x (so we didn't call closeView
1313 // programmatically), we want to clear all existing entries.
1315 theSession().lastOpened().clear();
1320 // it can happen that this event arrives without selecting the view,
1321 // e.g. when clicking the close button on a background window.
1323 if (!closeWorkAreaAll()) {
1325 close_event->ignore();
1329 // Make sure that nothing will use this to be closed View.
1330 guiApp->unregisterView(this);
1332 if (isFullScreen()) {
1333 // Switch off fullscreen before closing.
1338 // Make sure the timer time out will not trigger a statusbar update.
1339 d.statusbar_timer_.stop();
1340 d.statusbar_stats_timer_.stop();
1342 // Saving fullscreen requires additional tweaks in the toolbar code.
1343 // It wouldn't also work under linux natively.
1344 if (lyxrc.allow_geometry_session) {
1349 close_event->accept();
1353 void GuiView::dragEnterEvent(QDragEnterEvent * event)
1355 if (event->mimeData()->hasUrls())
1357 /// \todo Ask lyx-devel is this is enough:
1358 /// if (event->mimeData()->hasFormat("text/plain"))
1359 /// event->acceptProposedAction();
1363 void GuiView::dropEvent(QDropEvent * event)
1365 QList<QUrl> files = event->mimeData()->urls();
1366 if (files.isEmpty())
1369 LYXERR(Debug::GUI, "GuiView::dropEvent: got URLs!");
1370 for (int i = 0; i != files.size(); ++i) {
1371 string const file = os::internal_path(fromqstr(
1372 files.at(i).toLocalFile()));
1376 string const ext = support::getExtension(file);
1377 vector<const Format *> found_formats;
1379 // Find all formats that have the correct extension.
1380 for (const Format * fmt : theConverters().importableFormats())
1381 if (fmt->hasExtension(ext))
1382 found_formats.push_back(fmt);
1385 if (!found_formats.empty()) {
1386 if (found_formats.size() > 1) {
1387 //FIXME: show a dialog to choose the correct importable format
1388 LYXERR(Debug::FILES,
1389 "Multiple importable formats found, selecting first");
1391 string const arg = found_formats[0]->name() + " " + file;
1392 cmd = FuncRequest(LFUN_BUFFER_IMPORT, arg);
1395 //FIXME: do we have to explicitly check whether it's a lyx file?
1396 LYXERR(Debug::FILES,
1397 "No formats found, trying to open it as a lyx file");
1398 cmd = FuncRequest(LFUN_FILE_OPEN, file);
1400 // add the functions to the queue
1401 guiApp->addToFuncRequestQueue(cmd);
1404 // now process the collected functions. We perform the events
1405 // asynchronously. This prevents potential problems in case the
1406 // BufferView is closed within an event.
1407 guiApp->processFuncRequestQueueAsync();
1411 void GuiView::message(docstring const & str)
1413 if (ForkedProcess::iAmAChild())
1416 // call is moved to GUI-thread by GuiProgress
1417 d.progress_->appendMessage(toqstr(str));
1421 void GuiView::clearMessageText()
1423 message(docstring());
1427 void GuiView::updateStatusBarMessage(QString const & str)
1429 statusBar()->showMessage(str);
1430 d.statusbar_timer_.stop();
1431 d.statusbar_timer_.start(3000);
1435 void GuiView::clearMessage()
1437 // FIXME: This code was introduced in r19643 to fix bug #4123. However,
1438 // the hasFocus function mostly returns false, even if the focus is on
1439 // a workarea in this view.
1443 d.statusbar_timer_.stop();
1446 void GuiView::showStats()
1448 if (!statsEnabled())
1451 d.time_to_update -= d.timer_rate;
1453 BufferView * bv = currentBufferView();
1454 Buffer * buf = bv ? &bv->buffer() : nullptr;
1456 stat_counts_->hide();
1460 Cursor const & cur = bv->cursor();
1462 // we start new selection and need faster update
1463 if (!d.already_in_selection_ && cur.selection())
1464 d.time_to_update = 0;
1466 if (d.time_to_update > 0)
1469 DocIterator from, to;
1470 if (cur.selection()) {
1471 from = cur.selectionBegin();
1472 to = cur.selectionEnd();
1473 d.already_in_selection_ = true;
1475 from = doc_iterator_begin(buf);
1476 to = doc_iterator_end(buf);
1477 d.already_in_selection_ = false;
1480 buf->updateStatistics(from, to);
1483 if (word_count_enabled_) {
1484 int const words = buf->wordCount();
1486 stats << toqstr(bformat(_("%1$d Word"), words));
1488 stats << toqstr(bformat(_("%1$d Words"), words));
1490 int const chars_with_blanks = buf->charCount(true);
1491 if (char_count_enabled_) {
1492 if (chars_with_blanks == 1)
1493 stats << toqstr(bformat(_("%1$d Character"), chars_with_blanks));
1495 stats << toqstr(bformat(_("%1$d Characters"), chars_with_blanks));
1497 if (char_nb_count_enabled_) {
1498 int const chars = buf->charCount(false);
1500 stats << toqstr(bformat(_("%1$d Character (no Blanks)"), chars));
1502 stats << toqstr(bformat(_("%1$d Characters (no Blanks)"), chars));
1504 stat_counts_->setText(stats.join(qt_(", [[stats separator]]")));
1505 stat_counts_->show();
1507 d.time_to_update = d.default_stats_rate;
1508 // fast updates for small selections
1509 if (chars_with_blanks < d.max_sel_chars && cur.selection())
1510 d.time_to_update = d.timer_rate;
1514 void GuiView::updateWindowTitle(GuiWorkArea * wa)
1516 if (wa != d.current_work_area_
1517 || wa->bufferView().buffer().isInternal())
1519 Buffer const & buf = wa->bufferView().buffer();
1520 // Set the windows title
1521 docstring title = buf.fileName().displayName(130) + from_ascii("[*]");
1522 if (buf.notifiesExternalModification()) {
1523 title = bformat(_("%1$s (modified externally)"), title);
1524 // If the external modification status has changed, then maybe the status of
1525 // buffer-save has changed too.
1529 title += from_ascii(" - LyX");
1531 setWindowTitle(toqstr(title));
1532 // Sets the path for the window: this is used by OSX to
1533 // allow a context click on the title bar showing a menu
1534 // with the path up to the file
1535 setWindowFilePath(toqstr(buf.absFileName()));
1536 // Tell Qt whether the current document is changed
1537 setWindowModified(!buf.isClean());
1539 if (buf.params().shell_escape)
1540 shell_escape_->show();
1542 shell_escape_->hide();
1544 if (buf.hasReadonlyFlag())
1549 if (buf.lyxvc().inUse()) {
1550 version_control_->show();
1551 version_control_->setText(toqstr(buf.lyxvc().vcstatus()));
1553 version_control_->hide();
1557 void GuiView::on_currentWorkAreaChanged(GuiWorkArea * wa)
1559 if (d.current_work_area_)
1560 // disconnect the current work area from all slots
1561 QObject::disconnect(d.current_work_area_, nullptr, this, nullptr);
1563 disconnectBufferView();
1564 connectBufferView(wa->bufferView());
1565 connectBuffer(wa->bufferView().buffer());
1566 d.current_work_area_ = wa;
1567 QObject::connect(wa, SIGNAL(titleChanged(GuiWorkArea *)),
1568 this, SLOT(updateWindowTitle(GuiWorkArea *)));
1569 QObject::connect(wa, SIGNAL(busy(bool)),
1570 this, SLOT(setBusy(bool)));
1571 // connection of a signal to a signal
1572 QObject::connect(wa, SIGNAL(bufferViewChanged()),
1573 this, SIGNAL(bufferViewChanged()));
1574 Q_EMIT updateWindowTitle(wa);
1575 Q_EMIT bufferViewChanged();
1579 void GuiView::onBufferViewChanged()
1582 // Buffer-dependent dialogs must be updated. This is done here because
1583 // some dialogs require buffer()->text.
1585 zoom_slider_->setEnabled(currentBufferView());
1586 zoom_value_->setEnabled(currentBufferView());
1587 zoom_in_->setEnabled(currentBufferView());
1588 zoom_out_->setEnabled(currentBufferView());
1592 void GuiView::on_lastWorkAreaRemoved()
1595 // We already are in a close event. Nothing more to do.
1598 if (d.splitter_->count() > 1)
1599 // We have a splitter so don't close anything.
1602 // Reset and updates the dialogs.
1603 Q_EMIT bufferViewChanged();
1608 if (lyxrc.open_buffers_in_tabs)
1609 // Nothing more to do, the window should stay open.
1612 if (guiApp->viewIds().size() > 1) {
1618 // On Mac we also close the last window because the application stay
1619 // resident in memory. On other platforms we don't close the last
1620 // window because this would quit the application.
1626 void GuiView::updateStatusBar()
1628 // let the user see the explicit message
1629 if (d.statusbar_timer_.isActive())
1636 void GuiView::showMessage()
1640 QString msg = toqstr(theGuiApp()->viewStatusMessage());
1641 if (msg.isEmpty()) {
1642 BufferView const * bv = currentBufferView();
1644 msg = toqstr(bv->cursor().currentState(devel_mode_));
1646 msg = qt_("Welcome to LyX!");
1648 statusBar()->showMessage(msg);
1652 bool GuiView::statsEnabled() const
1654 return word_count_enabled_ || char_count_enabled_ || char_nb_count_enabled_;
1658 bool GuiView::event(QEvent * e)
1662 // Useful debug code:
1663 //case QEvent::ActivationChange:
1664 //case QEvent::WindowDeactivate:
1665 //case QEvent::Paint:
1666 //case QEvent::Enter:
1667 //case QEvent::Leave:
1668 //case QEvent::HoverEnter:
1669 //case QEvent::HoverLeave:
1670 //case QEvent::HoverMove:
1671 //case QEvent::StatusTip:
1672 //case QEvent::DragEnter:
1673 //case QEvent::DragLeave:
1674 //case QEvent::Drop:
1677 case QEvent::WindowStateChange: {
1678 QWindowStateChangeEvent * ev = (QWindowStateChangeEvent*)e;
1679 bool ofstate = (ev->oldState() & Qt::WindowFullScreen);
1680 bool result = QMainWindow::event(e);
1681 bool nfstate = (windowState() & Qt::WindowFullScreen);
1682 if (!ofstate && nfstate) {
1683 LYXERR(Debug::DEBUG, "GuiView: WindowStateChange(): full-screen " << nfstate);
1684 // switch to full-screen state
1685 if (lyxrc.full_screen_statusbar)
1686 statusBar()->hide();
1687 if (lyxrc.full_screen_menubar)
1689 if (lyxrc.full_screen_toolbars) {
1690 for (auto const & tb_p : d.toolbars_)
1691 if (tb_p.second->isVisibilityOn() && tb_p.second->isVisible())
1692 tb_p.second->hide();
1694 for (int i = 0; i != d.splitter_->count(); ++i)
1695 d.tabWorkArea(i)->setFullScreen(true);
1696 #if QT_VERSION > 0x050903
1697 //Qt's 5.9.4 ba44cdae38406c safe area measures won't allow us to go negative in margins
1698 setAttribute(Qt::WA_ContentsMarginsRespectsSafeArea, false);
1700 setContentsMargins(-2, -2, -2, -2);
1702 hideDialogs("prefs", nullptr);
1703 } else if (ofstate && !nfstate) {
1704 LYXERR(Debug::DEBUG, "GuiView: WindowStateChange(): full-screen " << nfstate);
1705 // switch back from full-screen state
1706 if (lyxrc.full_screen_statusbar && !statusBar()->isVisible())
1707 statusBar()->show();
1708 if (lyxrc.full_screen_menubar && !menuBar()->isVisible())
1710 if (lyxrc.full_screen_toolbars) {
1711 for (auto const & tb_p : d.toolbars_)
1712 if (tb_p.second->isVisibilityOn() && !tb_p.second->isVisible())
1713 tb_p.second->show();
1716 for (int i = 0; i != d.splitter_->count(); ++i)
1717 d.tabWorkArea(i)->setFullScreen(false);
1718 #if QT_VERSION > 0x050903
1719 setAttribute(Qt::WA_ContentsMarginsRespectsSafeArea, true);
1721 setContentsMargins(0, 0, 0, 0);
1726 case QEvent::WindowActivate: {
1727 GuiView * old_view = guiApp->currentView();
1728 if (this == old_view) {
1730 return QMainWindow::event(e);
1732 if (old_view && old_view->currentBufferView()) {
1733 // save current selection to the selection buffer to allow
1734 // middle-button paste in this window.
1735 cap::saveSelection(old_view->currentBufferView()->cursor());
1737 guiApp->setCurrentView(this);
1738 if (d.current_work_area_)
1739 on_currentWorkAreaChanged(d.current_work_area_);
1743 return QMainWindow::event(e);
1746 case QEvent::ShortcutOverride: {
1748 if (isFullScreen() && menuBar()->isHidden()) {
1749 QKeyEvent * ke = static_cast<QKeyEvent*>(e);
1750 // FIXME: we should also try to detect special LyX shortcut such as
1751 // Alt-P and Alt-M. Right now there is a hack in
1752 // GuiWorkArea::processKeySym() that hides again the menubar for
1754 if (ke->modifiers() & Qt::AltModifier && ke->key() != Qt::Key_Alt) {
1756 return QMainWindow::event(e);
1759 return QMainWindow::event(e);
1762 case QEvent::ApplicationPaletteChange: {
1763 // runtime switch from/to dark mode
1765 return QMainWindow::event(e);
1768 case QEvent::Gesture: {
1769 QGestureEvent *ge = static_cast<QGestureEvent*>(e);
1770 QGesture *gp = ge->gesture(Qt::PinchGesture);
1772 QPinchGesture *pinch = static_cast<QPinchGesture *>(gp);
1773 QPinchGesture::ChangeFlags changeFlags = pinch->changeFlags();
1774 qreal totalScaleFactor = pinch->totalScaleFactor();
1775 LYXERR(Debug::GUI, "totalScaleFactor: " << totalScaleFactor);
1776 if (pinch->state() == Qt::GestureStarted) {
1777 initialZoom_ = lyxrc.currentZoom;
1778 LYXERR(Debug::GUI, "initialZoom_: " << initialZoom_);
1780 if (changeFlags & QPinchGesture::ScaleFactorChanged) {
1781 qreal factor = initialZoom_ * totalScaleFactor;
1782 LYXERR(Debug::GUI, "scaleFactor: " << factor);
1783 zoomValueChanged(factor);
1786 return QMainWindow::event(e);
1790 return QMainWindow::event(e);
1794 void GuiView::resetWindowTitle()
1796 setWindowTitle(qt_("LyX"));
1799 bool GuiView::focusNextPrevChild(bool /*next*/)
1806 bool GuiView::busy() const
1812 void GuiView::setBusy(bool busy)
1814 bool const busy_before = busy_ > 0;
1815 busy ? ++busy_ : --busy_;
1816 if ((busy_ > 0) == busy_before)
1817 // busy state didn't change
1821 QApplication::setOverrideCursor(Qt::WaitCursor);
1824 QApplication::restoreOverrideCursor();
1829 void GuiView::resetCommandExecute()
1831 command_execute_ = false;
1836 double GuiView::pixelRatio() const
1838 #if QT_VERSION >= 0x050000
1839 return qt_scale_factor * devicePixelRatio();
1846 GuiWorkArea * GuiView::workArea(int index)
1848 if (TabWorkArea * twa = d.currentTabWorkArea())
1849 if (index < twa->count())
1850 return twa->workArea(index);
1855 GuiWorkArea * GuiView::workArea(Buffer & buffer)
1857 if (currentWorkArea()
1858 && ¤tWorkArea()->bufferView().buffer() == &buffer)
1859 return currentWorkArea();
1860 if (TabWorkArea * twa = d.currentTabWorkArea())
1861 return twa->workArea(buffer);
1866 GuiWorkArea * GuiView::addWorkArea(Buffer & buffer)
1868 // Automatically create a TabWorkArea if there are none yet.
1869 TabWorkArea * tab_widget = d.splitter_->count()
1870 ? d.currentTabWorkArea() : addTabWorkArea();
1871 return tab_widget->addWorkArea(buffer, *this);
1875 TabWorkArea * GuiView::addTabWorkArea()
1877 TabWorkArea * twa = new TabWorkArea;
1878 QObject::connect(twa, SIGNAL(currentWorkAreaChanged(GuiWorkArea *)),
1879 this, SLOT(on_currentWorkAreaChanged(GuiWorkArea *)));
1880 QObject::connect(twa, SIGNAL(lastWorkAreaRemoved()),
1881 this, SLOT(on_lastWorkAreaRemoved()));
1883 d.splitter_->addWidget(twa);
1884 d.stack_widget_->setCurrentWidget(d.splitter_);
1889 GuiWorkArea const * GuiView::currentWorkArea() const
1891 return d.current_work_area_;
1895 GuiWorkArea * GuiView::currentWorkArea()
1897 return d.current_work_area_;
1901 GuiWorkArea const * GuiView::currentMainWorkArea() const
1903 if (!d.currentTabWorkArea())
1905 return d.currentTabWorkArea()->currentWorkArea();
1909 GuiWorkArea * GuiView::currentMainWorkArea()
1911 if (!d.currentTabWorkArea())
1913 return d.currentTabWorkArea()->currentWorkArea();
1917 void GuiView::setCurrentWorkArea(GuiWorkArea * wa)
1919 LYXERR(Debug::DEBUG, "Setting current wa: " << wa << endl);
1921 d.current_work_area_ = nullptr;
1923 Q_EMIT bufferViewChanged();
1927 // FIXME: I've no clue why this is here and why it accesses
1928 // theGuiApp()->currentView, which might be 0 (bug 6464).
1929 // See also 27525 (vfr).
1930 if (theGuiApp()->currentView() == this
1931 && theGuiApp()->currentView()->currentWorkArea() == wa)
1934 if (currentBufferView())
1935 cap::saveSelection(currentBufferView()->cursor());
1937 theGuiApp()->setCurrentView(this);
1938 d.current_work_area_ = wa;
1940 // We need to reset this now, because it will need to be
1941 // right if the tabWorkArea gets reset in the for loop. We
1942 // will change it back if we aren't in that case.
1943 GuiWorkArea * const old_cmwa = d.current_main_work_area_;
1944 d.current_main_work_area_ = wa;
1946 for (int i = 0; i != d.splitter_->count(); ++i) {
1947 if (d.tabWorkArea(i)->setCurrentWorkArea(wa)) {
1948 LYXERR(Debug::DEBUG, "Current wa: " << currentWorkArea()
1949 << ", Current main wa: " << currentMainWorkArea());
1954 d.current_main_work_area_ = old_cmwa;
1956 LYXERR(Debug::DEBUG, "This is not a tabbed wa");
1957 on_currentWorkAreaChanged(wa);
1958 BufferView & bv = wa->bufferView();
1959 bv.cursor().fixIfBroken();
1961 wa->setUpdatesEnabled(true);
1962 LYXERR(Debug::DEBUG, "Current wa: " << currentWorkArea() << ", Current main wa: " << currentMainWorkArea());
1966 void GuiView::removeWorkArea(GuiWorkArea * wa)
1968 LASSERT(wa, return);
1969 if (wa == d.current_work_area_) {
1971 disconnectBufferView();
1972 d.current_work_area_ = nullptr;
1973 d.current_main_work_area_ = nullptr;
1976 bool found_twa = false;
1977 for (int i = 0; i != d.splitter_->count(); ++i) {
1978 TabWorkArea * twa = d.tabWorkArea(i);
1979 if (twa->removeWorkArea(wa)) {
1980 // Found in this tab group, and deleted the GuiWorkArea.
1982 if (twa->count() != 0) {
1983 if (d.current_work_area_ == nullptr)
1984 // This means that we are closing the current GuiWorkArea, so
1985 // switch to the next GuiWorkArea in the found TabWorkArea.
1986 setCurrentWorkArea(twa->currentWorkArea());
1988 // No more WorkAreas in this tab group, so delete it.
1995 // It is not a tabbed work area (i.e., the search work area), so it
1996 // should be deleted by other means.
1997 LASSERT(found_twa, return);
1999 if (d.current_work_area_ == nullptr) {
2000 if (d.splitter_->count() != 0) {
2001 TabWorkArea * twa = d.currentTabWorkArea();
2002 setCurrentWorkArea(twa->currentWorkArea());
2004 // No more work areas, switch to the background widget.
2005 setCurrentWorkArea(nullptr);
2011 bool GuiView::hasVisibleWorkArea(GuiWorkArea * wa) const
2013 for (int i = 0; i < d.splitter_->count(); ++i)
2014 if (d.tabWorkArea(i)->currentWorkArea() == wa)
2017 FindAndReplace * fr = static_cast<FindAndReplace*>(find("findreplaceadv", false));
2018 return fr->isVisible() && fr->hasWorkArea(wa);
2022 LayoutBox * GuiView::getLayoutDialog() const
2028 void GuiView::updateLayoutList()
2031 d.layout_->updateContents(false);
2035 void GuiView::updateToolbars()
2037 if (d.current_work_area_) {
2039 if (d.current_work_area_->bufferView().cursor().inMathed()
2040 && !d.current_work_area_->bufferView().cursor().inRegexped())
2041 context |= Toolbars::MATH;
2042 if (lyx::getStatus(FuncRequest(LFUN_LAYOUT_TABULAR)).enabled())
2043 context |= Toolbars::TABLE;
2044 if (currentBufferView()->buffer().areChangesPresent()
2045 || (lyx::getStatus(FuncRequest(LFUN_CHANGES_TRACK)).enabled()
2046 && lyx::getStatus(FuncRequest(LFUN_CHANGES_TRACK)).onOff(true))
2047 || (lyx::getStatus(FuncRequest(LFUN_CHANGES_OUTPUT)).enabled()
2048 && lyx::getStatus(FuncRequest(LFUN_CHANGES_OUTPUT)).onOff(true)))
2049 context |= Toolbars::REVIEW;
2050 if (lyx::getStatus(FuncRequest(LFUN_IN_MATHMACROTEMPLATE)).enabled())
2051 context |= Toolbars::MATHMACROTEMPLATE;
2052 if (lyx::getStatus(FuncRequest(LFUN_IN_IPA)).enabled())
2053 context |= Toolbars::IPA;
2054 if (command_execute_)
2055 context |= Toolbars::MINIBUFFER;
2056 if (minibuffer_focus_) {
2057 context |= Toolbars::MINIBUFFER_FOCUS;
2058 minibuffer_focus_ = false;
2061 for (auto const & tb_p : d.toolbars_)
2062 tb_p.second->update(context);
2064 for (auto const & tb_p : d.toolbars_)
2065 tb_p.second->update();
2069 void GuiView::refillToolbars()
2071 DynamicMenuButton::resetIconCache();
2072 for (auto const & tb_p : d.toolbars_)
2073 tb_p.second->refill();
2077 void GuiView::setBuffer(Buffer * newBuffer, bool switch_to)
2079 LYXERR(Debug::DEBUG, "Setting buffer: " << newBuffer << endl);
2080 LASSERT(newBuffer, return);
2082 GuiWorkArea * wa = workArea(*newBuffer);
2083 if (wa == nullptr) {
2085 newBuffer->masterBuffer()->updateBuffer();
2087 wa = addWorkArea(*newBuffer);
2088 // scroll to the position when the BufferView was last closed
2089 if (lyxrc.use_lastfilepos) {
2090 LastFilePosSection::FilePos filepos =
2091 theSession().lastFilePos().load(newBuffer->fileName());
2092 wa->bufferView().moveToPosition(filepos.pit, filepos.pos, 0, 0);
2095 //Disconnect the old buffer...there's no new one.
2098 connectBuffer(*newBuffer);
2099 connectBufferView(wa->bufferView());
2101 setCurrentWorkArea(wa);
2105 void GuiView::connectBuffer(Buffer & buf)
2107 buf.setGuiDelegate(this);
2111 void GuiView::disconnectBuffer()
2113 if (d.current_work_area_)
2114 d.current_work_area_->bufferView().buffer().setGuiDelegate(nullptr);
2118 void GuiView::connectBufferView(BufferView & bv)
2120 bv.setGuiDelegate(this);
2124 void GuiView::disconnectBufferView()
2126 if (d.current_work_area_)
2127 d.current_work_area_->bufferView().setGuiDelegate(nullptr);
2131 void GuiView::errors(string const & error_type, bool from_master)
2133 BufferView const * const bv = currentBufferView();
2137 ErrorList const & el = from_master ?
2138 bv->buffer().masterBuffer()->errorList(error_type) :
2139 bv->buffer().errorList(error_type);
2144 string err = error_type;
2146 err = "from_master|" + error_type;
2147 showDialog("errorlist", err);
2151 void GuiView::updateTocItem(string const & type, DocIterator const & dit)
2153 d.toc_models_.updateItem(toqstr(type), dit);
2157 void GuiView::structureChanged()
2159 // This is called from the Buffer, which has no way to ensure that cursors
2160 // in BufferView remain valid.
2161 if (documentBufferView())
2162 documentBufferView()->cursor().sanitize();
2163 // FIXME: This is slightly expensive, though less than the tocBackend update
2164 // (#9880). This also resets the view in the Toc Widget (#6675).
2165 d.toc_models_.reset(documentBufferView());
2166 // Navigator needs more than a simple update in this case. It needs to be
2168 updateDialog("toc", "");
2172 void GuiView::updateDialog(string const & name, string const & sdata)
2174 if (!isDialogVisible(name))
2177 map<string, DialogPtr>::const_iterator it = d.dialogs_.find(name);
2178 if (it == d.dialogs_.end())
2181 Dialog * const dialog = it->second.get();
2182 if (dialog->isVisibleView())
2183 dialog->initialiseParams(sdata);
2187 BufferView * GuiView::documentBufferView()
2189 return currentMainWorkArea()
2190 ? ¤tMainWorkArea()->bufferView()
2195 BufferView const * GuiView::documentBufferView() const
2197 return currentMainWorkArea()
2198 ? ¤tMainWorkArea()->bufferView()
2203 BufferView * GuiView::currentBufferView()
2205 return d.current_work_area_ ? &d.current_work_area_->bufferView() : nullptr;
2209 BufferView const * GuiView::currentBufferView() const
2211 return d.current_work_area_ ? &d.current_work_area_->bufferView() : nullptr;
2215 docstring GuiView::GuiViewPrivate::autosaveAndDestroy(
2216 Buffer const * orig, Buffer * clone)
2218 bool const success = clone->autoSave();
2220 busyBuffers.remove(orig);
2222 ? _("Automatic save done.")
2223 : _("Automatic save failed!");
2227 void GuiView::autoSave()
2229 LYXERR(Debug::INFO, "Running autoSave()");
2231 Buffer * buffer = documentBufferView()
2232 ? &documentBufferView()->buffer() : nullptr;
2234 resetAutosaveTimers();
2238 GuiViewPrivate::busyBuffers.insert(buffer);
2239 QFuture<docstring> f = QtConcurrent::run(GuiViewPrivate::autosaveAndDestroy,
2240 buffer, buffer->cloneBufferOnly());
2241 d.autosave_watcher_.setFuture(f);
2242 resetAutosaveTimers();
2246 void GuiView::resetAutosaveTimers()
2249 d.autosave_timeout_.restart();
2255 double zoomRatio(FuncRequest const & cmd, double const zr)
2257 if (cmd.argument().empty()) {
2258 if (cmd.action() == LFUN_BUFFER_ZOOM)
2260 else if (cmd.action() == LFUN_BUFFER_ZOOM_IN)
2262 else // cmd.action() == LFUN_BUFFER_ZOOM_OUT
2265 if (cmd.action() == LFUN_BUFFER_ZOOM)
2266 return convert<int>(cmd.argument()) / double(lyxrc.defaultZoom);
2267 else if (cmd.action() == LFUN_BUFFER_ZOOM_IN)
2268 return zr + convert<int>(cmd.argument()) / 100.0;
2269 else // cmd.action() == LFUN_BUFFER_ZOOM_OUT
2270 return zr - convert<int>(cmd.argument()) / 100.0;
2277 bool GuiView::getStatus(FuncRequest const & cmd, FuncStatus & flag)
2280 Buffer * buf = currentBufferView()
2281 ? ¤tBufferView()->buffer() : nullptr;
2282 Buffer * doc_buffer = documentBufferView()
2283 ? &(documentBufferView()->buffer()) : nullptr;
2286 /* In LyX/Mac, when a dialog is open, the menus of the
2287 application can still be accessed without giving focus to
2288 the main window. In this case, we want to disable the menu
2289 entries that are buffer-related.
2290 This code must not be used on Linux and Windows, since it
2291 would disable buffer-related entries when hovering over the
2292 menu (see bug #9574).
2294 if (cmd.origin() == FuncRequest::MENU && !hasFocus()) {
2300 // Check whether we need a buffer
2301 if (!lyxaction.funcHasFlag(cmd.action(), LyXAction::NoBuffer) && !buf) {
2302 // no, exit directly
2303 flag.message(from_utf8(N_("Command not allowed with"
2304 "out any document open")));
2305 flag.setEnabled(false);
2309 if (cmd.origin() == FuncRequest::TOC) {
2310 GuiToc * toc = static_cast<GuiToc*>(findOrBuild("toc", false));
2311 if (!toc || !toc->getStatus(documentBufferView()->cursor(), cmd, flag))
2312 flag.setEnabled(false);
2316 switch(cmd.action()) {
2317 case LFUN_BUFFER_IMPORT:
2320 case LFUN_MASTER_BUFFER_EXPORT:
2322 && (doc_buffer->parent() != nullptr
2323 || doc_buffer->hasChildren())
2324 && !d.processing_thread_watcher_.isRunning()
2325 // this launches a dialog, which would be in the wrong Buffer
2326 && !(::lyx::operator==(cmd.argument(), "custom"));
2329 case LFUN_MASTER_BUFFER_UPDATE:
2330 case LFUN_MASTER_BUFFER_VIEW:
2332 && (doc_buffer->parent() != nullptr
2333 || doc_buffer->hasChildren())
2334 && !d.processing_thread_watcher_.isRunning();
2337 case LFUN_BUFFER_UPDATE:
2338 case LFUN_BUFFER_VIEW: {
2339 if (!doc_buffer || d.processing_thread_watcher_.isRunning()) {
2343 string format = to_utf8(cmd.argument());
2344 if (cmd.argument().empty())
2345 format = doc_buffer->params().getDefaultOutputFormat();
2346 enable = doc_buffer->params().isExportable(format, true);
2350 case LFUN_BUFFER_RELOAD:
2351 enable = doc_buffer && !doc_buffer->isUnnamed()
2352 && doc_buffer->fileName().exists()
2353 && (!doc_buffer->isClean() || doc_buffer->notifiesExternalModification());
2356 case LFUN_BUFFER_RESET_EXPORT:
2357 enable = doc_buffer != nullptr;
2360 case LFUN_BUFFER_CHILD_OPEN:
2361 enable = doc_buffer != nullptr;
2364 case LFUN_MASTER_BUFFER_FORALL: {
2365 if (doc_buffer == nullptr) {
2366 flag.message(from_utf8(N_("Command not allowed without a buffer open")));
2370 FuncRequest const cmdToPass = lyxaction.lookupFunc(cmd.getLongArg(0));
2371 if (cmdToPass.action() == LFUN_UNKNOWN_ACTION) {
2372 flag.message(from_utf8(N_("Invalid argument of master-buffer-forall")));
2377 for (Buffer * buf : doc_buffer->allRelatives()) {
2378 GuiWorkArea * wa = workArea(*buf);
2381 if (wa->bufferView().getStatus(cmdToPass, flag)) {
2382 enable = flag.enabled();
2389 case LFUN_BUFFER_WRITE:
2390 enable = doc_buffer && (doc_buffer->isUnnamed()
2391 || (!doc_buffer->isClean()
2392 || cmd.argument() == "force"));
2395 //FIXME: This LFUN should be moved to GuiApplication.
2396 case LFUN_BUFFER_WRITE_ALL: {
2397 // We enable the command only if there are some modified buffers
2398 Buffer * first = theBufferList().first();
2403 // We cannot use a for loop as the buffer list is a cycle.
2405 if (!b->isClean()) {
2409 b = theBufferList().next(b);
2410 } while (b != first);
2414 case LFUN_BUFFER_EXTERNAL_MODIFICATION_CLEAR:
2415 enable = doc_buffer && doc_buffer->notifiesExternalModification();
2418 case LFUN_BUFFER_EXPORT: {
2419 if (!doc_buffer || d.processing_thread_watcher_.isRunning()) {
2423 return doc_buffer->getStatus(cmd, flag);
2426 case LFUN_BUFFER_EXPORT_AS:
2427 if (!doc_buffer || d.processing_thread_watcher_.isRunning()) {
2432 case LFUN_BUFFER_WRITE_AS:
2433 case LFUN_BUFFER_WRITE_AS_TEMPLATE:
2434 enable = doc_buffer != nullptr;
2437 case LFUN_EXPORT_CANCEL:
2438 enable = d.processing_thread_watcher_.isRunning();
2441 case LFUN_BUFFER_CLOSE:
2442 case LFUN_VIEW_CLOSE:
2443 enable = doc_buffer != nullptr;
2446 case LFUN_BUFFER_CLOSE_ALL:
2447 enable = theBufferList().last() != theBufferList().first();
2450 case LFUN_BUFFER_CHKTEX: {
2451 // hide if we have no checktex command
2452 if (lyxrc.chktex_command.empty()) {
2453 flag.setUnknown(true);
2457 if (!doc_buffer || !doc_buffer->params().isLatex()
2458 || d.processing_thread_watcher_.isRunning()) {
2459 // grey out, don't hide
2467 case LFUN_VIEW_SPLIT:
2468 if (cmd.getArg(0) == "vertical")
2469 enable = doc_buffer && (d.splitter_->count() == 1 ||
2470 d.splitter_->orientation() == Qt::Vertical);
2472 enable = doc_buffer && (d.splitter_->count() == 1 ||
2473 d.splitter_->orientation() == Qt::Horizontal);
2476 case LFUN_TAB_GROUP_NEXT:
2477 case LFUN_TAB_GROUP_PREVIOUS:
2478 enable = (d.splitter_->count() > 1);
2481 case LFUN_TAB_GROUP_CLOSE:
2482 enable = d.tabWorkAreaCount() > 1;
2485 case LFUN_DEVEL_MODE_TOGGLE:
2486 flag.setOnOff(devel_mode_);
2489 case LFUN_TOOLBAR_SET: {
2490 string const name = cmd.getArg(0);
2491 string const state = cmd.getArg(1);
2492 if (name.empty() || state.empty()) {
2494 docstring const msg =
2495 _("Function toolbar-set requires two arguments!");
2499 if (state != "on" && state != "off" && state != "auto") {
2501 docstring const msg =
2502 bformat(_("Invalid argument \"%1$s\" to function toolbar-set!"),
2507 if (GuiToolbar * t = toolbar(name)) {
2508 bool const autovis = t->visibility() & Toolbars::AUTO;
2510 flag.setOnOff(t->isVisible() && !autovis);
2511 else if (state == "off")
2512 flag.setOnOff(!t->isVisible() && !autovis);
2513 else if (state == "auto")
2514 flag.setOnOff(autovis);
2517 docstring const msg =
2518 bformat(_("Unknown toolbar \"%1$s\""), from_utf8(name));
2524 case LFUN_TOOLBAR_TOGGLE: {
2525 string const name = cmd.getArg(0);
2526 if (GuiToolbar * t = toolbar(name))
2527 flag.setOnOff(t->isVisible());
2530 docstring const msg =
2531 bformat(_("Unknown toolbar \"%1$s\""), from_utf8(name));
2537 case LFUN_TOOLBAR_MOVABLE: {
2538 string const name = cmd.getArg(0);
2539 // use negation since locked == !movable
2541 // toolbar name * locks all toolbars
2542 flag.setOnOff(!toolbarsMovable_);
2543 else if (GuiToolbar * t = toolbar(name))
2544 flag.setOnOff(!(t->isMovable()));
2547 docstring const msg =
2548 bformat(_("Unknown toolbar \"%1$s\""), from_utf8(name));
2554 case LFUN_ICON_SIZE:
2555 flag.setOnOff(d.iconSize(cmd.argument()) == iconSize());
2558 case LFUN_DROP_LAYOUTS_CHOICE:
2559 enable = buf != nullptr;
2562 case LFUN_UI_TOGGLE:
2563 if (cmd.argument() == "zoomlevel") {
2564 flag.setOnOff(zoom_value_ ? zoom_value_->isVisible() : false);
2565 } else if (cmd.argument() == "zoomslider") {
2566 flag.setOnOff(zoom_slider_ ? zoom_slider_->isVisible() : false);
2567 } else if (cmd.argument() == "statistics-w") {
2568 flag.setOnOff(word_count_enabled_);
2569 } else if (cmd.argument() == "statistics-cb") {
2570 flag.setOnOff(char_count_enabled_);
2571 } else if (cmd.argument() == "statistics-c") {
2572 flag.setOnOff(char_nb_count_enabled_);
2574 flag.setOnOff(isFullScreen());
2577 case LFUN_DIALOG_DISCONNECT_INSET:
2580 case LFUN_DIALOG_HIDE:
2581 // FIXME: should we check if the dialog is shown?
2584 case LFUN_DIALOG_TOGGLE:
2585 flag.setOnOff(isDialogVisible(cmd.getArg(0)));
2588 case LFUN_DIALOG_SHOW: {
2589 string const name = cmd.getArg(0);
2591 enable = name == "aboutlyx"
2592 || name == "file" //FIXME: should be removed.
2593 || name == "lyxfiles"
2595 || name == "texinfo"
2596 || name == "progress"
2597 || name == "compare";
2598 else if (name == "character" || name == "symbols"
2599 || name == "mathdelimiter" || name == "mathmatrix") {
2600 if (!buf || buf->isReadonly())
2603 Cursor const & cur = currentBufferView()->cursor();
2604 enable = !(cur.inTexted() && cur.paragraph().isPassThru());
2607 else if (name == "latexlog")
2608 enable = FileName(doc_buffer->logName()).isReadableFile();
2609 else if (name == "spellchecker")
2610 enable = theSpellChecker()
2611 && !doc_buffer->text().empty();
2612 else if (name == "vclog")
2613 enable = doc_buffer->lyxvc().inUse();
2617 case LFUN_DIALOG_UPDATE: {
2618 string const name = cmd.getArg(0);
2620 enable = name == "prefs";
2624 case LFUN_COMMAND_EXECUTE:
2626 case LFUN_MENU_OPEN:
2627 // Nothing to check.
2630 case LFUN_COMPLETION_INLINE:
2631 if (!d.current_work_area_
2632 || !d.current_work_area_->completer().inlinePossible(
2633 currentBufferView()->cursor()))
2637 case LFUN_COMPLETION_POPUP:
2638 if (!d.current_work_area_
2639 || !d.current_work_area_->completer().popupPossible(
2640 currentBufferView()->cursor()))
2645 if (!d.current_work_area_
2646 || !d.current_work_area_->completer().inlinePossible(
2647 currentBufferView()->cursor()))
2651 case LFUN_COMPLETION_ACCEPT:
2652 if (!d.current_work_area_
2653 || (!d.current_work_area_->completer().popupVisible()
2654 && !d.current_work_area_->completer().inlineVisible()
2655 && !d.current_work_area_->completer().completionAvailable()))
2659 case LFUN_COMPLETION_CANCEL:
2660 if (!d.current_work_area_
2661 || (!d.current_work_area_->completer().popupVisible()
2662 && !d.current_work_area_->completer().inlineVisible()))
2666 case LFUN_BUFFER_ZOOM_OUT:
2667 case LFUN_BUFFER_ZOOM_IN:
2668 case LFUN_BUFFER_ZOOM: {
2669 int const zoom = (int)(lyxrc.defaultZoom * zoomRatio(cmd, zoom_ratio_));
2670 if (zoom < zoom_min_) {
2671 docstring const msg =
2672 bformat(_("Zoom level cannot be less than %1$d%."), zoom_min_);
2675 } else if (zoom > zoom_max_) {
2676 docstring const msg =
2677 bformat(_("Zoom level cannot be more than %1$d%."), zoom_max_);
2681 enable = doc_buffer;
2686 case LFUN_BUFFER_MOVE_NEXT:
2687 case LFUN_BUFFER_MOVE_PREVIOUS:
2688 // we do not cycle when moving
2689 case LFUN_BUFFER_NEXT:
2690 case LFUN_BUFFER_PREVIOUS:
2691 // because we cycle, it doesn't matter whether on first or last
2692 enable = (d.currentTabWorkArea()->count() > 1);
2694 case LFUN_BUFFER_SWITCH:
2695 // toggle on the current buffer, but do not toggle off
2696 // the other ones (is that a good idea?)
2698 && to_utf8(cmd.argument()) == doc_buffer->absFileName())
2699 flag.setOnOff(true);
2702 case LFUN_VC_REGISTER:
2703 enable = doc_buffer && !doc_buffer->lyxvc().inUse();
2705 case LFUN_VC_RENAME:
2706 enable = doc_buffer && doc_buffer->lyxvc().renameEnabled();
2709 enable = doc_buffer && doc_buffer->lyxvc().copyEnabled();
2711 case LFUN_VC_CHECK_IN:
2712 enable = doc_buffer && doc_buffer->lyxvc().checkInEnabled();
2714 case LFUN_VC_CHECK_OUT:
2715 enable = doc_buffer && doc_buffer->lyxvc().checkOutEnabled();
2717 case LFUN_VC_LOCKING_TOGGLE:
2718 enable = doc_buffer && !doc_buffer->hasReadonlyFlag()
2719 && doc_buffer->lyxvc().lockingToggleEnabled();
2720 flag.setOnOff(enable && doc_buffer->lyxvc().locking());
2722 case LFUN_VC_REVERT:
2723 enable = doc_buffer && doc_buffer->lyxvc().inUse()
2724 && !doc_buffer->hasReadonlyFlag();
2726 case LFUN_VC_UNDO_LAST:
2727 enable = doc_buffer && doc_buffer->lyxvc().undoLastEnabled();
2729 case LFUN_VC_REPO_UPDATE:
2730 enable = doc_buffer && doc_buffer->lyxvc().repoUpdateEnabled();
2732 case LFUN_VC_COMMAND: {
2733 if (cmd.argument().empty())
2735 if (!doc_buffer && contains(cmd.getArg(0), 'D'))
2739 case LFUN_VC_COMPARE:
2740 enable = doc_buffer && doc_buffer->lyxvc().prepareFileRevisionEnabled();
2743 case LFUN_SERVER_GOTO_FILE_ROW:
2744 case LFUN_LYX_ACTIVATE:
2745 case LFUN_WINDOW_RAISE:
2747 case LFUN_FORWARD_SEARCH:
2748 enable = !(lyxrc.forward_search_dvi.empty() && lyxrc.forward_search_pdf.empty()) &&
2749 doc_buffer && doc_buffer->isSyncTeXenabled();
2752 case LFUN_FILE_INSERT_PLAINTEXT:
2753 case LFUN_FILE_INSERT_PLAINTEXT_PARA:
2754 enable = documentBufferView() && documentBufferView()->cursor().inTexted();
2757 case LFUN_SPELLING_CONTINUOUSLY:
2758 flag.setOnOff(lyxrc.spellcheck_continuously);
2761 case LFUN_CITATION_OPEN:
2770 flag.setEnabled(false);
2776 static FileName selectTemplateFile()
2778 FileDialog dlg(qt_("Select template file"));
2779 dlg.setButton1(qt_("D&ocuments"), toqstr(lyxrc.document_path));
2780 dlg.setButton2(qt_("&Templates"), toqstr(lyxrc.template_path));
2782 FileDialog::Result result = dlg.open(toqstr(lyxrc.template_path),
2783 QStringList(qt_("LyX Documents (*.lyx)")));
2785 if (result.first == FileDialog::Later)
2787 if (result.second.isEmpty())
2789 return FileName(fromqstr(result.second));
2793 Buffer * GuiView::loadDocument(FileName const & filename, bool tolastfiles)
2797 Buffer * newBuffer = nullptr;
2799 newBuffer = checkAndLoadLyXFile(filename);
2800 } catch (ExceptionMessage const &) {
2807 message(_("Document not loaded."));
2811 setBuffer(newBuffer);
2812 newBuffer->errors("Parse");
2815 theSession().lastFiles().add(filename);
2816 theSession().writeFile();
2823 void GuiView::openDocument(string const & fname)
2825 string initpath = lyxrc.document_path;
2827 if (documentBufferView()) {
2828 string const trypath = documentBufferView()->buffer().filePath();
2829 // If directory is writeable, use this as default.
2830 if (FileName(trypath).isDirWritable())
2836 if (fname.empty()) {
2837 FileDialog dlg(qt_("Select document to open"));
2838 dlg.setButton1(qt_("D&ocuments"), toqstr(lyxrc.document_path));
2839 dlg.setButton2(qt_("&Examples"), toqstr(lyxrc.example_path));
2841 QStringList const filter({
2842 qt_("LyX Documents (*.lyx)"),
2843 qt_("LyX Document Backups (*.lyx~)"),
2844 qt_("All Files (*.*)")
2846 FileDialog::Result result =
2847 dlg.open(toqstr(initpath), filter);
2849 if (result.first == FileDialog::Later)
2852 filename = fromqstr(result.second);
2854 // check selected filename
2855 if (filename.empty()) {
2856 message(_("Canceled."));
2862 // get absolute path of file and add ".lyx" to the filename if
2864 FileName const fullname =
2865 fileSearch(string(), filename, "lyx", support::may_not_exist);
2866 if (!fullname.empty())
2867 filename = fullname.absFileName();
2869 if (!fullname.onlyPath().isDirectory()) {
2870 Alert::warning(_("Invalid filename"),
2871 bformat(_("The directory in the given path\n%1$s\ndoes not exist."),
2872 from_utf8(fullname.absFileName())));
2876 // if the file doesn't exist and isn't already open (bug 6645),
2877 // let the user create one
2878 if (!fullname.exists() && !theBufferList().exists(fullname) &&
2879 !LyXVC::file_not_found_hook(fullname)) {
2880 // the user specifically chose this name. Believe him.
2881 Buffer * const b = newFile(filename, string(), true);
2887 docstring const disp_fn = makeDisplayPath(filename);
2888 message(bformat(_("Opening document %1$s..."), disp_fn));
2891 Buffer * buf = loadDocument(fullname);
2893 str2 = bformat(_("Document %1$s opened."), disp_fn);
2894 if (buf->lyxvc().inUse())
2895 str2 += " " + from_utf8(buf->lyxvc().versionString()) +
2896 " " + _("Version control detected.");
2898 str2 = bformat(_("Could not open document %1$s"), disp_fn);
2903 // FIXME: clean that
2904 static bool import(GuiView * lv, FileName const & filename,
2905 string const & format, ErrorList & errorList)
2907 FileName const lyxfile(support::changeExtension(filename.absFileName(), ".lyx"));
2909 string loader_format;
2910 vector<string> loaders = theConverters().loaders();
2911 if (find(loaders.begin(), loaders.end(), format) == loaders.end()) {
2912 for (string const & loader : loaders) {
2913 if (!theConverters().isReachable(format, loader))
2916 string const tofile =
2917 support::changeExtension(filename.absFileName(),
2918 theFormats().extension(loader));
2919 if (theConverters().convert(nullptr, filename, FileName(tofile),
2920 filename, format, loader, errorList) != Converters::SUCCESS)
2922 loader_format = loader;
2925 if (loader_format.empty()) {
2926 frontend::Alert::error(_("Couldn't import file"),
2927 bformat(_("No information for importing the format %1$s."),
2928 translateIfPossible(theFormats().prettyName(format))));
2932 loader_format = format;
2934 if (loader_format == "lyx") {
2935 Buffer * buf = lv->loadDocument(lyxfile);
2939 Buffer * const b = newFile(lyxfile.absFileName(), string(), true);
2943 bool as_paragraphs = loader_format == "textparagraph";
2944 string filename2 = (loader_format == format) ? filename.absFileName()
2945 : support::changeExtension(filename.absFileName(),
2946 theFormats().extension(loader_format));
2947 lv->currentBufferView()->insertPlaintextFile(FileName(filename2),
2949 guiApp->setCurrentView(lv);
2950 lyx::dispatch(FuncRequest(LFUN_MARK_OFF));
2957 void GuiView::importDocument(string const & argument)
2960 string filename = split(argument, format, ' ');
2962 LYXERR(Debug::INFO, format << " file: " << filename);
2964 // need user interaction
2965 if (filename.empty()) {
2966 string initpath = lyxrc.document_path;
2967 if (documentBufferView()) {
2968 string const trypath = documentBufferView()->buffer().filePath();
2969 // If directory is writeable, use this as default.
2970 if (FileName(trypath).isDirWritable())
2974 docstring const text = bformat(_("Select %1$s file to import"),
2975 translateIfPossible(theFormats().prettyName(format)));
2977 FileDialog dlg(toqstr(text));
2978 dlg.setButton1(qt_("D&ocuments"), toqstr(lyxrc.document_path));
2979 dlg.setButton2(qt_("&Examples"), toqstr(lyxrc.example_path));
2981 docstring filter = translateIfPossible(theFormats().prettyName(format));
2984 filter += from_utf8(theFormats().extensions(format));
2987 FileDialog::Result result =
2988 dlg.open(toqstr(initpath), fileFilters(toqstr(filter)));
2990 if (result.first == FileDialog::Later)
2993 filename = fromqstr(result.second);
2995 // check selected filename
2996 if (filename.empty())
2997 message(_("Canceled."));
3000 if (filename.empty())
3003 // get absolute path of file
3004 FileName const fullname(support::makeAbsPath(filename));
3006 // Can happen if the user entered a path into the dialog
3008 if (fullname.onlyFileName().empty()) {
3009 docstring msg = bformat(_("The file name '%1$s' is invalid!\n"
3010 "Aborting import."),
3011 from_utf8(fullname.absFileName()));
3012 frontend::Alert::error(_("File name error"), msg);
3013 message(_("Canceled."));
3018 FileName const lyxfile(support::changeExtension(fullname.absFileName(), ".lyx"));
3020 // Check if the document already is open
3021 Buffer * buf = theBufferList().getBuffer(lyxfile);
3024 if (!closeBuffer()) {
3025 message(_("Canceled."));
3030 docstring const displaypath = makeDisplayPath(lyxfile.absFileName(), 30);
3032 // if the file exists already, and we didn't do
3033 // -i lyx thefile.lyx, warn
3034 if (lyxfile.exists() && fullname != lyxfile) {
3036 docstring text = bformat(_("The document %1$s already exists.\n\n"
3037 "Do you want to overwrite that document?"), displaypath);
3038 int const ret = Alert::prompt(_("Overwrite document?"),
3039 text, 0, 1, _("&Overwrite"), _("&Cancel"));
3042 message(_("Canceled."));
3047 message(bformat(_("Importing %1$s..."), displaypath));
3048 ErrorList errorList;
3049 if (import(this, fullname, format, errorList))
3050 message(_("imported."));
3052 message(_("file not imported!"));
3054 // FIXME (Abdel 12/08/06): Is there a need to display the error list here?
3058 void GuiView::newDocument(string const & filename, string templatefile,
3061 FileName initpath(lyxrc.document_path);
3062 if (documentBufferView()) {
3063 FileName const trypath(documentBufferView()->buffer().filePath());
3064 // If directory is writeable, use this as default.
3065 if (trypath.isDirWritable())
3069 if (from_template) {
3070 if (templatefile.empty())
3071 templatefile = selectTemplateFile().absFileName();
3072 if (templatefile.empty())
3077 if (filename.empty())
3078 b = newUnnamedFile(initpath, to_utf8(_("newfile")), templatefile);
3080 b = newFile(filename, templatefile, true);
3085 // If no new document could be created, it is unsure
3086 // whether there is a valid BufferView.
3087 if (currentBufferView())
3088 // Ensure the cursor is correctly positioned on screen.
3089 currentBufferView()->showCursor();
3093 bool GuiView::insertLyXFile(docstring const & fname, bool ignorelang)
3095 BufferView * bv = documentBufferView();
3100 FileName filename(to_utf8(fname));
3101 if (filename.empty()) {
3102 // Launch a file browser
3104 string initpath = lyxrc.document_path;
3105 string const trypath = bv->buffer().filePath();
3106 // If directory is writeable, use this as default.
3107 if (FileName(trypath).isDirWritable())
3111 FileDialog dlg(qt_("Select LyX document to insert"));
3112 dlg.setButton1(qt_("D&ocuments"), toqstr(lyxrc.document_path));
3113 dlg.setButton2(qt_("&Examples"), toqstr(lyxrc.example_path));
3115 FileDialog::Result result = dlg.open(toqstr(initpath),
3116 QStringList(qt_("LyX Documents (*.lyx)")));
3118 if (result.first == FileDialog::Later)
3122 filename.set(fromqstr(result.second));
3124 // check selected filename
3125 if (filename.empty()) {
3126 // emit message signal.
3127 message(_("Canceled."));
3132 bv->insertLyXFile(filename, ignorelang);
3133 bv->buffer().errors("Parse");
3138 string const GuiView::getTemplatesPath(Buffer & b)
3140 // We start off with the user's templates path
3141 string result = addPath(package().user_support().absFileName(), "templates");
3142 // Check for the document language
3143 string const langcode = b.params().language->code();
3144 string const shortcode = langcode.substr(0, 2);
3145 if (!langcode.empty() && shortcode != "en") {
3146 string subpath = addPath(result, shortcode);
3147 string subpath_long = addPath(result, langcode);
3148 // If we have a subdirectory for the language already,
3150 FileName sp = FileName(subpath);
3151 if (sp.isDirectory())
3153 else if (FileName(subpath_long).isDirectory())
3154 result = subpath_long;
3156 // Ask whether we should create such a subdirectory
3157 docstring const text =
3158 bformat(_("It is suggested to save the template in a subdirectory\n"
3159 "appropriate to the document language (%1$s).\n"
3160 "This subdirectory does not exists yet.\n"
3161 "Do you want to create it?"),
3162 _(b.params().language->display()));
3163 if (Alert::prompt(_("Create Language Directory?"),
3164 text, 0, 1, _("&Yes, Create"), _("&No, Save Template in Parent Directory")) == 0) {
3165 // If the user agreed, we try to create it and report if this failed.
3166 if (!sp.createDirectory(0777))
3167 Alert::error(_("Subdirectory creation failed!"),
3168 _("Could not create subdirectory.\n"
3169 "The template will be saved in the parent directory."));
3175 // Do we have a layout category?
3176 string const cat = b.params().baseClass() ?
3177 b.params().baseClass()->category()
3180 string subpath = addPath(result, cat);
3181 // If we have a subdirectory for the category already,
3183 FileName sp = FileName(subpath);
3184 if (sp.isDirectory())
3187 // Ask whether we should create such a subdirectory
3188 docstring const text =
3189 bformat(_("It is suggested to save the template in a subdirectory\n"
3190 "appropriate to the layout category (%1$s).\n"
3191 "This subdirectory does not exists yet.\n"
3192 "Do you want to create it?"),
3194 if (Alert::prompt(_("Create Category Directory?"),
3195 text, 0, 1, _("&Yes, Create"), _("&No, Save Template in Parent Directory")) == 0) {
3196 // If the user agreed, we try to create it and report if this failed.
3197 if (!sp.createDirectory(0777))
3198 Alert::error(_("Subdirectory creation failed!"),
3199 _("Could not create subdirectory.\n"
3200 "The template will be saved in the parent directory."));
3210 bool GuiView::renameBuffer(Buffer & b, docstring const & newname, RenameKind kind)
3212 FileName fname = b.fileName();
3213 FileName const oldname = fname;
3214 bool const as_template = (kind == LV_WRITE_AS_TEMPLATE);
3216 if (!newname.empty()) {
3219 fname = support::makeAbsPath(to_utf8(newname), getTemplatesPath(b));
3221 fname = support::makeAbsPath(to_utf8(newname),
3222 oldname.onlyPath().absFileName());
3224 // Switch to this Buffer.
3227 // No argument? Ask user through dialog.
3229 QString const title = as_template ? qt_("Choose a filename to save template as")
3230 : qt_("Choose a filename to save document as");
3231 FileDialog dlg(title);
3232 dlg.setButton1(qt_("D&ocuments"), toqstr(lyxrc.document_path));
3233 dlg.setButton2(qt_("&Templates"), toqstr(lyxrc.template_path));
3235 fname.ensureExtension(".lyx");
3237 string const path = as_template ?
3239 : fname.onlyPath().absFileName();
3240 FileDialog::Result result =
3241 dlg.save(toqstr(path),
3242 QStringList(qt_("LyX Documents (*.lyx)")),
3243 toqstr(fname.onlyFileName()));
3245 if (result.first == FileDialog::Later)
3248 fname.set(fromqstr(result.second));
3253 fname.ensureExtension(".lyx");
3256 // fname is now the new Buffer location.
3258 // if there is already a Buffer open with this name, we do not want
3259 // to have another one. (the second test makes sure we're not just
3260 // trying to overwrite ourselves, which is fine.)
3261 if (theBufferList().exists(fname) && fname != oldname
3262 && theBufferList().getBuffer(fname) != &b) {
3263 docstring const text =
3264 bformat(_("The file\n%1$s\nis already open in your current session.\n"
3265 "Please close it before attempting to overwrite it.\n"
3266 "Do you want to choose a new filename?"),
3267 from_utf8(fname.absFileName()));
3268 int const ret = Alert::prompt(_("Chosen File Already Open"),
3269 text, 0, 1, _("&Rename"), _("&Cancel"));
3271 case 0: return renameBuffer(b, docstring(), kind);
3272 case 1: return false;
3277 bool const existsLocal = fname.exists();
3278 bool const existsInVC = LyXVC::fileInVC(fname);
3279 if (existsLocal || existsInVC) {
3280 docstring const file = makeDisplayPath(fname.absFileName(), 30);
3281 if (kind != LV_WRITE_AS && existsInVC) {
3282 // renaming to a name that is already in VC
3284 docstring text = bformat(_("The document %1$s "
3285 "is already registered.\n\n"
3286 "Do you want to choose a new name?"),
3288 docstring const title = (kind == LV_VC_RENAME) ?
3289 _("Rename document?") : _("Copy document?");
3290 docstring const button = (kind == LV_VC_RENAME) ?
3291 _("&Rename") : _("&Copy");
3292 int const ret = Alert::prompt(title, text, 0, 1,
3293 button, _("&Cancel"));
3295 case 0: return renameBuffer(b, docstring(), kind);
3296 case 1: return false;
3301 docstring text = bformat(_("The document %1$s "
3302 "already exists.\n\n"
3303 "Do you want to overwrite that document?"),
3305 int const ret = Alert::prompt(_("Overwrite document?"),
3306 text, 0, 2, _("&Overwrite"),
3307 _("&Rename"), _("&Cancel"));
3310 case 1: return renameBuffer(b, docstring(), kind);
3311 case 2: return false;
3317 case LV_VC_RENAME: {
3318 string msg = b.lyxvc().rename(fname);
3321 message(from_utf8(msg));
3325 string msg = b.lyxvc().copy(fname);
3328 message(from_utf8(msg));
3332 case LV_WRITE_AS_TEMPLATE:
3335 // LyXVC created the file already in case of LV_VC_RENAME or
3336 // LV_VC_COPY, but call saveBuffer() nevertheless to get
3337 // relative paths of included stuff right if we moved e.g. from
3338 // /a/b.lyx to /a/c/b.lyx.
3340 bool const saved = saveBuffer(b, fname);
3347 bool GuiView::exportBufferAs(Buffer & b, docstring const & iformat)
3349 FileName fname = b.fileName();
3351 FileDialog dlg(qt_("Choose a filename to export the document as"));
3352 dlg.setButton1(qt_("D&ocuments"), toqstr(lyxrc.document_path));
3355 QString const anyformat = qt_("Guess from extension (*.*)");
3358 vector<Format const *> export_formats;
3359 for (Format const & f : theFormats())
3360 if (f.documentFormat())
3361 export_formats.push_back(&f);
3362 sort(export_formats.begin(), export_formats.end(), Format::formatSorter);
3363 map<QString, string> fmap;
3366 for (Format const * f : export_formats) {
3367 docstring const loc_prettyname = translateIfPossible(f->prettyname());
3368 QString const loc_filter = toqstr(bformat(from_ascii("%1$s (*.%2$s)"),
3370 from_ascii(f->extension())));
3371 types << loc_filter;
3372 fmap[loc_filter] = f->name();
3373 if (from_ascii(f->name()) == iformat) {
3374 filter = loc_filter;
3375 ext = f->extension();
3378 string ofname = fname.onlyFileName();
3380 ofname = support::changeExtension(ofname, ext);
3381 FileDialog::Result result =
3382 dlg.save(toqstr(fname.onlyPath().absFileName()),
3386 if (result.first != FileDialog::Chosen)
3390 fname.set(fromqstr(result.second));
3391 if (filter == anyformat)
3392 fmt_name = theFormats().getFormatFromExtension(fname.extension());
3394 fmt_name = fmap[filter];
3395 LYXERR(Debug::FILES, "filter=" << fromqstr(filter)
3396 << ", fmt_name=" << fmt_name << ", fname=" << fname.absFileName());
3398 if (fmt_name.empty() || fname.empty())
3401 fname.ensureExtension(theFormats().extension(fmt_name));
3403 // fname is now the new Buffer location.
3404 if (fname.exists()) {
3405 docstring const file = makeDisplayPath(fname.absFileName(), 30);
3406 docstring text = bformat(_("The document %1$s already "
3407 "exists.\n\nDo you want to "
3408 "overwrite that document?"),
3410 int const ret = Alert::prompt(_("Overwrite document?"),
3411 text, 0, 2, _("&Overwrite"), _("&Rename"), _("&Cancel"));
3414 case 1: return exportBufferAs(b, from_ascii(fmt_name));
3415 case 2: return false;
3419 FuncRequest cmd(LFUN_BUFFER_EXPORT, fmt_name + " " + fname.absFileName());
3422 return dr.dispatched();
3426 bool GuiView::saveBuffer(Buffer & b)
3428 return saveBuffer(b, FileName());
3432 bool GuiView::saveBuffer(Buffer & b, FileName const & fn)
3434 if (workArea(b) && workArea(b)->inDialogMode())
3437 if (fn.empty() && b.isUnnamed())
3438 return renameBuffer(b, docstring());
3440 bool const success = (fn.empty() ? b.save() : b.saveAs(fn));
3442 theSession().lastFiles().add(b.fileName());
3443 theSession().writeFile();
3447 // Switch to this Buffer.
3450 // FIXME: we don't tell the user *WHY* the save failed !!
3451 docstring const file = makeDisplayPath(b.absFileName(), 30);
3452 docstring text = bformat(_("The document %1$s could not be saved.\n\n"
3453 "Do you want to rename the document and "
3454 "try again?"), file);
3455 int const ret = Alert::prompt(_("Rename and save?"),
3456 text, 0, 2, _("&Rename"), _("&Retry"), _("&Cancel"));
3459 if (!renameBuffer(b, docstring()))
3468 return saveBuffer(b, fn);
3472 bool GuiView::hideWorkArea(GuiWorkArea * wa)
3474 return closeWorkArea(wa, false);
3478 // We only want to close the buffer if it is not visible in other workareas
3479 // of the same view, nor in other views, and if this is not a child
3480 bool GuiView::closeWorkArea(GuiWorkArea * wa)
3482 Buffer & buf = wa->bufferView().buffer();
3484 bool last_wa = d.countWorkAreasOf(buf) == 1
3485 && !inOtherView(buf) && !buf.parent();
3487 bool close_buffer = last_wa;
3490 if (lyxrc.close_buffer_with_last_view == "yes")
3492 else if (lyxrc.close_buffer_with_last_view == "no")
3493 close_buffer = false;
3496 if (buf.isUnnamed())
3497 file = from_utf8(buf.fileName().onlyFileName());
3499 file = buf.fileName().displayName(30);
3500 docstring const text = bformat(
3501 _("Last view on document %1$s is being closed.\n"
3502 "Would you like to close or hide the document?\n"
3504 "Hidden documents can be displayed back through\n"
3505 "the menu: View->Hidden->...\n"
3507 "To remove this question, set your preference in:\n"
3508 " Tools->Preferences->Look&Feel->UserInterface\n"
3510 int ret = Alert::prompt(_("Close or hide document?"),
3511 text, 0, 2, _("&Close"), _("&Hide"), _("&Cancel"));
3514 close_buffer = (ret == 0);
3518 return closeWorkArea(wa, close_buffer);
3522 bool GuiView::closeBuffer()
3524 GuiWorkArea * wa = currentMainWorkArea();
3525 // coverity complained about this
3526 // it seems unnecessary, but perhaps is worth the check
3527 LASSERT(wa, return false);
3529 setCurrentWorkArea(wa);
3530 Buffer & buf = wa->bufferView().buffer();
3531 return closeWorkArea(wa, !buf.parent());
3535 void GuiView::writeSession() const {
3536 GuiWorkArea const * active_wa = currentMainWorkArea();
3537 for (int i = 0; i < d.splitter_->count(); ++i) {
3538 TabWorkArea * twa = d.tabWorkArea(i);
3539 for (int j = 0; j < twa->count(); ++j) {
3540 GuiWorkArea * wa = twa->workArea(j);
3541 Buffer & buf = wa->bufferView().buffer();
3542 theSession().lastOpened().add(buf.fileName(), wa == active_wa);
3548 bool GuiView::closeBufferAll()
3551 for (auto & buf : theBufferList()) {
3552 if (!saveBufferIfNeeded(*buf, false)) {
3553 // Closing has been cancelled, so abort.
3558 // Close the workareas in all other views
3559 QList<int> const ids = guiApp->viewIds();
3560 for (int i = 0; i != ids.size(); ++i) {
3561 if (id_ != ids[i] && !guiApp->view(ids[i]).closeWorkAreaAll())
3565 // Close our own workareas
3566 if (!closeWorkAreaAll())
3573 bool GuiView::closeWorkAreaAll()
3575 setCurrentWorkArea(currentMainWorkArea());
3577 // We might be in a situation that there is still a tabWorkArea, but
3578 // there are no tabs anymore. This can happen when we get here after a
3579 // TabWorkArea::lastWorkAreaRemoved() signal. Therefore we count how
3580 // many TabWorkArea's have no documents anymore.
3583 // We have to call count() each time, because it can happen that
3584 // more than one splitter will disappear in one iteration (bug 5998).
3585 while (d.splitter_->count() > empty_twa) {
3586 TabWorkArea * twa = d.tabWorkArea(empty_twa);
3588 if (twa->count() == 0)
3591 setCurrentWorkArea(twa->currentWorkArea());
3592 if (!closeTabWorkArea(twa))
3600 bool GuiView::closeWorkArea(GuiWorkArea * wa, bool close_buffer)
3605 Buffer & buf = wa->bufferView().buffer();
3607 if (GuiViewPrivate::busyBuffers.contains(&buf)) {
3608 Alert::warning(_("Close document"),
3609 _("Document could not be closed because it is being processed by LyX."));
3614 return closeBuffer(buf);
3616 if (!inMultiTabs(wa))
3617 if (!saveBufferIfNeeded(buf, true))
3625 bool GuiView::closeBuffer(Buffer & buf)
3627 bool success = true;
3628 for (Buffer * child_buf : buf.getChildren()) {
3629 if (theBufferList().isOthersChild(&buf, child_buf)) {
3630 child_buf->setParent(nullptr);
3634 // FIXME: should we look in other tabworkareas?
3635 // ANSWER: I don't think so. I've tested, and if the child is
3636 // open in some other window, it closes without a problem.
3637 GuiWorkArea * child_wa = workArea(*child_buf);
3640 // If we are in a close_event all children will be closed in some time,
3641 // so no need to do it here. This will ensure that the children end up
3642 // in the session file in the correct order. If we close the master
3643 // buffer, we can close or release the child buffers here too.
3645 success = closeWorkArea(child_wa, true);
3649 // In this case the child buffer is open but hidden.
3650 // Even in this case, children can be dirty (e.g.,
3651 // after a label change in the master, see #11405).
3652 // Therefore, check this
3653 if (closing_ && (child_buf->isClean() || child_buf->paragraphs().empty())) {
3654 // If we are in a close_event all children will be closed in some time,
3655 // so no need to do it here. This will ensure that the children end up
3656 // in the session file in the correct order. If we close the master
3657 // buffer, we can close or release the child buffers here too.
3660 // Save dirty buffers also if closing_!
3661 if (saveBufferIfNeeded(*child_buf, false)) {
3662 child_buf->removeAutosaveFile();
3663 theBufferList().release(child_buf);
3665 // Saving of dirty children has been cancelled.
3666 // Cancel the whole process.
3673 // goto bookmark to update bookmark pit.
3674 // FIXME: we should update only the bookmarks related to this buffer!
3675 // FIXME: this is done also in LFUN_WINDOW_CLOSE!
3676 LYXERR(Debug::DEBUG, "GuiView::closeBuffer()");
3677 for (unsigned int i = 1; i < theSession().bookmarks().size(); ++i)
3678 guiApp->gotoBookmark(i, false, false);
3680 if (saveBufferIfNeeded(buf, false)) {
3681 buf.removeAutosaveFile();
3682 theBufferList().release(&buf);
3686 // open all children again to avoid a crash because of dangling
3687 // pointers (bug 6603)
3693 bool GuiView::closeTabWorkArea(TabWorkArea * twa)
3695 while (twa == d.currentTabWorkArea()) {
3696 twa->setCurrentIndex(twa->count() - 1);
3698 GuiWorkArea * wa = twa->currentWorkArea();
3699 Buffer & b = wa->bufferView().buffer();
3701 // We only want to close the buffer if the same buffer is not visible
3702 // in another view, and if this is not a child and if we are closing
3703 // a view (not a tabgroup).
3704 bool const close_buffer =
3705 !inOtherView(b) && !b.parent() && closing_;
3707 if (!closeWorkArea(wa, close_buffer))
3714 bool GuiView::saveBufferIfNeeded(Buffer & buf, bool hiding)
3716 if (buf.isClean() || buf.paragraphs().empty())
3719 // Switch to this Buffer.
3725 if (buf.isUnnamed()) {
3726 file = from_utf8(buf.fileName().onlyFileName());
3729 FileName filename = buf.fileName();
3731 file = filename.displayName(30);
3732 exists = filename.exists();
3735 // Bring this window to top before asking questions.
3740 if (hiding && buf.isUnnamed()) {
3741 docstring const text = bformat(_("The document %1$s has not been "
3742 "saved yet.\n\nDo you want to save "
3743 "the document?"), file);
3744 ret = Alert::prompt(_("Save new document?"),
3745 text, 0, 1, _("&Save"), _("&Cancel"));
3749 docstring const text = exists ?
3750 bformat(_("The document %1$s has unsaved changes."
3751 "\n\nDo you want to save the document or "
3752 "discard the changes?"), file) :
3753 bformat(_("The document %1$s has not been saved yet."
3754 "\n\nDo you want to save the document or "
3755 "discard it entirely?"), file);
3756 docstring const title = exists ?
3757 _("Save changed document?") : _("Save document?");
3758 ret = Alert::prompt(title, text, 0, 2,
3759 _("&Save"), _("&Discard"), _("&Cancel"));
3764 if (!saveBuffer(buf))
3768 // If we crash after this we could have no autosave file
3769 // but I guess this is really improbable (Jug).
3770 // Sometimes improbable things happen:
3771 // - see bug http://www.lyx.org/trac/ticket/6587 (ps)
3772 // buf.removeAutosaveFile();
3774 // revert all changes
3785 bool GuiView::inMultiTabs(GuiWorkArea * wa)
3787 Buffer & buf = wa->bufferView().buffer();
3789 for (int i = 0; i != d.splitter_->count(); ++i) {
3790 GuiWorkArea * wa_ = d.tabWorkArea(i)->workArea(buf);
3791 if (wa_ && wa_ != wa)
3794 return inOtherView(buf);
3798 bool GuiView::inOtherView(Buffer & buf)
3800 QList<int> const ids = guiApp->viewIds();
3802 for (int i = 0; i != ids.size(); ++i) {
3806 if (guiApp->view(ids[i]).workArea(buf))
3813 void GuiView::gotoNextOrPreviousBuffer(NextOrPrevious np, bool const move)
3815 if (!documentBufferView())
3818 if (TabWorkArea * twa = d.currentTabWorkArea()) {
3819 Buffer * const curbuf = &documentBufferView()->buffer();
3820 int nwa = twa->count();
3821 for (int i = 0; i < nwa; ++i) {
3822 if (&workArea(i)->bufferView().buffer() == curbuf) {
3825 next_index = (i == nwa - 1 ? 0 : i + 1);
3827 next_index = (i == 0 ? nwa - 1 : i - 1);
3829 twa->moveTab(i, next_index);
3831 setBuffer(&workArea(next_index)->bufferView().buffer());
3839 void GuiView::gotoNextTabWorkArea(NextOrPrevious np)
3841 int count = d.splitter_->count();
3842 for (int i = 0; i < count; ++i) {
3843 if (d.tabWorkArea(i) == d.currentTabWorkArea()) {
3846 new_index = (i == count - 1 ? 0 : i + 1);
3848 new_index = (i == 0 ? count - 1 : i - 1);
3849 setCurrentWorkArea(d.tabWorkArea(new_index)->currentWorkArea());
3856 /// make sure the document is saved
3857 static bool ensureBufferClean(Buffer * buffer)
3859 LASSERT(buffer, return false);
3860 if (buffer->isClean() && !buffer->isUnnamed())
3863 docstring const file = buffer->fileName().displayName(30);
3866 if (!buffer->isUnnamed()) {
3867 text = bformat(_("The document %1$s has unsaved "
3868 "changes.\n\nDo you want to save "
3869 "the document?"), file);
3870 title = _("Save changed document?");
3873 text = bformat(_("The document %1$s has not been "
3874 "saved yet.\n\nDo you want to save "
3875 "the document?"), file);
3876 title = _("Save new document?");
3878 int const ret = Alert::prompt(title, text, 0, 1, _("&Save"), _("&Cancel"));
3881 dispatch(FuncRequest(LFUN_BUFFER_WRITE));
3883 return buffer->isClean() && !buffer->isUnnamed();
3887 bool GuiView::reloadBuffer(Buffer & buf)
3889 currentBufferView()->cursor().reset();
3890 Buffer::ReadStatus status = buf.reload();
3891 return status == Buffer::ReadSuccess;
3895 void GuiView::checkExternallyModifiedBuffers()
3897 for (Buffer * buf : theBufferList()) {
3898 if (buf->fileName().exists() && buf->isChecksumModified()) {
3899 docstring text = bformat(_("Document \n%1$s\n has been externally modified."
3900 " Reload now? Any local changes will be lost."),
3901 from_utf8(buf->absFileName()));
3902 int const ret = Alert::prompt(_("Reload externally changed document?"),
3903 text, 0, 1, _("&Reload"), _("&Cancel"));
3911 void GuiView::dispatchVC(FuncRequest const & cmd, DispatchResult & dr)
3913 Buffer * buffer = documentBufferView()
3914 ? &(documentBufferView()->buffer()) : nullptr;
3916 switch (cmd.action()) {
3917 case LFUN_VC_REGISTER:
3918 if (!buffer || !ensureBufferClean(buffer))
3920 if (!buffer->lyxvc().inUse()) {
3921 if (buffer->lyxvc().registrer()) {
3922 reloadBuffer(*buffer);
3923 dr.clearMessageUpdate();
3928 case LFUN_VC_RENAME:
3929 case LFUN_VC_COPY: {
3930 if (!buffer || !ensureBufferClean(buffer))
3932 if (buffer->lyxvc().inUse() && !buffer->hasReadonlyFlag()) {
3933 if (buffer->lyxvc().isCheckInWithConfirmation()) {
3934 // Some changes are not yet committed.
3935 // We test here and not in getStatus(), since
3936 // this test is expensive.
3938 LyXVC::CommandResult ret =
3939 buffer->lyxvc().checkIn(log);
3941 if (ret == LyXVC::ErrorCommand ||
3942 ret == LyXVC::VCSuccess)
3943 reloadBuffer(*buffer);
3944 if (buffer->lyxvc().isCheckInWithConfirmation()) {
3945 frontend::Alert::error(
3946 _("Revision control error."),
3947 _("Document could not be checked in."));
3951 RenameKind const kind = (cmd.action() == LFUN_VC_RENAME) ?
3952 LV_VC_RENAME : LV_VC_COPY;
3953 renameBuffer(*buffer, cmd.argument(), kind);
3958 case LFUN_VC_CHECK_IN:
3959 if (!buffer || !ensureBufferClean(buffer))
3961 if (buffer->lyxvc().inUse() && !buffer->hasReadonlyFlag()) {
3963 LyXVC::CommandResult ret = buffer->lyxvc().checkIn(log);
3965 // Only skip reloading if the checkin was cancelled or
3966 // an error occurred before the real checkin VCS command
3967 // was executed, since the VCS might have changed the
3968 // file even if it could not checkin successfully.
3969 if (ret == LyXVC::ErrorCommand || ret == LyXVC::VCSuccess)
3970 reloadBuffer(*buffer);
3974 case LFUN_VC_CHECK_OUT:
3975 if (!buffer || !ensureBufferClean(buffer))
3977 if (buffer->lyxvc().inUse()) {
3978 dr.setMessage(buffer->lyxvc().checkOut());
3979 reloadBuffer(*buffer);
3983 case LFUN_VC_LOCKING_TOGGLE:
3984 if (!buffer || !ensureBufferClean(buffer) || buffer->hasReadonlyFlag())
3986 if (buffer->lyxvc().inUse()) {
3987 string res = buffer->lyxvc().lockingToggle();
3989 frontend::Alert::error(_("Revision control error."),
3990 _("Error when setting the locking property."));
3993 reloadBuffer(*buffer);
3998 case LFUN_VC_REVERT:
4001 if (buffer->lyxvc().revert()) {
4002 reloadBuffer(*buffer);
4003 dr.clearMessageUpdate();
4007 case LFUN_VC_UNDO_LAST:
4010 buffer->lyxvc().undoLast();
4011 reloadBuffer(*buffer);
4012 dr.clearMessageUpdate();
4015 case LFUN_VC_REPO_UPDATE:
4018 if (ensureBufferClean(buffer)) {
4019 dr.setMessage(buffer->lyxvc().repoUpdate());
4020 checkExternallyModifiedBuffers();
4024 case LFUN_VC_COMMAND: {
4025 string flag = cmd.getArg(0);
4026 if (buffer && contains(flag, 'R') && !ensureBufferClean(buffer))
4029 if (contains(flag, 'M')) {
4030 if (!Alert::askForText(message, _("LyX VC: Log Message")))
4033 string path = cmd.getArg(1);
4034 if (contains(path, "$$p") && buffer)
4035 path = subst(path, "$$p", buffer->filePath());
4036 LYXERR(Debug::LYXVC, "Directory: " << path);
4038 if (!pp.isReadableDirectory()) {
4039 lyxerr << _("Directory is not accessible.") << endl;
4042 support::PathChanger p(pp);
4044 string command = cmd.getArg(2);
4045 if (command.empty())
4048 command = subst(command, "$$i", buffer->absFileName());
4049 command = subst(command, "$$p", buffer->filePath());
4051 command = subst(command, "$$m", to_utf8(message));
4052 LYXERR(Debug::LYXVC, "Command: " << command);
4054 one.startscript(Systemcall::Wait, command);
4058 if (contains(flag, 'I'))
4059 buffer->markDirty();
4060 if (contains(flag, 'R'))
4061 reloadBuffer(*buffer);
4066 case LFUN_VC_COMPARE: {
4067 if (cmd.argument().empty()) {
4068 lyx::dispatch(FuncRequest(LFUN_DIALOG_SHOW, "comparehistory"));
4074 string rev1 = cmd.getArg(0);
4078 if (!buffer->lyxvc().prepareFileRevision(rev1, f1))
4081 if (isStrInt(rev1) && convert<int>(rev1) <= 0) {
4082 f2 = buffer->absFileName();
4084 string rev2 = cmd.getArg(1);
4088 if (!buffer->lyxvc().prepareFileRevision(rev2, f2))
4092 LYXERR(Debug::LYXVC, "Launching comparison for fetched revisions:\n" <<
4093 f1 << "\n" << f2 << "\n" );
4094 string par = "compare run " + quoteName(f1) + " " + quoteName(f2);
4095 lyx::dispatch(FuncRequest(LFUN_DIALOG_SHOW, par));
4105 void GuiView::openChildDocument(string const & fname)
4107 LASSERT(documentBufferView(), return);
4108 Buffer & buffer = documentBufferView()->buffer();
4109 FileName const filename = support::makeAbsPath(fname, buffer.filePath());
4110 documentBufferView()->saveBookmark(false);
4111 Buffer * child = nullptr;
4112 if (theBufferList().exists(filename)) {
4113 child = theBufferList().getBuffer(filename);
4116 message(bformat(_("Opening child document %1$s..."),
4117 makeDisplayPath(filename.absFileName())));
4118 child = loadDocument(filename, false);
4120 // Set the parent name of the child document.
4121 // This makes insertion of citations and references in the child work,
4122 // when the target is in the parent or another child document.
4124 child->setParent(&buffer);
4128 bool GuiView::goToFileRow(string const & argument)
4132 size_t i = argument.find_last_of(' ');
4133 if (i != string::npos) {
4134 file_name = os::internal_path(FileName(trim(argument.substr(0, i))).realPath());
4135 istringstream is(argument.substr(i + 1));
4140 if (i == string::npos) {
4141 LYXERR0("Wrong argument: " << argument);
4144 Buffer * buf = nullptr;
4145 string const realtmp = package().temp_dir().realPath();
4146 // We have to use os::path_prefix_is() here, instead of
4147 // simply prefixIs(), because the file name comes from
4148 // an external application and may need case adjustment.
4149 if (os::path_prefix_is(file_name, realtmp, os::CASE_ADJUSTED)) {
4150 buf = theBufferList().getBufferFromTmp(file_name, true);
4151 LYXERR(Debug::FILES, "goToFileRow: buffer lookup for " << file_name
4152 << (buf ? " success" : " failed"));
4154 // Must replace extension of the file to be .lyx
4155 // and get full path
4156 FileName const s = fileSearch(string(),
4157 support::changeExtension(file_name, ".lyx"), "lyx");
4158 // Either change buffer or load the file
4159 if (theBufferList().exists(s))
4160 buf = theBufferList().getBuffer(s);
4161 else if (s.exists()) {
4162 buf = loadDocument(s);
4167 _("File does not exist: %1$s"),
4168 makeDisplayPath(file_name)));
4174 _("No buffer for file: %1$s."),
4175 makeDisplayPath(file_name))
4180 bool success = documentBufferView()->setCursorFromRow(row);
4182 LYXERR(Debug::OUTFILE,
4183 "setCursorFromRow: invalid position for row " << row);
4184 frontend::Alert::error(_("Inverse Search Failed"),
4185 _("Invalid position requested by inverse search.\n"
4186 "You may need to update the viewed document."));
4192 void GuiView::toolBarPopup(const QPoint & /*pos*/)
4194 QMenu * menu = guiApp->menus().menu(toqstr("context-toolbars"), * this);
4195 menu->exec(QCursor::pos());
4200 Buffer::ExportStatus GuiView::GuiViewPrivate::runAndDestroy(const T& func,
4201 Buffer const * orig, Buffer * clone, string const & format)
4203 Buffer::ExportStatus const status = func(format);
4205 // the cloning operation will have produced a clone of the entire set of
4206 // documents, starting from the master. so we must delete those.
4207 Buffer * mbuf = const_cast<Buffer *>(clone->masterBuffer());
4209 busyBuffers.remove(orig);
4214 Buffer::ExportStatus GuiView::GuiViewPrivate::compileAndDestroy(
4215 Buffer const * orig, Buffer * clone, string const & format)
4217 Buffer::ExportStatus (Buffer::* mem_func)(std::string const &, bool) const =
4219 return runAndDestroy(lyx::bind(mem_func, clone, _1, true), orig, clone, format);
4223 Buffer::ExportStatus GuiView::GuiViewPrivate::exportAndDestroy(
4224 Buffer const * orig, Buffer * clone, string const & format)
4226 Buffer::ExportStatus (Buffer::* mem_func)(std::string const &, bool) const =
4228 return runAndDestroy(lyx::bind(mem_func, clone, _1, false), orig, clone, format);
4232 Buffer::ExportStatus GuiView::GuiViewPrivate::previewAndDestroy(
4233 Buffer const * orig, Buffer * clone, string const & format)
4235 Buffer::ExportStatus (Buffer::* mem_func)(std::string const &) const =
4237 return runAndDestroy(lyx::bind(mem_func, clone, _1), orig, clone, format);
4241 bool GuiView::GuiViewPrivate::asyncBufferProcessing(string const & argument,
4242 Buffer const * used_buffer,
4243 docstring const & msg,
4244 Buffer::ExportStatus (*asyncFunc)(Buffer const *, Buffer *, string const &),
4245 Buffer::ExportStatus (Buffer::*syncFunc)(string const &, bool) const,
4246 Buffer::ExportStatus (Buffer::*previewFunc)(string const &) const,
4247 bool allow_async, bool use_tmpdir)
4252 string format = argument;
4254 format = used_buffer->params().getDefaultOutputFormat();
4255 processing_format = format;
4257 progress_->clearMessages();
4260 #if EXPORT_in_THREAD
4262 GuiViewPrivate::busyBuffers.insert(used_buffer);
4263 Buffer * cloned_buffer = used_buffer->cloneWithChildren();
4264 if (!cloned_buffer) {
4265 Alert::error(_("Export Error"),
4266 _("Error cloning the Buffer."));
4269 QFuture<Buffer::ExportStatus> f = QtConcurrent::run(
4274 setPreviewFuture(f);
4275 last_export_format = used_buffer->params().bufferFormat();
4278 // We are asynchronous, so we don't know here anything about the success
4281 Buffer::ExportStatus status;
4283 status = (used_buffer->*syncFunc)(format, use_tmpdir);
4284 } else if (previewFunc) {
4285 status = (used_buffer->*previewFunc)(format);
4288 handleExportStatus(gv_, status, format);
4290 return (status == Buffer::ExportSuccess
4291 || status == Buffer::PreviewSuccess);
4295 Buffer::ExportStatus status;
4297 status = (used_buffer->*syncFunc)(format, true);
4298 } else if (previewFunc) {
4299 status = (used_buffer->*previewFunc)(format);
4302 handleExportStatus(gv_, status, format);
4304 return (status == Buffer::ExportSuccess
4305 || status == Buffer::PreviewSuccess);
4309 void GuiView::dispatchToBufferView(FuncRequest const & cmd, DispatchResult & dr)
4311 BufferView * bv = currentBufferView();
4312 LASSERT(bv, return);
4314 // Let the current BufferView dispatch its own actions.
4315 bv->dispatch(cmd, dr);
4316 if (dr.dispatched()) {
4317 if (cmd.action() == LFUN_REDO || cmd.action() == LFUN_UNDO)
4318 updateDialog("document", "");
4322 // Try with the document BufferView dispatch if any.
4323 BufferView * doc_bv = documentBufferView();
4324 if (doc_bv && doc_bv != bv) {
4325 doc_bv->dispatch(cmd, dr);
4326 if (dr.dispatched()) {
4327 if (cmd.action() == LFUN_REDO || cmd.action() == LFUN_UNDO)
4328 updateDialog("document", "");
4333 // Then let the current Cursor dispatch its own actions.
4334 bv->cursor().dispatch(cmd);
4336 // update completion. We do it here and not in
4337 // processKeySym to avoid another redraw just for a
4338 // changed inline completion
4339 if (cmd.origin() == FuncRequest::KEYBOARD) {
4340 if (cmd.action() == LFUN_SELF_INSERT
4341 || (cmd.action() == LFUN_ERT_INSERT && bv->cursor().inMathed()))
4342 updateCompletion(bv->cursor(), true, true);
4343 else if (cmd.action() == LFUN_CHAR_DELETE_BACKWARD)
4344 updateCompletion(bv->cursor(), false, true);
4346 updateCompletion(bv->cursor(), false, false);
4349 dr = bv->cursor().result();
4353 void GuiView::dispatch(FuncRequest const & cmd, DispatchResult & dr)
4355 BufferView * bv = currentBufferView();
4356 // By default we won't need any update.
4357 dr.screenUpdate(Update::None);
4358 // assume cmd will be dispatched
4359 dr.dispatched(true);
4361 Buffer * doc_buffer = documentBufferView()
4362 ? &(documentBufferView()->buffer()) : nullptr;
4364 if (cmd.origin() == FuncRequest::TOC) {
4365 GuiToc * toc = static_cast<GuiToc*>(findOrBuild("toc", false));
4366 toc->doDispatch(bv->cursor(), cmd, dr);
4370 string const argument = to_utf8(cmd.argument());
4372 switch(cmd.action()) {
4373 case LFUN_BUFFER_CHILD_OPEN:
4374 openChildDocument(to_utf8(cmd.argument()));
4377 case LFUN_BUFFER_IMPORT:
4378 importDocument(to_utf8(cmd.argument()));
4381 case LFUN_MASTER_BUFFER_EXPORT:
4383 doc_buffer = const_cast<Buffer *>(doc_buffer->masterBuffer());
4385 case LFUN_BUFFER_EXPORT: {
4388 // GCC only sees strfwd.h when building merged
4389 if (::lyx::operator==(cmd.argument(), "custom")) {
4390 // LFUN_MASTER_BUFFER_EXPORT is not enabled for this case,
4391 // so the following test should not be needed.
4392 // In principle, we could try to switch to such a view...
4393 // if (cmd.action() == LFUN_BUFFER_EXPORT)
4394 dispatch(FuncRequest(LFUN_DIALOG_SHOW, "sendto"), dr);
4398 string const dest = cmd.getArg(1);
4399 FileName target_dir;
4400 if (!dest.empty() && FileName::isAbsolute(dest))
4401 target_dir = FileName(support::onlyPath(dest));
4403 target_dir = doc_buffer->fileName().onlyPath();
4405 string const format = (argument.empty() || argument == "default") ?
4406 doc_buffer->params().getDefaultOutputFormat() : argument;
4408 if ((dest.empty() && doc_buffer->isUnnamed())
4409 || !target_dir.isDirWritable()) {
4410 exportBufferAs(*doc_buffer, from_utf8(format));
4413 /* TODO/Review: Is it a problem to also export the children?
4414 See the update_unincluded flag */
4415 d.asyncBufferProcessing(format,
4418 &GuiViewPrivate::exportAndDestroy,
4420 nullptr, cmd.allowAsync());
4421 // TODO Inform user about success
4425 case LFUN_BUFFER_EXPORT_AS: {
4426 LASSERT(doc_buffer, break);
4427 docstring f = cmd.argument();
4429 f = from_ascii(doc_buffer->params().getDefaultOutputFormat());
4430 exportBufferAs(*doc_buffer, f);
4434 case LFUN_BUFFER_UPDATE: {
4435 d.asyncBufferProcessing(argument,
4438 &GuiViewPrivate::compileAndDestroy,
4440 nullptr, cmd.allowAsync(), true);
4443 case LFUN_BUFFER_VIEW: {
4444 d.asyncBufferProcessing(argument,
4446 _("Previewing ..."),
4447 &GuiViewPrivate::previewAndDestroy,
4449 &Buffer::preview, cmd.allowAsync());
4452 case LFUN_MASTER_BUFFER_UPDATE: {
4453 d.asyncBufferProcessing(argument,
4454 (doc_buffer ? doc_buffer->masterBuffer() : nullptr),
4456 &GuiViewPrivate::compileAndDestroy,
4458 nullptr, cmd.allowAsync(), true);
4461 case LFUN_MASTER_BUFFER_VIEW: {
4462 d.asyncBufferProcessing(argument,
4463 (doc_buffer ? doc_buffer->masterBuffer() : nullptr),
4465 &GuiViewPrivate::previewAndDestroy,
4466 nullptr, &Buffer::preview, cmd.allowAsync());
4469 case LFUN_EXPORT_CANCEL: {
4473 case LFUN_BUFFER_SWITCH: {
4474 string const file_name = to_utf8(cmd.argument());
4475 if (!FileName::isAbsolute(file_name)) {
4477 dr.setMessage(_("Absolute filename expected."));
4481 Buffer * buffer = theBufferList().getBuffer(FileName(file_name));
4484 dr.setMessage(_("Document not loaded"));
4488 // Do we open or switch to the buffer in this view ?
4489 if (workArea(*buffer)
4490 || lyxrc.open_buffers_in_tabs || !documentBufferView()) {
4495 // Look for the buffer in other views
4496 QList<int> const ids = guiApp->viewIds();
4498 for (; i != ids.size(); ++i) {
4499 GuiView & gv = guiApp->view(ids[i]);
4500 if (gv.workArea(*buffer)) {
4502 gv.activateWindow();
4504 gv.setBuffer(buffer);
4509 // If necessary, open a new window as a last resort
4510 if (i == ids.size()) {
4511 lyx::dispatch(FuncRequest(LFUN_WINDOW_NEW));
4517 case LFUN_BUFFER_NEXT:
4518 gotoNextOrPreviousBuffer(NEXT, false);
4521 case LFUN_BUFFER_MOVE_NEXT:
4522 gotoNextOrPreviousBuffer(NEXT, true);
4525 case LFUN_BUFFER_PREVIOUS:
4526 gotoNextOrPreviousBuffer(PREV, false);
4529 case LFUN_BUFFER_MOVE_PREVIOUS:
4530 gotoNextOrPreviousBuffer(PREV, true);
4533 case LFUN_BUFFER_CHKTEX:
4534 LASSERT(doc_buffer, break);
4535 doc_buffer->runChktex();
4538 case LFUN_COMMAND_EXECUTE: {
4539 command_execute_ = true;
4540 minibuffer_focus_ = true;
4543 case LFUN_DROP_LAYOUTS_CHOICE:
4544 d.layout_->showPopup();
4547 case LFUN_MENU_OPEN:
4548 if (QMenu * menu = guiApp->menus().menu(toqstr(cmd.argument()), *this))
4549 menu->exec(QCursor::pos());
4552 case LFUN_FILE_INSERT: {
4553 bool const ignore_lang = cmd.getArg(1) == "ignorelang";
4554 if (insertLyXFile(from_utf8(cmd.getArg(0)), ignore_lang)) {
4555 dr.forceBufferUpdate();
4556 dr.screenUpdate(Update::Force);
4561 case LFUN_FILE_INSERT_PLAINTEXT:
4562 case LFUN_FILE_INSERT_PLAINTEXT_PARA: {
4563 string const fname = to_utf8(cmd.argument());
4564 if (!fname.empty() && !FileName::isAbsolute(fname)) {
4565 dr.setMessage(_("Absolute filename expected."));
4569 FileName filename(fname);
4570 if (fname.empty()) {
4571 FileDialog dlg(qt_("Select file to insert"));
4573 FileDialog::Result result = dlg.open(toqstr(bv->buffer().filePath()),
4574 QStringList(qt_("All Files (*)")));
4576 if (result.first == FileDialog::Later || result.second.isEmpty()) {
4577 dr.setMessage(_("Canceled."));
4581 filename.set(fromqstr(result.second));
4585 FuncRequest const new_cmd(cmd, filename.absoluteFilePath());
4586 bv->dispatch(new_cmd, dr);
4591 case LFUN_BUFFER_RELOAD: {
4592 LASSERT(doc_buffer, break);
4595 bool drop = (cmd.argument() == "dump");
4598 if (!drop && !doc_buffer->isClean()) {
4599 docstring const file =
4600 makeDisplayPath(doc_buffer->absFileName(), 20);
4601 if (doc_buffer->notifiesExternalModification()) {
4602 docstring text = _("The current version will be lost. "
4603 "Are you sure you want to load the version on disk "
4604 "of the document %1$s?");
4605 ret = Alert::prompt(_("Reload saved document?"),
4606 bformat(text, file), 1, 1,
4607 _("&Reload"), _("&Cancel"));
4609 docstring text = _("Any changes will be lost. "
4610 "Are you sure you want to revert to the saved version "
4611 "of the document %1$s?");
4612 ret = Alert::prompt(_("Revert to saved document?"),
4613 bformat(text, file), 1, 1,
4614 _("&Revert"), _("&Cancel"));
4619 doc_buffer->markClean();
4620 reloadBuffer(*doc_buffer);
4621 dr.forceBufferUpdate();
4626 case LFUN_BUFFER_RESET_EXPORT:
4627 LASSERT(doc_buffer, break);
4628 doc_buffer->requireFreshStart(true);
4629 dr.setMessage(_("Buffer export reset."));
4632 case LFUN_BUFFER_WRITE:
4633 LASSERT(doc_buffer, break);
4634 saveBuffer(*doc_buffer);
4637 case LFUN_BUFFER_WRITE_AS:
4638 LASSERT(doc_buffer, break);
4639 renameBuffer(*doc_buffer, cmd.argument());
4642 case LFUN_BUFFER_WRITE_AS_TEMPLATE:
4643 LASSERT(doc_buffer, break);
4644 renameBuffer(*doc_buffer, cmd.argument(),
4645 LV_WRITE_AS_TEMPLATE);
4648 case LFUN_BUFFER_WRITE_ALL: {
4649 Buffer * first = theBufferList().first();
4652 message(_("Saving all documents..."));
4653 // We cannot use a for loop as the buffer list cycles.
4656 if (!b->isClean()) {
4658 LYXERR(Debug::ACTION, "Saved " << b->absFileName());
4660 b = theBufferList().next(b);
4661 } while (b != first);
4662 dr.setMessage(_("All documents saved."));
4666 case LFUN_MASTER_BUFFER_FORALL: {
4670 FuncRequest funcToRun = lyxaction.lookupFunc(cmd.getLongArg(0));
4671 funcToRun.allowAsync(false);
4673 for (Buffer const * buf : doc_buffer->allRelatives()) {
4674 // Switch to other buffer view and resend cmd
4675 lyx::dispatch(FuncRequest(
4676 LFUN_BUFFER_SWITCH, buf->absFileName()));
4677 lyx::dispatch(funcToRun);
4680 lyx::dispatch(FuncRequest(
4681 LFUN_BUFFER_SWITCH, doc_buffer->absFileName()));
4685 case LFUN_BUFFER_EXTERNAL_MODIFICATION_CLEAR:
4686 LASSERT(doc_buffer, break);
4687 doc_buffer->clearExternalModification();
4690 case LFUN_BUFFER_CLOSE:
4694 case LFUN_BUFFER_CLOSE_ALL:
4698 case LFUN_DEVEL_MODE_TOGGLE:
4699 devel_mode_ = !devel_mode_;
4701 dr.setMessage(_("Developer mode is now enabled."));
4703 dr.setMessage(_("Developer mode is now disabled."));
4706 case LFUN_TOOLBAR_SET: {
4707 string const name = cmd.getArg(0);
4708 string const state = cmd.getArg(1);
4709 if (GuiToolbar * t = toolbar(name))
4714 case LFUN_TOOLBAR_TOGGLE: {
4715 string const name = cmd.getArg(0);
4716 if (GuiToolbar * t = toolbar(name))
4721 case LFUN_TOOLBAR_MOVABLE: {
4722 string const name = cmd.getArg(0);
4724 // toggle (all) toolbars movablility
4725 toolbarsMovable_ = !toolbarsMovable_;
4726 for (ToolbarInfo const & ti : guiApp->toolbars()) {
4727 GuiToolbar * tb = toolbar(ti.name);
4728 if (tb && tb->isMovable() != toolbarsMovable_)
4729 // toggle toolbar movablity if it does not fit lock
4730 // (all) toolbars positions state silent = true, since
4731 // status bar notifications are slow
4734 if (toolbarsMovable_)
4735 dr.setMessage(_("Toolbars unlocked."));
4737 dr.setMessage(_("Toolbars locked."));
4738 } else if (GuiToolbar * tb = toolbar(name))
4739 // toggle current toolbar movablity
4741 // update lock (all) toolbars positions
4742 updateLockToolbars();
4746 case LFUN_ICON_SIZE: {
4747 QSize size = d.iconSize(cmd.argument());
4749 dr.setMessage(bformat(_("Icon size set to %1$dx%2$d."),
4750 size.width(), size.height()));
4754 case LFUN_DIALOG_UPDATE: {
4755 string const name = to_utf8(cmd.argument());
4756 if (name == "prefs" || name == "document")
4757 updateDialog(name, string());
4758 else if (name == "paragraph")
4759 lyx::dispatch(FuncRequest(LFUN_PARAGRAPH_UPDATE));
4760 else if (currentBufferView()) {
4761 Inset * inset = currentBufferView()->editedInset(name);
4762 // Can only update a dialog connected to an existing inset
4764 // FIXME: get rid of this indirection; GuiView ask the inset
4765 // if he is kind enough to update itself...
4766 FuncRequest fr(LFUN_INSET_DIALOG_UPDATE, cmd.argument());
4767 //FIXME: pass DispatchResult here?
4768 inset->dispatch(currentBufferView()->cursor(), fr);
4774 case LFUN_DIALOG_TOGGLE: {
4775 FuncCode const func_code = isDialogVisible(cmd.getArg(0))
4776 ? LFUN_DIALOG_HIDE : LFUN_DIALOG_SHOW;
4777 dispatch(FuncRequest(func_code, cmd.argument()), dr);
4781 case LFUN_DIALOG_DISCONNECT_INSET:
4782 disconnectDialog(to_utf8(cmd.argument()));
4785 case LFUN_DIALOG_HIDE: {
4786 guiApp->hideDialogs(to_utf8(cmd.argument()), nullptr);
4790 case LFUN_DIALOG_SHOW: {
4791 string const name = cmd.getArg(0);
4792 string sdata = trim(to_utf8(cmd.argument()).substr(name.size()));
4794 if (name == "latexlog") {
4795 // getStatus checks that
4796 LASSERT(doc_buffer, break);
4797 Buffer::LogType type;
4798 string const logfile = doc_buffer->logName(&type);
4800 case Buffer::latexlog:
4803 case Buffer::buildlog:
4804 sdata = "literate ";
4807 sdata += Lexer::quoteString(logfile);
4808 showDialog("log", sdata);
4809 } else if (name == "vclog") {
4810 // getStatus checks that
4811 LASSERT(doc_buffer, break);
4812 string const sdata2 = "vc " +
4813 Lexer::quoteString(doc_buffer->lyxvc().getLogFile());
4814 showDialog("log", sdata2);
4815 } else if (name == "symbols") {
4816 sdata = bv->cursor().getEncoding()->name();
4818 showDialog("symbols", sdata);
4819 } else if (name == "findreplace") {
4820 sdata = to_utf8(bv->cursor().selectionAsString(false));
4821 showDialog(name, sdata);
4823 } else if (name == "prefs" && isFullScreen()) {
4824 lfunUiToggle("fullscreen");
4825 showDialog("prefs", sdata);
4827 showDialog(name, sdata);
4832 dr.setMessage(cmd.argument());
4835 case LFUN_UI_TOGGLE: {
4836 string arg = cmd.getArg(0);
4837 if (!lfunUiToggle(arg)) {
4838 docstring const msg = "ui-toggle " + _("%1$s unknown command!");
4839 dr.setMessage(bformat(msg, from_utf8(arg)));
4841 // Make sure the keyboard focus stays in the work area.
4846 case LFUN_VIEW_SPLIT: {
4847 LASSERT(doc_buffer, break);
4848 string const orientation = cmd.getArg(0);
4849 d.splitter_->setOrientation(orientation == "vertical"
4850 ? Qt::Vertical : Qt::Horizontal);
4851 TabWorkArea * twa = addTabWorkArea();
4852 GuiWorkArea * wa = twa->addWorkArea(*doc_buffer, *this);
4853 setCurrentWorkArea(wa);
4857 case LFUN_TAB_GROUP_NEXT:
4858 gotoNextTabWorkArea(NEXT);
4861 case LFUN_TAB_GROUP_PREVIOUS:
4862 gotoNextTabWorkArea(PREV);
4865 case LFUN_TAB_GROUP_CLOSE:
4866 if (TabWorkArea * twa = d.currentTabWorkArea()) {
4867 closeTabWorkArea(twa);
4868 d.current_work_area_ = nullptr;
4869 twa = d.currentTabWorkArea();
4870 // Switch to the next GuiWorkArea in the found TabWorkArea.
4872 // Make sure the work area is up to date.
4873 setCurrentWorkArea(twa->currentWorkArea());
4875 setCurrentWorkArea(nullptr);
4880 case LFUN_VIEW_CLOSE:
4881 if (TabWorkArea * twa = d.currentTabWorkArea()) {
4882 closeWorkArea(twa->currentWorkArea());
4883 d.current_work_area_ = nullptr;
4884 twa = d.currentTabWorkArea();
4885 // Switch to the next GuiWorkArea in the found TabWorkArea.
4887 // Make sure the work area is up to date.
4888 setCurrentWorkArea(twa->currentWorkArea());
4890 setCurrentWorkArea(nullptr);
4895 case LFUN_COMPLETION_INLINE:
4896 if (d.current_work_area_)
4897 d.current_work_area_->completer().showInline();
4900 case LFUN_COMPLETION_POPUP:
4901 if (d.current_work_area_)
4902 d.current_work_area_->completer().showPopup();
4907 if (d.current_work_area_)
4908 d.current_work_area_->completer().tab();
4911 case LFUN_COMPLETION_CANCEL:
4912 if (d.current_work_area_) {
4913 if (d.current_work_area_->completer().popupVisible())
4914 d.current_work_area_->completer().hidePopup();
4916 d.current_work_area_->completer().hideInline();
4920 case LFUN_COMPLETION_ACCEPT:
4921 if (d.current_work_area_)
4922 d.current_work_area_->completer().activate();
4925 case LFUN_BUFFER_ZOOM_IN:
4926 case LFUN_BUFFER_ZOOM_OUT:
4927 case LFUN_BUFFER_ZOOM: {
4928 zoom_ratio_ = zoomRatio(cmd, zoom_ratio_);
4930 // Actual zoom value: default zoom + fractional extra value
4931 int zoom = (int)(lyxrc.defaultZoom * zoom_ratio_);
4932 zoom = min(max(zoom, zoom_min_), zoom_max_);
4934 setCurrentZoom(zoom);
4936 dr.setMessage(bformat(_("Zoom level is now %1$d% (default value: %2$d%)"),
4937 lyxrc.currentZoom, lyxrc.defaultZoom));
4939 guiApp->fontLoader().update();
4940 // Regenerate instant previews
4941 if (lyxrc.preview != LyXRC::PREVIEW_OFF
4942 && doc_buffer && doc_buffer->loader())
4943 doc_buffer->loader()->refreshPreviews();
4944 dr.screenUpdate(Update::ForceAll | Update::FitCursor);
4948 case LFUN_VC_REGISTER:
4949 case LFUN_VC_RENAME:
4951 case LFUN_VC_CHECK_IN:
4952 case LFUN_VC_CHECK_OUT:
4953 case LFUN_VC_REPO_UPDATE:
4954 case LFUN_VC_LOCKING_TOGGLE:
4955 case LFUN_VC_REVERT:
4956 case LFUN_VC_UNDO_LAST:
4957 case LFUN_VC_COMMAND:
4958 case LFUN_VC_COMPARE:
4959 dispatchVC(cmd, dr);
4962 case LFUN_SERVER_GOTO_FILE_ROW:
4963 if(goToFileRow(to_utf8(cmd.argument())))
4964 dr.screenUpdate(Update::Force | Update::FitCursor);
4967 case LFUN_LYX_ACTIVATE:
4971 case LFUN_WINDOW_RAISE:
4977 case LFUN_FORWARD_SEARCH: {
4978 // it seems safe to assume we have a document buffer, since
4979 // getStatus wants one.
4980 LASSERT(doc_buffer, break);
4981 Buffer const * doc_master = doc_buffer->masterBuffer();
4982 FileName const path(doc_master->temppath());
4983 string const texname = doc_master->isChild(doc_buffer)
4984 ? DocFileName(changeExtension(
4985 doc_buffer->absFileName(),
4986 "tex")).mangledFileName()
4987 : doc_buffer->latexName();
4988 string const fulltexname =
4989 support::makeAbsPath(texname, doc_master->temppath()).absFileName();
4990 string const mastername =
4991 removeExtension(doc_master->latexName());
4992 FileName const dviname(addName(path.absFileName(),
4993 addExtension(mastername, "dvi")));
4994 FileName const pdfname(addName(path.absFileName(),
4995 addExtension(mastername, "pdf")));
4996 bool const have_dvi = dviname.exists();
4997 bool const have_pdf = pdfname.exists();
4998 if (!have_dvi && !have_pdf) {
4999 dr.setMessage(_("Please, preview the document first."));
5002 bool const goto_dvi = have_dvi && !lyxrc.forward_search_dvi.empty();
5003 bool const goto_pdf = have_pdf && !lyxrc.forward_search_pdf.empty();
5004 string outname = dviname.onlyFileName();
5005 string command = lyxrc.forward_search_dvi;
5006 if ((!goto_dvi || goto_pdf) &&
5007 pdfname.lastModified() > dviname.lastModified()) {
5008 outname = pdfname.onlyFileName();
5009 command = lyxrc.forward_search_pdf;
5012 DocIterator cur = bv->cursor();
5013 int row = doc_buffer->texrow().rowFromDocIterator(cur).first;
5014 LYXERR(Debug::ACTION, "Forward search: row:" << row
5016 if (row == -1 || command.empty()) {
5017 dr.setMessage(_("Couldn't proceed."));
5020 string texrow = convert<string>(row);
5022 command = subst(command, "$$n", texrow);
5023 command = subst(command, "$$f", fulltexname);
5024 command = subst(command, "$$t", texname);
5025 command = subst(command, "$$o", outname);
5027 volatile PathChanger p(path);
5029 one.startscript(Systemcall::DontWait, command);
5033 case LFUN_SPELLING_CONTINUOUSLY:
5034 lyxrc.spellcheck_continuously = !lyxrc.spellcheck_continuously;
5035 dr.screenUpdate(Update::Force);
5038 case LFUN_CITATION_OPEN: {
5040 if (theFormats().getFormat("pdf"))
5041 pdfv = theFormats().getFormat("pdf")->viewer();
5042 if (theFormats().getFormat("ps"))
5043 psv = theFormats().getFormat("ps")->viewer();
5044 frontend::showTarget(argument, pdfv, psv);
5049 // The LFUN must be for one of BufferView, Buffer or Cursor;
5051 dispatchToBufferView(cmd, dr);
5055 // Need to update bv because many LFUNs here might have destroyed it
5056 bv = currentBufferView();
5058 // Clear non-empty selections
5059 // (e.g. from a "char-forward-select" followed by "char-backward-select")
5061 Cursor & cur = bv->cursor();
5062 if ((cur.selection() && cur.selBegin() == cur.selEnd())) {
5063 cur.clearSelection();
5069 bool GuiView::lfunUiToggle(string const & ui_component)
5071 if (ui_component == "scrollbar") {
5072 // hide() is of no help
5073 if (d.current_work_area_->verticalScrollBarPolicy() ==
5074 Qt::ScrollBarAlwaysOff)
5076 d.current_work_area_->setVerticalScrollBarPolicy(
5077 Qt::ScrollBarAsNeeded);
5079 d.current_work_area_->setVerticalScrollBarPolicy(
5080 Qt::ScrollBarAlwaysOff);
5081 } else if (ui_component == "statusbar") {
5082 statusBar()->setVisible(!statusBar()->isVisible());
5083 } else if (ui_component == "menubar") {
5084 menuBar()->setVisible(!menuBar()->isVisible());
5085 } else if (ui_component == "zoomlevel") {
5086 zoom_value_->setVisible(!zoom_value_->isVisible());
5087 } else if (ui_component == "zoomslider") {
5088 zoom_slider_->setVisible(!zoom_slider_->isVisible());
5089 zoom_in_->setVisible(zoom_slider_->isVisible());
5090 zoom_out_->setVisible(zoom_slider_->isVisible());
5091 } else if (ui_component == "statistics-w") {
5092 word_count_enabled_ = !word_count_enabled_;
5095 } else if (ui_component == "statistics-cb") {
5096 char_count_enabled_ = !char_count_enabled_;
5099 } else if (ui_component == "statistics-c") {
5100 char_nb_count_enabled_ = !char_nb_count_enabled_;
5103 } else if (ui_component == "frame") {
5104 int const l = contentsMargins().left();
5106 //are the frames in default state?
5107 d.current_work_area_->setFrameStyle(QFrame::NoFrame);
5109 #if QT_VERSION > 0x050903
5110 setAttribute(Qt::WA_ContentsMarginsRespectsSafeArea, false);
5112 setContentsMargins(-2, -2, -2, -2);
5114 #if QT_VERSION > 0x050903
5115 setAttribute(Qt::WA_ContentsMarginsRespectsSafeArea, true);
5117 setContentsMargins(0, 0, 0, 0);
5120 if (ui_component == "fullscreen") {
5124 stat_counts_->setVisible(statsEnabled());
5129 void GuiView::cancelExport()
5131 Systemcall::killscript();
5132 // stop busy signal immediately so that in the subsequent
5133 // "Export canceled" prompt the status bar icons are accurate.
5134 Q_EMIT scriptKilled();
5138 void GuiView::toggleFullScreen()
5140 setWindowState(windowState() ^ Qt::WindowFullScreen);
5144 Buffer const * GuiView::updateInset(Inset const * inset)
5149 Buffer const * inset_buffer = &(inset->buffer());
5151 for (int i = 0; i != d.splitter_->count(); ++i) {
5152 GuiWorkArea * wa = d.tabWorkArea(i)->currentWorkArea();
5155 Buffer const * buffer = &(wa->bufferView().buffer());
5156 if (inset_buffer == buffer)
5157 wa->scheduleRedraw(true);
5159 return inset_buffer;
5163 void GuiView::restartCaret()
5165 /* When we move around, or type, it's nice to be able to see
5166 * the caret immediately after the keypress.
5168 if (d.current_work_area_)
5169 d.current_work_area_->startBlinkingCaret();
5171 // Take this occasion to update the other GUI elements.
5177 void GuiView::updateCompletion(Cursor & cur, bool start, bool keep)
5179 if (d.current_work_area_)
5180 d.current_work_area_->completer().updateVisibility(cur, start, keep);
5185 // This list should be kept in sync with the list of insets in
5186 // src/insets/Inset.cpp. I.e., if a dialog goes with an inset, the
5187 // dialog should have the same name as the inset.
5188 // Changes should be also recorded in LFUN_DIALOG_SHOW doxygen
5189 // docs in LyXAction.cpp.
5191 char const * const dialognames[] = {
5193 "aboutlyx", "bibitem", "bibtex", "box", "branch", "changes", "character",
5194 "citation", "compare", "comparehistory", "counter", "document", "errorlist", "ert",
5195 "external", "file", "findreplace", "findreplaceadv", "float", "graphics",
5196 "href", "include", "index", "index_print", "info", "listings", "label", "line",
5197 "log", "lyxfiles", "mathdelimiter", "mathmatrix", "mathspace", "nomenclature",
5198 "nomencl_print", "note", "paragraph", "phantom", "prefs", "ref",
5199 "sendto", "space", "spellchecker", "symbols", "tabular", "tabularcreate",
5200 "thesaurus", "texinfo", "toc", "view-source", "vspace", "wrap", "progress"};
5202 char const * const * const end_dialognames =
5203 dialognames + (sizeof(dialognames) / sizeof(char *));
5207 cmpCStr(char const * name) : name_(name) {}
5208 bool operator()(char const * other) {
5209 return strcmp(other, name_) == 0;
5216 bool isValidName(string const & name)
5218 return find_if(dialognames, end_dialognames,
5219 cmpCStr(name.c_str())) != end_dialognames;
5225 void GuiView::resetDialogs()
5227 // Make sure that no LFUN uses any GuiView.
5228 guiApp->setCurrentView(nullptr);
5232 constructToolbars();
5233 guiApp->menus().fillMenuBar(menuBar(), this, false);
5234 d.layout_->updateContents(true);
5235 // Now update controls with current buffer.
5236 guiApp->setCurrentView(this);
5242 void GuiView::flatGroupBoxes(const QObject * widget, bool flag)
5244 for (QObject * child: widget->children()) {
5245 if (child->inherits("QGroupBox")) {
5246 QGroupBox * box = (QGroupBox*) child;
5249 flatGroupBoxes(child, flag);
5255 Dialog * GuiView::find(string const & name, bool hide_it) const
5257 if (!isValidName(name))
5260 map<string, DialogPtr>::iterator it = d.dialogs_.find(name);
5262 if (it != d.dialogs_.end()) {
5264 it->second->hideView();
5265 return it->second.get();
5271 Dialog * GuiView::findOrBuild(string const & name, bool hide_it)
5273 Dialog * dialog = find(name, hide_it);
5274 if (dialog != nullptr)
5277 dialog = build(name);
5278 d.dialogs_[name].reset(dialog);
5279 #if (QT_VERSION >= QT_VERSION_CHECK(5, 0, 0))
5280 // Force a uniform style for group boxes
5281 // On Mac non-flat works better, on Linux flat is standard
5282 flatGroupBoxes(dialog->asQWidget(), guiApp->platformName() != "cocoa");
5284 if (lyxrc.allow_geometry_session)
5285 dialog->restoreSession();
5292 void GuiView::showDialog(string const & name, string const & sdata,
5295 triggerShowDialog(toqstr(name), toqstr(sdata), inset);
5299 void GuiView::doShowDialog(QString const & qname, QString const & qdata,
5305 const string name = fromqstr(qname);
5306 const string sdata = fromqstr(qdata);
5310 Dialog * dialog = findOrBuild(name, false);
5312 bool const visible = dialog->isVisibleView();
5313 dialog->showData(sdata);
5314 if (currentBufferView())
5315 currentBufferView()->editInset(name, inset);
5316 // We only set the focus to the new dialog if it was not yet
5317 // visible in order not to change the existing previous behaviour
5319 // activateWindow is needed for floating dockviews
5320 dialog->asQWidget()->raise();
5321 dialog->asQWidget()->activateWindow();
5322 if (dialog->wantInitialFocus())
5323 dialog->asQWidget()->setFocus();
5327 catch (ExceptionMessage const &) {
5335 bool GuiView::isDialogVisible(string const & name) const
5337 map<string, DialogPtr>::const_iterator it = d.dialogs_.find(name);
5338 if (it == d.dialogs_.end())
5340 return it->second.get()->isVisibleView() && !it->second.get()->isClosing();
5344 void GuiView::hideDialog(string const & name, Inset * inset)
5346 map<string, DialogPtr>::const_iterator it = d.dialogs_.find(name);
5347 if (it == d.dialogs_.end())
5351 if (!currentBufferView())
5353 if (inset != currentBufferView()->editedInset(name))
5357 Dialog * const dialog = it->second.get();
5358 if (dialog->isVisibleView())
5360 if (currentBufferView())
5361 currentBufferView()->editInset(name, nullptr);
5365 void GuiView::disconnectDialog(string const & name)
5367 if (!isValidName(name))
5369 if (currentBufferView())
5370 currentBufferView()->editInset(name, nullptr);
5374 void GuiView::hideAll() const
5376 for(auto const & dlg_p : d.dialogs_)
5377 dlg_p.second->hideView();
5381 void GuiView::updateDialogs()
5383 for(auto const & dlg_p : d.dialogs_) {
5384 Dialog * dialog = dlg_p.second.get();
5386 if (dialog->needBufferOpen() && !documentBufferView())
5387 hideDialog(fromqstr(dialog->name()), nullptr);
5388 else if (dialog->isVisibleView())
5389 dialog->checkStatus();
5397 Dialog * GuiView::build(string const & name)
5399 return createDialog(*this, name);
5403 SEMenu::SEMenu(QWidget * parent)
5405 QAction * action = addAction(qt_("Disable Shell Escape"));
5406 connect(action, SIGNAL(triggered()),
5407 parent, SLOT(disableShellEscape()));
5411 void PressableSvgWidget::mousePressEvent(QMouseEvent * event)
5413 if (event->button() == Qt::LeftButton) {
5418 } // namespace frontend
5421 #include "moc_GuiView.cpp"