3 * This file is part of LyX, the document processor.
4 * Licence details can be found in the file COPYING.
6 * \author Lars Gullik Bjønnes
8 * \author Abdelrazak Younes
11 * Full author contact details are available in file CREDITS.
18 #include "DialogFactory.h"
19 #include "DispatchResult.h"
20 #include "FileDialog.h"
21 #include "FindAndReplace.h"
22 #include "FontLoader.h"
23 #include "GuiApplication.h"
24 #include "GuiClickableLabel.h"
25 #include "GuiCompleter.h"
26 #include "GuiFontMetrics.h"
27 #include "GuiKeySymbol.h"
29 #include "GuiToolbar.h"
30 #include "GuiWorkArea.h"
31 #include "GuiProgress.h"
32 #include "LayoutBox.h"
36 #include "qt_helpers.h"
37 #include "support/filetools.h"
39 #include "frontends/alert.h"
40 #include "frontends/KeySymbol.h"
42 #include "buffer_funcs.h"
44 #include "BufferList.h"
45 #include "BufferParams.h"
46 #include "BufferView.h"
48 #include "Converter.h"
50 #include "CutAndPaste.h"
52 #include "ErrorList.h"
54 #include "FuncStatus.h"
55 #include "FuncRequest.h"
56 #include "KeySymbol.h"
58 #include "LayoutFile.h"
60 #include "LyXAction.h"
64 #include "Paragraph.h"
65 #include "SpellChecker.h"
72 #include "support/convert.h"
73 #include "support/debug.h"
74 #include "support/ExceptionMessage.h"
75 #include "support/FileName.h"
76 #include "support/gettext.h"
77 #include "support/ForkedCalls.h"
78 #include "support/lassert.h"
79 #include "support/lstrings.h"
80 #include "support/os.h"
81 #include "support/Package.h"
82 #include "support/PathChanger.h"
83 #include "support/Systemcall.h"
84 #include "support/Timeout.h"
85 #include "support/ProgressInterface.h"
88 #include <QApplication>
89 #include <QCloseEvent>
90 #include <QDragEnterEvent>
93 #include <QFutureWatcher>
104 #include <QShowEvent>
107 #include <QStackedWidget>
108 #include <QStatusBar>
109 #include <QSvgRenderer>
110 #include <QtConcurrentRun>
113 #include <QWindowStateChangeEvent>
114 #include <QGestureEvent>
115 #include <QPinchGesture>
118 // sync with GuiAlert.cpp
119 #define EXPORT_in_THREAD 1
122 #include "support/bind.h"
126 #ifdef HAVE_SYS_TIME_H
127 # include <sys/time.h>
135 using namespace lyx::support;
139 using support::addExtension;
140 using support::changeExtension;
141 using support::removeExtension;
147 class BackgroundWidget : public QWidget
150 BackgroundWidget(int width, int height)
151 : width_(width), height_(height)
153 LYXERR(Debug::GUI, "show banner: " << lyxrc.show_banner);
154 if (!lyxrc.show_banner)
156 /// The text to be written on top of the pixmap
157 QString const htext = qt_("The Document\nProcessor[[welcome banner]]");
158 QString const htextsize = qt_("1.0[[possibly scale the welcome banner text size]]");
159 /// The text to be written on top of the pixmap
160 QString const text = lyx_version ?
161 qt_("version ") + lyx_version : qt_("unknown version");
162 #if QT_VERSION >= 0x050000
163 QString imagedir = "images/";
164 FileName fname = imageLibFileSearch(imagedir, "banner", "svgz");
165 QSvgRenderer svgRenderer(toqstr(fname.absFileName()));
166 if (svgRenderer.isValid()) {
167 splash_ = QPixmap(splashSize());
168 QPainter painter(&splash_);
169 svgRenderer.render(&painter);
170 splash_.setDevicePixelRatio(pixelRatio());
172 splash_ = getPixmap("images/", "banner", "png");
175 splash_ = getPixmap("images/", "banner", "svgz,png");
178 QPainter pain(&splash_);
179 pain.setPen(QColor(0, 0, 0));
180 qreal const fsize = fontSize();
183 qreal locscale = htextsize.toFloat(&ok);
186 QPointF const position = textPosition(false);
187 QPointF const hposition = textPosition(true);
188 QRectF const hrect(hposition, splashSize());
190 "widget pixel ratio: " << pixelRatio() <<
191 " splash pixel ratio: " << splashPixelRatio() <<
192 " version text size,position: " << fsize << "@" << position.x() << "+" << position.y());
194 // The font used to display the version info
195 font.setStyleHint(QFont::SansSerif);
196 font.setWeight(QFont::Bold);
197 font.setPointSizeF(fsize);
199 pain.drawText(position, text);
200 // The font used to display the version info
201 font.setStyleHint(QFont::SansSerif);
202 font.setWeight(QFont::Normal);
203 font.setPointSizeF(hfsize);
204 // Check how long the logo gets with the current font
205 // and adapt if the font is running wider than what
207 GuiFontMetrics fm(font);
208 // Split the title into lines to measure the longest line
209 // in the current l7n.
210 QStringList titlesegs = htext.split('\n');
212 int hline = fm.maxHeight();
213 for (QString const & seg : titlesegs) {
214 if (fm.width(seg) > wline)
215 wline = fm.width(seg);
217 // The longest line in the reference font (for English)
218 // is 180. Calculate scale factor from that.
219 double const wscale = wline > 0 ? (180.0 / wline) : 1;
220 // Now do the same for the height (necessary for condensed fonts)
221 double const hscale = (34.0 / hline);
222 // take the lower of the two scale factors.
223 double const scale = min(wscale, hscale);
224 // Now rescale. Also consider l7n's offset factor.
225 font.setPointSizeF(hfsize * scale * locscale);
228 pain.drawText(hrect, Qt::AlignLeft, htext);
229 setFocusPolicy(Qt::StrongFocus);
232 void paintEvent(QPaintEvent *) override
234 int const w = width_;
235 int const h = height_;
236 int const x = (width() - w) / 2;
237 int const y = (height() - h) / 2;
239 "widget pixel ratio: " << pixelRatio() <<
240 " splash pixel ratio: " << splashPixelRatio() <<
241 " paint pixmap: " << w << "x" << h << "@" << x << "+" << y);
243 pain.drawPixmap(x, y, w, h, splash_);
246 void keyPressEvent(QKeyEvent * ev) override
249 setKeySymbol(&sym, ev);
251 guiApp->processKeySym(sym, q_key_state(ev->modifiers()));
263 /// Current ratio between physical pixels and device-independent pixels
264 double pixelRatio() const {
265 #if QT_VERSION >= 0x050000
266 return qt_scale_factor * devicePixelRatio();
272 qreal fontSize() const {
273 return toqstr(lyxrc.font_sizes[NORMAL_SIZE]).toDouble();
276 QPointF textPosition(bool const heading) const {
277 return heading ? QPointF(width_/2 - 18, height_/2 - 45)
278 : QPointF(width_/2 - 18, height_/2 + 45);
281 QSize splashSize() const {
283 static_cast<unsigned int>(width_ * pixelRatio()),
284 static_cast<unsigned int>(height_ * pixelRatio()));
287 /// Ratio between physical pixels and device-independent pixels of splash image
288 double splashPixelRatio() const {
289 #if QT_VERSION >= 0x050000
290 return splash_.devicePixelRatio();
298 /// Toolbar store providing access to individual toolbars by name.
299 typedef map<string, GuiToolbar *> ToolbarMap;
301 typedef shared_ptr<Dialog> DialogPtr;
306 class GuiView::GuiViewPrivate
309 GuiViewPrivate(GuiViewPrivate const &);
310 void operator=(GuiViewPrivate const &);
312 GuiViewPrivate(GuiView * gv)
313 : gv_(gv), current_work_area_(nullptr), current_main_work_area_(nullptr),
314 layout_(nullptr), autosave_timeout_(5000),
317 // hardcode here the platform specific icon size
318 smallIconSize = 16; // scaling problems
319 normalIconSize = 20; // ok, default if iconsize.png is missing
320 bigIconSize = 26; // better for some math icons
321 hugeIconSize = 32; // better for hires displays
324 // if it exists, use width of iconsize.png as normal size
325 QString const dir = toqstr(addPath("images", lyxrc.icon_set));
326 FileName const fn = lyx::libFileSearch(dir, "iconsize.png");
328 QImage image(toqstr(fn.absFileName()));
329 if (image.width() < int(smallIconSize))
330 normalIconSize = smallIconSize;
331 else if (image.width() > int(giantIconSize))
332 normalIconSize = giantIconSize;
334 normalIconSize = image.width();
337 splitter_ = new QSplitter;
338 bg_widget_ = new BackgroundWidget(400, 250);
339 stack_widget_ = new QStackedWidget;
340 stack_widget_->addWidget(bg_widget_);
341 stack_widget_->addWidget(splitter_);
344 // TODO cleanup, remove the singleton, handle multiple Windows?
345 progress_ = ProgressInterface::instance();
346 if (!dynamic_cast<GuiProgress*>(progress_)) {
347 progress_ = new GuiProgress; // TODO who deletes it
348 ProgressInterface::setInstance(progress_);
351 dynamic_cast<GuiProgress*>(progress_),
352 SIGNAL(updateStatusBarMessage(QString const&)),
353 gv, SLOT(updateStatusBarMessage(QString const&)));
355 dynamic_cast<GuiProgress*>(progress_),
356 SIGNAL(clearMessageText()),
357 gv, SLOT(clearMessageText()));
364 delete stack_widget_;
369 stack_widget_->setCurrentWidget(bg_widget_);
370 bg_widget_->setUpdatesEnabled(true);
371 bg_widget_->setFocus();
374 int tabWorkAreaCount()
376 return splitter_->count();
379 TabWorkArea * tabWorkArea(int i)
381 return dynamic_cast<TabWorkArea *>(splitter_->widget(i));
384 TabWorkArea * currentTabWorkArea()
386 int areas = tabWorkAreaCount();
388 // The first TabWorkArea is always the first one, if any.
389 return tabWorkArea(0);
391 for (int i = 0; i != areas; ++i) {
392 TabWorkArea * twa = tabWorkArea(i);
393 if (current_main_work_area_ == twa->currentWorkArea())
397 // None has the focus so we just take the first one.
398 return tabWorkArea(0);
401 int countWorkAreasOf(Buffer & buf)
403 int areas = tabWorkAreaCount();
405 for (int i = 0; i != areas; ++i) {
406 TabWorkArea * twa = tabWorkArea(i);
407 if (twa->workArea(buf))
413 void setPreviewFuture(QFuture<Buffer::ExportStatus> const & f)
415 if (processing_thread_watcher_.isRunning()) {
416 // we prefer to cancel this preview in order to keep a snappy
420 processing_thread_watcher_.setFuture(f);
423 QSize iconSize(docstring const & icon_size)
426 if (icon_size == "small")
427 size = smallIconSize;
428 else if (icon_size == "normal")
429 size = normalIconSize;
430 else if (icon_size == "big")
432 else if (icon_size == "huge")
434 else if (icon_size == "giant")
435 size = giantIconSize;
437 size = icon_size.empty() ? normalIconSize : convert<int>(icon_size);
439 if (size < smallIconSize)
440 size = smallIconSize;
442 return QSize(size, size);
445 QSize iconSize(QString const & icon_size)
447 return iconSize(qstring_to_ucs4(icon_size));
450 string & iconSize(QSize const & qsize)
452 LATTEST(qsize.width() == qsize.height());
454 static string icon_size;
456 unsigned int size = qsize.width();
458 if (size < smallIconSize)
459 size = smallIconSize;
461 if (size == smallIconSize)
463 else if (size == normalIconSize)
464 icon_size = "normal";
465 else if (size == bigIconSize)
467 else if (size == hugeIconSize)
469 else if (size == giantIconSize)
472 icon_size = convert<string>(size);
477 static Buffer::ExportStatus previewAndDestroy(Buffer const * orig,
478 Buffer * buffer, string const & format);
479 static Buffer::ExportStatus exportAndDestroy(Buffer const * orig,
480 Buffer * buffer, string const & format);
481 static Buffer::ExportStatus compileAndDestroy(Buffer const * orig,
482 Buffer * buffer, string const & format);
483 static docstring autosaveAndDestroy(Buffer const * orig, Buffer * buffer);
486 static Buffer::ExportStatus runAndDestroy(const T& func,
487 Buffer const * orig, Buffer * buffer, string const & format);
489 // TODO syncFunc/previewFunc: use bind
490 bool asyncBufferProcessing(string const & argument,
491 Buffer const * used_buffer,
492 docstring const & msg,
493 Buffer::ExportStatus (*asyncFunc)(Buffer const *, Buffer *, string const &),
494 Buffer::ExportStatus (Buffer::*syncFunc)(string const &, bool) const,
495 Buffer::ExportStatus (Buffer::*previewFunc)(string const &) const,
496 bool allow_async, bool use_tmpdir = false);
498 QVector<GuiWorkArea*> guiWorkAreas();
502 GuiWorkArea * current_work_area_;
503 GuiWorkArea * current_main_work_area_;
504 QSplitter * splitter_;
505 QStackedWidget * stack_widget_;
506 BackgroundWidget * bg_widget_;
508 ToolbarMap toolbars_;
509 ProgressInterface* progress_;
510 /// The main layout box.
512 * \warning Don't Delete! The layout box is actually owned by
513 * whichever toolbar contains it. All the GuiView class needs is a
514 * means of accessing it.
516 * FIXME: replace that with a proper model so that we are not limited
517 * to only one dialog.
522 map<string, DialogPtr> dialogs_;
525 QTimer statusbar_timer_;
526 /// auto-saving of buffers
527 Timeout autosave_timeout_;
530 TocModels toc_models_;
533 QFutureWatcher<docstring> autosave_watcher_;
534 QFutureWatcher<Buffer::ExportStatus> processing_thread_watcher_;
536 string last_export_format;
537 string processing_format;
539 static QSet<Buffer const *> busyBuffers;
541 unsigned int smallIconSize;
542 unsigned int normalIconSize;
543 unsigned int bigIconSize;
544 unsigned int hugeIconSize;
545 unsigned int giantIconSize;
547 /// flag against a race condition due to multiclicks, see bug #1119
551 QSet<Buffer const *> GuiView::GuiViewPrivate::busyBuffers;
554 GuiView::GuiView(int id)
555 : d(*new GuiViewPrivate(this)), id_(id), closing_(false), busy_(0),
556 command_execute_(false), minibuffer_focus_(false), toolbarsMovable_(true),
559 connect(this, SIGNAL(bufferViewChanged()),
560 this, SLOT(onBufferViewChanged()));
562 // GuiToolbars *must* be initialised before the menu bar.
563 setIconSize(QSize(d.normalIconSize, d.normalIconSize)); // at least on Mac the default is 32 otherwise, which is huge
566 // set ourself as the current view. This is needed for the menu bar
567 // filling, at least for the static special menu item on Mac. Otherwise
568 // they are greyed out.
569 guiApp->setCurrentView(this);
571 // Fill up the menu bar.
572 guiApp->menus().fillMenuBar(menuBar(), this, true);
574 setCentralWidget(d.stack_widget_);
576 // Start autosave timer
577 if (lyxrc.autosave) {
578 // The connection is closed when this is destroyed.
579 d.autosave_timeout_.timeout.connect([this](){ autoSave();});
580 d.autosave_timeout_.setTimeout(lyxrc.autosave * 1000);
581 d.autosave_timeout_.start();
583 connect(&d.statusbar_timer_, SIGNAL(timeout()),
584 this, SLOT(clearMessage()));
586 // We don't want to keep the window in memory if it is closed.
587 setAttribute(Qt::WA_DeleteOnClose, true);
589 #if !(defined(Q_OS_WIN) || defined(Q_CYGWIN_WIN)) && !defined(Q_OS_MAC)
590 // QIcon::fromTheme was introduced in Qt 4.6
591 #if (QT_VERSION >= 0x040600)
592 // assign an icon to main form. We do not do it under Qt/Win or Qt/Mac,
593 // since the icon is provided in the application bundle. We use a themed
594 // version when available and use the bundled one as fallback.
595 setWindowIcon(QIcon::fromTheme("lyx", getPixmap("images/", "lyx", "svg,png")));
597 setWindowIcon(getPixmap("images/", "lyx", "svg,png"));
603 // use tabbed dock area for multiple docks
604 // (such as "source" and "messages")
605 setDockOptions(QMainWindow::ForceTabbedDocks);
608 // use document mode tabs on docks
609 setDocumentMode(true);
613 setAcceptDrops(true);
615 // add busy indicator to statusbar
616 search_mode mode = theGuiApp()->imageSearchMode();
617 QString fn = toqstr(lyx::libFileSearch("images", "busy", "svgz", mode).absFileName());
618 PressableSvgWidget * busySVG = new PressableSvgWidget(fn);
619 statusBar()->addPermanentWidget(busySVG);
620 // make busy indicator square with 5px margins
621 busySVG->setMaximumSize(busySVG->height() - 5, busySVG->height() - 5);
624 connect(&d.processing_thread_watcher_, SIGNAL(started()),
625 busySVG, SLOT(show()));
626 connect(&d.processing_thread_watcher_, SIGNAL(finished()),
627 busySVG, SLOT(hide()));
628 connect(busySVG, SIGNAL(pressed()), this, SLOT(checkCancelBackground()));
630 QFontMetrics const fm(statusBar()->fontMetrics());
632 zoom_slider_ = new QSlider(Qt::Horizontal, statusBar());
633 // Small size slider for macOS to prevent the status bar from enlarging
634 zoom_slider_->setAttribute(Qt::WA_MacSmallSize);
635 #if (QT_VERSION >= QT_VERSION_CHECK(5, 11, 0))
636 zoom_slider_->setFixedWidth(fm.horizontalAdvance('x') * 15);
638 zoom_slider_->setFixedWidth(fm.width('x') * 15);
640 // Make the defaultZoom center
641 zoom_slider_->setRange(10, (lyxrc.defaultZoom * 2) - 10);
642 // Initialize proper zoom value
644 zoom_ratio_ = settings.value("zoom_ratio", 1.0).toDouble();
645 // Actual zoom value: default zoom + fractional offset
646 int zoom = (int)(lyxrc.defaultZoom * zoom_ratio_);
647 zoom = min(max(zoom, zoom_min_), zoom_max_);
648 zoom_slider_->setValue(zoom);
649 zoom_slider_->setToolTip(qt_("Workarea zoom level. Drag, use Ctrl-+/- or Shift-Mousewheel to adjust."));
651 // Buttons to change zoom stepwise
652 #if (QT_VERSION >= QT_VERSION_CHECK(5, 11, 0))
653 QSize s(fm.horizontalAdvance('+'), fm.height());
655 QSize s(fm.width('+'), fm.height());
657 zoom_in_ = new GuiClickableLabel(statusBar());
658 zoom_in_->setText("+");
659 zoom_in_->setFixedSize(s);
660 zoom_in_->setAlignment(Qt::AlignCenter);
661 zoom_out_ = new GuiClickableLabel(statusBar());
662 zoom_out_->setText(QString(QChar(0x2212)));
663 zoom_out_->setFixedSize(s);
664 zoom_out_->setAlignment(Qt::AlignCenter);
666 statusBar()->addPermanentWidget(zoom_out_);
667 zoom_out_->setEnabled(currentBufferView());
668 statusBar()->addPermanentWidget(zoom_slider_);
669 zoom_slider_->setEnabled(currentBufferView());
670 zoom_in_->setEnabled(currentBufferView());
671 statusBar()->addPermanentWidget(zoom_in_);
673 connect(zoom_slider_, SIGNAL(sliderMoved(int)), this, SLOT(zoomSliderMoved(int)));
674 connect(zoom_slider_, SIGNAL(valueChanged(int)), this, SLOT(zoomValueChanged(int)));
675 connect(this, SIGNAL(currentZoomChanged(int)), zoom_slider_, SLOT(setValue(int)));
676 connect(zoom_in_, SIGNAL(clicked()), this, SLOT(zoomInPressed()));
677 connect(zoom_out_, SIGNAL(clicked()), this, SLOT(zoomOutPressed()));
679 // QPalette palette = statusBar()->palette();
681 zoom_value_ = new QLabel(statusBar());
682 // zoom_value_->setPalette(palette);
683 zoom_value_->setForegroundRole(statusBar()->foregroundRole());
684 zoom_value_->setFixedHeight(fm.height());
685 #if (QT_VERSION >= QT_VERSION_CHECK(5, 11, 0))
686 zoom_value_->setMinimumWidth(fm.horizontalAdvance("444\%"));
688 zoom_value_->setMinimumWidth(fm.width("444\%"));
690 zoom_value_->setAlignment(Qt::AlignCenter);
691 zoom_value_->setText(toqstr(bformat(_("[[ZOOM]]%1$d%"), zoom)));
692 statusBar()->addPermanentWidget(zoom_value_);
693 zoom_value_->setEnabled(currentBufferView());
695 statusBar()->setContextMenuPolicy(Qt::CustomContextMenu);
696 connect(statusBar(), SIGNAL(customContextMenuRequested(QPoint)),
697 this, SLOT(showZoomContextMenu()));
699 // enable pinch to zoom
700 grabGesture(Qt::PinchGesture);
702 int const iconheight = max(int(d.normalIconSize), fm.height());
703 QSize const iconsize(iconheight, iconheight);
705 QPixmap shellescape = QIcon(getPixmap("images/", "emblem-shellescape", "svgz,png")).pixmap(iconsize);
706 shell_escape_ = new QLabel(statusBar());
707 shell_escape_->setPixmap(shellescape);
708 shell_escape_->setAlignment(Qt::AlignCenter);
709 shell_escape_->setContextMenuPolicy(Qt::CustomContextMenu);
710 shell_escape_->setToolTip(qt_("WARNING: LaTeX is allowed to execute "
711 "external commands for this document. "
712 "Right click to change."));
713 SEMenu * menu = new SEMenu(this);
714 connect(shell_escape_, SIGNAL(customContextMenuRequested(QPoint)),
715 menu, SLOT(showMenu(QPoint)));
716 shell_escape_->hide();
717 statusBar()->addPermanentWidget(shell_escape_);
719 QPixmap readonly = QIcon(getPixmap("images/", "emblem-readonly", "svgz,png")).pixmap(iconsize);
720 read_only_ = new QLabel(statusBar());
721 read_only_->setPixmap(readonly);
722 read_only_->setAlignment(Qt::AlignCenter);
724 statusBar()->addPermanentWidget(read_only_);
726 version_control_ = new QLabel(statusBar());
727 version_control_->setAlignment(Qt::AlignCenter);
728 version_control_->setFrameStyle(QFrame::StyledPanel);
729 version_control_->hide();
730 statusBar()->addPermanentWidget(version_control_);
732 statusBar()->setSizeGripEnabled(true);
735 connect(&d.autosave_watcher_, SIGNAL(finished()), this,
736 SLOT(autoSaveThreadFinished()));
738 connect(&d.processing_thread_watcher_, SIGNAL(started()), this,
739 SLOT(processingThreadStarted()));
740 connect(&d.processing_thread_watcher_, SIGNAL(finished()), this,
741 SLOT(processingThreadFinished()));
743 connect(this, SIGNAL(triggerShowDialog(QString const &, QString const &, Inset *)),
744 SLOT(doShowDialog(QString const &, QString const &, Inset *)));
746 // set custom application bars context menu, e.g. tool bar and menu bar
747 setContextMenuPolicy(Qt::CustomContextMenu);
748 connect(this, SIGNAL(customContextMenuRequested(const QPoint &)),
749 SLOT(toolBarPopup(const QPoint &)));
751 // Forbid too small unresizable window because it can happen
752 // with some window manager under X11.
753 setMinimumSize(300, 200);
755 if (lyxrc.allow_geometry_session) {
756 // Now take care of session management.
761 // no session handling, default to a sane size.
762 setGeometry(50, 50, 690, 510);
765 // clear session data if any.
766 settings.remove("views");
776 void GuiView::disableShellEscape()
778 BufferView * bv = documentBufferView();
781 theSession().shellescapeFiles().remove(bv->buffer().absFileName());
782 bv->buffer().params().shell_escape = false;
783 bv->processUpdateFlags(Update::Force);
787 void GuiView::checkCancelBackground()
789 docstring const ttl = _("Cancel Export?");
790 docstring const msg = _("Do you want to cancel the background export process?");
792 Alert::prompt(ttl, msg, 1, 1,
793 _("&Cancel export"), _("Co&ntinue"));
795 Systemcall::killscript();
799 void GuiView::zoomSliderMoved(int value)
802 dispatch(FuncRequest(LFUN_BUFFER_ZOOM, convert<string>(value)), dr);
803 scheduleRedrawWorkAreas();
804 zoom_value_->setText(toqstr(bformat(_("[[ZOOM]]%1$d%"), value)));
808 void GuiView::zoomValueChanged(int value)
810 if (value != lyxrc.currentZoom)
811 zoomSliderMoved(value);
815 void GuiView::zoomInPressed()
818 dispatch(FuncRequest(LFUN_BUFFER_ZOOM_IN), dr);
819 scheduleRedrawWorkAreas();
823 void GuiView::zoomOutPressed()
826 dispatch(FuncRequest(LFUN_BUFFER_ZOOM_OUT), dr);
827 scheduleRedrawWorkAreas();
831 void GuiView::showZoomContextMenu()
833 QMenu * menu = guiApp->menus().menu(toqstr("context-statusbar"), * this);
836 menu->exec(QCursor::pos());
840 void GuiView::scheduleRedrawWorkAreas()
842 for (int i = 0; i < d.tabWorkAreaCount(); i++) {
843 TabWorkArea* ta = d.tabWorkArea(i);
844 for (int u = 0; u < ta->count(); u++) {
845 ta->workArea(u)->scheduleRedraw(true);
851 QVector<GuiWorkArea*> GuiView::GuiViewPrivate::guiWorkAreas()
853 QVector<GuiWorkArea*> areas;
854 for (int i = 0; i < tabWorkAreaCount(); i++) {
855 TabWorkArea* ta = tabWorkArea(i);
856 for (int u = 0; u < ta->count(); u++) {
857 areas << ta->workArea(u);
863 static void handleExportStatus(GuiView * view, Buffer::ExportStatus status,
864 string const & format)
866 docstring const fmt = translateIfPossible(theFormats().prettyName(format));
869 case Buffer::ExportSuccess:
870 msg = bformat(_("Successful export to format: %1$s"), fmt);
872 case Buffer::ExportCancel:
873 msg = _("Document export cancelled.");
875 case Buffer::ExportError:
876 case Buffer::ExportNoPathToFormat:
877 case Buffer::ExportTexPathHasSpaces:
878 case Buffer::ExportConverterError:
879 msg = bformat(_("Error while exporting format: %1$s"), fmt);
881 case Buffer::PreviewSuccess:
882 msg = bformat(_("Successful preview of format: %1$s"), fmt);
884 case Buffer::PreviewError:
885 msg = bformat(_("Error while previewing format: %1$s"), fmt);
887 case Buffer::ExportKilled:
888 msg = bformat(_("Conversion cancelled while previewing format: %1$s"), fmt);
895 void GuiView::processingThreadStarted()
900 void GuiView::processingThreadFinished()
902 QFutureWatcher<Buffer::ExportStatus> const * watcher =
903 static_cast<QFutureWatcher<Buffer::ExportStatus> const *>(sender());
905 Buffer::ExportStatus const status = watcher->result();
906 handleExportStatus(this, status, d.processing_format);
909 BufferView const * const bv = currentBufferView();
910 if (bv && !bv->buffer().errorList("Export").empty()) {
915 bool const error = (status != Buffer::ExportSuccess &&
916 status != Buffer::PreviewSuccess &&
917 status != Buffer::ExportCancel);
919 ErrorList & el = bv->buffer().errorList(d.last_export_format);
920 // at this point, we do not know if buffer-view or
921 // master-buffer-view was called. If there was an export error,
922 // and the current buffer's error log is empty, we guess that
923 // it must be master-buffer-view that was called so we set
925 errors(d.last_export_format, el.empty());
930 void GuiView::autoSaveThreadFinished()
932 QFutureWatcher<docstring> const * watcher =
933 static_cast<QFutureWatcher<docstring> const *>(sender());
934 message(watcher->result());
939 void GuiView::saveLayout() const
942 settings.setValue("zoom_ratio", zoom_ratio_);
943 settings.setValue("devel_mode", devel_mode_);
944 settings.beginGroup("views");
945 settings.beginGroup(QString::number(id_));
946 if (guiApp->platformName() == "qt4x11" || guiApp->platformName() == "xcb") {
947 settings.setValue("pos", pos());
948 settings.setValue("size", size());
950 settings.setValue("geometry", saveGeometry());
951 settings.setValue("layout", saveState(0));
952 settings.setValue("icon_size", toqstr(d.iconSize(iconSize())));
953 settings.setValue("zoom_value_visible", zoom_value_->isVisible());
954 settings.setValue("zoom_slider_visible", zoom_slider_->isVisible());
958 void GuiView::saveUISettings() const
962 // Save the toolbar private states
963 for (auto const & tb_p : d.toolbars_)
964 tb_p.second->saveSession(settings);
965 // Now take care of all other dialogs
966 for (auto const & dlg_p : d.dialogs_)
967 dlg_p.second->saveSession(settings);
971 void GuiView::setCurrentZoom(const int v)
973 lyxrc.currentZoom = v;
974 zoom_value_->setText(toqstr(bformat(_("[[ZOOM]]%1$d%"), v)));
975 Q_EMIT currentZoomChanged(v);
979 bool GuiView::restoreLayout()
982 zoom_ratio_ = settings.value("zoom_ratio", 1.0).toDouble();
983 // Actual zoom value: default zoom + fractional offset
984 int zoom = (int)(lyxrc.defaultZoom * zoom_ratio_);
985 zoom = min(max(zoom, zoom_min_), zoom_max_);
986 setCurrentZoom(zoom);
987 devel_mode_ = settings.value("devel_mode", devel_mode_).toBool();
988 settings.beginGroup("views");
989 settings.beginGroup(QString::number(id_));
990 QString const icon_key = "icon_size";
991 if (!settings.contains(icon_key))
994 //code below is skipped when when ~/.config/LyX is (re)created
995 setIconSize(d.iconSize(settings.value(icon_key).toString()));
997 zoom_value_->setVisible(settings.value("zoom_value_visible", true).toBool());
999 bool const show_zoom_slider = settings.value("zoom_slider_visible", true).toBool();
1000 zoom_slider_->setVisible(show_zoom_slider);
1001 zoom_in_->setVisible(show_zoom_slider);
1002 zoom_out_->setVisible(show_zoom_slider);
1004 if (guiApp->platformName() == "qt4x11" || guiApp->platformName() == "xcb") {
1005 QPoint pos = settings.value("pos", QPoint(50, 50)).toPoint();
1006 QSize size = settings.value("size", QSize(690, 510)).toSize();
1010 // Work-around for bug #6034: the window ends up in an undetermined
1011 // state when trying to restore a maximized window when it is
1012 // already maximized.
1013 if (!(windowState() & Qt::WindowMaximized))
1014 if (!restoreGeometry(settings.value("geometry").toByteArray()))
1015 setGeometry(50, 50, 690, 510);
1018 // Make sure layout is correctly oriented.
1019 setLayoutDirection(qApp->layoutDirection());
1021 // Allow the toc and view-source dock widget to be restored if needed.
1023 if ((dialog = findOrBuild("toc", true)))
1024 // see bug 5082. At least setup title and enabled state.
1025 // Visibility will be adjusted by restoreState below.
1026 dialog->prepareView();
1027 if ((dialog = findOrBuild("view-source", true)))
1028 dialog->prepareView();
1029 if ((dialog = findOrBuild("progress", true)))
1030 dialog->prepareView();
1032 if (!restoreState(settings.value("layout").toByteArray(), 0))
1035 // init the toolbars that have not been restored
1036 for (auto const & tb_p : guiApp->toolbars()) {
1037 GuiToolbar * tb = toolbar(tb_p.name);
1038 if (tb && !tb->isRestored())
1039 initToolbar(tb_p.name);
1042 // update lock (all) toolbars positions
1043 updateLockToolbars();
1050 GuiToolbar * GuiView::toolbar(string const & name)
1052 ToolbarMap::iterator it = d.toolbars_.find(name);
1053 if (it != d.toolbars_.end())
1056 LYXERR(Debug::GUI, "Toolbar::display: no toolbar named " << name);
1061 void GuiView::updateLockToolbars()
1063 toolbarsMovable_ = false;
1064 for (ToolbarInfo const & info : guiApp->toolbars()) {
1065 GuiToolbar * tb = toolbar(info.name);
1066 if (tb && tb->isMovable())
1067 toolbarsMovable_ = true;
1069 #if QT_VERSION >= 0x050200
1070 // set unified mac toolbars only when not movable as recommended:
1071 // https://doc.qt.io/qt-5/qmainwindow.html#unifiedTitleAndToolBarOnMac-prop
1072 setUnifiedTitleAndToolBarOnMac(!toolbarsMovable_);
1077 void GuiView::constructToolbars()
1079 for (auto const & tb_p : d.toolbars_)
1081 d.toolbars_.clear();
1083 // I don't like doing this here, but the standard toolbar
1084 // destroys this object when it's destroyed itself (vfr)
1085 d.layout_ = new LayoutBox(*this);
1086 d.stack_widget_->addWidget(d.layout_);
1087 d.layout_->move(0,0);
1089 // extracts the toolbars from the backend
1090 for (ToolbarInfo const & inf : guiApp->toolbars())
1091 d.toolbars_[inf.name] = new GuiToolbar(inf, *this);
1093 DynamicMenuButton::resetIconCache();
1097 void GuiView::initToolbars()
1099 // extracts the toolbars from the backend
1100 for (ToolbarInfo const & inf : guiApp->toolbars())
1101 initToolbar(inf.name);
1105 void GuiView::initToolbar(string const & name)
1107 GuiToolbar * tb = toolbar(name);
1110 int const visibility = guiApp->toolbars().defaultVisibility(name);
1111 bool newline = !(visibility & Toolbars::SAMEROW);
1112 tb->setVisible(false);
1113 tb->setVisibility(visibility);
1115 if (visibility & Toolbars::TOP) {
1117 addToolBarBreak(Qt::TopToolBarArea);
1118 addToolBar(Qt::TopToolBarArea, tb);
1121 if (visibility & Toolbars::BOTTOM) {
1123 addToolBarBreak(Qt::BottomToolBarArea);
1124 addToolBar(Qt::BottomToolBarArea, tb);
1127 if (visibility & Toolbars::LEFT) {
1129 addToolBarBreak(Qt::LeftToolBarArea);
1130 addToolBar(Qt::LeftToolBarArea, tb);
1133 if (visibility & Toolbars::RIGHT) {
1135 addToolBarBreak(Qt::RightToolBarArea);
1136 addToolBar(Qt::RightToolBarArea, tb);
1139 if (visibility & Toolbars::ON)
1140 tb->setVisible(true);
1142 tb->setMovable(true);
1146 TocModels & GuiView::tocModels()
1148 return d.toc_models_;
1152 void GuiView::setFocus()
1154 LYXERR(Debug::DEBUG, "GuiView::setFocus()" << this);
1155 QMainWindow::setFocus();
1159 bool GuiView::hasFocus() const
1161 if (currentWorkArea())
1162 return currentWorkArea()->hasFocus();
1163 if (currentMainWorkArea())
1164 return currentMainWorkArea()->hasFocus();
1165 return d.bg_widget_->hasFocus();
1169 void GuiView::focusInEvent(QFocusEvent * e)
1171 LYXERR(Debug::DEBUG, "GuiView::focusInEvent()" << this);
1172 QMainWindow::focusInEvent(e);
1173 // Make sure guiApp points to the correct view.
1174 guiApp->setCurrentView(this);
1175 if (currentWorkArea())
1176 currentWorkArea()->setFocus();
1177 else if (currentMainWorkArea())
1178 currentMainWorkArea()->setFocus();
1180 d.bg_widget_->setFocus();
1184 void GuiView::showEvent(QShowEvent * e)
1186 LYXERR(Debug::GUI, "Passed Geometry "
1187 << size().height() << "x" << size().width()
1188 << "+" << pos().x() << "+" << pos().y());
1190 if (d.splitter_->count() == 0)
1191 // No work area, switch to the background widget.
1195 QMainWindow::showEvent(e);
1199 bool GuiView::closeScheduled()
1206 bool GuiView::prepareAllBuffersForLogout()
1208 Buffer * first = theBufferList().first();
1212 // First, iterate over all buffers and ask the users if unsaved
1213 // changes should be saved.
1214 // We cannot use a for loop as the buffer list cycles.
1217 if (!saveBufferIfNeeded(*b, false))
1219 b = theBufferList().next(b);
1220 } while (b != first);
1222 // Next, save session state
1223 // When a view/window was closed before without quitting LyX, there
1224 // are already entries in the lastOpened list.
1225 theSession().lastOpened().clear();
1232 /** Destroy only all tabbed WorkAreas. Destruction of other WorkAreas
1233 ** is responsibility of the container (e.g., dialog)
1235 void GuiView::closeEvent(QCloseEvent * close_event)
1237 LYXERR(Debug::DEBUG, "GuiView::closeEvent()");
1239 if (!GuiViewPrivate::busyBuffers.isEmpty()) {
1240 Alert::warning(_("Exit LyX"),
1241 _("LyX could not be closed because documents are being processed by LyX."));
1242 close_event->setAccepted(false);
1246 // If the user pressed the x (so we didn't call closeView
1247 // programmatically), we want to clear all existing entries.
1249 theSession().lastOpened().clear();
1254 // it can happen that this event arrives without selecting the view,
1255 // e.g. when clicking the close button on a background window.
1257 if (!closeWorkAreaAll()) {
1259 close_event->ignore();
1263 // Make sure that nothing will use this to be closed View.
1264 guiApp->unregisterView(this);
1266 if (isFullScreen()) {
1267 // Switch off fullscreen before closing.
1272 // Make sure the timer time out will not trigger a statusbar update.
1273 d.statusbar_timer_.stop();
1275 // Saving fullscreen requires additional tweaks in the toolbar code.
1276 // It wouldn't also work under linux natively.
1277 if (lyxrc.allow_geometry_session) {
1282 close_event->accept();
1286 void GuiView::dragEnterEvent(QDragEnterEvent * event)
1288 if (event->mimeData()->hasUrls())
1290 /// \todo Ask lyx-devel is this is enough:
1291 /// if (event->mimeData()->hasFormat("text/plain"))
1292 /// event->acceptProposedAction();
1296 void GuiView::dropEvent(QDropEvent * event)
1298 QList<QUrl> files = event->mimeData()->urls();
1299 if (files.isEmpty())
1302 LYXERR(Debug::GUI, "GuiView::dropEvent: got URLs!");
1303 for (int i = 0; i != files.size(); ++i) {
1304 string const file = os::internal_path(fromqstr(
1305 files.at(i).toLocalFile()));
1309 string const ext = support::getExtension(file);
1310 vector<const Format *> found_formats;
1312 // Find all formats that have the correct extension.
1313 for (const Format * fmt : theConverters().importableFormats())
1314 if (fmt->hasExtension(ext))
1315 found_formats.push_back(fmt);
1318 if (!found_formats.empty()) {
1319 if (found_formats.size() > 1) {
1320 //FIXME: show a dialog to choose the correct importable format
1321 LYXERR(Debug::FILES,
1322 "Multiple importable formats found, selecting first");
1324 string const arg = found_formats[0]->name() + " " + file;
1325 cmd = FuncRequest(LFUN_BUFFER_IMPORT, arg);
1328 //FIXME: do we have to explicitly check whether it's a lyx file?
1329 LYXERR(Debug::FILES,
1330 "No formats found, trying to open it as a lyx file");
1331 cmd = FuncRequest(LFUN_FILE_OPEN, file);
1333 // add the functions to the queue
1334 guiApp->addToFuncRequestQueue(cmd);
1337 // now process the collected functions. We perform the events
1338 // asynchronously. This prevents potential problems in case the
1339 // BufferView is closed within an event.
1340 guiApp->processFuncRequestQueueAsync();
1344 void GuiView::message(docstring const & str)
1346 if (ForkedProcess::iAmAChild())
1349 // call is moved to GUI-thread by GuiProgress
1350 d.progress_->appendMessage(toqstr(str));
1354 void GuiView::clearMessageText()
1356 message(docstring());
1360 void GuiView::updateStatusBarMessage(QString const & str)
1362 statusBar()->showMessage(str);
1363 d.statusbar_timer_.stop();
1364 d.statusbar_timer_.start(3000);
1368 void GuiView::clearMessage()
1370 // FIXME: This code was introduced in r19643 to fix bug #4123. However,
1371 // the hasFocus function mostly returns false, even if the focus is on
1372 // a workarea in this view.
1376 d.statusbar_timer_.stop();
1380 void GuiView::updateWindowTitle(GuiWorkArea * wa)
1382 if (wa != d.current_work_area_
1383 || wa->bufferView().buffer().isInternal())
1385 Buffer const & buf = wa->bufferView().buffer();
1386 // Set the windows title
1387 docstring title = buf.fileName().displayName(130) + from_ascii("[*]");
1388 if (buf.notifiesExternalModification()) {
1389 title = bformat(_("%1$s (modified externally)"), title);
1390 // If the external modification status has changed, then maybe the status of
1391 // buffer-save has changed too.
1395 title += from_ascii(" - LyX");
1397 setWindowTitle(toqstr(title));
1398 // Sets the path for the window: this is used by OSX to
1399 // allow a context click on the title bar showing a menu
1400 // with the path up to the file
1401 setWindowFilePath(toqstr(buf.absFileName()));
1402 // Tell Qt whether the current document is changed
1403 setWindowModified(!buf.isClean());
1405 if (buf.params().shell_escape)
1406 shell_escape_->show();
1408 shell_escape_->hide();
1410 if (buf.hasReadonlyFlag())
1415 if (buf.lyxvc().inUse()) {
1416 version_control_->show();
1417 version_control_->setText(toqstr(buf.lyxvc().vcstatus()));
1419 version_control_->hide();
1423 void GuiView::on_currentWorkAreaChanged(GuiWorkArea * wa)
1425 if (d.current_work_area_)
1426 // disconnect the current work area from all slots
1427 QObject::disconnect(d.current_work_area_, nullptr, this, nullptr);
1429 disconnectBufferView();
1430 connectBufferView(wa->bufferView());
1431 connectBuffer(wa->bufferView().buffer());
1432 d.current_work_area_ = wa;
1433 QObject::connect(wa, SIGNAL(titleChanged(GuiWorkArea *)),
1434 this, SLOT(updateWindowTitle(GuiWorkArea *)));
1435 QObject::connect(wa, SIGNAL(busy(bool)),
1436 this, SLOT(setBusy(bool)));
1437 // connection of a signal to a signal
1438 QObject::connect(wa, SIGNAL(bufferViewChanged()),
1439 this, SIGNAL(bufferViewChanged()));
1440 Q_EMIT updateWindowTitle(wa);
1441 Q_EMIT bufferViewChanged();
1445 void GuiView::onBufferViewChanged()
1448 // Buffer-dependent dialogs must be updated. This is done here because
1449 // some dialogs require buffer()->text.
1451 zoom_slider_->setEnabled(currentBufferView());
1452 zoom_value_->setEnabled(currentBufferView());
1453 zoom_in_->setEnabled(currentBufferView());
1454 zoom_out_->setEnabled(currentBufferView());
1458 void GuiView::on_lastWorkAreaRemoved()
1461 // We already are in a close event. Nothing more to do.
1464 if (d.splitter_->count() > 1)
1465 // We have a splitter so don't close anything.
1468 // Reset and updates the dialogs.
1469 Q_EMIT bufferViewChanged();
1474 if (lyxrc.open_buffers_in_tabs)
1475 // Nothing more to do, the window should stay open.
1478 if (guiApp->viewIds().size() > 1) {
1484 // On Mac we also close the last window because the application stay
1485 // resident in memory. On other platforms we don't close the last
1486 // window because this would quit the application.
1492 void GuiView::updateStatusBar()
1494 // let the user see the explicit message
1495 if (d.statusbar_timer_.isActive())
1502 void GuiView::showMessage()
1506 QString msg = toqstr(theGuiApp()->viewStatusMessage());
1507 if (msg.isEmpty()) {
1508 BufferView const * bv = currentBufferView();
1510 msg = toqstr(bv->cursor().currentState(devel_mode_));
1512 msg = qt_("Welcome to LyX!");
1514 statusBar()->showMessage(msg);
1518 bool GuiView::event(QEvent * e)
1522 // Useful debug code:
1523 //case QEvent::ActivationChange:
1524 //case QEvent::WindowDeactivate:
1525 //case QEvent::Paint:
1526 //case QEvent::Enter:
1527 //case QEvent::Leave:
1528 //case QEvent::HoverEnter:
1529 //case QEvent::HoverLeave:
1530 //case QEvent::HoverMove:
1531 //case QEvent::StatusTip:
1532 //case QEvent::DragEnter:
1533 //case QEvent::DragLeave:
1534 //case QEvent::Drop:
1537 case QEvent::WindowStateChange: {
1538 QWindowStateChangeEvent * ev = (QWindowStateChangeEvent*)e;
1539 bool ofstate = (ev->oldState() & Qt::WindowFullScreen);
1540 bool result = QMainWindow::event(e);
1541 bool nfstate = (windowState() & Qt::WindowFullScreen);
1542 if (!ofstate && nfstate) {
1543 LYXERR(Debug::DEBUG, "GuiView: WindowStateChange(): full-screen " << nfstate);
1544 // switch to full-screen state
1545 if (lyxrc.full_screen_statusbar)
1546 statusBar()->hide();
1547 if (lyxrc.full_screen_menubar)
1549 if (lyxrc.full_screen_toolbars) {
1550 for (auto const & tb_p : d.toolbars_)
1551 if (tb_p.second->isVisibilityOn() && tb_p.second->isVisible())
1552 tb_p.second->hide();
1554 for (int i = 0; i != d.splitter_->count(); ++i)
1555 d.tabWorkArea(i)->setFullScreen(true);
1556 #if QT_VERSION > 0x050903
1557 //Qt's 5.9.4 ba44cdae38406c safe area measures won't allow us to go negative in margins
1558 setAttribute(Qt::WA_ContentsMarginsRespectsSafeArea, false);
1560 setContentsMargins(-2, -2, -2, -2);
1562 hideDialogs("prefs", nullptr);
1563 } else if (ofstate && !nfstate) {
1564 LYXERR(Debug::DEBUG, "GuiView: WindowStateChange(): full-screen " << nfstate);
1565 // switch back from full-screen state
1566 if (lyxrc.full_screen_statusbar && !statusBar()->isVisible())
1567 statusBar()->show();
1568 if (lyxrc.full_screen_menubar && !menuBar()->isVisible())
1570 if (lyxrc.full_screen_toolbars) {
1571 for (auto const & tb_p : d.toolbars_)
1572 if (tb_p.second->isVisibilityOn() && !tb_p.second->isVisible())
1573 tb_p.second->show();
1576 for (int i = 0; i != d.splitter_->count(); ++i)
1577 d.tabWorkArea(i)->setFullScreen(false);
1578 #if QT_VERSION > 0x050903
1579 setAttribute(Qt::WA_ContentsMarginsRespectsSafeArea, true);
1581 setContentsMargins(0, 0, 0, 0);
1586 case QEvent::WindowActivate: {
1587 GuiView * old_view = guiApp->currentView();
1588 if (this == old_view) {
1590 return QMainWindow::event(e);
1592 if (old_view && old_view->currentBufferView()) {
1593 // save current selection to the selection buffer to allow
1594 // middle-button paste in this window.
1595 cap::saveSelection(old_view->currentBufferView()->cursor());
1597 guiApp->setCurrentView(this);
1598 if (d.current_work_area_)
1599 on_currentWorkAreaChanged(d.current_work_area_);
1603 return QMainWindow::event(e);
1606 case QEvent::ShortcutOverride: {
1608 if (isFullScreen() && menuBar()->isHidden()) {
1609 QKeyEvent * ke = static_cast<QKeyEvent*>(e);
1610 // FIXME: we should also try to detect special LyX shortcut such as
1611 // Alt-P and Alt-M. Right now there is a hack in
1612 // GuiWorkArea::processKeySym() that hides again the menubar for
1614 if (ke->modifiers() & Qt::AltModifier && ke->key() != Qt::Key_Alt) {
1616 return QMainWindow::event(e);
1619 return QMainWindow::event(e);
1622 case QEvent::ApplicationPaletteChange: {
1623 // runtime switch from/to dark mode
1625 return QMainWindow::event(e);
1628 case QEvent::Gesture: {
1629 QGestureEvent *ge = static_cast<QGestureEvent*>(e);
1630 QGesture *gp = ge->gesture(Qt::PinchGesture);
1632 QPinchGesture *pinch = static_cast<QPinchGesture *>(gp);
1633 QPinchGesture::ChangeFlags changeFlags = pinch->changeFlags();
1634 qreal totalScaleFactor = pinch->totalScaleFactor();
1635 LYXERR(Debug::GUI, "totalScaleFactor: " << totalScaleFactor);
1636 if (pinch->state() == Qt::GestureStarted) {
1637 initialZoom_ = lyxrc.currentZoom;
1638 LYXERR(Debug::GUI, "initialZoom_: " << initialZoom_);
1640 if (changeFlags & QPinchGesture::ScaleFactorChanged) {
1641 qreal factor = initialZoom_ * totalScaleFactor;
1642 LYXERR(Debug::GUI, "scaleFactor: " << factor);
1643 zoomValueChanged(factor);
1646 return QMainWindow::event(e);
1650 return QMainWindow::event(e);
1654 void GuiView::resetWindowTitle()
1656 setWindowTitle(qt_("LyX"));
1659 bool GuiView::focusNextPrevChild(bool /*next*/)
1666 bool GuiView::busy() const
1672 void GuiView::setBusy(bool busy)
1674 bool const busy_before = busy_ > 0;
1675 busy ? ++busy_ : --busy_;
1676 if ((busy_ > 0) == busy_before)
1677 // busy state didn't change
1681 QApplication::setOverrideCursor(Qt::WaitCursor);
1684 QApplication::restoreOverrideCursor();
1689 void GuiView::resetCommandExecute()
1691 command_execute_ = false;
1696 double GuiView::pixelRatio() const
1698 #if QT_VERSION >= 0x050000
1699 return qt_scale_factor * devicePixelRatio();
1706 GuiWorkArea * GuiView::workArea(int index)
1708 if (TabWorkArea * twa = d.currentTabWorkArea())
1709 if (index < twa->count())
1710 return twa->workArea(index);
1715 GuiWorkArea * GuiView::workArea(Buffer & buffer)
1717 if (currentWorkArea()
1718 && ¤tWorkArea()->bufferView().buffer() == &buffer)
1719 return currentWorkArea();
1720 if (TabWorkArea * twa = d.currentTabWorkArea())
1721 return twa->workArea(buffer);
1726 GuiWorkArea * GuiView::addWorkArea(Buffer & buffer)
1728 // Automatically create a TabWorkArea if there are none yet.
1729 TabWorkArea * tab_widget = d.splitter_->count()
1730 ? d.currentTabWorkArea() : addTabWorkArea();
1731 return tab_widget->addWorkArea(buffer, *this);
1735 TabWorkArea * GuiView::addTabWorkArea()
1737 TabWorkArea * twa = new TabWorkArea;
1738 QObject::connect(twa, SIGNAL(currentWorkAreaChanged(GuiWorkArea *)),
1739 this, SLOT(on_currentWorkAreaChanged(GuiWorkArea *)));
1740 QObject::connect(twa, SIGNAL(lastWorkAreaRemoved()),
1741 this, SLOT(on_lastWorkAreaRemoved()));
1743 d.splitter_->addWidget(twa);
1744 d.stack_widget_->setCurrentWidget(d.splitter_);
1749 GuiWorkArea const * GuiView::currentWorkArea() const
1751 return d.current_work_area_;
1755 GuiWorkArea * GuiView::currentWorkArea()
1757 return d.current_work_area_;
1761 GuiWorkArea const * GuiView::currentMainWorkArea() const
1763 if (!d.currentTabWorkArea())
1765 return d.currentTabWorkArea()->currentWorkArea();
1769 GuiWorkArea * GuiView::currentMainWorkArea()
1771 if (!d.currentTabWorkArea())
1773 return d.currentTabWorkArea()->currentWorkArea();
1777 void GuiView::setCurrentWorkArea(GuiWorkArea * wa)
1779 LYXERR(Debug::DEBUG, "Setting current wa: " << wa << endl);
1781 d.current_work_area_ = nullptr;
1783 Q_EMIT bufferViewChanged();
1787 // FIXME: I've no clue why this is here and why it accesses
1788 // theGuiApp()->currentView, which might be 0 (bug 6464).
1789 // See also 27525 (vfr).
1790 if (theGuiApp()->currentView() == this
1791 && theGuiApp()->currentView()->currentWorkArea() == wa)
1794 if (currentBufferView())
1795 cap::saveSelection(currentBufferView()->cursor());
1797 theGuiApp()->setCurrentView(this);
1798 d.current_work_area_ = wa;
1800 // We need to reset this now, because it will need to be
1801 // right if the tabWorkArea gets reset in the for loop. We
1802 // will change it back if we aren't in that case.
1803 GuiWorkArea * const old_cmwa = d.current_main_work_area_;
1804 d.current_main_work_area_ = wa;
1806 for (int i = 0; i != d.splitter_->count(); ++i) {
1807 if (d.tabWorkArea(i)->setCurrentWorkArea(wa)) {
1808 LYXERR(Debug::DEBUG, "Current wa: " << currentWorkArea()
1809 << ", Current main wa: " << currentMainWorkArea());
1814 d.current_main_work_area_ = old_cmwa;
1816 LYXERR(Debug::DEBUG, "This is not a tabbed wa");
1817 on_currentWorkAreaChanged(wa);
1818 BufferView & bv = wa->bufferView();
1819 bv.cursor().fixIfBroken();
1821 wa->setUpdatesEnabled(true);
1822 LYXERR(Debug::DEBUG, "Current wa: " << currentWorkArea() << ", Current main wa: " << currentMainWorkArea());
1826 void GuiView::removeWorkArea(GuiWorkArea * wa)
1828 LASSERT(wa, return);
1829 if (wa == d.current_work_area_) {
1831 disconnectBufferView();
1832 d.current_work_area_ = nullptr;
1833 d.current_main_work_area_ = nullptr;
1836 bool found_twa = false;
1837 for (int i = 0; i != d.splitter_->count(); ++i) {
1838 TabWorkArea * twa = d.tabWorkArea(i);
1839 if (twa->removeWorkArea(wa)) {
1840 // Found in this tab group, and deleted the GuiWorkArea.
1842 if (twa->count() != 0) {
1843 if (d.current_work_area_ == nullptr)
1844 // This means that we are closing the current GuiWorkArea, so
1845 // switch to the next GuiWorkArea in the found TabWorkArea.
1846 setCurrentWorkArea(twa->currentWorkArea());
1848 // No more WorkAreas in this tab group, so delete it.
1855 // It is not a tabbed work area (i.e., the search work area), so it
1856 // should be deleted by other means.
1857 LASSERT(found_twa, return);
1859 if (d.current_work_area_ == nullptr) {
1860 if (d.splitter_->count() != 0) {
1861 TabWorkArea * twa = d.currentTabWorkArea();
1862 setCurrentWorkArea(twa->currentWorkArea());
1864 // No more work areas, switch to the background widget.
1865 setCurrentWorkArea(nullptr);
1871 bool GuiView::hasVisibleWorkArea(GuiWorkArea * wa) const
1873 for (int i = 0; i < d.splitter_->count(); ++i)
1874 if (d.tabWorkArea(i)->currentWorkArea() == wa)
1877 FindAndReplace * fr = static_cast<FindAndReplace*>(find("findreplaceadv", false));
1878 return fr->isVisible() && fr->hasWorkArea(wa);
1882 LayoutBox * GuiView::getLayoutDialog() const
1888 void GuiView::updateLayoutList()
1891 d.layout_->updateContents(false);
1895 void GuiView::updateToolbars()
1897 if (d.current_work_area_) {
1899 if (d.current_work_area_->bufferView().cursor().inMathed()
1900 && !d.current_work_area_->bufferView().cursor().inRegexped())
1901 context |= Toolbars::MATH;
1902 if (lyx::getStatus(FuncRequest(LFUN_LAYOUT_TABULAR)).enabled())
1903 context |= Toolbars::TABLE;
1904 if (currentBufferView()->buffer().areChangesPresent()
1905 || (lyx::getStatus(FuncRequest(LFUN_CHANGES_TRACK)).enabled()
1906 && lyx::getStatus(FuncRequest(LFUN_CHANGES_TRACK)).onOff(true))
1907 || (lyx::getStatus(FuncRequest(LFUN_CHANGES_OUTPUT)).enabled()
1908 && lyx::getStatus(FuncRequest(LFUN_CHANGES_OUTPUT)).onOff(true)))
1909 context |= Toolbars::REVIEW;
1910 if (lyx::getStatus(FuncRequest(LFUN_IN_MATHMACROTEMPLATE)).enabled())
1911 context |= Toolbars::MATHMACROTEMPLATE;
1912 if (lyx::getStatus(FuncRequest(LFUN_IN_IPA)).enabled())
1913 context |= Toolbars::IPA;
1914 if (command_execute_)
1915 context |= Toolbars::MINIBUFFER;
1916 if (minibuffer_focus_) {
1917 context |= Toolbars::MINIBUFFER_FOCUS;
1918 minibuffer_focus_ = false;
1921 for (auto const & tb_p : d.toolbars_)
1922 tb_p.second->update(context);
1924 for (auto const & tb_p : d.toolbars_)
1925 tb_p.second->update();
1929 void GuiView::refillToolbars()
1931 DynamicMenuButton::resetIconCache();
1932 for (auto const & tb_p : d.toolbars_)
1933 tb_p.second->refill();
1937 void GuiView::setBuffer(Buffer * newBuffer, bool switch_to)
1939 LYXERR(Debug::DEBUG, "Setting buffer: " << newBuffer << endl);
1940 LASSERT(newBuffer, return);
1942 GuiWorkArea * wa = workArea(*newBuffer);
1943 if (wa == nullptr) {
1945 newBuffer->masterBuffer()->updateBuffer();
1947 wa = addWorkArea(*newBuffer);
1948 // scroll to the position when the BufferView was last closed
1949 if (lyxrc.use_lastfilepos) {
1950 LastFilePosSection::FilePos filepos =
1951 theSession().lastFilePos().load(newBuffer->fileName());
1952 wa->bufferView().moveToPosition(filepos.pit, filepos.pos, 0, 0);
1955 //Disconnect the old buffer...there's no new one.
1958 connectBuffer(*newBuffer);
1959 connectBufferView(wa->bufferView());
1961 setCurrentWorkArea(wa);
1965 void GuiView::connectBuffer(Buffer & buf)
1967 buf.setGuiDelegate(this);
1971 void GuiView::disconnectBuffer()
1973 if (d.current_work_area_)
1974 d.current_work_area_->bufferView().buffer().setGuiDelegate(nullptr);
1978 void GuiView::connectBufferView(BufferView & bv)
1980 bv.setGuiDelegate(this);
1984 void GuiView::disconnectBufferView()
1986 if (d.current_work_area_)
1987 d.current_work_area_->bufferView().setGuiDelegate(nullptr);
1991 void GuiView::errors(string const & error_type, bool from_master)
1993 BufferView const * const bv = currentBufferView();
1997 ErrorList const & el = from_master ?
1998 bv->buffer().masterBuffer()->errorList(error_type) :
1999 bv->buffer().errorList(error_type);
2004 string err = error_type;
2006 err = "from_master|" + error_type;
2007 showDialog("errorlist", err);
2011 void GuiView::updateTocItem(string const & type, DocIterator const & dit)
2013 d.toc_models_.updateItem(toqstr(type), dit);
2017 void GuiView::structureChanged()
2019 // This is called from the Buffer, which has no way to ensure that cursors
2020 // in BufferView remain valid.
2021 if (documentBufferView())
2022 documentBufferView()->cursor().sanitize();
2023 // FIXME: This is slightly expensive, though less than the tocBackend update
2024 // (#9880). This also resets the view in the Toc Widget (#6675).
2025 d.toc_models_.reset(documentBufferView());
2026 // Navigator needs more than a simple update in this case. It needs to be
2028 updateDialog("toc", "");
2032 void GuiView::updateDialog(string const & name, string const & sdata)
2034 if (!isDialogVisible(name))
2037 map<string, DialogPtr>::const_iterator it = d.dialogs_.find(name);
2038 if (it == d.dialogs_.end())
2041 Dialog * const dialog = it->second.get();
2042 if (dialog->isVisibleView())
2043 dialog->initialiseParams(sdata);
2047 BufferView * GuiView::documentBufferView()
2049 return currentMainWorkArea()
2050 ? ¤tMainWorkArea()->bufferView()
2055 BufferView const * GuiView::documentBufferView() const
2057 return currentMainWorkArea()
2058 ? ¤tMainWorkArea()->bufferView()
2063 BufferView * GuiView::currentBufferView()
2065 return d.current_work_area_ ? &d.current_work_area_->bufferView() : nullptr;
2069 BufferView const * GuiView::currentBufferView() const
2071 return d.current_work_area_ ? &d.current_work_area_->bufferView() : nullptr;
2075 docstring GuiView::GuiViewPrivate::autosaveAndDestroy(
2076 Buffer const * orig, Buffer * clone)
2078 bool const success = clone->autoSave();
2080 busyBuffers.remove(orig);
2082 ? _("Automatic save done.")
2083 : _("Automatic save failed!");
2087 void GuiView::autoSave()
2089 LYXERR(Debug::INFO, "Running autoSave()");
2091 Buffer * buffer = documentBufferView()
2092 ? &documentBufferView()->buffer() : nullptr;
2094 resetAutosaveTimers();
2098 GuiViewPrivate::busyBuffers.insert(buffer);
2099 QFuture<docstring> f = QtConcurrent::run(GuiViewPrivate::autosaveAndDestroy,
2100 buffer, buffer->cloneBufferOnly());
2101 d.autosave_watcher_.setFuture(f);
2102 resetAutosaveTimers();
2106 void GuiView::resetAutosaveTimers()
2109 d.autosave_timeout_.restart();
2115 double zoomRatio(FuncRequest const & cmd, double const zr)
2117 if (cmd.argument().empty()) {
2118 if (cmd.action() == LFUN_BUFFER_ZOOM)
2120 else if (cmd.action() == LFUN_BUFFER_ZOOM_IN)
2122 else // cmd.action() == LFUN_BUFFER_ZOOM_OUT
2125 if (cmd.action() == LFUN_BUFFER_ZOOM)
2126 return convert<int>(cmd.argument()) / double(lyxrc.defaultZoom);
2127 else if (cmd.action() == LFUN_BUFFER_ZOOM_IN)
2128 return zr + convert<int>(cmd.argument()) / 100.0;
2129 else // cmd.action() == LFUN_BUFFER_ZOOM_OUT
2130 return zr - convert<int>(cmd.argument()) / 100.0;
2137 bool GuiView::getStatus(FuncRequest const & cmd, FuncStatus & flag)
2140 Buffer * buf = currentBufferView()
2141 ? ¤tBufferView()->buffer() : nullptr;
2142 Buffer * doc_buffer = documentBufferView()
2143 ? &(documentBufferView()->buffer()) : nullptr;
2146 /* In LyX/Mac, when a dialog is open, the menus of the
2147 application can still be accessed without giving focus to
2148 the main window. In this case, we want to disable the menu
2149 entries that are buffer-related.
2150 This code must not be used on Linux and Windows, since it
2151 would disable buffer-related entries when hovering over the
2152 menu (see bug #9574).
2154 if (cmd.origin() == FuncRequest::MENU && !hasFocus()) {
2160 // Check whether we need a buffer
2161 if (!lyxaction.funcHasFlag(cmd.action(), LyXAction::NoBuffer) && !buf) {
2162 // no, exit directly
2163 flag.message(from_utf8(N_("Command not allowed with"
2164 "out any document open")));
2165 flag.setEnabled(false);
2169 if (cmd.origin() == FuncRequest::TOC) {
2170 GuiToc * toc = static_cast<GuiToc*>(findOrBuild("toc", false));
2171 if (!toc || !toc->getStatus(documentBufferView()->cursor(), cmd, flag))
2172 flag.setEnabled(false);
2176 switch(cmd.action()) {
2177 case LFUN_BUFFER_IMPORT:
2180 case LFUN_MASTER_BUFFER_EXPORT:
2182 && (doc_buffer->parent() != nullptr
2183 || doc_buffer->hasChildren())
2184 && !d.processing_thread_watcher_.isRunning()
2185 // this launches a dialog, which would be in the wrong Buffer
2186 && !(::lyx::operator==(cmd.argument(), "custom"));
2189 case LFUN_MASTER_BUFFER_UPDATE:
2190 case LFUN_MASTER_BUFFER_VIEW:
2192 && (doc_buffer->parent() != nullptr
2193 || doc_buffer->hasChildren())
2194 && !d.processing_thread_watcher_.isRunning();
2197 case LFUN_BUFFER_UPDATE:
2198 case LFUN_BUFFER_VIEW: {
2199 if (!doc_buffer || d.processing_thread_watcher_.isRunning()) {
2203 string format = to_utf8(cmd.argument());
2204 if (cmd.argument().empty())
2205 format = doc_buffer->params().getDefaultOutputFormat();
2206 enable = doc_buffer->params().isExportable(format, true);
2210 case LFUN_BUFFER_RELOAD:
2211 enable = doc_buffer && !doc_buffer->isUnnamed()
2212 && doc_buffer->fileName().exists()
2213 && (!doc_buffer->isClean() || doc_buffer->notifiesExternalModification());
2216 case LFUN_BUFFER_RESET_EXPORT:
2217 enable = doc_buffer != nullptr;
2220 case LFUN_BUFFER_CHILD_OPEN:
2221 enable = doc_buffer != nullptr;
2224 case LFUN_MASTER_BUFFER_FORALL: {
2225 if (doc_buffer == nullptr) {
2226 flag.message(from_utf8(N_("Command not allowed without a buffer open")));
2230 FuncRequest const cmdToPass = lyxaction.lookupFunc(cmd.getLongArg(0));
2231 if (cmdToPass.action() == LFUN_UNKNOWN_ACTION) {
2232 flag.message(from_utf8(N_("Invalid argument of master-buffer-forall")));
2237 for (Buffer * buf : doc_buffer->allRelatives()) {
2238 GuiWorkArea * wa = workArea(*buf);
2241 if (wa->bufferView().getStatus(cmdToPass, flag)) {
2242 enable = flag.enabled();
2249 case LFUN_BUFFER_WRITE:
2250 enable = doc_buffer && (doc_buffer->isUnnamed()
2251 || (!doc_buffer->isClean()
2252 || cmd.argument() == "force"));
2255 //FIXME: This LFUN should be moved to GuiApplication.
2256 case LFUN_BUFFER_WRITE_ALL: {
2257 // We enable the command only if there are some modified buffers
2258 Buffer * first = theBufferList().first();
2263 // We cannot use a for loop as the buffer list is a cycle.
2265 if (!b->isClean()) {
2269 b = theBufferList().next(b);
2270 } while (b != first);
2274 case LFUN_BUFFER_EXTERNAL_MODIFICATION_CLEAR:
2275 enable = doc_buffer && doc_buffer->notifiesExternalModification();
2278 case LFUN_BUFFER_EXPORT: {
2279 if (!doc_buffer || d.processing_thread_watcher_.isRunning()) {
2283 return doc_buffer->getStatus(cmd, flag);
2286 case LFUN_BUFFER_EXPORT_AS:
2287 if (!doc_buffer || d.processing_thread_watcher_.isRunning()) {
2292 case LFUN_BUFFER_WRITE_AS:
2293 case LFUN_BUFFER_WRITE_AS_TEMPLATE:
2294 enable = doc_buffer != nullptr;
2297 case LFUN_EXPORT_CANCEL:
2298 enable = d.processing_thread_watcher_.isRunning();
2301 case LFUN_BUFFER_CLOSE:
2302 case LFUN_VIEW_CLOSE:
2303 enable = doc_buffer != nullptr;
2306 case LFUN_BUFFER_CLOSE_ALL:
2307 enable = theBufferList().last() != theBufferList().first();
2310 case LFUN_BUFFER_CHKTEX: {
2311 // hide if we have no checktex command
2312 if (lyxrc.chktex_command.empty()) {
2313 flag.setUnknown(true);
2317 if (!doc_buffer || !doc_buffer->params().isLatex()
2318 || d.processing_thread_watcher_.isRunning()) {
2319 // grey out, don't hide
2327 case LFUN_VIEW_SPLIT:
2328 if (cmd.getArg(0) == "vertical")
2329 enable = doc_buffer && (d.splitter_->count() == 1 ||
2330 d.splitter_->orientation() == Qt::Vertical);
2332 enable = doc_buffer && (d.splitter_->count() == 1 ||
2333 d.splitter_->orientation() == Qt::Horizontal);
2336 case LFUN_TAB_GROUP_CLOSE:
2337 enable = d.tabWorkAreaCount() > 1;
2340 case LFUN_DEVEL_MODE_TOGGLE:
2341 flag.setOnOff(devel_mode_);
2344 case LFUN_TOOLBAR_SET: {
2345 string const name = cmd.getArg(0);
2346 string const state = cmd.getArg(1);
2347 if (name.empty() || state.empty()) {
2349 docstring const msg =
2350 _("Function toolbar-set requires two arguments!");
2354 if (state != "on" && state != "off" && state != "auto") {
2356 docstring const msg =
2357 bformat(_("Invalid argument \"%1$s\" to function toolbar-set!"),
2362 if (GuiToolbar * t = toolbar(name)) {
2363 bool const autovis = t->visibility() & Toolbars::AUTO;
2365 flag.setOnOff(t->isVisible() && !autovis);
2366 else if (state == "off")
2367 flag.setOnOff(!t->isVisible() && !autovis);
2368 else if (state == "auto")
2369 flag.setOnOff(autovis);
2372 docstring const msg =
2373 bformat(_("Unknown toolbar \"%1$s\""), from_utf8(name));
2379 case LFUN_TOOLBAR_TOGGLE: {
2380 string const name = cmd.getArg(0);
2381 if (GuiToolbar * t = toolbar(name))
2382 flag.setOnOff(t->isVisible());
2385 docstring const msg =
2386 bformat(_("Unknown toolbar \"%1$s\""), from_utf8(name));
2392 case LFUN_TOOLBAR_MOVABLE: {
2393 string const name = cmd.getArg(0);
2394 // use negation since locked == !movable
2396 // toolbar name * locks all toolbars
2397 flag.setOnOff(!toolbarsMovable_);
2398 else if (GuiToolbar * t = toolbar(name))
2399 flag.setOnOff(!(t->isMovable()));
2402 docstring const msg =
2403 bformat(_("Unknown toolbar \"%1$s\""), from_utf8(name));
2409 case LFUN_ICON_SIZE:
2410 flag.setOnOff(d.iconSize(cmd.argument()) == iconSize());
2413 case LFUN_DROP_LAYOUTS_CHOICE:
2414 enable = buf != nullptr;
2417 case LFUN_UI_TOGGLE:
2418 if (cmd.argument() == "zoomlevel") {
2419 flag.setOnOff(zoom_value_ ? zoom_value_->isVisible() : false);
2420 } else if (cmd.argument() == "zoomslider") {
2421 flag.setOnOff(zoom_slider_ ? zoom_slider_->isVisible() : false);
2423 flag.setOnOff(isFullScreen());
2426 case LFUN_DIALOG_DISCONNECT_INSET:
2429 case LFUN_DIALOG_HIDE:
2430 // FIXME: should we check if the dialog is shown?
2433 case LFUN_DIALOG_TOGGLE:
2434 flag.setOnOff(isDialogVisible(cmd.getArg(0)));
2437 case LFUN_DIALOG_SHOW: {
2438 string const name = cmd.getArg(0);
2440 enable = name == "aboutlyx"
2441 || name == "file" //FIXME: should be removed.
2442 || name == "lyxfiles"
2444 || name == "texinfo"
2445 || name == "progress"
2446 || name == "compare";
2447 else if (name == "character" || name == "symbols"
2448 || name == "mathdelimiter" || name == "mathmatrix") {
2449 if (!buf || buf->isReadonly())
2452 Cursor const & cur = currentBufferView()->cursor();
2453 enable = !(cur.inTexted() && cur.paragraph().isPassThru());
2456 else if (name == "latexlog")
2457 enable = FileName(doc_buffer->logName()).isReadableFile();
2458 else if (name == "spellchecker")
2459 enable = theSpellChecker()
2460 && !doc_buffer->text().empty();
2461 else if (name == "vclog")
2462 enable = doc_buffer->lyxvc().inUse();
2466 case LFUN_DIALOG_UPDATE: {
2467 string const name = cmd.getArg(0);
2469 enable = name == "prefs";
2473 case LFUN_COMMAND_EXECUTE:
2475 case LFUN_MENU_OPEN:
2476 // Nothing to check.
2479 case LFUN_COMPLETION_INLINE:
2480 if (!d.current_work_area_
2481 || !d.current_work_area_->completer().inlinePossible(
2482 currentBufferView()->cursor()))
2486 case LFUN_COMPLETION_POPUP:
2487 if (!d.current_work_area_
2488 || !d.current_work_area_->completer().popupPossible(
2489 currentBufferView()->cursor()))
2494 if (!d.current_work_area_
2495 || !d.current_work_area_->completer().inlinePossible(
2496 currentBufferView()->cursor()))
2500 case LFUN_COMPLETION_ACCEPT:
2501 if (!d.current_work_area_
2502 || (!d.current_work_area_->completer().popupVisible()
2503 && !d.current_work_area_->completer().inlineVisible()
2504 && !d.current_work_area_->completer().completionAvailable()))
2508 case LFUN_COMPLETION_CANCEL:
2509 if (!d.current_work_area_
2510 || (!d.current_work_area_->completer().popupVisible()
2511 && !d.current_work_area_->completer().inlineVisible()))
2515 case LFUN_BUFFER_ZOOM_OUT:
2516 case LFUN_BUFFER_ZOOM_IN:
2517 case LFUN_BUFFER_ZOOM: {
2518 int const zoom = (int)(lyxrc.defaultZoom * zoomRatio(cmd, zoom_ratio_));
2519 if (zoom < zoom_min_) {
2520 docstring const msg =
2521 bformat(_("Zoom level cannot be less than %1$d%."), zoom_min_);
2524 } else if (zoom > zoom_max_) {
2525 docstring const msg =
2526 bformat(_("Zoom level cannot be more than %1$d%."), zoom_max_);
2530 enable = doc_buffer;
2535 case LFUN_BUFFER_MOVE_NEXT:
2536 case LFUN_BUFFER_MOVE_PREVIOUS:
2537 // we do not cycle when moving
2538 case LFUN_BUFFER_NEXT:
2539 case LFUN_BUFFER_PREVIOUS:
2540 // because we cycle, it doesn't matter whether on first or last
2541 enable = (d.currentTabWorkArea()->count() > 1);
2543 case LFUN_BUFFER_SWITCH:
2544 // toggle on the current buffer, but do not toggle off
2545 // the other ones (is that a good idea?)
2547 && to_utf8(cmd.argument()) == doc_buffer->absFileName())
2548 flag.setOnOff(true);
2551 case LFUN_VC_REGISTER:
2552 enable = doc_buffer && !doc_buffer->lyxvc().inUse();
2554 case LFUN_VC_RENAME:
2555 enable = doc_buffer && doc_buffer->lyxvc().renameEnabled();
2558 enable = doc_buffer && doc_buffer->lyxvc().copyEnabled();
2560 case LFUN_VC_CHECK_IN:
2561 enable = doc_buffer && doc_buffer->lyxvc().checkInEnabled();
2563 case LFUN_VC_CHECK_OUT:
2564 enable = doc_buffer && doc_buffer->lyxvc().checkOutEnabled();
2566 case LFUN_VC_LOCKING_TOGGLE:
2567 enable = doc_buffer && !doc_buffer->hasReadonlyFlag()
2568 && doc_buffer->lyxvc().lockingToggleEnabled();
2569 flag.setOnOff(enable && doc_buffer->lyxvc().locking());
2571 case LFUN_VC_REVERT:
2572 enable = doc_buffer && doc_buffer->lyxvc().inUse()
2573 && !doc_buffer->hasReadonlyFlag();
2575 case LFUN_VC_UNDO_LAST:
2576 enable = doc_buffer && doc_buffer->lyxvc().undoLastEnabled();
2578 case LFUN_VC_REPO_UPDATE:
2579 enable = doc_buffer && doc_buffer->lyxvc().repoUpdateEnabled();
2581 case LFUN_VC_COMMAND: {
2582 if (cmd.argument().empty())
2584 if (!doc_buffer && contains(cmd.getArg(0), 'D'))
2588 case LFUN_VC_COMPARE:
2589 enable = doc_buffer && doc_buffer->lyxvc().prepareFileRevisionEnabled();
2592 case LFUN_SERVER_GOTO_FILE_ROW:
2593 case LFUN_LYX_ACTIVATE:
2594 case LFUN_WINDOW_RAISE:
2596 case LFUN_FORWARD_SEARCH:
2597 enable = !(lyxrc.forward_search_dvi.empty() && lyxrc.forward_search_pdf.empty());
2600 case LFUN_FILE_INSERT_PLAINTEXT:
2601 case LFUN_FILE_INSERT_PLAINTEXT_PARA:
2602 enable = documentBufferView() && documentBufferView()->cursor().inTexted();
2605 case LFUN_SPELLING_CONTINUOUSLY:
2606 flag.setOnOff(lyxrc.spellcheck_continuously);
2609 case LFUN_CITATION_OPEN:
2618 flag.setEnabled(false);
2624 static FileName selectTemplateFile()
2626 FileDialog dlg(qt_("Select template file"));
2627 dlg.setButton1(qt_("D&ocuments"), toqstr(lyxrc.document_path));
2628 dlg.setButton2(qt_("&Templates"), toqstr(lyxrc.template_path));
2630 FileDialog::Result result = dlg.open(toqstr(lyxrc.template_path),
2631 QStringList(qt_("LyX Documents (*.lyx)")));
2633 if (result.first == FileDialog::Later)
2635 if (result.second.isEmpty())
2637 return FileName(fromqstr(result.second));
2641 Buffer * GuiView::loadDocument(FileName const & filename, bool tolastfiles)
2645 Buffer * newBuffer = nullptr;
2647 newBuffer = checkAndLoadLyXFile(filename);
2648 } catch (ExceptionMessage const &) {
2655 message(_("Document not loaded."));
2659 setBuffer(newBuffer);
2660 newBuffer->errors("Parse");
2663 theSession().lastFiles().add(filename);
2664 theSession().writeFile();
2671 void GuiView::openDocument(string const & fname)
2673 string initpath = lyxrc.document_path;
2675 if (documentBufferView()) {
2676 string const trypath = documentBufferView()->buffer().filePath();
2677 // If directory is writeable, use this as default.
2678 if (FileName(trypath).isDirWritable())
2684 if (fname.empty()) {
2685 FileDialog dlg(qt_("Select document to open"));
2686 dlg.setButton1(qt_("D&ocuments"), toqstr(lyxrc.document_path));
2687 dlg.setButton2(qt_("&Examples"), toqstr(lyxrc.example_path));
2689 QStringList const filter({
2690 qt_("LyX Documents (*.lyx)"),
2691 qt_("LyX Document Backups (*.lyx~)"),
2692 qt_("All Files (*.*)")
2694 FileDialog::Result result =
2695 dlg.open(toqstr(initpath), filter);
2697 if (result.first == FileDialog::Later)
2700 filename = fromqstr(result.second);
2702 // check selected filename
2703 if (filename.empty()) {
2704 message(_("Canceled."));
2710 // get absolute path of file and add ".lyx" to the filename if
2712 FileName const fullname =
2713 fileSearch(string(), filename, "lyx", support::may_not_exist);
2714 if (!fullname.empty())
2715 filename = fullname.absFileName();
2717 if (!fullname.onlyPath().isDirectory()) {
2718 Alert::warning(_("Invalid filename"),
2719 bformat(_("The directory in the given path\n%1$s\ndoes not exist."),
2720 from_utf8(fullname.absFileName())));
2724 // if the file doesn't exist and isn't already open (bug 6645),
2725 // let the user create one
2726 if (!fullname.exists() && !theBufferList().exists(fullname) &&
2727 !LyXVC::file_not_found_hook(fullname)) {
2728 // the user specifically chose this name. Believe him.
2729 Buffer * const b = newFile(filename, string(), true);
2735 docstring const disp_fn = makeDisplayPath(filename);
2736 message(bformat(_("Opening document %1$s..."), disp_fn));
2739 Buffer * buf = loadDocument(fullname);
2741 str2 = bformat(_("Document %1$s opened."), disp_fn);
2742 if (buf->lyxvc().inUse())
2743 str2 += " " + from_utf8(buf->lyxvc().versionString()) +
2744 " " + _("Version control detected.");
2746 str2 = bformat(_("Could not open document %1$s"), disp_fn);
2751 // FIXME: clean that
2752 static bool import(GuiView * lv, FileName const & filename,
2753 string const & format, ErrorList & errorList)
2755 FileName const lyxfile(support::changeExtension(filename.absFileName(), ".lyx"));
2757 string loader_format;
2758 vector<string> loaders = theConverters().loaders();
2759 if (find(loaders.begin(), loaders.end(), format) == loaders.end()) {
2760 for (string const & loader : loaders) {
2761 if (!theConverters().isReachable(format, loader))
2764 string const tofile =
2765 support::changeExtension(filename.absFileName(),
2766 theFormats().extension(loader));
2767 if (theConverters().convert(nullptr, filename, FileName(tofile),
2768 filename, format, loader, errorList) != Converters::SUCCESS)
2770 loader_format = loader;
2773 if (loader_format.empty()) {
2774 frontend::Alert::error(_("Couldn't import file"),
2775 bformat(_("No information for importing the format %1$s."),
2776 translateIfPossible(theFormats().prettyName(format))));
2780 loader_format = format;
2782 if (loader_format == "lyx") {
2783 Buffer * buf = lv->loadDocument(lyxfile);
2787 Buffer * const b = newFile(lyxfile.absFileName(), string(), true);
2791 bool as_paragraphs = loader_format == "textparagraph";
2792 string filename2 = (loader_format == format) ? filename.absFileName()
2793 : support::changeExtension(filename.absFileName(),
2794 theFormats().extension(loader_format));
2795 lv->currentBufferView()->insertPlaintextFile(FileName(filename2),
2797 guiApp->setCurrentView(lv);
2798 lyx::dispatch(FuncRequest(LFUN_MARK_OFF));
2805 void GuiView::importDocument(string const & argument)
2808 string filename = split(argument, format, ' ');
2810 LYXERR(Debug::INFO, format << " file: " << filename);
2812 // need user interaction
2813 if (filename.empty()) {
2814 string initpath = lyxrc.document_path;
2815 if (documentBufferView()) {
2816 string const trypath = documentBufferView()->buffer().filePath();
2817 // If directory is writeable, use this as default.
2818 if (FileName(trypath).isDirWritable())
2822 docstring const text = bformat(_("Select %1$s file to import"),
2823 translateIfPossible(theFormats().prettyName(format)));
2825 FileDialog dlg(toqstr(text));
2826 dlg.setButton1(qt_("D&ocuments"), toqstr(lyxrc.document_path));
2827 dlg.setButton2(qt_("&Examples"), toqstr(lyxrc.example_path));
2829 docstring filter = translateIfPossible(theFormats().prettyName(format));
2832 filter += from_utf8(theFormats().extensions(format));
2835 FileDialog::Result result =
2836 dlg.open(toqstr(initpath), fileFilters(toqstr(filter)));
2838 if (result.first == FileDialog::Later)
2841 filename = fromqstr(result.second);
2843 // check selected filename
2844 if (filename.empty())
2845 message(_("Canceled."));
2848 if (filename.empty())
2851 // get absolute path of file
2852 FileName const fullname(support::makeAbsPath(filename));
2854 // Can happen if the user entered a path into the dialog
2856 if (fullname.onlyFileName().empty()) {
2857 docstring msg = bformat(_("The file name '%1$s' is invalid!\n"
2858 "Aborting import."),
2859 from_utf8(fullname.absFileName()));
2860 frontend::Alert::error(_("File name error"), msg);
2861 message(_("Canceled."));
2866 FileName const lyxfile(support::changeExtension(fullname.absFileName(), ".lyx"));
2868 // Check if the document already is open
2869 Buffer * buf = theBufferList().getBuffer(lyxfile);
2872 if (!closeBuffer()) {
2873 message(_("Canceled."));
2878 docstring const displaypath = makeDisplayPath(lyxfile.absFileName(), 30);
2880 // if the file exists already, and we didn't do
2881 // -i lyx thefile.lyx, warn
2882 if (lyxfile.exists() && fullname != lyxfile) {
2884 docstring text = bformat(_("The document %1$s already exists.\n\n"
2885 "Do you want to overwrite that document?"), displaypath);
2886 int const ret = Alert::prompt(_("Overwrite document?"),
2887 text, 0, 1, _("&Overwrite"), _("&Cancel"));
2890 message(_("Canceled."));
2895 message(bformat(_("Importing %1$s..."), displaypath));
2896 ErrorList errorList;
2897 if (import(this, fullname, format, errorList))
2898 message(_("imported."));
2900 message(_("file not imported!"));
2902 // FIXME (Abdel 12/08/06): Is there a need to display the error list here?
2906 void GuiView::newDocument(string const & filename, string templatefile,
2909 FileName initpath(lyxrc.document_path);
2910 if (documentBufferView()) {
2911 FileName const trypath(documentBufferView()->buffer().filePath());
2912 // If directory is writeable, use this as default.
2913 if (trypath.isDirWritable())
2917 if (from_template) {
2918 if (templatefile.empty())
2919 templatefile = selectTemplateFile().absFileName();
2920 if (templatefile.empty())
2925 if (filename.empty())
2926 b = newUnnamedFile(initpath, to_utf8(_("newfile")), templatefile);
2928 b = newFile(filename, templatefile, true);
2933 // If no new document could be created, it is unsure
2934 // whether there is a valid BufferView.
2935 if (currentBufferView())
2936 // Ensure the cursor is correctly positioned on screen.
2937 currentBufferView()->showCursor();
2941 bool GuiView::insertLyXFile(docstring const & fname, bool ignorelang)
2943 BufferView * bv = documentBufferView();
2948 FileName filename(to_utf8(fname));
2949 if (filename.empty()) {
2950 // Launch a file browser
2952 string initpath = lyxrc.document_path;
2953 string const trypath = bv->buffer().filePath();
2954 // If directory is writeable, use this as default.
2955 if (FileName(trypath).isDirWritable())
2959 FileDialog dlg(qt_("Select LyX document to insert"));
2960 dlg.setButton1(qt_("D&ocuments"), toqstr(lyxrc.document_path));
2961 dlg.setButton2(qt_("&Examples"), toqstr(lyxrc.example_path));
2963 FileDialog::Result result = dlg.open(toqstr(initpath),
2964 QStringList(qt_("LyX Documents (*.lyx)")));
2966 if (result.first == FileDialog::Later)
2970 filename.set(fromqstr(result.second));
2972 // check selected filename
2973 if (filename.empty()) {
2974 // emit message signal.
2975 message(_("Canceled."));
2980 bv->insertLyXFile(filename, ignorelang);
2981 bv->buffer().errors("Parse");
2986 string const GuiView::getTemplatesPath(Buffer & b)
2988 // We start off with the user's templates path
2989 string result = addPath(package().user_support().absFileName(), "templates");
2990 // Check for the document language
2991 string const langcode = b.params().language->code();
2992 string const shortcode = langcode.substr(0, 2);
2993 if (!langcode.empty() && shortcode != "en") {
2994 string subpath = addPath(result, shortcode);
2995 string subpath_long = addPath(result, langcode);
2996 // If we have a subdirectory for the language already,
2998 FileName sp = FileName(subpath);
2999 if (sp.isDirectory())
3001 else if (FileName(subpath_long).isDirectory())
3002 result = subpath_long;
3004 // Ask whether we should create such a subdirectory
3005 docstring const text =
3006 bformat(_("It is suggested to save the template in a subdirectory\n"
3007 "appropriate to the document language (%1$s).\n"
3008 "This subdirectory does not exists yet.\n"
3009 "Do you want to create it?"),
3010 _(b.params().language->display()));
3011 if (Alert::prompt(_("Create Language Directory?"),
3012 text, 0, 1, _("&Yes, Create"), _("&No, Save Template in Parent Directory")) == 0) {
3013 // If the user agreed, we try to create it and report if this failed.
3014 if (!sp.createDirectory(0777))
3015 Alert::error(_("Subdirectory creation failed!"),
3016 _("Could not create subdirectory.\n"
3017 "The template will be saved in the parent directory."));
3023 // Do we have a layout category?
3024 string const cat = b.params().baseClass() ?
3025 b.params().baseClass()->category()
3028 string subpath = addPath(result, cat);
3029 // If we have a subdirectory for the category already,
3031 FileName sp = FileName(subpath);
3032 if (sp.isDirectory())
3035 // Ask whether we should create such a subdirectory
3036 docstring const text =
3037 bformat(_("It is suggested to save the template in a subdirectory\n"
3038 "appropriate to the layout category (%1$s).\n"
3039 "This subdirectory does not exists yet.\n"
3040 "Do you want to create it?"),
3042 if (Alert::prompt(_("Create Category Directory?"),
3043 text, 0, 1, _("&Yes, Create"), _("&No, Save Template in Parent Directory")) == 0) {
3044 // If the user agreed, we try to create it and report if this failed.
3045 if (!sp.createDirectory(0777))
3046 Alert::error(_("Subdirectory creation failed!"),
3047 _("Could not create subdirectory.\n"
3048 "The template will be saved in the parent directory."));
3058 bool GuiView::renameBuffer(Buffer & b, docstring const & newname, RenameKind kind)
3060 FileName fname = b.fileName();
3061 FileName const oldname = fname;
3062 bool const as_template = (kind == LV_WRITE_AS_TEMPLATE);
3064 if (!newname.empty()) {
3067 fname = support::makeAbsPath(to_utf8(newname), getTemplatesPath(b));
3069 fname = support::makeAbsPath(to_utf8(newname),
3070 oldname.onlyPath().absFileName());
3072 // Switch to this Buffer.
3075 // No argument? Ask user through dialog.
3077 QString const title = as_template ? qt_("Choose a filename to save template as")
3078 : qt_("Choose a filename to save document as");
3079 FileDialog dlg(title);
3080 dlg.setButton1(qt_("D&ocuments"), toqstr(lyxrc.document_path));
3081 dlg.setButton2(qt_("&Templates"), toqstr(lyxrc.template_path));
3083 fname.ensureExtension(".lyx");
3085 string const path = as_template ?
3087 : fname.onlyPath().absFileName();
3088 FileDialog::Result result =
3089 dlg.save(toqstr(path),
3090 QStringList(qt_("LyX Documents (*.lyx)")),
3091 toqstr(fname.onlyFileName()));
3093 if (result.first == FileDialog::Later)
3096 fname.set(fromqstr(result.second));
3101 fname.ensureExtension(".lyx");
3104 // fname is now the new Buffer location.
3106 // if there is already a Buffer open with this name, we do not want
3107 // to have another one. (the second test makes sure we're not just
3108 // trying to overwrite ourselves, which is fine.)
3109 if (theBufferList().exists(fname) && fname != oldname
3110 && theBufferList().getBuffer(fname) != &b) {
3111 docstring const text =
3112 bformat(_("The file\n%1$s\nis already open in your current session.\n"
3113 "Please close it before attempting to overwrite it.\n"
3114 "Do you want to choose a new filename?"),
3115 from_utf8(fname.absFileName()));
3116 int const ret = Alert::prompt(_("Chosen File Already Open"),
3117 text, 0, 1, _("&Rename"), _("&Cancel"));
3119 case 0: return renameBuffer(b, docstring(), kind);
3120 case 1: return false;
3125 bool const existsLocal = fname.exists();
3126 bool const existsInVC = LyXVC::fileInVC(fname);
3127 if (existsLocal || existsInVC) {
3128 docstring const file = makeDisplayPath(fname.absFileName(), 30);
3129 if (kind != LV_WRITE_AS && existsInVC) {
3130 // renaming to a name that is already in VC
3132 docstring text = bformat(_("The document %1$s "
3133 "is already registered.\n\n"
3134 "Do you want to choose a new name?"),
3136 docstring const title = (kind == LV_VC_RENAME) ?
3137 _("Rename document?") : _("Copy document?");
3138 docstring const button = (kind == LV_VC_RENAME) ?
3139 _("&Rename") : _("&Copy");
3140 int const ret = Alert::prompt(title, text, 0, 1,
3141 button, _("&Cancel"));
3143 case 0: return renameBuffer(b, docstring(), kind);
3144 case 1: return false;
3149 docstring text = bformat(_("The document %1$s "
3150 "already exists.\n\n"
3151 "Do you want to overwrite that document?"),
3153 int const ret = Alert::prompt(_("Overwrite document?"),
3154 text, 0, 2, _("&Overwrite"),
3155 _("&Rename"), _("&Cancel"));
3158 case 1: return renameBuffer(b, docstring(), kind);
3159 case 2: return false;
3165 case LV_VC_RENAME: {
3166 string msg = b.lyxvc().rename(fname);
3169 message(from_utf8(msg));
3173 string msg = b.lyxvc().copy(fname);
3176 message(from_utf8(msg));
3180 case LV_WRITE_AS_TEMPLATE:
3183 // LyXVC created the file already in case of LV_VC_RENAME or
3184 // LV_VC_COPY, but call saveBuffer() nevertheless to get
3185 // relative paths of included stuff right if we moved e.g. from
3186 // /a/b.lyx to /a/c/b.lyx.
3188 bool const saved = saveBuffer(b, fname);
3195 bool GuiView::exportBufferAs(Buffer & b, docstring const & iformat)
3197 FileName fname = b.fileName();
3199 FileDialog dlg(qt_("Choose a filename to export the document as"));
3200 dlg.setButton1(qt_("D&ocuments"), toqstr(lyxrc.document_path));
3203 QString const anyformat = qt_("Guess from extension (*.*)");
3206 vector<Format const *> export_formats;
3207 for (Format const & f : theFormats())
3208 if (f.documentFormat())
3209 export_formats.push_back(&f);
3210 sort(export_formats.begin(), export_formats.end(), Format::formatSorter);
3211 map<QString, string> fmap;
3214 for (Format const * f : export_formats) {
3215 docstring const loc_prettyname = translateIfPossible(f->prettyname());
3216 QString const loc_filter = toqstr(bformat(from_ascii("%1$s (*.%2$s)"),
3218 from_ascii(f->extension())));
3219 types << loc_filter;
3220 fmap[loc_filter] = f->name();
3221 if (from_ascii(f->name()) == iformat) {
3222 filter = loc_filter;
3223 ext = f->extension();
3226 string ofname = fname.onlyFileName();
3228 ofname = support::changeExtension(ofname, ext);
3229 FileDialog::Result result =
3230 dlg.save(toqstr(fname.onlyPath().absFileName()),
3234 if (result.first != FileDialog::Chosen)
3238 fname.set(fromqstr(result.second));
3239 if (filter == anyformat)
3240 fmt_name = theFormats().getFormatFromExtension(fname.extension());
3242 fmt_name = fmap[filter];
3243 LYXERR(Debug::FILES, "filter=" << fromqstr(filter)
3244 << ", fmt_name=" << fmt_name << ", fname=" << fname.absFileName());
3246 if (fmt_name.empty() || fname.empty())
3249 fname.ensureExtension(theFormats().extension(fmt_name));
3251 // fname is now the new Buffer location.
3252 if (fname.exists()) {
3253 docstring const file = makeDisplayPath(fname.absFileName(), 30);
3254 docstring text = bformat(_("The document %1$s already "
3255 "exists.\n\nDo you want to "
3256 "overwrite that document?"),
3258 int const ret = Alert::prompt(_("Overwrite document?"),
3259 text, 0, 2, _("&Overwrite"), _("&Rename"), _("&Cancel"));
3262 case 1: return exportBufferAs(b, from_ascii(fmt_name));
3263 case 2: return false;
3267 FuncRequest cmd(LFUN_BUFFER_EXPORT, fmt_name + " " + fname.absFileName());
3270 return dr.dispatched();
3274 bool GuiView::saveBuffer(Buffer & b)
3276 return saveBuffer(b, FileName());
3280 bool GuiView::saveBuffer(Buffer & b, FileName const & fn)
3282 if (workArea(b) && workArea(b)->inDialogMode())
3285 if (fn.empty() && b.isUnnamed())
3286 return renameBuffer(b, docstring());
3288 bool const success = (fn.empty() ? b.save() : b.saveAs(fn));
3290 theSession().lastFiles().add(b.fileName());
3291 theSession().writeFile();
3295 // Switch to this Buffer.
3298 // FIXME: we don't tell the user *WHY* the save failed !!
3299 docstring const file = makeDisplayPath(b.absFileName(), 30);
3300 docstring text = bformat(_("The document %1$s could not be saved.\n\n"
3301 "Do you want to rename the document and "
3302 "try again?"), file);
3303 int const ret = Alert::prompt(_("Rename and save?"),
3304 text, 0, 2, _("&Rename"), _("&Retry"), _("&Cancel"));
3307 if (!renameBuffer(b, docstring()))
3316 return saveBuffer(b, fn);
3320 bool GuiView::hideWorkArea(GuiWorkArea * wa)
3322 return closeWorkArea(wa, false);
3326 // We only want to close the buffer if it is not visible in other workareas
3327 // of the same view, nor in other views, and if this is not a child
3328 bool GuiView::closeWorkArea(GuiWorkArea * wa)
3330 Buffer & buf = wa->bufferView().buffer();
3332 bool last_wa = d.countWorkAreasOf(buf) == 1
3333 && !inOtherView(buf) && !buf.parent();
3335 bool close_buffer = last_wa;
3338 if (lyxrc.close_buffer_with_last_view == "yes")
3340 else if (lyxrc.close_buffer_with_last_view == "no")
3341 close_buffer = false;
3344 if (buf.isUnnamed())
3345 file = from_utf8(buf.fileName().onlyFileName());
3347 file = buf.fileName().displayName(30);
3348 docstring const text = bformat(
3349 _("Last view on document %1$s is being closed.\n"
3350 "Would you like to close or hide the document?\n"
3352 "Hidden documents can be displayed back through\n"
3353 "the menu: View->Hidden->...\n"
3355 "To remove this question, set your preference in:\n"
3356 " Tools->Preferences->Look&Feel->UserInterface\n"
3358 int ret = Alert::prompt(_("Close or hide document?"),
3359 text, 0, 2, _("&Close"), _("&Hide"), _("&Cancel"));
3362 close_buffer = (ret == 0);
3366 return closeWorkArea(wa, close_buffer);
3370 bool GuiView::closeBuffer()
3372 GuiWorkArea * wa = currentMainWorkArea();
3373 // coverity complained about this
3374 // it seems unnecessary, but perhaps is worth the check
3375 LASSERT(wa, return false);
3377 setCurrentWorkArea(wa);
3378 Buffer & buf = wa->bufferView().buffer();
3379 return closeWorkArea(wa, !buf.parent());
3383 void GuiView::writeSession() const {
3384 GuiWorkArea const * active_wa = currentMainWorkArea();
3385 for (int i = 0; i < d.splitter_->count(); ++i) {
3386 TabWorkArea * twa = d.tabWorkArea(i);
3387 for (int j = 0; j < twa->count(); ++j) {
3388 GuiWorkArea * wa = twa->workArea(j);
3389 Buffer & buf = wa->bufferView().buffer();
3390 theSession().lastOpened().add(buf.fileName(), wa == active_wa);
3396 bool GuiView::closeBufferAll()
3399 for (auto & buf : theBufferList()) {
3400 if (!saveBufferIfNeeded(*buf, false)) {
3401 // Closing has been cancelled, so abort.
3406 // Close the workareas in all other views
3407 QList<int> const ids = guiApp->viewIds();
3408 for (int i = 0; i != ids.size(); ++i) {
3409 if (id_ != ids[i] && !guiApp->view(ids[i]).closeWorkAreaAll())
3413 // Close our own workareas
3414 if (!closeWorkAreaAll())
3421 bool GuiView::closeWorkAreaAll()
3423 setCurrentWorkArea(currentMainWorkArea());
3425 // We might be in a situation that there is still a tabWorkArea, but
3426 // there are no tabs anymore. This can happen when we get here after a
3427 // TabWorkArea::lastWorkAreaRemoved() signal. Therefore we count how
3428 // many TabWorkArea's have no documents anymore.
3431 // We have to call count() each time, because it can happen that
3432 // more than one splitter will disappear in one iteration (bug 5998).
3433 while (d.splitter_->count() > empty_twa) {
3434 TabWorkArea * twa = d.tabWorkArea(empty_twa);
3436 if (twa->count() == 0)
3439 setCurrentWorkArea(twa->currentWorkArea());
3440 if (!closeTabWorkArea(twa))
3448 bool GuiView::closeWorkArea(GuiWorkArea * wa, bool close_buffer)
3453 Buffer & buf = wa->bufferView().buffer();
3455 if (GuiViewPrivate::busyBuffers.contains(&buf)) {
3456 Alert::warning(_("Close document"),
3457 _("Document could not be closed because it is being processed by LyX."));
3462 return closeBuffer(buf);
3464 if (!inMultiTabs(wa))
3465 if (!saveBufferIfNeeded(buf, true))
3473 bool GuiView::closeBuffer(Buffer & buf)
3475 bool success = true;
3476 for (Buffer * child_buf : buf.getChildren()) {
3477 if (theBufferList().isOthersChild(&buf, child_buf)) {
3478 child_buf->setParent(nullptr);
3482 // FIXME: should we look in other tabworkareas?
3483 // ANSWER: I don't think so. I've tested, and if the child is
3484 // open in some other window, it closes without a problem.
3485 GuiWorkArea * child_wa = workArea(*child_buf);
3488 // If we are in a close_event all children will be closed in some time,
3489 // so no need to do it here. This will ensure that the children end up
3490 // in the session file in the correct order. If we close the master
3491 // buffer, we can close or release the child buffers here too.
3493 success = closeWorkArea(child_wa, true);
3497 // In this case the child buffer is open but hidden.
3498 // Even in this case, children can be dirty (e.g.,
3499 // after a label change in the master, see #11405).
3500 // Therefore, check this
3501 if (closing_ && (child_buf->isClean() || child_buf->paragraphs().empty())) {
3502 // If we are in a close_event all children will be closed in some time,
3503 // so no need to do it here. This will ensure that the children end up
3504 // in the session file in the correct order. If we close the master
3505 // buffer, we can close or release the child buffers here too.
3508 // Save dirty buffers also if closing_!
3509 if (saveBufferIfNeeded(*child_buf, false)) {
3510 child_buf->removeAutosaveFile();
3511 theBufferList().release(child_buf);
3513 // Saving of dirty children has been cancelled.
3514 // Cancel the whole process.
3521 // goto bookmark to update bookmark pit.
3522 // FIXME: we should update only the bookmarks related to this buffer!
3523 // FIXME: this is done also in LFUN_WINDOW_CLOSE!
3524 LYXERR(Debug::DEBUG, "GuiView::closeBuffer()");
3525 for (unsigned int i = 1; i < theSession().bookmarks().size(); ++i)
3526 guiApp->gotoBookmark(i, false, false);
3528 if (saveBufferIfNeeded(buf, false)) {
3529 buf.removeAutosaveFile();
3530 theBufferList().release(&buf);
3534 // open all children again to avoid a crash because of dangling
3535 // pointers (bug 6603)
3541 bool GuiView::closeTabWorkArea(TabWorkArea * twa)
3543 while (twa == d.currentTabWorkArea()) {
3544 twa->setCurrentIndex(twa->count() - 1);
3546 GuiWorkArea * wa = twa->currentWorkArea();
3547 Buffer & b = wa->bufferView().buffer();
3549 // We only want to close the buffer if the same buffer is not visible
3550 // in another view, and if this is not a child and if we are closing
3551 // a view (not a tabgroup).
3552 bool const close_buffer =
3553 !inOtherView(b) && !b.parent() && closing_;
3555 if (!closeWorkArea(wa, close_buffer))
3562 bool GuiView::saveBufferIfNeeded(Buffer & buf, bool hiding)
3564 if (buf.isClean() || buf.paragraphs().empty())
3567 // Switch to this Buffer.
3573 if (buf.isUnnamed()) {
3574 file = from_utf8(buf.fileName().onlyFileName());
3577 FileName filename = buf.fileName();
3579 file = filename.displayName(30);
3580 exists = filename.exists();
3583 // Bring this window to top before asking questions.
3588 if (hiding && buf.isUnnamed()) {
3589 docstring const text = bformat(_("The document %1$s has not been "
3590 "saved yet.\n\nDo you want to save "
3591 "the document?"), file);
3592 ret = Alert::prompt(_("Save new document?"),
3593 text, 0, 1, _("&Save"), _("&Cancel"));
3597 docstring const text = exists ?
3598 bformat(_("The document %1$s has unsaved changes."
3599 "\n\nDo you want to save the document or "
3600 "discard the changes?"), file) :
3601 bformat(_("The document %1$s has not been saved yet."
3602 "\n\nDo you want to save the document or "
3603 "discard it entirely?"), file);
3604 docstring const title = exists ?
3605 _("Save changed document?") : _("Save document?");
3606 ret = Alert::prompt(title, text, 0, 2,
3607 _("&Save"), _("&Discard"), _("&Cancel"));
3612 if (!saveBuffer(buf))
3616 // If we crash after this we could have no autosave file
3617 // but I guess this is really improbable (Jug).
3618 // Sometimes improbable things happen:
3619 // - see bug http://www.lyx.org/trac/ticket/6587 (ps)
3620 // buf.removeAutosaveFile();
3622 // revert all changes
3633 bool GuiView::inMultiTabs(GuiWorkArea * wa)
3635 Buffer & buf = wa->bufferView().buffer();
3637 for (int i = 0; i != d.splitter_->count(); ++i) {
3638 GuiWorkArea * wa_ = d.tabWorkArea(i)->workArea(buf);
3639 if (wa_ && wa_ != wa)
3642 return inOtherView(buf);
3646 bool GuiView::inOtherView(Buffer & buf)
3648 QList<int> const ids = guiApp->viewIds();
3650 for (int i = 0; i != ids.size(); ++i) {
3654 if (guiApp->view(ids[i]).workArea(buf))
3661 void GuiView::gotoNextOrPreviousBuffer(NextOrPrevious np, bool const move)
3663 if (!documentBufferView())
3666 if (TabWorkArea * twa = d.currentTabWorkArea()) {
3667 Buffer * const curbuf = &documentBufferView()->buffer();
3668 int nwa = twa->count();
3669 for (int i = 0; i < nwa; ++i) {
3670 if (&workArea(i)->bufferView().buffer() == curbuf) {
3672 if (np == NEXTBUFFER)
3673 next_index = (i == nwa - 1 ? 0 : i + 1);
3675 next_index = (i == 0 ? nwa - 1 : i - 1);
3677 twa->moveTab(i, next_index);
3679 setBuffer(&workArea(next_index)->bufferView().buffer());
3687 /// make sure the document is saved
3688 static bool ensureBufferClean(Buffer * buffer)
3690 LASSERT(buffer, return false);
3691 if (buffer->isClean() && !buffer->isUnnamed())
3694 docstring const file = buffer->fileName().displayName(30);
3697 if (!buffer->isUnnamed()) {
3698 text = bformat(_("The document %1$s has unsaved "
3699 "changes.\n\nDo you want to save "
3700 "the document?"), file);
3701 title = _("Save changed document?");
3704 text = bformat(_("The document %1$s has not been "
3705 "saved yet.\n\nDo you want to save "
3706 "the document?"), file);
3707 title = _("Save new document?");
3709 int const ret = Alert::prompt(title, text, 0, 1, _("&Save"), _("&Cancel"));
3712 dispatch(FuncRequest(LFUN_BUFFER_WRITE));
3714 return buffer->isClean() && !buffer->isUnnamed();
3718 bool GuiView::reloadBuffer(Buffer & buf)
3720 currentBufferView()->cursor().reset();
3721 Buffer::ReadStatus status = buf.reload();
3722 return status == Buffer::ReadSuccess;
3726 void GuiView::checkExternallyModifiedBuffers()
3728 for (Buffer * buf : theBufferList()) {
3729 if (buf->fileName().exists() && buf->isChecksumModified()) {
3730 docstring text = bformat(_("Document \n%1$s\n has been externally modified."
3731 " Reload now? Any local changes will be lost."),
3732 from_utf8(buf->absFileName()));
3733 int const ret = Alert::prompt(_("Reload externally changed document?"),
3734 text, 0, 1, _("&Reload"), _("&Cancel"));
3742 void GuiView::dispatchVC(FuncRequest const & cmd, DispatchResult & dr)
3744 Buffer * buffer = documentBufferView()
3745 ? &(documentBufferView()->buffer()) : nullptr;
3747 switch (cmd.action()) {
3748 case LFUN_VC_REGISTER:
3749 if (!buffer || !ensureBufferClean(buffer))
3751 if (!buffer->lyxvc().inUse()) {
3752 if (buffer->lyxvc().registrer()) {
3753 reloadBuffer(*buffer);
3754 dr.clearMessageUpdate();
3759 case LFUN_VC_RENAME:
3760 case LFUN_VC_COPY: {
3761 if (!buffer || !ensureBufferClean(buffer))
3763 if (buffer->lyxvc().inUse() && !buffer->hasReadonlyFlag()) {
3764 if (buffer->lyxvc().isCheckInWithConfirmation()) {
3765 // Some changes are not yet committed.
3766 // We test here and not in getStatus(), since
3767 // this test is expensive.
3769 LyXVC::CommandResult ret =
3770 buffer->lyxvc().checkIn(log);
3772 if (ret == LyXVC::ErrorCommand ||
3773 ret == LyXVC::VCSuccess)
3774 reloadBuffer(*buffer);
3775 if (buffer->lyxvc().isCheckInWithConfirmation()) {
3776 frontend::Alert::error(
3777 _("Revision control error."),
3778 _("Document could not be checked in."));
3782 RenameKind const kind = (cmd.action() == LFUN_VC_RENAME) ?
3783 LV_VC_RENAME : LV_VC_COPY;
3784 renameBuffer(*buffer, cmd.argument(), kind);
3789 case LFUN_VC_CHECK_IN:
3790 if (!buffer || !ensureBufferClean(buffer))
3792 if (buffer->lyxvc().inUse() && !buffer->hasReadonlyFlag()) {
3794 LyXVC::CommandResult ret = buffer->lyxvc().checkIn(log);
3796 // Only skip reloading if the checkin was cancelled or
3797 // an error occurred before the real checkin VCS command
3798 // was executed, since the VCS might have changed the
3799 // file even if it could not checkin successfully.
3800 if (ret == LyXVC::ErrorCommand || ret == LyXVC::VCSuccess)
3801 reloadBuffer(*buffer);
3805 case LFUN_VC_CHECK_OUT:
3806 if (!buffer || !ensureBufferClean(buffer))
3808 if (buffer->lyxvc().inUse()) {
3809 dr.setMessage(buffer->lyxvc().checkOut());
3810 reloadBuffer(*buffer);
3814 case LFUN_VC_LOCKING_TOGGLE:
3815 if (!buffer || !ensureBufferClean(buffer) || buffer->hasReadonlyFlag())
3817 if (buffer->lyxvc().inUse()) {
3818 string res = buffer->lyxvc().lockingToggle();
3820 frontend::Alert::error(_("Revision control error."),
3821 _("Error when setting the locking property."));
3824 reloadBuffer(*buffer);
3829 case LFUN_VC_REVERT:
3832 if (buffer->lyxvc().revert()) {
3833 reloadBuffer(*buffer);
3834 dr.clearMessageUpdate();
3838 case LFUN_VC_UNDO_LAST:
3841 buffer->lyxvc().undoLast();
3842 reloadBuffer(*buffer);
3843 dr.clearMessageUpdate();
3846 case LFUN_VC_REPO_UPDATE:
3849 if (ensureBufferClean(buffer)) {
3850 dr.setMessage(buffer->lyxvc().repoUpdate());
3851 checkExternallyModifiedBuffers();
3855 case LFUN_VC_COMMAND: {
3856 string flag = cmd.getArg(0);
3857 if (buffer && contains(flag, 'R') && !ensureBufferClean(buffer))
3860 if (contains(flag, 'M')) {
3861 if (!Alert::askForText(message, _("LyX VC: Log Message")))
3864 string path = cmd.getArg(1);
3865 if (contains(path, "$$p") && buffer)
3866 path = subst(path, "$$p", buffer->filePath());
3867 LYXERR(Debug::LYXVC, "Directory: " << path);
3869 if (!pp.isReadableDirectory()) {
3870 lyxerr << _("Directory is not accessible.") << endl;
3873 support::PathChanger p(pp);
3875 string command = cmd.getArg(2);
3876 if (command.empty())
3879 command = subst(command, "$$i", buffer->absFileName());
3880 command = subst(command, "$$p", buffer->filePath());
3882 command = subst(command, "$$m", to_utf8(message));
3883 LYXERR(Debug::LYXVC, "Command: " << command);
3885 one.startscript(Systemcall::Wait, command);
3889 if (contains(flag, 'I'))
3890 buffer->markDirty();
3891 if (contains(flag, 'R'))
3892 reloadBuffer(*buffer);
3897 case LFUN_VC_COMPARE: {
3898 if (cmd.argument().empty()) {
3899 lyx::dispatch(FuncRequest(LFUN_DIALOG_SHOW, "comparehistory"));
3905 string rev1 = cmd.getArg(0);
3909 if (!buffer->lyxvc().prepareFileRevision(rev1, f1))
3912 if (isStrInt(rev1) && convert<int>(rev1) <= 0) {
3913 f2 = buffer->absFileName();
3915 string rev2 = cmd.getArg(1);
3919 if (!buffer->lyxvc().prepareFileRevision(rev2, f2))
3923 LYXERR(Debug::LYXVC, "Launching comparison for fetched revisions:\n" <<
3924 f1 << "\n" << f2 << "\n" );
3925 string par = "compare run " + quoteName(f1) + " " + quoteName(f2);
3926 lyx::dispatch(FuncRequest(LFUN_DIALOG_SHOW, par));
3936 void GuiView::openChildDocument(string const & fname)
3938 LASSERT(documentBufferView(), return);
3939 Buffer & buffer = documentBufferView()->buffer();
3940 FileName const filename = support::makeAbsPath(fname, buffer.filePath());
3941 documentBufferView()->saveBookmark(false);
3942 Buffer * child = nullptr;
3943 if (theBufferList().exists(filename)) {
3944 child = theBufferList().getBuffer(filename);
3947 message(bformat(_("Opening child document %1$s..."),
3948 makeDisplayPath(filename.absFileName())));
3949 child = loadDocument(filename, false);
3951 // Set the parent name of the child document.
3952 // This makes insertion of citations and references in the child work,
3953 // when the target is in the parent or another child document.
3955 child->setParent(&buffer);
3959 bool GuiView::goToFileRow(string const & argument)
3963 size_t i = argument.find_last_of(' ');
3964 if (i != string::npos) {
3965 file_name = os::internal_path(FileName(trim(argument.substr(0, i))).realPath());
3966 istringstream is(argument.substr(i + 1));
3971 if (i == string::npos) {
3972 LYXERR0("Wrong argument: " << argument);
3975 Buffer * buf = nullptr;
3976 string const realtmp = package().temp_dir().realPath();
3977 // We have to use os::path_prefix_is() here, instead of
3978 // simply prefixIs(), because the file name comes from
3979 // an external application and may need case adjustment.
3980 if (os::path_prefix_is(file_name, realtmp, os::CASE_ADJUSTED)) {
3981 buf = theBufferList().getBufferFromTmp(file_name, true);
3982 LYXERR(Debug::FILES, "goToFileRow: buffer lookup for " << file_name
3983 << (buf ? " success" : " failed"));
3985 // Must replace extension of the file to be .lyx
3986 // and get full path
3987 FileName const s = fileSearch(string(),
3988 support::changeExtension(file_name, ".lyx"), "lyx");
3989 // Either change buffer or load the file
3990 if (theBufferList().exists(s))
3991 buf = theBufferList().getBuffer(s);
3992 else if (s.exists()) {
3993 buf = loadDocument(s);
3998 _("File does not exist: %1$s"),
3999 makeDisplayPath(file_name)));
4005 _("No buffer for file: %1$s."),
4006 makeDisplayPath(file_name))
4011 bool success = documentBufferView()->setCursorFromRow(row);
4013 LYXERR(Debug::OUTFILE,
4014 "setCursorFromRow: invalid position for row " << row);
4015 frontend::Alert::error(_("Inverse Search Failed"),
4016 _("Invalid position requested by inverse search.\n"
4017 "You may need to update the viewed document."));
4023 void GuiView::toolBarPopup(const QPoint & /*pos*/)
4025 QMenu * menu = guiApp->menus().menu(toqstr("context-toolbars"), * this);
4026 menu->exec(QCursor::pos());
4031 Buffer::ExportStatus GuiView::GuiViewPrivate::runAndDestroy(const T& func,
4032 Buffer const * orig, Buffer * clone, string const & format)
4034 Buffer::ExportStatus const status = func(format);
4036 // the cloning operation will have produced a clone of the entire set of
4037 // documents, starting from the master. so we must delete those.
4038 Buffer * mbuf = const_cast<Buffer *>(clone->masterBuffer());
4040 busyBuffers.remove(orig);
4045 Buffer::ExportStatus GuiView::GuiViewPrivate::compileAndDestroy(
4046 Buffer const * orig, Buffer * clone, string const & format)
4048 Buffer::ExportStatus (Buffer::* mem_func)(std::string const &, bool) const =
4050 return runAndDestroy(lyx::bind(mem_func, clone, _1, true), orig, clone, format);
4054 Buffer::ExportStatus GuiView::GuiViewPrivate::exportAndDestroy(
4055 Buffer const * orig, Buffer * clone, string const & format)
4057 Buffer::ExportStatus (Buffer::* mem_func)(std::string const &, bool) const =
4059 return runAndDestroy(lyx::bind(mem_func, clone, _1, false), orig, clone, format);
4063 Buffer::ExportStatus GuiView::GuiViewPrivate::previewAndDestroy(
4064 Buffer const * orig, Buffer * clone, string const & format)
4066 Buffer::ExportStatus (Buffer::* mem_func)(std::string const &) const =
4068 return runAndDestroy(lyx::bind(mem_func, clone, _1), orig, clone, format);
4072 bool GuiView::GuiViewPrivate::asyncBufferProcessing(string const & argument,
4073 Buffer const * used_buffer,
4074 docstring const & msg,
4075 Buffer::ExportStatus (*asyncFunc)(Buffer const *, Buffer *, string const &),
4076 Buffer::ExportStatus (Buffer::*syncFunc)(string const &, bool) const,
4077 Buffer::ExportStatus (Buffer::*previewFunc)(string const &) const,
4078 bool allow_async, bool use_tmpdir)
4083 string format = argument;
4085 format = used_buffer->params().getDefaultOutputFormat();
4086 processing_format = format;
4088 progress_->clearMessages();
4091 #if EXPORT_in_THREAD
4093 GuiViewPrivate::busyBuffers.insert(used_buffer);
4094 Buffer * cloned_buffer = used_buffer->cloneWithChildren();
4095 if (!cloned_buffer) {
4096 Alert::error(_("Export Error"),
4097 _("Error cloning the Buffer."));
4100 QFuture<Buffer::ExportStatus> f = QtConcurrent::run(
4105 setPreviewFuture(f);
4106 last_export_format = used_buffer->params().bufferFormat();
4109 // We are asynchronous, so we don't know here anything about the success
4112 Buffer::ExportStatus status;
4114 status = (used_buffer->*syncFunc)(format, use_tmpdir);
4115 } else if (previewFunc) {
4116 status = (used_buffer->*previewFunc)(format);
4119 handleExportStatus(gv_, status, format);
4121 return (status == Buffer::ExportSuccess
4122 || status == Buffer::PreviewSuccess);
4126 Buffer::ExportStatus status;
4128 status = (used_buffer->*syncFunc)(format, true);
4129 } else if (previewFunc) {
4130 status = (used_buffer->*previewFunc)(format);
4133 handleExportStatus(gv_, status, format);
4135 return (status == Buffer::ExportSuccess
4136 || status == Buffer::PreviewSuccess);
4140 void GuiView::dispatchToBufferView(FuncRequest const & cmd, DispatchResult & dr)
4142 BufferView * bv = currentBufferView();
4143 LASSERT(bv, return);
4145 // Let the current BufferView dispatch its own actions.
4146 bv->dispatch(cmd, dr);
4147 if (dr.dispatched()) {
4148 if (cmd.action() == LFUN_REDO || cmd.action() == LFUN_UNDO)
4149 updateDialog("document", "");
4153 // Try with the document BufferView dispatch if any.
4154 BufferView * doc_bv = documentBufferView();
4155 if (doc_bv && doc_bv != bv) {
4156 doc_bv->dispatch(cmd, dr);
4157 if (dr.dispatched()) {
4158 if (cmd.action() == LFUN_REDO || cmd.action() == LFUN_UNDO)
4159 updateDialog("document", "");
4164 // Then let the current Cursor dispatch its own actions.
4165 bv->cursor().dispatch(cmd);
4167 // update completion. We do it here and not in
4168 // processKeySym to avoid another redraw just for a
4169 // changed inline completion
4170 if (cmd.origin() == FuncRequest::KEYBOARD) {
4171 if (cmd.action() == LFUN_SELF_INSERT
4172 || (cmd.action() == LFUN_ERT_INSERT && bv->cursor().inMathed()))
4173 updateCompletion(bv->cursor(), true, true);
4174 else if (cmd.action() == LFUN_CHAR_DELETE_BACKWARD)
4175 updateCompletion(bv->cursor(), false, true);
4177 updateCompletion(bv->cursor(), false, false);
4180 dr = bv->cursor().result();
4184 void GuiView::dispatch(FuncRequest const & cmd, DispatchResult & dr)
4186 BufferView * bv = currentBufferView();
4187 // By default we won't need any update.
4188 dr.screenUpdate(Update::None);
4189 // assume cmd will be dispatched
4190 dr.dispatched(true);
4192 Buffer * doc_buffer = documentBufferView()
4193 ? &(documentBufferView()->buffer()) : nullptr;
4195 if (cmd.origin() == FuncRequest::TOC) {
4196 GuiToc * toc = static_cast<GuiToc*>(findOrBuild("toc", false));
4197 toc->doDispatch(bv->cursor(), cmd, dr);
4201 string const argument = to_utf8(cmd.argument());
4203 switch(cmd.action()) {
4204 case LFUN_BUFFER_CHILD_OPEN:
4205 openChildDocument(to_utf8(cmd.argument()));
4208 case LFUN_BUFFER_IMPORT:
4209 importDocument(to_utf8(cmd.argument()));
4212 case LFUN_MASTER_BUFFER_EXPORT:
4214 doc_buffer = const_cast<Buffer *>(doc_buffer->masterBuffer());
4216 case LFUN_BUFFER_EXPORT: {
4219 // GCC only sees strfwd.h when building merged
4220 if (::lyx::operator==(cmd.argument(), "custom")) {
4221 // LFUN_MASTER_BUFFER_EXPORT is not enabled for this case,
4222 // so the following test should not be needed.
4223 // In principle, we could try to switch to such a view...
4224 // if (cmd.action() == LFUN_BUFFER_EXPORT)
4225 dispatch(FuncRequest(LFUN_DIALOG_SHOW, "sendto"), dr);
4229 string const dest = cmd.getArg(1);
4230 FileName target_dir;
4231 if (!dest.empty() && FileName::isAbsolute(dest))
4232 target_dir = FileName(support::onlyPath(dest));
4234 target_dir = doc_buffer->fileName().onlyPath();
4236 string const format = (argument.empty() || argument == "default") ?
4237 doc_buffer->params().getDefaultOutputFormat() : argument;
4239 if ((dest.empty() && doc_buffer->isUnnamed())
4240 || !target_dir.isDirWritable()) {
4241 exportBufferAs(*doc_buffer, from_utf8(format));
4244 /* TODO/Review: Is it a problem to also export the children?
4245 See the update_unincluded flag */
4246 d.asyncBufferProcessing(format,
4249 &GuiViewPrivate::exportAndDestroy,
4251 nullptr, cmd.allowAsync());
4252 // TODO Inform user about success
4256 case LFUN_BUFFER_EXPORT_AS: {
4257 LASSERT(doc_buffer, break);
4258 docstring f = cmd.argument();
4260 f = from_ascii(doc_buffer->params().getDefaultOutputFormat());
4261 exportBufferAs(*doc_buffer, f);
4265 case LFUN_BUFFER_UPDATE: {
4266 d.asyncBufferProcessing(argument,
4269 &GuiViewPrivate::compileAndDestroy,
4271 nullptr, cmd.allowAsync(), true);
4274 case LFUN_BUFFER_VIEW: {
4275 d.asyncBufferProcessing(argument,
4277 _("Previewing ..."),
4278 &GuiViewPrivate::previewAndDestroy,
4280 &Buffer::preview, cmd.allowAsync());
4283 case LFUN_MASTER_BUFFER_UPDATE: {
4284 d.asyncBufferProcessing(argument,
4285 (doc_buffer ? doc_buffer->masterBuffer() : nullptr),
4287 &GuiViewPrivate::compileAndDestroy,
4289 nullptr, cmd.allowAsync(), true);
4292 case LFUN_MASTER_BUFFER_VIEW: {
4293 d.asyncBufferProcessing(argument,
4294 (doc_buffer ? doc_buffer->masterBuffer() : nullptr),
4296 &GuiViewPrivate::previewAndDestroy,
4297 nullptr, &Buffer::preview, cmd.allowAsync());
4300 case LFUN_EXPORT_CANCEL: {
4301 Systemcall::killscript();
4304 case LFUN_BUFFER_SWITCH: {
4305 string const file_name = to_utf8(cmd.argument());
4306 if (!FileName::isAbsolute(file_name)) {
4308 dr.setMessage(_("Absolute filename expected."));
4312 Buffer * buffer = theBufferList().getBuffer(FileName(file_name));
4315 dr.setMessage(_("Document not loaded"));
4319 // Do we open or switch to the buffer in this view ?
4320 if (workArea(*buffer)
4321 || lyxrc.open_buffers_in_tabs || !documentBufferView()) {
4326 // Look for the buffer in other views
4327 QList<int> const ids = guiApp->viewIds();
4329 for (; i != ids.size(); ++i) {
4330 GuiView & gv = guiApp->view(ids[i]);
4331 if (gv.workArea(*buffer)) {
4333 gv.activateWindow();
4335 gv.setBuffer(buffer);
4340 // If necessary, open a new window as a last resort
4341 if (i == ids.size()) {
4342 lyx::dispatch(FuncRequest(LFUN_WINDOW_NEW));
4348 case LFUN_BUFFER_NEXT:
4349 gotoNextOrPreviousBuffer(NEXTBUFFER, false);
4352 case LFUN_BUFFER_MOVE_NEXT:
4353 gotoNextOrPreviousBuffer(NEXTBUFFER, true);
4356 case LFUN_BUFFER_PREVIOUS:
4357 gotoNextOrPreviousBuffer(PREVBUFFER, false);
4360 case LFUN_BUFFER_MOVE_PREVIOUS:
4361 gotoNextOrPreviousBuffer(PREVBUFFER, true);
4364 case LFUN_BUFFER_CHKTEX:
4365 LASSERT(doc_buffer, break);
4366 doc_buffer->runChktex();
4369 case LFUN_COMMAND_EXECUTE: {
4370 command_execute_ = true;
4371 minibuffer_focus_ = true;
4374 case LFUN_DROP_LAYOUTS_CHOICE:
4375 d.layout_->showPopup();
4378 case LFUN_MENU_OPEN:
4379 if (QMenu * menu = guiApp->menus().menu(toqstr(cmd.argument()), *this))
4380 menu->exec(QCursor::pos());
4383 case LFUN_FILE_INSERT: {
4384 bool const ignore_lang = cmd.getArg(1) == "ignorelang";
4385 if (insertLyXFile(from_utf8(cmd.getArg(0)), ignore_lang)) {
4386 dr.forceBufferUpdate();
4387 dr.screenUpdate(Update::Force);
4392 case LFUN_FILE_INSERT_PLAINTEXT:
4393 case LFUN_FILE_INSERT_PLAINTEXT_PARA: {
4394 string const fname = to_utf8(cmd.argument());
4395 if (!fname.empty() && !FileName::isAbsolute(fname)) {
4396 dr.setMessage(_("Absolute filename expected."));
4400 FileName filename(fname);
4401 if (fname.empty()) {
4402 FileDialog dlg(qt_("Select file to insert"));
4404 FileDialog::Result result = dlg.open(toqstr(bv->buffer().filePath()),
4405 QStringList(qt_("All Files (*)")));
4407 if (result.first == FileDialog::Later || result.second.isEmpty()) {
4408 dr.setMessage(_("Canceled."));
4412 filename.set(fromqstr(result.second));
4416 FuncRequest const new_cmd(cmd, filename.absoluteFilePath());
4417 bv->dispatch(new_cmd, dr);
4422 case LFUN_BUFFER_RELOAD: {
4423 LASSERT(doc_buffer, break);
4426 bool drop = (cmd.argument() == "dump");
4429 if (!drop && !doc_buffer->isClean()) {
4430 docstring const file =
4431 makeDisplayPath(doc_buffer->absFileName(), 20);
4432 if (doc_buffer->notifiesExternalModification()) {
4433 docstring text = _("The current version will be lost. "
4434 "Are you sure you want to load the version on disk "
4435 "of the document %1$s?");
4436 ret = Alert::prompt(_("Reload saved document?"),
4437 bformat(text, file), 1, 1,
4438 _("&Reload"), _("&Cancel"));
4440 docstring text = _("Any changes will be lost. "
4441 "Are you sure you want to revert to the saved version "
4442 "of the document %1$s?");
4443 ret = Alert::prompt(_("Revert to saved document?"),
4444 bformat(text, file), 1, 1,
4445 _("&Revert"), _("&Cancel"));
4450 doc_buffer->markClean();
4451 reloadBuffer(*doc_buffer);
4452 dr.forceBufferUpdate();
4457 case LFUN_BUFFER_RESET_EXPORT:
4458 LASSERT(doc_buffer, break);
4459 doc_buffer->requireFreshStart(true);
4460 dr.setMessage(_("Buffer export reset."));
4463 case LFUN_BUFFER_WRITE:
4464 LASSERT(doc_buffer, break);
4465 saveBuffer(*doc_buffer);
4468 case LFUN_BUFFER_WRITE_AS:
4469 LASSERT(doc_buffer, break);
4470 renameBuffer(*doc_buffer, cmd.argument());
4473 case LFUN_BUFFER_WRITE_AS_TEMPLATE:
4474 LASSERT(doc_buffer, break);
4475 renameBuffer(*doc_buffer, cmd.argument(),
4476 LV_WRITE_AS_TEMPLATE);
4479 case LFUN_BUFFER_WRITE_ALL: {
4480 Buffer * first = theBufferList().first();
4483 message(_("Saving all documents..."));
4484 // We cannot use a for loop as the buffer list cycles.
4487 if (!b->isClean()) {
4489 LYXERR(Debug::ACTION, "Saved " << b->absFileName());
4491 b = theBufferList().next(b);
4492 } while (b != first);
4493 dr.setMessage(_("All documents saved."));
4497 case LFUN_MASTER_BUFFER_FORALL: {
4501 FuncRequest funcToRun = lyxaction.lookupFunc(cmd.getLongArg(0));
4502 funcToRun.allowAsync(false);
4504 for (Buffer const * buf : doc_buffer->allRelatives()) {
4505 // Switch to other buffer view and resend cmd
4506 lyx::dispatch(FuncRequest(
4507 LFUN_BUFFER_SWITCH, buf->absFileName()));
4508 lyx::dispatch(funcToRun);
4511 lyx::dispatch(FuncRequest(
4512 LFUN_BUFFER_SWITCH, doc_buffer->absFileName()));
4516 case LFUN_BUFFER_EXTERNAL_MODIFICATION_CLEAR:
4517 LASSERT(doc_buffer, break);
4518 doc_buffer->clearExternalModification();
4521 case LFUN_BUFFER_CLOSE:
4525 case LFUN_BUFFER_CLOSE_ALL:
4529 case LFUN_DEVEL_MODE_TOGGLE:
4530 devel_mode_ = !devel_mode_;
4532 dr.setMessage(_("Developer mode is now enabled."));
4534 dr.setMessage(_("Developer mode is now disabled."));
4537 case LFUN_TOOLBAR_SET: {
4538 string const name = cmd.getArg(0);
4539 string const state = cmd.getArg(1);
4540 if (GuiToolbar * t = toolbar(name))
4545 case LFUN_TOOLBAR_TOGGLE: {
4546 string const name = cmd.getArg(0);
4547 if (GuiToolbar * t = toolbar(name))
4552 case LFUN_TOOLBAR_MOVABLE: {
4553 string const name = cmd.getArg(0);
4555 // toggle (all) toolbars movablility
4556 toolbarsMovable_ = !toolbarsMovable_;
4557 for (ToolbarInfo const & ti : guiApp->toolbars()) {
4558 GuiToolbar * tb = toolbar(ti.name);
4559 if (tb && tb->isMovable() != toolbarsMovable_)
4560 // toggle toolbar movablity if it does not fit lock
4561 // (all) toolbars positions state silent = true, since
4562 // status bar notifications are slow
4565 if (toolbarsMovable_)
4566 dr.setMessage(_("Toolbars unlocked."));
4568 dr.setMessage(_("Toolbars locked."));
4569 } else if (GuiToolbar * tb = toolbar(name))
4570 // toggle current toolbar movablity
4572 // update lock (all) toolbars positions
4573 updateLockToolbars();
4577 case LFUN_ICON_SIZE: {
4578 QSize size = d.iconSize(cmd.argument());
4580 dr.setMessage(bformat(_("Icon size set to %1$dx%2$d."),
4581 size.width(), size.height()));
4585 case LFUN_DIALOG_UPDATE: {
4586 string const name = to_utf8(cmd.argument());
4587 if (name == "prefs" || name == "document")
4588 updateDialog(name, string());
4589 else if (name == "paragraph")
4590 lyx::dispatch(FuncRequest(LFUN_PARAGRAPH_UPDATE));
4591 else if (currentBufferView()) {
4592 Inset * inset = currentBufferView()->editedInset(name);
4593 // Can only update a dialog connected to an existing inset
4595 // FIXME: get rid of this indirection; GuiView ask the inset
4596 // if he is kind enough to update itself...
4597 FuncRequest fr(LFUN_INSET_DIALOG_UPDATE, cmd.argument());
4598 //FIXME: pass DispatchResult here?
4599 inset->dispatch(currentBufferView()->cursor(), fr);
4605 case LFUN_DIALOG_TOGGLE: {
4606 FuncCode const func_code = isDialogVisible(cmd.getArg(0))
4607 ? LFUN_DIALOG_HIDE : LFUN_DIALOG_SHOW;
4608 dispatch(FuncRequest(func_code, cmd.argument()), dr);
4612 case LFUN_DIALOG_DISCONNECT_INSET:
4613 disconnectDialog(to_utf8(cmd.argument()));
4616 case LFUN_DIALOG_HIDE: {
4617 guiApp->hideDialogs(to_utf8(cmd.argument()), nullptr);
4621 case LFUN_DIALOG_SHOW: {
4622 string const name = cmd.getArg(0);
4623 string sdata = trim(to_utf8(cmd.argument()).substr(name.size()));
4625 if (name == "latexlog") {
4626 // getStatus checks that
4627 LASSERT(doc_buffer, break);
4628 Buffer::LogType type;
4629 string const logfile = doc_buffer->logName(&type);
4631 case Buffer::latexlog:
4634 case Buffer::buildlog:
4635 sdata = "literate ";
4638 sdata += Lexer::quoteString(logfile);
4639 showDialog("log", sdata);
4640 } else if (name == "vclog") {
4641 // getStatus checks that
4642 LASSERT(doc_buffer, break);
4643 string const sdata2 = "vc " +
4644 Lexer::quoteString(doc_buffer->lyxvc().getLogFile());
4645 showDialog("log", sdata2);
4646 } else if (name == "symbols") {
4647 sdata = bv->cursor().getEncoding()->name();
4649 showDialog("symbols", sdata);
4650 } else if (name == "findreplace") {
4651 sdata = to_utf8(bv->cursor().selectionAsString(false));
4652 showDialog(name, sdata);
4654 } else if (name == "prefs" && isFullScreen()) {
4655 lfunUiToggle("fullscreen");
4656 showDialog("prefs", sdata);
4658 showDialog(name, sdata);
4663 dr.setMessage(cmd.argument());
4666 case LFUN_UI_TOGGLE: {
4667 string arg = cmd.getArg(0);
4668 if (!lfunUiToggle(arg)) {
4669 docstring const msg = "ui-toggle " + _("%1$s unknown command!");
4670 dr.setMessage(bformat(msg, from_utf8(arg)));
4672 // Make sure the keyboard focus stays in the work area.
4677 case LFUN_VIEW_SPLIT: {
4678 LASSERT(doc_buffer, break);
4679 string const orientation = cmd.getArg(0);
4680 d.splitter_->setOrientation(orientation == "vertical"
4681 ? Qt::Vertical : Qt::Horizontal);
4682 TabWorkArea * twa = addTabWorkArea();
4683 GuiWorkArea * wa = twa->addWorkArea(*doc_buffer, *this);
4684 setCurrentWorkArea(wa);
4687 case LFUN_TAB_GROUP_CLOSE:
4688 if (TabWorkArea * twa = d.currentTabWorkArea()) {
4689 closeTabWorkArea(twa);
4690 d.current_work_area_ = nullptr;
4691 twa = d.currentTabWorkArea();
4692 // Switch to the next GuiWorkArea in the found TabWorkArea.
4694 // Make sure the work area is up to date.
4695 setCurrentWorkArea(twa->currentWorkArea());
4697 setCurrentWorkArea(nullptr);
4702 case LFUN_VIEW_CLOSE:
4703 if (TabWorkArea * twa = d.currentTabWorkArea()) {
4704 closeWorkArea(twa->currentWorkArea());
4705 d.current_work_area_ = nullptr;
4706 twa = d.currentTabWorkArea();
4707 // Switch to the next GuiWorkArea in the found TabWorkArea.
4709 // Make sure the work area is up to date.
4710 setCurrentWorkArea(twa->currentWorkArea());
4712 setCurrentWorkArea(nullptr);
4717 case LFUN_COMPLETION_INLINE:
4718 if (d.current_work_area_)
4719 d.current_work_area_->completer().showInline();
4722 case LFUN_COMPLETION_POPUP:
4723 if (d.current_work_area_)
4724 d.current_work_area_->completer().showPopup();
4729 if (d.current_work_area_)
4730 d.current_work_area_->completer().tab();
4733 case LFUN_COMPLETION_CANCEL:
4734 if (d.current_work_area_) {
4735 if (d.current_work_area_->completer().popupVisible())
4736 d.current_work_area_->completer().hidePopup();
4738 d.current_work_area_->completer().hideInline();
4742 case LFUN_COMPLETION_ACCEPT:
4743 if (d.current_work_area_)
4744 d.current_work_area_->completer().activate();
4747 case LFUN_BUFFER_ZOOM_IN:
4748 case LFUN_BUFFER_ZOOM_OUT:
4749 case LFUN_BUFFER_ZOOM: {
4750 zoom_ratio_ = zoomRatio(cmd, zoom_ratio_);
4752 // Actual zoom value: default zoom + fractional extra value
4753 int zoom = (int)(lyxrc.defaultZoom * zoom_ratio_);
4754 zoom = min(max(zoom, zoom_min_), zoom_max_);
4756 setCurrentZoom(zoom);
4758 dr.setMessage(bformat(_("Zoom level is now %1$d% (default value: %2$d%)"),
4759 lyxrc.currentZoom, lyxrc.defaultZoom));
4761 guiApp->fontLoader().update();
4762 dr.screenUpdate(Update::ForceAll | Update::FitCursor);
4766 case LFUN_VC_REGISTER:
4767 case LFUN_VC_RENAME:
4769 case LFUN_VC_CHECK_IN:
4770 case LFUN_VC_CHECK_OUT:
4771 case LFUN_VC_REPO_UPDATE:
4772 case LFUN_VC_LOCKING_TOGGLE:
4773 case LFUN_VC_REVERT:
4774 case LFUN_VC_UNDO_LAST:
4775 case LFUN_VC_COMMAND:
4776 case LFUN_VC_COMPARE:
4777 dispatchVC(cmd, dr);
4780 case LFUN_SERVER_GOTO_FILE_ROW:
4781 if(goToFileRow(to_utf8(cmd.argument())))
4782 dr.screenUpdate(Update::Force | Update::FitCursor);
4785 case LFUN_LYX_ACTIVATE:
4789 case LFUN_WINDOW_RAISE:
4795 case LFUN_FORWARD_SEARCH: {
4796 // it seems safe to assume we have a document buffer, since
4797 // getStatus wants one.
4798 LASSERT(doc_buffer, break);
4799 Buffer const * doc_master = doc_buffer->masterBuffer();
4800 FileName const path(doc_master->temppath());
4801 string const texname = doc_master->isChild(doc_buffer)
4802 ? DocFileName(changeExtension(
4803 doc_buffer->absFileName(),
4804 "tex")).mangledFileName()
4805 : doc_buffer->latexName();
4806 string const fulltexname =
4807 support::makeAbsPath(texname, doc_master->temppath()).absFileName();
4808 string const mastername =
4809 removeExtension(doc_master->latexName());
4810 FileName const dviname(addName(path.absFileName(),
4811 addExtension(mastername, "dvi")));
4812 FileName const pdfname(addName(path.absFileName(),
4813 addExtension(mastername, "pdf")));
4814 bool const have_dvi = dviname.exists();
4815 bool const have_pdf = pdfname.exists();
4816 if (!have_dvi && !have_pdf) {
4817 dr.setMessage(_("Please, preview the document first."));
4820 string outname = dviname.onlyFileName();
4821 string command = lyxrc.forward_search_dvi;
4822 if (!have_dvi || (have_pdf &&
4823 pdfname.lastModified() > dviname.lastModified())) {
4824 outname = pdfname.onlyFileName();
4825 command = lyxrc.forward_search_pdf;
4828 DocIterator cur = bv->cursor();
4829 int row = doc_buffer->texrow().rowFromDocIterator(cur).first;
4830 LYXERR(Debug::ACTION, "Forward search: row:" << row
4832 if (row == -1 || command.empty()) {
4833 dr.setMessage(_("Couldn't proceed."));
4836 string texrow = convert<string>(row);
4838 command = subst(command, "$$n", texrow);
4839 command = subst(command, "$$f", fulltexname);
4840 command = subst(command, "$$t", texname);
4841 command = subst(command, "$$o", outname);
4843 volatile PathChanger p(path);
4845 one.startscript(Systemcall::DontWait, command);
4849 case LFUN_SPELLING_CONTINUOUSLY:
4850 lyxrc.spellcheck_continuously = !lyxrc.spellcheck_continuously;
4851 dr.screenUpdate(Update::Force);
4854 case LFUN_CITATION_OPEN: {
4856 if (theFormats().getFormat("pdf"))
4857 pdfv = theFormats().getFormat("pdf")->viewer();
4858 if (theFormats().getFormat("ps"))
4859 psv = theFormats().getFormat("ps")->viewer();
4860 frontend::showTarget(argument, pdfv, psv);
4865 // The LFUN must be for one of BufferView, Buffer or Cursor;
4867 dispatchToBufferView(cmd, dr);
4871 // Need to update bv because many LFUNs here might have destroyed it
4872 bv = currentBufferView();
4874 // Clear non-empty selections
4875 // (e.g. from a "char-forward-select" followed by "char-backward-select")
4877 Cursor & cur = bv->cursor();
4878 if ((cur.selection() && cur.selBegin() == cur.selEnd())) {
4879 cur.clearSelection();
4885 bool GuiView::lfunUiToggle(string const & ui_component)
4887 if (ui_component == "scrollbar") {
4888 // hide() is of no help
4889 if (d.current_work_area_->verticalScrollBarPolicy() ==
4890 Qt::ScrollBarAlwaysOff)
4892 d.current_work_area_->setVerticalScrollBarPolicy(
4893 Qt::ScrollBarAsNeeded);
4895 d.current_work_area_->setVerticalScrollBarPolicy(
4896 Qt::ScrollBarAlwaysOff);
4897 } else if (ui_component == "statusbar") {
4898 statusBar()->setVisible(!statusBar()->isVisible());
4899 } else if (ui_component == "menubar") {
4900 menuBar()->setVisible(!menuBar()->isVisible());
4901 } else if (ui_component == "zoomlevel") {
4902 zoom_value_->setVisible(!zoom_value_->isVisible());
4903 } else if (ui_component == "zoomslider") {
4904 zoom_slider_->setVisible(!zoom_slider_->isVisible());
4905 zoom_in_->setVisible(zoom_slider_->isVisible());
4906 zoom_out_->setVisible(zoom_slider_->isVisible());
4907 } else if (ui_component == "frame") {
4908 int const l = contentsMargins().left();
4910 //are the frames in default state?
4911 d.current_work_area_->setFrameStyle(QFrame::NoFrame);
4913 #if QT_VERSION > 0x050903
4914 setAttribute(Qt::WA_ContentsMarginsRespectsSafeArea, false);
4916 setContentsMargins(-2, -2, -2, -2);
4918 #if QT_VERSION > 0x050903
4919 setAttribute(Qt::WA_ContentsMarginsRespectsSafeArea, true);
4921 setContentsMargins(0, 0, 0, 0);
4924 if (ui_component == "fullscreen") {
4932 void GuiView::toggleFullScreen()
4934 setWindowState(windowState() ^ Qt::WindowFullScreen);
4938 Buffer const * GuiView::updateInset(Inset const * inset)
4943 Buffer const * inset_buffer = &(inset->buffer());
4945 for (int i = 0; i != d.splitter_->count(); ++i) {
4946 GuiWorkArea * wa = d.tabWorkArea(i)->currentWorkArea();
4949 Buffer const * buffer = &(wa->bufferView().buffer());
4950 if (inset_buffer == buffer)
4951 wa->scheduleRedraw(true);
4953 return inset_buffer;
4957 void GuiView::restartCaret()
4959 /* When we move around, or type, it's nice to be able to see
4960 * the caret immediately after the keypress.
4962 if (d.current_work_area_)
4963 d.current_work_area_->startBlinkingCaret();
4965 // Take this occasion to update the other GUI elements.
4971 void GuiView::updateCompletion(Cursor & cur, bool start, bool keep)
4973 if (d.current_work_area_)
4974 d.current_work_area_->completer().updateVisibility(cur, start, keep);
4979 // This list should be kept in sync with the list of insets in
4980 // src/insets/Inset.cpp. I.e., if a dialog goes with an inset, the
4981 // dialog should have the same name as the inset.
4982 // Changes should be also recorded in LFUN_DIALOG_SHOW doxygen
4983 // docs in LyXAction.cpp.
4985 char const * const dialognames[] = {
4987 "aboutlyx", "bibitem", "bibtex", "box", "branch", "changes", "character",
4988 "citation", "compare", "comparehistory", "counter", "document", "errorlist", "ert",
4989 "external", "file", "findreplace", "findreplaceadv", "float", "graphics",
4990 "href", "include", "index", "index_print", "info", "listings", "label", "line",
4991 "log", "lyxfiles", "mathdelimiter", "mathmatrix", "mathspace", "nomenclature",
4992 "nomencl_print", "note", "paragraph", "phantom", "prefs", "ref",
4993 "sendto", "space", "spellchecker", "symbols", "tabular", "tabularcreate",
4994 "thesaurus", "texinfo", "toc", "view-source", "vspace", "wrap", "progress"};
4996 char const * const * const end_dialognames =
4997 dialognames + (sizeof(dialognames) / sizeof(char *));
5001 cmpCStr(char const * name) : name_(name) {}
5002 bool operator()(char const * other) {
5003 return strcmp(other, name_) == 0;
5010 bool isValidName(string const & name)
5012 return find_if(dialognames, end_dialognames,
5013 cmpCStr(name.c_str())) != end_dialognames;
5019 void GuiView::resetDialogs()
5021 // Make sure that no LFUN uses any GuiView.
5022 guiApp->setCurrentView(nullptr);
5026 constructToolbars();
5027 guiApp->menus().fillMenuBar(menuBar(), this, false);
5028 d.layout_->updateContents(true);
5029 // Now update controls with current buffer.
5030 guiApp->setCurrentView(this);
5036 void GuiView::flatGroupBoxes(const QObject * widget, bool flag)
5038 for (QObject * child: widget->children()) {
5039 if (child->inherits("QGroupBox")) {
5040 QGroupBox * box = (QGroupBox*) child;
5043 flatGroupBoxes(child, flag);
5049 Dialog * GuiView::find(string const & name, bool hide_it) const
5051 if (!isValidName(name))
5054 map<string, DialogPtr>::iterator it = d.dialogs_.find(name);
5056 if (it != d.dialogs_.end()) {
5058 it->second->hideView();
5059 return it->second.get();
5065 Dialog * GuiView::findOrBuild(string const & name, bool hide_it)
5067 Dialog * dialog = find(name, hide_it);
5068 if (dialog != nullptr)
5071 dialog = build(name);
5072 d.dialogs_[name].reset(dialog);
5073 #if (QT_VERSION >= QT_VERSION_CHECK(5, 0, 0))
5074 // Force a uniform style for group boxes
5075 // On Mac non-flat works better, on Linux flat is standard
5076 flatGroupBoxes(dialog->asQWidget(), guiApp->platformName() != "cocoa");
5078 if (lyxrc.allow_geometry_session)
5079 dialog->restoreSession();
5086 void GuiView::showDialog(string const & name, string const & sdata,
5089 triggerShowDialog(toqstr(name), toqstr(sdata), inset);
5093 void GuiView::doShowDialog(QString const & qname, QString const & qdata,
5099 const string name = fromqstr(qname);
5100 const string sdata = fromqstr(qdata);
5104 Dialog * dialog = findOrBuild(name, false);
5106 bool const visible = dialog->isVisibleView();
5107 dialog->showData(sdata);
5108 if (currentBufferView())
5109 currentBufferView()->editInset(name, inset);
5110 // We only set the focus to the new dialog if it was not yet
5111 // visible in order not to change the existing previous behaviour
5113 // activateWindow is needed for floating dockviews
5114 dialog->asQWidget()->raise();
5115 dialog->asQWidget()->activateWindow();
5116 if (dialog->wantInitialFocus())
5117 dialog->asQWidget()->setFocus();
5121 catch (ExceptionMessage const &) {
5129 bool GuiView::isDialogVisible(string const & name) const
5131 map<string, DialogPtr>::const_iterator it = d.dialogs_.find(name);
5132 if (it == d.dialogs_.end())
5134 return it->second.get()->isVisibleView() && !it->second.get()->isClosing();
5138 void GuiView::hideDialog(string const & name, Inset * inset)
5140 map<string, DialogPtr>::const_iterator it = d.dialogs_.find(name);
5141 if (it == d.dialogs_.end())
5145 if (!currentBufferView())
5147 if (inset != currentBufferView()->editedInset(name))
5151 Dialog * const dialog = it->second.get();
5152 if (dialog->isVisibleView())
5154 if (currentBufferView())
5155 currentBufferView()->editInset(name, nullptr);
5159 void GuiView::disconnectDialog(string const & name)
5161 if (!isValidName(name))
5163 if (currentBufferView())
5164 currentBufferView()->editInset(name, nullptr);
5168 void GuiView::hideAll() const
5170 for(auto const & dlg_p : d.dialogs_)
5171 dlg_p.second->hideView();
5175 void GuiView::updateDialogs()
5177 for(auto const & dlg_p : d.dialogs_) {
5178 Dialog * dialog = dlg_p.second.get();
5180 if (dialog->needBufferOpen() && !documentBufferView())
5181 hideDialog(fromqstr(dialog->name()), nullptr);
5182 else if (dialog->isVisibleView())
5183 dialog->checkStatus();
5191 Dialog * GuiView::build(string const & name)
5193 return createDialog(*this, name);
5197 SEMenu::SEMenu(QWidget * parent)
5199 QAction * action = addAction(qt_("Disable Shell Escape"));
5200 connect(action, SIGNAL(triggered()),
5201 parent, SLOT(disableShellEscape()));
5205 void PressableSvgWidget::mousePressEvent(QMouseEvent * event)
5207 if (event->button() == Qt::LeftButton) {
5212 } // namespace frontend
5215 #include "moc_GuiView.cpp"