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_CLOSE:
2477 enable = d.tabWorkAreaCount() > 1;
2480 case LFUN_DEVEL_MODE_TOGGLE:
2481 flag.setOnOff(devel_mode_);
2484 case LFUN_TOOLBAR_SET: {
2485 string const name = cmd.getArg(0);
2486 string const state = cmd.getArg(1);
2487 if (name.empty() || state.empty()) {
2489 docstring const msg =
2490 _("Function toolbar-set requires two arguments!");
2494 if (state != "on" && state != "off" && state != "auto") {
2496 docstring const msg =
2497 bformat(_("Invalid argument \"%1$s\" to function toolbar-set!"),
2502 if (GuiToolbar * t = toolbar(name)) {
2503 bool const autovis = t->visibility() & Toolbars::AUTO;
2505 flag.setOnOff(t->isVisible() && !autovis);
2506 else if (state == "off")
2507 flag.setOnOff(!t->isVisible() && !autovis);
2508 else if (state == "auto")
2509 flag.setOnOff(autovis);
2512 docstring const msg =
2513 bformat(_("Unknown toolbar \"%1$s\""), from_utf8(name));
2519 case LFUN_TOOLBAR_TOGGLE: {
2520 string const name = cmd.getArg(0);
2521 if (GuiToolbar * t = toolbar(name))
2522 flag.setOnOff(t->isVisible());
2525 docstring const msg =
2526 bformat(_("Unknown toolbar \"%1$s\""), from_utf8(name));
2532 case LFUN_TOOLBAR_MOVABLE: {
2533 string const name = cmd.getArg(0);
2534 // use negation since locked == !movable
2536 // toolbar name * locks all toolbars
2537 flag.setOnOff(!toolbarsMovable_);
2538 else if (GuiToolbar * t = toolbar(name))
2539 flag.setOnOff(!(t->isMovable()));
2542 docstring const msg =
2543 bformat(_("Unknown toolbar \"%1$s\""), from_utf8(name));
2549 case LFUN_ICON_SIZE:
2550 flag.setOnOff(d.iconSize(cmd.argument()) == iconSize());
2553 case LFUN_DROP_LAYOUTS_CHOICE:
2554 enable = buf != nullptr;
2557 case LFUN_UI_TOGGLE:
2558 if (cmd.argument() == "zoomlevel") {
2559 flag.setOnOff(zoom_value_ ? zoom_value_->isVisible() : false);
2560 } else if (cmd.argument() == "zoomslider") {
2561 flag.setOnOff(zoom_slider_ ? zoom_slider_->isVisible() : false);
2562 } else if (cmd.argument() == "statistics-w") {
2563 flag.setOnOff(word_count_enabled_);
2564 } else if (cmd.argument() == "statistics-cb") {
2565 flag.setOnOff(char_count_enabled_);
2566 } else if (cmd.argument() == "statistics-c") {
2567 flag.setOnOff(char_nb_count_enabled_);
2569 flag.setOnOff(isFullScreen());
2572 case LFUN_DIALOG_DISCONNECT_INSET:
2575 case LFUN_DIALOG_HIDE:
2576 // FIXME: should we check if the dialog is shown?
2579 case LFUN_DIALOG_TOGGLE:
2580 flag.setOnOff(isDialogVisible(cmd.getArg(0)));
2583 case LFUN_DIALOG_SHOW: {
2584 string const name = cmd.getArg(0);
2586 enable = name == "aboutlyx"
2587 || name == "file" //FIXME: should be removed.
2588 || name == "lyxfiles"
2590 || name == "texinfo"
2591 || name == "progress"
2592 || name == "compare";
2593 else if (name == "character" || name == "symbols"
2594 || name == "mathdelimiter" || name == "mathmatrix") {
2595 if (!buf || buf->isReadonly())
2598 Cursor const & cur = currentBufferView()->cursor();
2599 enable = !(cur.inTexted() && cur.paragraph().isPassThru());
2602 else if (name == "latexlog")
2603 enable = FileName(doc_buffer->logName()).isReadableFile();
2604 else if (name == "spellchecker")
2605 enable = theSpellChecker()
2606 && !doc_buffer->text().empty();
2607 else if (name == "vclog")
2608 enable = doc_buffer->lyxvc().inUse();
2612 case LFUN_DIALOG_UPDATE: {
2613 string const name = cmd.getArg(0);
2615 enable = name == "prefs";
2619 case LFUN_COMMAND_EXECUTE:
2621 case LFUN_MENU_OPEN:
2622 // Nothing to check.
2625 case LFUN_COMPLETION_INLINE:
2626 if (!d.current_work_area_
2627 || !d.current_work_area_->completer().inlinePossible(
2628 currentBufferView()->cursor()))
2632 case LFUN_COMPLETION_POPUP:
2633 if (!d.current_work_area_
2634 || !d.current_work_area_->completer().popupPossible(
2635 currentBufferView()->cursor()))
2640 if (!d.current_work_area_
2641 || !d.current_work_area_->completer().inlinePossible(
2642 currentBufferView()->cursor()))
2646 case LFUN_COMPLETION_ACCEPT:
2647 if (!d.current_work_area_
2648 || (!d.current_work_area_->completer().popupVisible()
2649 && !d.current_work_area_->completer().inlineVisible()
2650 && !d.current_work_area_->completer().completionAvailable()))
2654 case LFUN_COMPLETION_CANCEL:
2655 if (!d.current_work_area_
2656 || (!d.current_work_area_->completer().popupVisible()
2657 && !d.current_work_area_->completer().inlineVisible()))
2661 case LFUN_BUFFER_ZOOM_OUT:
2662 case LFUN_BUFFER_ZOOM_IN:
2663 case LFUN_BUFFER_ZOOM: {
2664 int const zoom = (int)(lyxrc.defaultZoom * zoomRatio(cmd, zoom_ratio_));
2665 if (zoom < zoom_min_) {
2666 docstring const msg =
2667 bformat(_("Zoom level cannot be less than %1$d%."), zoom_min_);
2670 } else if (zoom > zoom_max_) {
2671 docstring const msg =
2672 bformat(_("Zoom level cannot be more than %1$d%."), zoom_max_);
2676 enable = doc_buffer;
2681 case LFUN_BUFFER_MOVE_NEXT:
2682 case LFUN_BUFFER_MOVE_PREVIOUS:
2683 // we do not cycle when moving
2684 case LFUN_BUFFER_NEXT:
2685 case LFUN_BUFFER_PREVIOUS:
2686 // because we cycle, it doesn't matter whether on first or last
2687 enable = (d.currentTabWorkArea()->count() > 1);
2689 case LFUN_BUFFER_SWITCH:
2690 // toggle on the current buffer, but do not toggle off
2691 // the other ones (is that a good idea?)
2693 && to_utf8(cmd.argument()) == doc_buffer->absFileName())
2694 flag.setOnOff(true);
2697 case LFUN_VC_REGISTER:
2698 enable = doc_buffer && !doc_buffer->lyxvc().inUse();
2700 case LFUN_VC_RENAME:
2701 enable = doc_buffer && doc_buffer->lyxvc().renameEnabled();
2704 enable = doc_buffer && doc_buffer->lyxvc().copyEnabled();
2706 case LFUN_VC_CHECK_IN:
2707 enable = doc_buffer && doc_buffer->lyxvc().checkInEnabled();
2709 case LFUN_VC_CHECK_OUT:
2710 enable = doc_buffer && doc_buffer->lyxvc().checkOutEnabled();
2712 case LFUN_VC_LOCKING_TOGGLE:
2713 enable = doc_buffer && !doc_buffer->hasReadonlyFlag()
2714 && doc_buffer->lyxvc().lockingToggleEnabled();
2715 flag.setOnOff(enable && doc_buffer->lyxvc().locking());
2717 case LFUN_VC_REVERT:
2718 enable = doc_buffer && doc_buffer->lyxvc().inUse()
2719 && !doc_buffer->hasReadonlyFlag();
2721 case LFUN_VC_UNDO_LAST:
2722 enable = doc_buffer && doc_buffer->lyxvc().undoLastEnabled();
2724 case LFUN_VC_REPO_UPDATE:
2725 enable = doc_buffer && doc_buffer->lyxvc().repoUpdateEnabled();
2727 case LFUN_VC_COMMAND: {
2728 if (cmd.argument().empty())
2730 if (!doc_buffer && contains(cmd.getArg(0), 'D'))
2734 case LFUN_VC_COMPARE:
2735 enable = doc_buffer && doc_buffer->lyxvc().prepareFileRevisionEnabled();
2738 case LFUN_SERVER_GOTO_FILE_ROW:
2739 case LFUN_LYX_ACTIVATE:
2740 case LFUN_WINDOW_RAISE:
2742 case LFUN_FORWARD_SEARCH:
2743 enable = !(lyxrc.forward_search_dvi.empty() && lyxrc.forward_search_pdf.empty()) &&
2744 doc_buffer && doc_buffer->isSyncTeXenabled();
2747 case LFUN_FILE_INSERT_PLAINTEXT:
2748 case LFUN_FILE_INSERT_PLAINTEXT_PARA:
2749 enable = documentBufferView() && documentBufferView()->cursor().inTexted();
2752 case LFUN_SPELLING_CONTINUOUSLY:
2753 flag.setOnOff(lyxrc.spellcheck_continuously);
2756 case LFUN_CITATION_OPEN:
2765 flag.setEnabled(false);
2771 static FileName selectTemplateFile()
2773 FileDialog dlg(qt_("Select template file"));
2774 dlg.setButton1(qt_("D&ocuments"), toqstr(lyxrc.document_path));
2775 dlg.setButton2(qt_("&Templates"), toqstr(lyxrc.template_path));
2777 FileDialog::Result result = dlg.open(toqstr(lyxrc.template_path),
2778 QStringList(qt_("LyX Documents (*.lyx)")));
2780 if (result.first == FileDialog::Later)
2782 if (result.second.isEmpty())
2784 return FileName(fromqstr(result.second));
2788 Buffer * GuiView::loadDocument(FileName const & filename, bool tolastfiles)
2792 Buffer * newBuffer = nullptr;
2794 newBuffer = checkAndLoadLyXFile(filename);
2795 } catch (ExceptionMessage const &) {
2802 message(_("Document not loaded."));
2806 setBuffer(newBuffer);
2807 newBuffer->errors("Parse");
2810 theSession().lastFiles().add(filename);
2811 theSession().writeFile();
2818 void GuiView::openDocument(string const & fname)
2820 string initpath = lyxrc.document_path;
2822 if (documentBufferView()) {
2823 string const trypath = documentBufferView()->buffer().filePath();
2824 // If directory is writeable, use this as default.
2825 if (FileName(trypath).isDirWritable())
2831 if (fname.empty()) {
2832 FileDialog dlg(qt_("Select document to open"));
2833 dlg.setButton1(qt_("D&ocuments"), toqstr(lyxrc.document_path));
2834 dlg.setButton2(qt_("&Examples"), toqstr(lyxrc.example_path));
2836 QStringList const filter({
2837 qt_("LyX Documents (*.lyx)"),
2838 qt_("LyX Document Backups (*.lyx~)"),
2839 qt_("All Files (*.*)")
2841 FileDialog::Result result =
2842 dlg.open(toqstr(initpath), filter);
2844 if (result.first == FileDialog::Later)
2847 filename = fromqstr(result.second);
2849 // check selected filename
2850 if (filename.empty()) {
2851 message(_("Canceled."));
2857 // get absolute path of file and add ".lyx" to the filename if
2859 FileName const fullname =
2860 fileSearch(string(), filename, "lyx", support::may_not_exist);
2861 if (!fullname.empty())
2862 filename = fullname.absFileName();
2864 if (!fullname.onlyPath().isDirectory()) {
2865 Alert::warning(_("Invalid filename"),
2866 bformat(_("The directory in the given path\n%1$s\ndoes not exist."),
2867 from_utf8(fullname.absFileName())));
2871 // if the file doesn't exist and isn't already open (bug 6645),
2872 // let the user create one
2873 if (!fullname.exists() && !theBufferList().exists(fullname) &&
2874 !LyXVC::file_not_found_hook(fullname)) {
2875 // the user specifically chose this name. Believe him.
2876 Buffer * const b = newFile(filename, string(), true);
2882 docstring const disp_fn = makeDisplayPath(filename);
2883 message(bformat(_("Opening document %1$s..."), disp_fn));
2886 Buffer * buf = loadDocument(fullname);
2888 str2 = bformat(_("Document %1$s opened."), disp_fn);
2889 if (buf->lyxvc().inUse())
2890 str2 += " " + from_utf8(buf->lyxvc().versionString()) +
2891 " " + _("Version control detected.");
2893 str2 = bformat(_("Could not open document %1$s"), disp_fn);
2898 // FIXME: clean that
2899 static bool import(GuiView * lv, FileName const & filename,
2900 string const & format, ErrorList & errorList)
2902 FileName const lyxfile(support::changeExtension(filename.absFileName(), ".lyx"));
2904 string loader_format;
2905 vector<string> loaders = theConverters().loaders();
2906 if (find(loaders.begin(), loaders.end(), format) == loaders.end()) {
2907 for (string const & loader : loaders) {
2908 if (!theConverters().isReachable(format, loader))
2911 string const tofile =
2912 support::changeExtension(filename.absFileName(),
2913 theFormats().extension(loader));
2914 if (theConverters().convert(nullptr, filename, FileName(tofile),
2915 filename, format, loader, errorList) != Converters::SUCCESS)
2917 loader_format = loader;
2920 if (loader_format.empty()) {
2921 frontend::Alert::error(_("Couldn't import file"),
2922 bformat(_("No information for importing the format %1$s."),
2923 translateIfPossible(theFormats().prettyName(format))));
2927 loader_format = format;
2929 if (loader_format == "lyx") {
2930 Buffer * buf = lv->loadDocument(lyxfile);
2934 Buffer * const b = newFile(lyxfile.absFileName(), string(), true);
2938 bool as_paragraphs = loader_format == "textparagraph";
2939 string filename2 = (loader_format == format) ? filename.absFileName()
2940 : support::changeExtension(filename.absFileName(),
2941 theFormats().extension(loader_format));
2942 lv->currentBufferView()->insertPlaintextFile(FileName(filename2),
2944 guiApp->setCurrentView(lv);
2945 lyx::dispatch(FuncRequest(LFUN_MARK_OFF));
2952 void GuiView::importDocument(string const & argument)
2955 string filename = split(argument, format, ' ');
2957 LYXERR(Debug::INFO, format << " file: " << filename);
2959 // need user interaction
2960 if (filename.empty()) {
2961 string initpath = lyxrc.document_path;
2962 if (documentBufferView()) {
2963 string const trypath = documentBufferView()->buffer().filePath();
2964 // If directory is writeable, use this as default.
2965 if (FileName(trypath).isDirWritable())
2969 docstring const text = bformat(_("Select %1$s file to import"),
2970 translateIfPossible(theFormats().prettyName(format)));
2972 FileDialog dlg(toqstr(text));
2973 dlg.setButton1(qt_("D&ocuments"), toqstr(lyxrc.document_path));
2974 dlg.setButton2(qt_("&Examples"), toqstr(lyxrc.example_path));
2976 docstring filter = translateIfPossible(theFormats().prettyName(format));
2979 filter += from_utf8(theFormats().extensions(format));
2982 FileDialog::Result result =
2983 dlg.open(toqstr(initpath), fileFilters(toqstr(filter)));
2985 if (result.first == FileDialog::Later)
2988 filename = fromqstr(result.second);
2990 // check selected filename
2991 if (filename.empty())
2992 message(_("Canceled."));
2995 if (filename.empty())
2998 // get absolute path of file
2999 FileName const fullname(support::makeAbsPath(filename));
3001 // Can happen if the user entered a path into the dialog
3003 if (fullname.onlyFileName().empty()) {
3004 docstring msg = bformat(_("The file name '%1$s' is invalid!\n"
3005 "Aborting import."),
3006 from_utf8(fullname.absFileName()));
3007 frontend::Alert::error(_("File name error"), msg);
3008 message(_("Canceled."));
3013 FileName const lyxfile(support::changeExtension(fullname.absFileName(), ".lyx"));
3015 // Check if the document already is open
3016 Buffer * buf = theBufferList().getBuffer(lyxfile);
3019 if (!closeBuffer()) {
3020 message(_("Canceled."));
3025 docstring const displaypath = makeDisplayPath(lyxfile.absFileName(), 30);
3027 // if the file exists already, and we didn't do
3028 // -i lyx thefile.lyx, warn
3029 if (lyxfile.exists() && fullname != lyxfile) {
3031 docstring text = bformat(_("The document %1$s already exists.\n\n"
3032 "Do you want to overwrite that document?"), displaypath);
3033 int const ret = Alert::prompt(_("Overwrite document?"),
3034 text, 0, 1, _("&Overwrite"), _("&Cancel"));
3037 message(_("Canceled."));
3042 message(bformat(_("Importing %1$s..."), displaypath));
3043 ErrorList errorList;
3044 if (import(this, fullname, format, errorList))
3045 message(_("imported."));
3047 message(_("file not imported!"));
3049 // FIXME (Abdel 12/08/06): Is there a need to display the error list here?
3053 void GuiView::newDocument(string const & filename, string templatefile,
3056 FileName initpath(lyxrc.document_path);
3057 if (documentBufferView()) {
3058 FileName const trypath(documentBufferView()->buffer().filePath());
3059 // If directory is writeable, use this as default.
3060 if (trypath.isDirWritable())
3064 if (from_template) {
3065 if (templatefile.empty())
3066 templatefile = selectTemplateFile().absFileName();
3067 if (templatefile.empty())
3072 if (filename.empty())
3073 b = newUnnamedFile(initpath, to_utf8(_("newfile")), templatefile);
3075 b = newFile(filename, templatefile, true);
3080 // If no new document could be created, it is unsure
3081 // whether there is a valid BufferView.
3082 if (currentBufferView())
3083 // Ensure the cursor is correctly positioned on screen.
3084 currentBufferView()->showCursor();
3088 bool GuiView::insertLyXFile(docstring const & fname, bool ignorelang)
3090 BufferView * bv = documentBufferView();
3095 FileName filename(to_utf8(fname));
3096 if (filename.empty()) {
3097 // Launch a file browser
3099 string initpath = lyxrc.document_path;
3100 string const trypath = bv->buffer().filePath();
3101 // If directory is writeable, use this as default.
3102 if (FileName(trypath).isDirWritable())
3106 FileDialog dlg(qt_("Select LyX document to insert"));
3107 dlg.setButton1(qt_("D&ocuments"), toqstr(lyxrc.document_path));
3108 dlg.setButton2(qt_("&Examples"), toqstr(lyxrc.example_path));
3110 FileDialog::Result result = dlg.open(toqstr(initpath),
3111 QStringList(qt_("LyX Documents (*.lyx)")));
3113 if (result.first == FileDialog::Later)
3117 filename.set(fromqstr(result.second));
3119 // check selected filename
3120 if (filename.empty()) {
3121 // emit message signal.
3122 message(_("Canceled."));
3127 bv->insertLyXFile(filename, ignorelang);
3128 bv->buffer().errors("Parse");
3133 string const GuiView::getTemplatesPath(Buffer & b)
3135 // We start off with the user's templates path
3136 string result = addPath(package().user_support().absFileName(), "templates");
3137 // Check for the document language
3138 string const langcode = b.params().language->code();
3139 string const shortcode = langcode.substr(0, 2);
3140 if (!langcode.empty() && shortcode != "en") {
3141 string subpath = addPath(result, shortcode);
3142 string subpath_long = addPath(result, langcode);
3143 // If we have a subdirectory for the language already,
3145 FileName sp = FileName(subpath);
3146 if (sp.isDirectory())
3148 else if (FileName(subpath_long).isDirectory())
3149 result = subpath_long;
3151 // Ask whether we should create such a subdirectory
3152 docstring const text =
3153 bformat(_("It is suggested to save the template in a subdirectory\n"
3154 "appropriate to the document language (%1$s).\n"
3155 "This subdirectory does not exists yet.\n"
3156 "Do you want to create it?"),
3157 _(b.params().language->display()));
3158 if (Alert::prompt(_("Create Language Directory?"),
3159 text, 0, 1, _("&Yes, Create"), _("&No, Save Template in Parent Directory")) == 0) {
3160 // If the user agreed, we try to create it and report if this failed.
3161 if (!sp.createDirectory(0777))
3162 Alert::error(_("Subdirectory creation failed!"),
3163 _("Could not create subdirectory.\n"
3164 "The template will be saved in the parent directory."));
3170 // Do we have a layout category?
3171 string const cat = b.params().baseClass() ?
3172 b.params().baseClass()->category()
3175 string subpath = addPath(result, cat);
3176 // If we have a subdirectory for the category already,
3178 FileName sp = FileName(subpath);
3179 if (sp.isDirectory())
3182 // Ask whether we should create such a subdirectory
3183 docstring const text =
3184 bformat(_("It is suggested to save the template in a subdirectory\n"
3185 "appropriate to the layout category (%1$s).\n"
3186 "This subdirectory does not exists yet.\n"
3187 "Do you want to create it?"),
3189 if (Alert::prompt(_("Create Category Directory?"),
3190 text, 0, 1, _("&Yes, Create"), _("&No, Save Template in Parent Directory")) == 0) {
3191 // If the user agreed, we try to create it and report if this failed.
3192 if (!sp.createDirectory(0777))
3193 Alert::error(_("Subdirectory creation failed!"),
3194 _("Could not create subdirectory.\n"
3195 "The template will be saved in the parent directory."));
3205 bool GuiView::renameBuffer(Buffer & b, docstring const & newname, RenameKind kind)
3207 FileName fname = b.fileName();
3208 FileName const oldname = fname;
3209 bool const as_template = (kind == LV_WRITE_AS_TEMPLATE);
3211 if (!newname.empty()) {
3214 fname = support::makeAbsPath(to_utf8(newname), getTemplatesPath(b));
3216 fname = support::makeAbsPath(to_utf8(newname),
3217 oldname.onlyPath().absFileName());
3219 // Switch to this Buffer.
3222 // No argument? Ask user through dialog.
3224 QString const title = as_template ? qt_("Choose a filename to save template as")
3225 : qt_("Choose a filename to save document as");
3226 FileDialog dlg(title);
3227 dlg.setButton1(qt_("D&ocuments"), toqstr(lyxrc.document_path));
3228 dlg.setButton2(qt_("&Templates"), toqstr(lyxrc.template_path));
3230 fname.ensureExtension(".lyx");
3232 string const path = as_template ?
3234 : fname.onlyPath().absFileName();
3235 FileDialog::Result result =
3236 dlg.save(toqstr(path),
3237 QStringList(qt_("LyX Documents (*.lyx)")),
3238 toqstr(fname.onlyFileName()));
3240 if (result.first == FileDialog::Later)
3243 fname.set(fromqstr(result.second));
3248 fname.ensureExtension(".lyx");
3251 // fname is now the new Buffer location.
3253 // if there is already a Buffer open with this name, we do not want
3254 // to have another one. (the second test makes sure we're not just
3255 // trying to overwrite ourselves, which is fine.)
3256 if (theBufferList().exists(fname) && fname != oldname
3257 && theBufferList().getBuffer(fname) != &b) {
3258 docstring const text =
3259 bformat(_("The file\n%1$s\nis already open in your current session.\n"
3260 "Please close it before attempting to overwrite it.\n"
3261 "Do you want to choose a new filename?"),
3262 from_utf8(fname.absFileName()));
3263 int const ret = Alert::prompt(_("Chosen File Already Open"),
3264 text, 0, 1, _("&Rename"), _("&Cancel"));
3266 case 0: return renameBuffer(b, docstring(), kind);
3267 case 1: return false;
3272 bool const existsLocal = fname.exists();
3273 bool const existsInVC = LyXVC::fileInVC(fname);
3274 if (existsLocal || existsInVC) {
3275 docstring const file = makeDisplayPath(fname.absFileName(), 30);
3276 if (kind != LV_WRITE_AS && existsInVC) {
3277 // renaming to a name that is already in VC
3279 docstring text = bformat(_("The document %1$s "
3280 "is already registered.\n\n"
3281 "Do you want to choose a new name?"),
3283 docstring const title = (kind == LV_VC_RENAME) ?
3284 _("Rename document?") : _("Copy document?");
3285 docstring const button = (kind == LV_VC_RENAME) ?
3286 _("&Rename") : _("&Copy");
3287 int const ret = Alert::prompt(title, text, 0, 1,
3288 button, _("&Cancel"));
3290 case 0: return renameBuffer(b, docstring(), kind);
3291 case 1: return false;
3296 docstring text = bformat(_("The document %1$s "
3297 "already exists.\n\n"
3298 "Do you want to overwrite that document?"),
3300 int const ret = Alert::prompt(_("Overwrite document?"),
3301 text, 0, 2, _("&Overwrite"),
3302 _("&Rename"), _("&Cancel"));
3305 case 1: return renameBuffer(b, docstring(), kind);
3306 case 2: return false;
3312 case LV_VC_RENAME: {
3313 string msg = b.lyxvc().rename(fname);
3316 message(from_utf8(msg));
3320 string msg = b.lyxvc().copy(fname);
3323 message(from_utf8(msg));
3327 case LV_WRITE_AS_TEMPLATE:
3330 // LyXVC created the file already in case of LV_VC_RENAME or
3331 // LV_VC_COPY, but call saveBuffer() nevertheless to get
3332 // relative paths of included stuff right if we moved e.g. from
3333 // /a/b.lyx to /a/c/b.lyx.
3335 bool const saved = saveBuffer(b, fname);
3342 bool GuiView::exportBufferAs(Buffer & b, docstring const & iformat)
3344 FileName fname = b.fileName();
3346 FileDialog dlg(qt_("Choose a filename to export the document as"));
3347 dlg.setButton1(qt_("D&ocuments"), toqstr(lyxrc.document_path));
3350 QString const anyformat = qt_("Guess from extension (*.*)");
3353 vector<Format const *> export_formats;
3354 for (Format const & f : theFormats())
3355 if (f.documentFormat())
3356 export_formats.push_back(&f);
3357 sort(export_formats.begin(), export_formats.end(), Format::formatSorter);
3358 map<QString, string> fmap;
3361 for (Format const * f : export_formats) {
3362 docstring const loc_prettyname = translateIfPossible(f->prettyname());
3363 QString const loc_filter = toqstr(bformat(from_ascii("%1$s (*.%2$s)"),
3365 from_ascii(f->extension())));
3366 types << loc_filter;
3367 fmap[loc_filter] = f->name();
3368 if (from_ascii(f->name()) == iformat) {
3369 filter = loc_filter;
3370 ext = f->extension();
3373 string ofname = fname.onlyFileName();
3375 ofname = support::changeExtension(ofname, ext);
3376 FileDialog::Result result =
3377 dlg.save(toqstr(fname.onlyPath().absFileName()),
3381 if (result.first != FileDialog::Chosen)
3385 fname.set(fromqstr(result.second));
3386 if (filter == anyformat)
3387 fmt_name = theFormats().getFormatFromExtension(fname.extension());
3389 fmt_name = fmap[filter];
3390 LYXERR(Debug::FILES, "filter=" << fromqstr(filter)
3391 << ", fmt_name=" << fmt_name << ", fname=" << fname.absFileName());
3393 if (fmt_name.empty() || fname.empty())
3396 fname.ensureExtension(theFormats().extension(fmt_name));
3398 // fname is now the new Buffer location.
3399 if (fname.exists()) {
3400 docstring const file = makeDisplayPath(fname.absFileName(), 30);
3401 docstring text = bformat(_("The document %1$s already "
3402 "exists.\n\nDo you want to "
3403 "overwrite that document?"),
3405 int const ret = Alert::prompt(_("Overwrite document?"),
3406 text, 0, 2, _("&Overwrite"), _("&Rename"), _("&Cancel"));
3409 case 1: return exportBufferAs(b, from_ascii(fmt_name));
3410 case 2: return false;
3414 FuncRequest cmd(LFUN_BUFFER_EXPORT, fmt_name + " " + fname.absFileName());
3417 return dr.dispatched();
3421 bool GuiView::saveBuffer(Buffer & b)
3423 return saveBuffer(b, FileName());
3427 bool GuiView::saveBuffer(Buffer & b, FileName const & fn)
3429 if (workArea(b) && workArea(b)->inDialogMode())
3432 if (fn.empty() && b.isUnnamed())
3433 return renameBuffer(b, docstring());
3435 bool const success = (fn.empty() ? b.save() : b.saveAs(fn));
3437 theSession().lastFiles().add(b.fileName());
3438 theSession().writeFile();
3442 // Switch to this Buffer.
3445 // FIXME: we don't tell the user *WHY* the save failed !!
3446 docstring const file = makeDisplayPath(b.absFileName(), 30);
3447 docstring text = bformat(_("The document %1$s could not be saved.\n\n"
3448 "Do you want to rename the document and "
3449 "try again?"), file);
3450 int const ret = Alert::prompt(_("Rename and save?"),
3451 text, 0, 2, _("&Rename"), _("&Retry"), _("&Cancel"));
3454 if (!renameBuffer(b, docstring()))
3463 return saveBuffer(b, fn);
3467 bool GuiView::hideWorkArea(GuiWorkArea * wa)
3469 return closeWorkArea(wa, false);
3473 // We only want to close the buffer if it is not visible in other workareas
3474 // of the same view, nor in other views, and if this is not a child
3475 bool GuiView::closeWorkArea(GuiWorkArea * wa)
3477 Buffer & buf = wa->bufferView().buffer();
3479 bool last_wa = d.countWorkAreasOf(buf) == 1
3480 && !inOtherView(buf) && !buf.parent();
3482 bool close_buffer = last_wa;
3485 if (lyxrc.close_buffer_with_last_view == "yes")
3487 else if (lyxrc.close_buffer_with_last_view == "no")
3488 close_buffer = false;
3491 if (buf.isUnnamed())
3492 file = from_utf8(buf.fileName().onlyFileName());
3494 file = buf.fileName().displayName(30);
3495 docstring const text = bformat(
3496 _("Last view on document %1$s is being closed.\n"
3497 "Would you like to close or hide the document?\n"
3499 "Hidden documents can be displayed back through\n"
3500 "the menu: View->Hidden->...\n"
3502 "To remove this question, set your preference in:\n"
3503 " Tools->Preferences->Look&Feel->UserInterface\n"
3505 int ret = Alert::prompt(_("Close or hide document?"),
3506 text, 0, 2, _("&Close"), _("&Hide"), _("&Cancel"));
3509 close_buffer = (ret == 0);
3513 return closeWorkArea(wa, close_buffer);
3517 bool GuiView::closeBuffer()
3519 GuiWorkArea * wa = currentMainWorkArea();
3520 // coverity complained about this
3521 // it seems unnecessary, but perhaps is worth the check
3522 LASSERT(wa, return false);
3524 setCurrentWorkArea(wa);
3525 Buffer & buf = wa->bufferView().buffer();
3526 return closeWorkArea(wa, !buf.parent());
3530 void GuiView::writeSession() const {
3531 GuiWorkArea const * active_wa = currentMainWorkArea();
3532 for (int i = 0; i < d.splitter_->count(); ++i) {
3533 TabWorkArea * twa = d.tabWorkArea(i);
3534 for (int j = 0; j < twa->count(); ++j) {
3535 GuiWorkArea * wa = twa->workArea(j);
3536 Buffer & buf = wa->bufferView().buffer();
3537 theSession().lastOpened().add(buf.fileName(), wa == active_wa);
3543 bool GuiView::closeBufferAll()
3546 for (auto & buf : theBufferList()) {
3547 if (!saveBufferIfNeeded(*buf, false)) {
3548 // Closing has been cancelled, so abort.
3553 // Close the workareas in all other views
3554 QList<int> const ids = guiApp->viewIds();
3555 for (int i = 0; i != ids.size(); ++i) {
3556 if (id_ != ids[i] && !guiApp->view(ids[i]).closeWorkAreaAll())
3560 // Close our own workareas
3561 if (!closeWorkAreaAll())
3568 bool GuiView::closeWorkAreaAll()
3570 setCurrentWorkArea(currentMainWorkArea());
3572 // We might be in a situation that there is still a tabWorkArea, but
3573 // there are no tabs anymore. This can happen when we get here after a
3574 // TabWorkArea::lastWorkAreaRemoved() signal. Therefore we count how
3575 // many TabWorkArea's have no documents anymore.
3578 // We have to call count() each time, because it can happen that
3579 // more than one splitter will disappear in one iteration (bug 5998).
3580 while (d.splitter_->count() > empty_twa) {
3581 TabWorkArea * twa = d.tabWorkArea(empty_twa);
3583 if (twa->count() == 0)
3586 setCurrentWorkArea(twa->currentWorkArea());
3587 if (!closeTabWorkArea(twa))
3595 bool GuiView::closeWorkArea(GuiWorkArea * wa, bool close_buffer)
3600 Buffer & buf = wa->bufferView().buffer();
3602 if (GuiViewPrivate::busyBuffers.contains(&buf)) {
3603 Alert::warning(_("Close document"),
3604 _("Document could not be closed because it is being processed by LyX."));
3609 return closeBuffer(buf);
3611 if (!inMultiTabs(wa))
3612 if (!saveBufferIfNeeded(buf, true))
3620 bool GuiView::closeBuffer(Buffer & buf)
3622 bool success = true;
3623 for (Buffer * child_buf : buf.getChildren()) {
3624 if (theBufferList().isOthersChild(&buf, child_buf)) {
3625 child_buf->setParent(nullptr);
3629 // FIXME: should we look in other tabworkareas?
3630 // ANSWER: I don't think so. I've tested, and if the child is
3631 // open in some other window, it closes without a problem.
3632 GuiWorkArea * child_wa = workArea(*child_buf);
3635 // If we are in a close_event all children will be closed in some time,
3636 // so no need to do it here. This will ensure that the children end up
3637 // in the session file in the correct order. If we close the master
3638 // buffer, we can close or release the child buffers here too.
3640 success = closeWorkArea(child_wa, true);
3644 // In this case the child buffer is open but hidden.
3645 // Even in this case, children can be dirty (e.g.,
3646 // after a label change in the master, see #11405).
3647 // Therefore, check this
3648 if (closing_ && (child_buf->isClean() || child_buf->paragraphs().empty())) {
3649 // If we are in a close_event all children will be closed in some time,
3650 // so no need to do it here. This will ensure that the children end up
3651 // in the session file in the correct order. If we close the master
3652 // buffer, we can close or release the child buffers here too.
3655 // Save dirty buffers also if closing_!
3656 if (saveBufferIfNeeded(*child_buf, false)) {
3657 child_buf->removeAutosaveFile();
3658 theBufferList().release(child_buf);
3660 // Saving of dirty children has been cancelled.
3661 // Cancel the whole process.
3668 // goto bookmark to update bookmark pit.
3669 // FIXME: we should update only the bookmarks related to this buffer!
3670 // FIXME: this is done also in LFUN_WINDOW_CLOSE!
3671 LYXERR(Debug::DEBUG, "GuiView::closeBuffer()");
3672 for (unsigned int i = 1; i < theSession().bookmarks().size(); ++i)
3673 guiApp->gotoBookmark(i, false, false);
3675 if (saveBufferIfNeeded(buf, false)) {
3676 buf.removeAutosaveFile();
3677 theBufferList().release(&buf);
3681 // open all children again to avoid a crash because of dangling
3682 // pointers (bug 6603)
3688 bool GuiView::closeTabWorkArea(TabWorkArea * twa)
3690 while (twa == d.currentTabWorkArea()) {
3691 twa->setCurrentIndex(twa->count() - 1);
3693 GuiWorkArea * wa = twa->currentWorkArea();
3694 Buffer & b = wa->bufferView().buffer();
3696 // We only want to close the buffer if the same buffer is not visible
3697 // in another view, and if this is not a child and if we are closing
3698 // a view (not a tabgroup).
3699 bool const close_buffer =
3700 !inOtherView(b) && !b.parent() && closing_;
3702 if (!closeWorkArea(wa, close_buffer))
3709 bool GuiView::saveBufferIfNeeded(Buffer & buf, bool hiding)
3711 if (buf.isClean() || buf.paragraphs().empty())
3714 // Switch to this Buffer.
3720 if (buf.isUnnamed()) {
3721 file = from_utf8(buf.fileName().onlyFileName());
3724 FileName filename = buf.fileName();
3726 file = filename.displayName(30);
3727 exists = filename.exists();
3730 // Bring this window to top before asking questions.
3735 if (hiding && buf.isUnnamed()) {
3736 docstring const text = bformat(_("The document %1$s has not been "
3737 "saved yet.\n\nDo you want to save "
3738 "the document?"), file);
3739 ret = Alert::prompt(_("Save new document?"),
3740 text, 0, 1, _("&Save"), _("&Cancel"));
3744 docstring const text = exists ?
3745 bformat(_("The document %1$s has unsaved changes."
3746 "\n\nDo you want to save the document or "
3747 "discard the changes?"), file) :
3748 bformat(_("The document %1$s has not been saved yet."
3749 "\n\nDo you want to save the document or "
3750 "discard it entirely?"), file);
3751 docstring const title = exists ?
3752 _("Save changed document?") : _("Save document?");
3753 ret = Alert::prompt(title, text, 0, 2,
3754 _("&Save"), _("&Discard"), _("&Cancel"));
3759 if (!saveBuffer(buf))
3763 // If we crash after this we could have no autosave file
3764 // but I guess this is really improbable (Jug).
3765 // Sometimes improbable things happen:
3766 // - see bug http://www.lyx.org/trac/ticket/6587 (ps)
3767 // buf.removeAutosaveFile();
3769 // revert all changes
3780 bool GuiView::inMultiTabs(GuiWorkArea * wa)
3782 Buffer & buf = wa->bufferView().buffer();
3784 for (int i = 0; i != d.splitter_->count(); ++i) {
3785 GuiWorkArea * wa_ = d.tabWorkArea(i)->workArea(buf);
3786 if (wa_ && wa_ != wa)
3789 return inOtherView(buf);
3793 bool GuiView::inOtherView(Buffer & buf)
3795 QList<int> const ids = guiApp->viewIds();
3797 for (int i = 0; i != ids.size(); ++i) {
3801 if (guiApp->view(ids[i]).workArea(buf))
3808 void GuiView::gotoNextOrPreviousBuffer(NextOrPrevious np, bool const move)
3810 if (!documentBufferView())
3813 if (TabWorkArea * twa = d.currentTabWorkArea()) {
3814 Buffer * const curbuf = &documentBufferView()->buffer();
3815 int nwa = twa->count();
3816 for (int i = 0; i < nwa; ++i) {
3817 if (&workArea(i)->bufferView().buffer() == curbuf) {
3819 if (np == NEXTBUFFER)
3820 next_index = (i == nwa - 1 ? 0 : i + 1);
3822 next_index = (i == 0 ? nwa - 1 : i - 1);
3824 twa->moveTab(i, next_index);
3826 setBuffer(&workArea(next_index)->bufferView().buffer());
3834 /// make sure the document is saved
3835 static bool ensureBufferClean(Buffer * buffer)
3837 LASSERT(buffer, return false);
3838 if (buffer->isClean() && !buffer->isUnnamed())
3841 docstring const file = buffer->fileName().displayName(30);
3844 if (!buffer->isUnnamed()) {
3845 text = bformat(_("The document %1$s has unsaved "
3846 "changes.\n\nDo you want to save "
3847 "the document?"), file);
3848 title = _("Save changed document?");
3851 text = bformat(_("The document %1$s has not been "
3852 "saved yet.\n\nDo you want to save "
3853 "the document?"), file);
3854 title = _("Save new document?");
3856 int const ret = Alert::prompt(title, text, 0, 1, _("&Save"), _("&Cancel"));
3859 dispatch(FuncRequest(LFUN_BUFFER_WRITE));
3861 return buffer->isClean() && !buffer->isUnnamed();
3865 bool GuiView::reloadBuffer(Buffer & buf)
3867 currentBufferView()->cursor().reset();
3868 Buffer::ReadStatus status = buf.reload();
3869 return status == Buffer::ReadSuccess;
3873 void GuiView::checkExternallyModifiedBuffers()
3875 for (Buffer * buf : theBufferList()) {
3876 if (buf->fileName().exists() && buf->isChecksumModified()) {
3877 docstring text = bformat(_("Document \n%1$s\n has been externally modified."
3878 " Reload now? Any local changes will be lost."),
3879 from_utf8(buf->absFileName()));
3880 int const ret = Alert::prompt(_("Reload externally changed document?"),
3881 text, 0, 1, _("&Reload"), _("&Cancel"));
3889 void GuiView::dispatchVC(FuncRequest const & cmd, DispatchResult & dr)
3891 Buffer * buffer = documentBufferView()
3892 ? &(documentBufferView()->buffer()) : nullptr;
3894 switch (cmd.action()) {
3895 case LFUN_VC_REGISTER:
3896 if (!buffer || !ensureBufferClean(buffer))
3898 if (!buffer->lyxvc().inUse()) {
3899 if (buffer->lyxvc().registrer()) {
3900 reloadBuffer(*buffer);
3901 dr.clearMessageUpdate();
3906 case LFUN_VC_RENAME:
3907 case LFUN_VC_COPY: {
3908 if (!buffer || !ensureBufferClean(buffer))
3910 if (buffer->lyxvc().inUse() && !buffer->hasReadonlyFlag()) {
3911 if (buffer->lyxvc().isCheckInWithConfirmation()) {
3912 // Some changes are not yet committed.
3913 // We test here and not in getStatus(), since
3914 // this test is expensive.
3916 LyXVC::CommandResult ret =
3917 buffer->lyxvc().checkIn(log);
3919 if (ret == LyXVC::ErrorCommand ||
3920 ret == LyXVC::VCSuccess)
3921 reloadBuffer(*buffer);
3922 if (buffer->lyxvc().isCheckInWithConfirmation()) {
3923 frontend::Alert::error(
3924 _("Revision control error."),
3925 _("Document could not be checked in."));
3929 RenameKind const kind = (cmd.action() == LFUN_VC_RENAME) ?
3930 LV_VC_RENAME : LV_VC_COPY;
3931 renameBuffer(*buffer, cmd.argument(), kind);
3936 case LFUN_VC_CHECK_IN:
3937 if (!buffer || !ensureBufferClean(buffer))
3939 if (buffer->lyxvc().inUse() && !buffer->hasReadonlyFlag()) {
3941 LyXVC::CommandResult ret = buffer->lyxvc().checkIn(log);
3943 // Only skip reloading if the checkin was cancelled or
3944 // an error occurred before the real checkin VCS command
3945 // was executed, since the VCS might have changed the
3946 // file even if it could not checkin successfully.
3947 if (ret == LyXVC::ErrorCommand || ret == LyXVC::VCSuccess)
3948 reloadBuffer(*buffer);
3952 case LFUN_VC_CHECK_OUT:
3953 if (!buffer || !ensureBufferClean(buffer))
3955 if (buffer->lyxvc().inUse()) {
3956 dr.setMessage(buffer->lyxvc().checkOut());
3957 reloadBuffer(*buffer);
3961 case LFUN_VC_LOCKING_TOGGLE:
3962 if (!buffer || !ensureBufferClean(buffer) || buffer->hasReadonlyFlag())
3964 if (buffer->lyxvc().inUse()) {
3965 string res = buffer->lyxvc().lockingToggle();
3967 frontend::Alert::error(_("Revision control error."),
3968 _("Error when setting the locking property."));
3971 reloadBuffer(*buffer);
3976 case LFUN_VC_REVERT:
3979 if (buffer->lyxvc().revert()) {
3980 reloadBuffer(*buffer);
3981 dr.clearMessageUpdate();
3985 case LFUN_VC_UNDO_LAST:
3988 buffer->lyxvc().undoLast();
3989 reloadBuffer(*buffer);
3990 dr.clearMessageUpdate();
3993 case LFUN_VC_REPO_UPDATE:
3996 if (ensureBufferClean(buffer)) {
3997 dr.setMessage(buffer->lyxvc().repoUpdate());
3998 checkExternallyModifiedBuffers();
4002 case LFUN_VC_COMMAND: {
4003 string flag = cmd.getArg(0);
4004 if (buffer && contains(flag, 'R') && !ensureBufferClean(buffer))
4007 if (contains(flag, 'M')) {
4008 if (!Alert::askForText(message, _("LyX VC: Log Message")))
4011 string path = cmd.getArg(1);
4012 if (contains(path, "$$p") && buffer)
4013 path = subst(path, "$$p", buffer->filePath());
4014 LYXERR(Debug::LYXVC, "Directory: " << path);
4016 if (!pp.isReadableDirectory()) {
4017 lyxerr << _("Directory is not accessible.") << endl;
4020 support::PathChanger p(pp);
4022 string command = cmd.getArg(2);
4023 if (command.empty())
4026 command = subst(command, "$$i", buffer->absFileName());
4027 command = subst(command, "$$p", buffer->filePath());
4029 command = subst(command, "$$m", to_utf8(message));
4030 LYXERR(Debug::LYXVC, "Command: " << command);
4032 one.startscript(Systemcall::Wait, command);
4036 if (contains(flag, 'I'))
4037 buffer->markDirty();
4038 if (contains(flag, 'R'))
4039 reloadBuffer(*buffer);
4044 case LFUN_VC_COMPARE: {
4045 if (cmd.argument().empty()) {
4046 lyx::dispatch(FuncRequest(LFUN_DIALOG_SHOW, "comparehistory"));
4052 string rev1 = cmd.getArg(0);
4056 if (!buffer->lyxvc().prepareFileRevision(rev1, f1))
4059 if (isStrInt(rev1) && convert<int>(rev1) <= 0) {
4060 f2 = buffer->absFileName();
4062 string rev2 = cmd.getArg(1);
4066 if (!buffer->lyxvc().prepareFileRevision(rev2, f2))
4070 LYXERR(Debug::LYXVC, "Launching comparison for fetched revisions:\n" <<
4071 f1 << "\n" << f2 << "\n" );
4072 string par = "compare run " + quoteName(f1) + " " + quoteName(f2);
4073 lyx::dispatch(FuncRequest(LFUN_DIALOG_SHOW, par));
4083 void GuiView::openChildDocument(string const & fname)
4085 LASSERT(documentBufferView(), return);
4086 Buffer & buffer = documentBufferView()->buffer();
4087 FileName const filename = support::makeAbsPath(fname, buffer.filePath());
4088 documentBufferView()->saveBookmark(false);
4089 Buffer * child = nullptr;
4090 if (theBufferList().exists(filename)) {
4091 child = theBufferList().getBuffer(filename);
4094 message(bformat(_("Opening child document %1$s..."),
4095 makeDisplayPath(filename.absFileName())));
4096 child = loadDocument(filename, false);
4098 // Set the parent name of the child document.
4099 // This makes insertion of citations and references in the child work,
4100 // when the target is in the parent or another child document.
4102 child->setParent(&buffer);
4106 bool GuiView::goToFileRow(string const & argument)
4110 size_t i = argument.find_last_of(' ');
4111 if (i != string::npos) {
4112 file_name = os::internal_path(FileName(trim(argument.substr(0, i))).realPath());
4113 istringstream is(argument.substr(i + 1));
4118 if (i == string::npos) {
4119 LYXERR0("Wrong argument: " << argument);
4122 Buffer * buf = nullptr;
4123 string const realtmp = package().temp_dir().realPath();
4124 // We have to use os::path_prefix_is() here, instead of
4125 // simply prefixIs(), because the file name comes from
4126 // an external application and may need case adjustment.
4127 if (os::path_prefix_is(file_name, realtmp, os::CASE_ADJUSTED)) {
4128 buf = theBufferList().getBufferFromTmp(file_name, true);
4129 LYXERR(Debug::FILES, "goToFileRow: buffer lookup for " << file_name
4130 << (buf ? " success" : " failed"));
4132 // Must replace extension of the file to be .lyx
4133 // and get full path
4134 FileName const s = fileSearch(string(),
4135 support::changeExtension(file_name, ".lyx"), "lyx");
4136 // Either change buffer or load the file
4137 if (theBufferList().exists(s))
4138 buf = theBufferList().getBuffer(s);
4139 else if (s.exists()) {
4140 buf = loadDocument(s);
4145 _("File does not exist: %1$s"),
4146 makeDisplayPath(file_name)));
4152 _("No buffer for file: %1$s."),
4153 makeDisplayPath(file_name))
4158 bool success = documentBufferView()->setCursorFromRow(row);
4160 LYXERR(Debug::OUTFILE,
4161 "setCursorFromRow: invalid position for row " << row);
4162 frontend::Alert::error(_("Inverse Search Failed"),
4163 _("Invalid position requested by inverse search.\n"
4164 "You may need to update the viewed document."));
4170 void GuiView::toolBarPopup(const QPoint & /*pos*/)
4172 QMenu * menu = guiApp->menus().menu(toqstr("context-toolbars"), * this);
4173 menu->exec(QCursor::pos());
4178 Buffer::ExportStatus GuiView::GuiViewPrivate::runAndDestroy(const T& func,
4179 Buffer const * orig, Buffer * clone, string const & format)
4181 Buffer::ExportStatus const status = func(format);
4183 // the cloning operation will have produced a clone of the entire set of
4184 // documents, starting from the master. so we must delete those.
4185 Buffer * mbuf = const_cast<Buffer *>(clone->masterBuffer());
4187 busyBuffers.remove(orig);
4192 Buffer::ExportStatus GuiView::GuiViewPrivate::compileAndDestroy(
4193 Buffer const * orig, Buffer * clone, string const & format)
4195 Buffer::ExportStatus (Buffer::* mem_func)(std::string const &, bool) const =
4197 return runAndDestroy(lyx::bind(mem_func, clone, _1, true), orig, clone, format);
4201 Buffer::ExportStatus GuiView::GuiViewPrivate::exportAndDestroy(
4202 Buffer const * orig, Buffer * clone, string const & format)
4204 Buffer::ExportStatus (Buffer::* mem_func)(std::string const &, bool) const =
4206 return runAndDestroy(lyx::bind(mem_func, clone, _1, false), orig, clone, format);
4210 Buffer::ExportStatus GuiView::GuiViewPrivate::previewAndDestroy(
4211 Buffer const * orig, Buffer * clone, string const & format)
4213 Buffer::ExportStatus (Buffer::* mem_func)(std::string const &) const =
4215 return runAndDestroy(lyx::bind(mem_func, clone, _1), orig, clone, format);
4219 bool GuiView::GuiViewPrivate::asyncBufferProcessing(string const & argument,
4220 Buffer const * used_buffer,
4221 docstring const & msg,
4222 Buffer::ExportStatus (*asyncFunc)(Buffer const *, Buffer *, string const &),
4223 Buffer::ExportStatus (Buffer::*syncFunc)(string const &, bool) const,
4224 Buffer::ExportStatus (Buffer::*previewFunc)(string const &) const,
4225 bool allow_async, bool use_tmpdir)
4230 string format = argument;
4232 format = used_buffer->params().getDefaultOutputFormat();
4233 processing_format = format;
4235 progress_->clearMessages();
4238 #if EXPORT_in_THREAD
4240 GuiViewPrivate::busyBuffers.insert(used_buffer);
4241 Buffer * cloned_buffer = used_buffer->cloneWithChildren();
4242 if (!cloned_buffer) {
4243 Alert::error(_("Export Error"),
4244 _("Error cloning the Buffer."));
4247 QFuture<Buffer::ExportStatus> f = QtConcurrent::run(
4252 setPreviewFuture(f);
4253 last_export_format = used_buffer->params().bufferFormat();
4256 // We are asynchronous, so we don't know here anything about the success
4259 Buffer::ExportStatus status;
4261 status = (used_buffer->*syncFunc)(format, use_tmpdir);
4262 } else if (previewFunc) {
4263 status = (used_buffer->*previewFunc)(format);
4266 handleExportStatus(gv_, status, format);
4268 return (status == Buffer::ExportSuccess
4269 || status == Buffer::PreviewSuccess);
4273 Buffer::ExportStatus status;
4275 status = (used_buffer->*syncFunc)(format, true);
4276 } else if (previewFunc) {
4277 status = (used_buffer->*previewFunc)(format);
4280 handleExportStatus(gv_, status, format);
4282 return (status == Buffer::ExportSuccess
4283 || status == Buffer::PreviewSuccess);
4287 void GuiView::dispatchToBufferView(FuncRequest const & cmd, DispatchResult & dr)
4289 BufferView * bv = currentBufferView();
4290 LASSERT(bv, return);
4292 // Let the current BufferView dispatch its own actions.
4293 bv->dispatch(cmd, dr);
4294 if (dr.dispatched()) {
4295 if (cmd.action() == LFUN_REDO || cmd.action() == LFUN_UNDO)
4296 updateDialog("document", "");
4300 // Try with the document BufferView dispatch if any.
4301 BufferView * doc_bv = documentBufferView();
4302 if (doc_bv && doc_bv != bv) {
4303 doc_bv->dispatch(cmd, dr);
4304 if (dr.dispatched()) {
4305 if (cmd.action() == LFUN_REDO || cmd.action() == LFUN_UNDO)
4306 updateDialog("document", "");
4311 // Then let the current Cursor dispatch its own actions.
4312 bv->cursor().dispatch(cmd);
4314 // update completion. We do it here and not in
4315 // processKeySym to avoid another redraw just for a
4316 // changed inline completion
4317 if (cmd.origin() == FuncRequest::KEYBOARD) {
4318 if (cmd.action() == LFUN_SELF_INSERT
4319 || (cmd.action() == LFUN_ERT_INSERT && bv->cursor().inMathed()))
4320 updateCompletion(bv->cursor(), true, true);
4321 else if (cmd.action() == LFUN_CHAR_DELETE_BACKWARD)
4322 updateCompletion(bv->cursor(), false, true);
4324 updateCompletion(bv->cursor(), false, false);
4327 dr = bv->cursor().result();
4331 void GuiView::dispatch(FuncRequest const & cmd, DispatchResult & dr)
4333 BufferView * bv = currentBufferView();
4334 // By default we won't need any update.
4335 dr.screenUpdate(Update::None);
4336 // assume cmd will be dispatched
4337 dr.dispatched(true);
4339 Buffer * doc_buffer = documentBufferView()
4340 ? &(documentBufferView()->buffer()) : nullptr;
4342 if (cmd.origin() == FuncRequest::TOC) {
4343 GuiToc * toc = static_cast<GuiToc*>(findOrBuild("toc", false));
4344 toc->doDispatch(bv->cursor(), cmd, dr);
4348 string const argument = to_utf8(cmd.argument());
4350 switch(cmd.action()) {
4351 case LFUN_BUFFER_CHILD_OPEN:
4352 openChildDocument(to_utf8(cmd.argument()));
4355 case LFUN_BUFFER_IMPORT:
4356 importDocument(to_utf8(cmd.argument()));
4359 case LFUN_MASTER_BUFFER_EXPORT:
4361 doc_buffer = const_cast<Buffer *>(doc_buffer->masterBuffer());
4363 case LFUN_BUFFER_EXPORT: {
4366 // GCC only sees strfwd.h when building merged
4367 if (::lyx::operator==(cmd.argument(), "custom")) {
4368 // LFUN_MASTER_BUFFER_EXPORT is not enabled for this case,
4369 // so the following test should not be needed.
4370 // In principle, we could try to switch to such a view...
4371 // if (cmd.action() == LFUN_BUFFER_EXPORT)
4372 dispatch(FuncRequest(LFUN_DIALOG_SHOW, "sendto"), dr);
4376 string const dest = cmd.getArg(1);
4377 FileName target_dir;
4378 if (!dest.empty() && FileName::isAbsolute(dest))
4379 target_dir = FileName(support::onlyPath(dest));
4381 target_dir = doc_buffer->fileName().onlyPath();
4383 string const format = (argument.empty() || argument == "default") ?
4384 doc_buffer->params().getDefaultOutputFormat() : argument;
4386 if ((dest.empty() && doc_buffer->isUnnamed())
4387 || !target_dir.isDirWritable()) {
4388 exportBufferAs(*doc_buffer, from_utf8(format));
4391 /* TODO/Review: Is it a problem to also export the children?
4392 See the update_unincluded flag */
4393 d.asyncBufferProcessing(format,
4396 &GuiViewPrivate::exportAndDestroy,
4398 nullptr, cmd.allowAsync());
4399 // TODO Inform user about success
4403 case LFUN_BUFFER_EXPORT_AS: {
4404 LASSERT(doc_buffer, break);
4405 docstring f = cmd.argument();
4407 f = from_ascii(doc_buffer->params().getDefaultOutputFormat());
4408 exportBufferAs(*doc_buffer, f);
4412 case LFUN_BUFFER_UPDATE: {
4413 d.asyncBufferProcessing(argument,
4416 &GuiViewPrivate::compileAndDestroy,
4418 nullptr, cmd.allowAsync(), true);
4421 case LFUN_BUFFER_VIEW: {
4422 d.asyncBufferProcessing(argument,
4424 _("Previewing ..."),
4425 &GuiViewPrivate::previewAndDestroy,
4427 &Buffer::preview, cmd.allowAsync());
4430 case LFUN_MASTER_BUFFER_UPDATE: {
4431 d.asyncBufferProcessing(argument,
4432 (doc_buffer ? doc_buffer->masterBuffer() : nullptr),
4434 &GuiViewPrivate::compileAndDestroy,
4436 nullptr, cmd.allowAsync(), true);
4439 case LFUN_MASTER_BUFFER_VIEW: {
4440 d.asyncBufferProcessing(argument,
4441 (doc_buffer ? doc_buffer->masterBuffer() : nullptr),
4443 &GuiViewPrivate::previewAndDestroy,
4444 nullptr, &Buffer::preview, cmd.allowAsync());
4447 case LFUN_EXPORT_CANCEL: {
4451 case LFUN_BUFFER_SWITCH: {
4452 string const file_name = to_utf8(cmd.argument());
4453 if (!FileName::isAbsolute(file_name)) {
4455 dr.setMessage(_("Absolute filename expected."));
4459 Buffer * buffer = theBufferList().getBuffer(FileName(file_name));
4462 dr.setMessage(_("Document not loaded"));
4466 // Do we open or switch to the buffer in this view ?
4467 if (workArea(*buffer)
4468 || lyxrc.open_buffers_in_tabs || !documentBufferView()) {
4473 // Look for the buffer in other views
4474 QList<int> const ids = guiApp->viewIds();
4476 for (; i != ids.size(); ++i) {
4477 GuiView & gv = guiApp->view(ids[i]);
4478 if (gv.workArea(*buffer)) {
4480 gv.activateWindow();
4482 gv.setBuffer(buffer);
4487 // If necessary, open a new window as a last resort
4488 if (i == ids.size()) {
4489 lyx::dispatch(FuncRequest(LFUN_WINDOW_NEW));
4495 case LFUN_BUFFER_NEXT:
4496 gotoNextOrPreviousBuffer(NEXTBUFFER, false);
4499 case LFUN_BUFFER_MOVE_NEXT:
4500 gotoNextOrPreviousBuffer(NEXTBUFFER, true);
4503 case LFUN_BUFFER_PREVIOUS:
4504 gotoNextOrPreviousBuffer(PREVBUFFER, false);
4507 case LFUN_BUFFER_MOVE_PREVIOUS:
4508 gotoNextOrPreviousBuffer(PREVBUFFER, true);
4511 case LFUN_BUFFER_CHKTEX:
4512 LASSERT(doc_buffer, break);
4513 doc_buffer->runChktex();
4516 case LFUN_COMMAND_EXECUTE: {
4517 command_execute_ = true;
4518 minibuffer_focus_ = true;
4521 case LFUN_DROP_LAYOUTS_CHOICE:
4522 d.layout_->showPopup();
4525 case LFUN_MENU_OPEN:
4526 if (QMenu * menu = guiApp->menus().menu(toqstr(cmd.argument()), *this))
4527 menu->exec(QCursor::pos());
4530 case LFUN_FILE_INSERT: {
4531 bool const ignore_lang = cmd.getArg(1) == "ignorelang";
4532 if (insertLyXFile(from_utf8(cmd.getArg(0)), ignore_lang)) {
4533 dr.forceBufferUpdate();
4534 dr.screenUpdate(Update::Force);
4539 case LFUN_FILE_INSERT_PLAINTEXT:
4540 case LFUN_FILE_INSERT_PLAINTEXT_PARA: {
4541 string const fname = to_utf8(cmd.argument());
4542 if (!fname.empty() && !FileName::isAbsolute(fname)) {
4543 dr.setMessage(_("Absolute filename expected."));
4547 FileName filename(fname);
4548 if (fname.empty()) {
4549 FileDialog dlg(qt_("Select file to insert"));
4551 FileDialog::Result result = dlg.open(toqstr(bv->buffer().filePath()),
4552 QStringList(qt_("All Files (*)")));
4554 if (result.first == FileDialog::Later || result.second.isEmpty()) {
4555 dr.setMessage(_("Canceled."));
4559 filename.set(fromqstr(result.second));
4563 FuncRequest const new_cmd(cmd, filename.absoluteFilePath());
4564 bv->dispatch(new_cmd, dr);
4569 case LFUN_BUFFER_RELOAD: {
4570 LASSERT(doc_buffer, break);
4573 bool drop = (cmd.argument() == "dump");
4576 if (!drop && !doc_buffer->isClean()) {
4577 docstring const file =
4578 makeDisplayPath(doc_buffer->absFileName(), 20);
4579 if (doc_buffer->notifiesExternalModification()) {
4580 docstring text = _("The current version will be lost. "
4581 "Are you sure you want to load the version on disk "
4582 "of the document %1$s?");
4583 ret = Alert::prompt(_("Reload saved document?"),
4584 bformat(text, file), 1, 1,
4585 _("&Reload"), _("&Cancel"));
4587 docstring text = _("Any changes will be lost. "
4588 "Are you sure you want to revert to the saved version "
4589 "of the document %1$s?");
4590 ret = Alert::prompt(_("Revert to saved document?"),
4591 bformat(text, file), 1, 1,
4592 _("&Revert"), _("&Cancel"));
4597 doc_buffer->markClean();
4598 reloadBuffer(*doc_buffer);
4599 dr.forceBufferUpdate();
4604 case LFUN_BUFFER_RESET_EXPORT:
4605 LASSERT(doc_buffer, break);
4606 doc_buffer->requireFreshStart(true);
4607 dr.setMessage(_("Buffer export reset."));
4610 case LFUN_BUFFER_WRITE:
4611 LASSERT(doc_buffer, break);
4612 saveBuffer(*doc_buffer);
4615 case LFUN_BUFFER_WRITE_AS:
4616 LASSERT(doc_buffer, break);
4617 renameBuffer(*doc_buffer, cmd.argument());
4620 case LFUN_BUFFER_WRITE_AS_TEMPLATE:
4621 LASSERT(doc_buffer, break);
4622 renameBuffer(*doc_buffer, cmd.argument(),
4623 LV_WRITE_AS_TEMPLATE);
4626 case LFUN_BUFFER_WRITE_ALL: {
4627 Buffer * first = theBufferList().first();
4630 message(_("Saving all documents..."));
4631 // We cannot use a for loop as the buffer list cycles.
4634 if (!b->isClean()) {
4636 LYXERR(Debug::ACTION, "Saved " << b->absFileName());
4638 b = theBufferList().next(b);
4639 } while (b != first);
4640 dr.setMessage(_("All documents saved."));
4644 case LFUN_MASTER_BUFFER_FORALL: {
4648 FuncRequest funcToRun = lyxaction.lookupFunc(cmd.getLongArg(0));
4649 funcToRun.allowAsync(false);
4651 for (Buffer const * buf : doc_buffer->allRelatives()) {
4652 // Switch to other buffer view and resend cmd
4653 lyx::dispatch(FuncRequest(
4654 LFUN_BUFFER_SWITCH, buf->absFileName()));
4655 lyx::dispatch(funcToRun);
4658 lyx::dispatch(FuncRequest(
4659 LFUN_BUFFER_SWITCH, doc_buffer->absFileName()));
4663 case LFUN_BUFFER_EXTERNAL_MODIFICATION_CLEAR:
4664 LASSERT(doc_buffer, break);
4665 doc_buffer->clearExternalModification();
4668 case LFUN_BUFFER_CLOSE:
4672 case LFUN_BUFFER_CLOSE_ALL:
4676 case LFUN_DEVEL_MODE_TOGGLE:
4677 devel_mode_ = !devel_mode_;
4679 dr.setMessage(_("Developer mode is now enabled."));
4681 dr.setMessage(_("Developer mode is now disabled."));
4684 case LFUN_TOOLBAR_SET: {
4685 string const name = cmd.getArg(0);
4686 string const state = cmd.getArg(1);
4687 if (GuiToolbar * t = toolbar(name))
4692 case LFUN_TOOLBAR_TOGGLE: {
4693 string const name = cmd.getArg(0);
4694 if (GuiToolbar * t = toolbar(name))
4699 case LFUN_TOOLBAR_MOVABLE: {
4700 string const name = cmd.getArg(0);
4702 // toggle (all) toolbars movablility
4703 toolbarsMovable_ = !toolbarsMovable_;
4704 for (ToolbarInfo const & ti : guiApp->toolbars()) {
4705 GuiToolbar * tb = toolbar(ti.name);
4706 if (tb && tb->isMovable() != toolbarsMovable_)
4707 // toggle toolbar movablity if it does not fit lock
4708 // (all) toolbars positions state silent = true, since
4709 // status bar notifications are slow
4712 if (toolbarsMovable_)
4713 dr.setMessage(_("Toolbars unlocked."));
4715 dr.setMessage(_("Toolbars locked."));
4716 } else if (GuiToolbar * tb = toolbar(name))
4717 // toggle current toolbar movablity
4719 // update lock (all) toolbars positions
4720 updateLockToolbars();
4724 case LFUN_ICON_SIZE: {
4725 QSize size = d.iconSize(cmd.argument());
4727 dr.setMessage(bformat(_("Icon size set to %1$dx%2$d."),
4728 size.width(), size.height()));
4732 case LFUN_DIALOG_UPDATE: {
4733 string const name = to_utf8(cmd.argument());
4734 if (name == "prefs" || name == "document")
4735 updateDialog(name, string());
4736 else if (name == "paragraph")
4737 lyx::dispatch(FuncRequest(LFUN_PARAGRAPH_UPDATE));
4738 else if (currentBufferView()) {
4739 Inset * inset = currentBufferView()->editedInset(name);
4740 // Can only update a dialog connected to an existing inset
4742 // FIXME: get rid of this indirection; GuiView ask the inset
4743 // if he is kind enough to update itself...
4744 FuncRequest fr(LFUN_INSET_DIALOG_UPDATE, cmd.argument());
4745 //FIXME: pass DispatchResult here?
4746 inset->dispatch(currentBufferView()->cursor(), fr);
4752 case LFUN_DIALOG_TOGGLE: {
4753 FuncCode const func_code = isDialogVisible(cmd.getArg(0))
4754 ? LFUN_DIALOG_HIDE : LFUN_DIALOG_SHOW;
4755 dispatch(FuncRequest(func_code, cmd.argument()), dr);
4759 case LFUN_DIALOG_DISCONNECT_INSET:
4760 disconnectDialog(to_utf8(cmd.argument()));
4763 case LFUN_DIALOG_HIDE: {
4764 guiApp->hideDialogs(to_utf8(cmd.argument()), nullptr);
4768 case LFUN_DIALOG_SHOW: {
4769 string const name = cmd.getArg(0);
4770 string sdata = trim(to_utf8(cmd.argument()).substr(name.size()));
4772 if (name == "latexlog") {
4773 // getStatus checks that
4774 LASSERT(doc_buffer, break);
4775 Buffer::LogType type;
4776 string const logfile = doc_buffer->logName(&type);
4778 case Buffer::latexlog:
4781 case Buffer::buildlog:
4782 sdata = "literate ";
4785 sdata += Lexer::quoteString(logfile);
4786 showDialog("log", sdata);
4787 } else if (name == "vclog") {
4788 // getStatus checks that
4789 LASSERT(doc_buffer, break);
4790 string const sdata2 = "vc " +
4791 Lexer::quoteString(doc_buffer->lyxvc().getLogFile());
4792 showDialog("log", sdata2);
4793 } else if (name == "symbols") {
4794 sdata = bv->cursor().getEncoding()->name();
4796 showDialog("symbols", sdata);
4797 } else if (name == "findreplace") {
4798 sdata = to_utf8(bv->cursor().selectionAsString(false));
4799 showDialog(name, sdata);
4801 } else if (name == "prefs" && isFullScreen()) {
4802 lfunUiToggle("fullscreen");
4803 showDialog("prefs", sdata);
4805 showDialog(name, sdata);
4810 dr.setMessage(cmd.argument());
4813 case LFUN_UI_TOGGLE: {
4814 string arg = cmd.getArg(0);
4815 if (!lfunUiToggle(arg)) {
4816 docstring const msg = "ui-toggle " + _("%1$s unknown command!");
4817 dr.setMessage(bformat(msg, from_utf8(arg)));
4819 // Make sure the keyboard focus stays in the work area.
4824 case LFUN_VIEW_SPLIT: {
4825 LASSERT(doc_buffer, break);
4826 string const orientation = cmd.getArg(0);
4827 d.splitter_->setOrientation(orientation == "vertical"
4828 ? Qt::Vertical : Qt::Horizontal);
4829 TabWorkArea * twa = addTabWorkArea();
4830 GuiWorkArea * wa = twa->addWorkArea(*doc_buffer, *this);
4831 setCurrentWorkArea(wa);
4834 case LFUN_TAB_GROUP_CLOSE:
4835 if (TabWorkArea * twa = d.currentTabWorkArea()) {
4836 closeTabWorkArea(twa);
4837 d.current_work_area_ = nullptr;
4838 twa = d.currentTabWorkArea();
4839 // Switch to the next GuiWorkArea in the found TabWorkArea.
4841 // Make sure the work area is up to date.
4842 setCurrentWorkArea(twa->currentWorkArea());
4844 setCurrentWorkArea(nullptr);
4849 case LFUN_VIEW_CLOSE:
4850 if (TabWorkArea * twa = d.currentTabWorkArea()) {
4851 closeWorkArea(twa->currentWorkArea());
4852 d.current_work_area_ = nullptr;
4853 twa = d.currentTabWorkArea();
4854 // Switch to the next GuiWorkArea in the found TabWorkArea.
4856 // Make sure the work area is up to date.
4857 setCurrentWorkArea(twa->currentWorkArea());
4859 setCurrentWorkArea(nullptr);
4864 case LFUN_COMPLETION_INLINE:
4865 if (d.current_work_area_)
4866 d.current_work_area_->completer().showInline();
4869 case LFUN_COMPLETION_POPUP:
4870 if (d.current_work_area_)
4871 d.current_work_area_->completer().showPopup();
4876 if (d.current_work_area_)
4877 d.current_work_area_->completer().tab();
4880 case LFUN_COMPLETION_CANCEL:
4881 if (d.current_work_area_) {
4882 if (d.current_work_area_->completer().popupVisible())
4883 d.current_work_area_->completer().hidePopup();
4885 d.current_work_area_->completer().hideInline();
4889 case LFUN_COMPLETION_ACCEPT:
4890 if (d.current_work_area_)
4891 d.current_work_area_->completer().activate();
4894 case LFUN_BUFFER_ZOOM_IN:
4895 case LFUN_BUFFER_ZOOM_OUT:
4896 case LFUN_BUFFER_ZOOM: {
4897 zoom_ratio_ = zoomRatio(cmd, zoom_ratio_);
4899 // Actual zoom value: default zoom + fractional extra value
4900 int zoom = (int)(lyxrc.defaultZoom * zoom_ratio_);
4901 zoom = min(max(zoom, zoom_min_), zoom_max_);
4903 setCurrentZoom(zoom);
4905 dr.setMessage(bformat(_("Zoom level is now %1$d% (default value: %2$d%)"),
4906 lyxrc.currentZoom, lyxrc.defaultZoom));
4908 guiApp->fontLoader().update();
4909 // Regenerate instant previews
4910 if (lyxrc.preview != LyXRC::PREVIEW_OFF
4911 && doc_buffer && doc_buffer->loader())
4912 doc_buffer->loader()->refreshPreviews();
4913 dr.screenUpdate(Update::ForceAll | Update::FitCursor);
4917 case LFUN_VC_REGISTER:
4918 case LFUN_VC_RENAME:
4920 case LFUN_VC_CHECK_IN:
4921 case LFUN_VC_CHECK_OUT:
4922 case LFUN_VC_REPO_UPDATE:
4923 case LFUN_VC_LOCKING_TOGGLE:
4924 case LFUN_VC_REVERT:
4925 case LFUN_VC_UNDO_LAST:
4926 case LFUN_VC_COMMAND:
4927 case LFUN_VC_COMPARE:
4928 dispatchVC(cmd, dr);
4931 case LFUN_SERVER_GOTO_FILE_ROW:
4932 if(goToFileRow(to_utf8(cmd.argument())))
4933 dr.screenUpdate(Update::Force | Update::FitCursor);
4936 case LFUN_LYX_ACTIVATE:
4940 case LFUN_WINDOW_RAISE:
4946 case LFUN_FORWARD_SEARCH: {
4947 // it seems safe to assume we have a document buffer, since
4948 // getStatus wants one.
4949 LASSERT(doc_buffer, break);
4950 Buffer const * doc_master = doc_buffer->masterBuffer();
4951 FileName const path(doc_master->temppath());
4952 string const texname = doc_master->isChild(doc_buffer)
4953 ? DocFileName(changeExtension(
4954 doc_buffer->absFileName(),
4955 "tex")).mangledFileName()
4956 : doc_buffer->latexName();
4957 string const fulltexname =
4958 support::makeAbsPath(texname, doc_master->temppath()).absFileName();
4959 string const mastername =
4960 removeExtension(doc_master->latexName());
4961 FileName const dviname(addName(path.absFileName(),
4962 addExtension(mastername, "dvi")));
4963 FileName const pdfname(addName(path.absFileName(),
4964 addExtension(mastername, "pdf")));
4965 bool const have_dvi = dviname.exists();
4966 bool const have_pdf = pdfname.exists();
4967 if (!have_dvi && !have_pdf) {
4968 dr.setMessage(_("Please, preview the document first."));
4971 bool const goto_dvi = have_dvi && !lyxrc.forward_search_dvi.empty();
4972 bool const goto_pdf = have_pdf && !lyxrc.forward_search_pdf.empty();
4973 string outname = dviname.onlyFileName();
4974 string command = lyxrc.forward_search_dvi;
4975 if ((!goto_dvi || goto_pdf) &&
4976 pdfname.lastModified() > dviname.lastModified()) {
4977 outname = pdfname.onlyFileName();
4978 command = lyxrc.forward_search_pdf;
4981 DocIterator cur = bv->cursor();
4982 int row = doc_buffer->texrow().rowFromDocIterator(cur).first;
4983 LYXERR(Debug::ACTION, "Forward search: row:" << row
4985 if (row == -1 || command.empty()) {
4986 dr.setMessage(_("Couldn't proceed."));
4989 string texrow = convert<string>(row);
4991 command = subst(command, "$$n", texrow);
4992 command = subst(command, "$$f", fulltexname);
4993 command = subst(command, "$$t", texname);
4994 command = subst(command, "$$o", outname);
4996 volatile PathChanger p(path);
4998 one.startscript(Systemcall::DontWait, command);
5002 case LFUN_SPELLING_CONTINUOUSLY:
5003 lyxrc.spellcheck_continuously = !lyxrc.spellcheck_continuously;
5004 dr.screenUpdate(Update::Force);
5007 case LFUN_CITATION_OPEN: {
5009 if (theFormats().getFormat("pdf"))
5010 pdfv = theFormats().getFormat("pdf")->viewer();
5011 if (theFormats().getFormat("ps"))
5012 psv = theFormats().getFormat("ps")->viewer();
5013 frontend::showTarget(argument, pdfv, psv);
5018 // The LFUN must be for one of BufferView, Buffer or Cursor;
5020 dispatchToBufferView(cmd, dr);
5024 // Need to update bv because many LFUNs here might have destroyed it
5025 bv = currentBufferView();
5027 // Clear non-empty selections
5028 // (e.g. from a "char-forward-select" followed by "char-backward-select")
5030 Cursor & cur = bv->cursor();
5031 if ((cur.selection() && cur.selBegin() == cur.selEnd())) {
5032 cur.clearSelection();
5038 bool GuiView::lfunUiToggle(string const & ui_component)
5040 if (ui_component == "scrollbar") {
5041 // hide() is of no help
5042 if (d.current_work_area_->verticalScrollBarPolicy() ==
5043 Qt::ScrollBarAlwaysOff)
5045 d.current_work_area_->setVerticalScrollBarPolicy(
5046 Qt::ScrollBarAsNeeded);
5048 d.current_work_area_->setVerticalScrollBarPolicy(
5049 Qt::ScrollBarAlwaysOff);
5050 } else if (ui_component == "statusbar") {
5051 statusBar()->setVisible(!statusBar()->isVisible());
5052 } else if (ui_component == "menubar") {
5053 menuBar()->setVisible(!menuBar()->isVisible());
5054 } else if (ui_component == "zoomlevel") {
5055 zoom_value_->setVisible(!zoom_value_->isVisible());
5056 } else if (ui_component == "zoomslider") {
5057 zoom_slider_->setVisible(!zoom_slider_->isVisible());
5058 zoom_in_->setVisible(zoom_slider_->isVisible());
5059 zoom_out_->setVisible(zoom_slider_->isVisible());
5060 } else if (ui_component == "statistics-w") {
5061 word_count_enabled_ = !word_count_enabled_;
5064 } else if (ui_component == "statistics-cb") {
5065 char_count_enabled_ = !char_count_enabled_;
5068 } else if (ui_component == "statistics-c") {
5069 char_nb_count_enabled_ = !char_nb_count_enabled_;
5072 } else if (ui_component == "frame") {
5073 int const l = contentsMargins().left();
5075 //are the frames in default state?
5076 d.current_work_area_->setFrameStyle(QFrame::NoFrame);
5078 #if QT_VERSION > 0x050903
5079 setAttribute(Qt::WA_ContentsMarginsRespectsSafeArea, false);
5081 setContentsMargins(-2, -2, -2, -2);
5083 #if QT_VERSION > 0x050903
5084 setAttribute(Qt::WA_ContentsMarginsRespectsSafeArea, true);
5086 setContentsMargins(0, 0, 0, 0);
5089 if (ui_component == "fullscreen") {
5093 stat_counts_->setVisible(statsEnabled());
5098 void GuiView::cancelExport()
5100 Systemcall::killscript();
5101 // stop busy signal immediately so that in the subsequent
5102 // "Export canceled" prompt the status bar icons are accurate.
5103 Q_EMIT scriptKilled();
5107 void GuiView::toggleFullScreen()
5109 setWindowState(windowState() ^ Qt::WindowFullScreen);
5113 Buffer const * GuiView::updateInset(Inset const * inset)
5118 Buffer const * inset_buffer = &(inset->buffer());
5120 for (int i = 0; i != d.splitter_->count(); ++i) {
5121 GuiWorkArea * wa = d.tabWorkArea(i)->currentWorkArea();
5124 Buffer const * buffer = &(wa->bufferView().buffer());
5125 if (inset_buffer == buffer)
5126 wa->scheduleRedraw(true);
5128 return inset_buffer;
5132 void GuiView::restartCaret()
5134 /* When we move around, or type, it's nice to be able to see
5135 * the caret immediately after the keypress.
5137 if (d.current_work_area_)
5138 d.current_work_area_->startBlinkingCaret();
5140 // Take this occasion to update the other GUI elements.
5146 void GuiView::updateCompletion(Cursor & cur, bool start, bool keep)
5148 if (d.current_work_area_)
5149 d.current_work_area_->completer().updateVisibility(cur, start, keep);
5154 // This list should be kept in sync with the list of insets in
5155 // src/insets/Inset.cpp. I.e., if a dialog goes with an inset, the
5156 // dialog should have the same name as the inset.
5157 // Changes should be also recorded in LFUN_DIALOG_SHOW doxygen
5158 // docs in LyXAction.cpp.
5160 char const * const dialognames[] = {
5162 "aboutlyx", "bibitem", "bibtex", "box", "branch", "changes", "character",
5163 "citation", "compare", "comparehistory", "counter", "document", "errorlist", "ert",
5164 "external", "file", "findreplace", "findreplaceadv", "float", "graphics",
5165 "href", "include", "index", "index_print", "info", "listings", "label", "line",
5166 "log", "lyxfiles", "mathdelimiter", "mathmatrix", "mathspace", "nomenclature",
5167 "nomencl_print", "note", "paragraph", "phantom", "prefs", "ref",
5168 "sendto", "space", "spellchecker", "symbols", "tabular", "tabularcreate",
5169 "thesaurus", "texinfo", "toc", "view-source", "vspace", "wrap", "progress"};
5171 char const * const * const end_dialognames =
5172 dialognames + (sizeof(dialognames) / sizeof(char *));
5176 cmpCStr(char const * name) : name_(name) {}
5177 bool operator()(char const * other) {
5178 return strcmp(other, name_) == 0;
5185 bool isValidName(string const & name)
5187 return find_if(dialognames, end_dialognames,
5188 cmpCStr(name.c_str())) != end_dialognames;
5194 void GuiView::resetDialogs()
5196 // Make sure that no LFUN uses any GuiView.
5197 guiApp->setCurrentView(nullptr);
5201 constructToolbars();
5202 guiApp->menus().fillMenuBar(menuBar(), this, false);
5203 d.layout_->updateContents(true);
5204 // Now update controls with current buffer.
5205 guiApp->setCurrentView(this);
5211 void GuiView::flatGroupBoxes(const QObject * widget, bool flag)
5213 for (QObject * child: widget->children()) {
5214 if (child->inherits("QGroupBox")) {
5215 QGroupBox * box = (QGroupBox*) child;
5218 flatGroupBoxes(child, flag);
5224 Dialog * GuiView::find(string const & name, bool hide_it) const
5226 if (!isValidName(name))
5229 map<string, DialogPtr>::iterator it = d.dialogs_.find(name);
5231 if (it != d.dialogs_.end()) {
5233 it->second->hideView();
5234 return it->second.get();
5240 Dialog * GuiView::findOrBuild(string const & name, bool hide_it)
5242 Dialog * dialog = find(name, hide_it);
5243 if (dialog != nullptr)
5246 dialog = build(name);
5247 d.dialogs_[name].reset(dialog);
5248 #if (QT_VERSION >= QT_VERSION_CHECK(5, 0, 0))
5249 // Force a uniform style for group boxes
5250 // On Mac non-flat works better, on Linux flat is standard
5251 flatGroupBoxes(dialog->asQWidget(), guiApp->platformName() != "cocoa");
5253 if (lyxrc.allow_geometry_session)
5254 dialog->restoreSession();
5261 void GuiView::showDialog(string const & name, string const & sdata,
5264 triggerShowDialog(toqstr(name), toqstr(sdata), inset);
5268 void GuiView::doShowDialog(QString const & qname, QString const & qdata,
5274 const string name = fromqstr(qname);
5275 const string sdata = fromqstr(qdata);
5279 Dialog * dialog = findOrBuild(name, false);
5281 bool const visible = dialog->isVisibleView();
5282 dialog->showData(sdata);
5283 if (currentBufferView())
5284 currentBufferView()->editInset(name, inset);
5285 // We only set the focus to the new dialog if it was not yet
5286 // visible in order not to change the existing previous behaviour
5288 // activateWindow is needed for floating dockviews
5289 dialog->asQWidget()->raise();
5290 dialog->asQWidget()->activateWindow();
5291 if (dialog->wantInitialFocus())
5292 dialog->asQWidget()->setFocus();
5296 catch (ExceptionMessage const &) {
5304 bool GuiView::isDialogVisible(string const & name) const
5306 map<string, DialogPtr>::const_iterator it = d.dialogs_.find(name);
5307 if (it == d.dialogs_.end())
5309 return it->second.get()->isVisibleView() && !it->second.get()->isClosing();
5313 void GuiView::hideDialog(string const & name, Inset * inset)
5315 map<string, DialogPtr>::const_iterator it = d.dialogs_.find(name);
5316 if (it == d.dialogs_.end())
5320 if (!currentBufferView())
5322 if (inset != currentBufferView()->editedInset(name))
5326 Dialog * const dialog = it->second.get();
5327 if (dialog->isVisibleView())
5329 if (currentBufferView())
5330 currentBufferView()->editInset(name, nullptr);
5334 void GuiView::disconnectDialog(string const & name)
5336 if (!isValidName(name))
5338 if (currentBufferView())
5339 currentBufferView()->editInset(name, nullptr);
5343 void GuiView::hideAll() const
5345 for(auto const & dlg_p : d.dialogs_)
5346 dlg_p.second->hideView();
5350 void GuiView::updateDialogs()
5352 for(auto const & dlg_p : d.dialogs_) {
5353 Dialog * dialog = dlg_p.second.get();
5355 if (dialog->needBufferOpen() && !documentBufferView())
5356 hideDialog(fromqstr(dialog->name()), nullptr);
5357 else if (dialog->isVisibleView())
5358 dialog->checkStatus();
5366 Dialog * GuiView::build(string const & name)
5368 return createDialog(*this, name);
5372 SEMenu::SEMenu(QWidget * parent)
5374 QAction * action = addAction(qt_("Disable Shell Escape"));
5375 connect(action, SIGNAL(triggered()),
5376 parent, SLOT(disableShellEscape()));
5380 void PressableSvgWidget::mousePressEvent(QMouseEvent * event)
5382 if (event->button() == Qt::LeftButton) {
5387 } // namespace frontend
5390 #include "moc_GuiView.cpp"