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 "FontLoader.h"
22 #include "GuiApplication.h"
23 #include "GuiClickableLabel.h"
24 #include "GuiCompleter.h"
25 #include "GuiFontMetrics.h"
26 #include "GuiKeySymbol.h"
28 #include "GuiToolbar.h"
29 #include "GuiWorkArea.h"
30 #include "GuiProgress.h"
31 #include "LayoutBox.h"
35 #include "qt_helpers.h"
36 #include "support/filetools.h"
38 #include "frontends/alert.h"
39 #include "frontends/KeySymbol.h"
41 #include "buffer_funcs.h"
43 #include "BufferList.h"
44 #include "BufferParams.h"
45 #include "BufferView.h"
47 #include "Converter.h"
49 #include "CutAndPaste.h"
51 #include "ErrorList.h"
53 #include "FuncStatus.h"
54 #include "FuncRequest.h"
55 #include "KeySymbol.h"
57 #include "LayoutFile.h"
59 #include "LyXAction.h"
63 #include "Paragraph.h"
64 #include "SpellChecker.h"
71 #include "support/convert.h"
72 #include "support/debug.h"
73 #include "support/ExceptionMessage.h"
74 #include "support/FileName.h"
75 #include "support/gettext.h"
76 #include "support/ForkedCalls.h"
77 #include "support/lassert.h"
78 #include "support/lstrings.h"
79 #include "support/os.h"
80 #include "support/Package.h"
81 #include "support/PathChanger.h"
82 #include "support/Systemcall.h"
83 #include "support/Timeout.h"
84 #include "support/ProgressInterface.h"
87 #include <QApplication>
88 #include <QCloseEvent>
89 #include <QDragEnterEvent>
92 #include <QFutureWatcher>
103 #include <QShowEvent>
106 #include <QStackedWidget>
107 #include <QStatusBar>
108 #include <QSvgRenderer>
109 #include <QtConcurrentRun>
112 #include <QWindowStateChangeEvent>
113 #include <QGestureEvent>
114 #include <QPinchGesture>
117 // sync with GuiAlert.cpp
118 #define EXPORT_in_THREAD 1
121 #include "support/bind.h"
125 #ifdef HAVE_SYS_TIME_H
126 # include <sys/time.h>
134 using namespace lyx::support;
138 using support::addExtension;
139 using support::changeExtension;
140 using support::removeExtension;
146 class BackgroundWidget : public QWidget
149 BackgroundWidget(int width, int height)
150 : width_(width), height_(height)
152 LYXERR(Debug::GUI, "show banner: " << lyxrc.show_banner);
153 if (!lyxrc.show_banner)
155 /// The text to be written on top of the pixmap
156 QString const htext = qt_("The Document\nProcessor[[welcome banner]]");
157 QString const htextsize = qt_("1.0[[possibly scale the welcome banner text size]]");
158 /// The text to be written on top of the pixmap
159 QString const text = lyx_version ?
160 qt_("version ") + lyx_version : qt_("unknown version");
161 #if QT_VERSION >= 0x050000
162 QString imagedir = "images/";
163 FileName fname = imageLibFileSearch(imagedir, "banner", "svgz");
164 QSvgRenderer svgRenderer(toqstr(fname.absFileName()));
165 if (svgRenderer.isValid()) {
166 splash_ = QPixmap(splashSize());
167 QPainter painter(&splash_);
168 svgRenderer.render(&painter);
169 splash_.setDevicePixelRatio(pixelRatio());
171 splash_ = getPixmap("images/", "banner", "png");
174 splash_ = getPixmap("images/", "banner", "svgz,png");
177 QPainter pain(&splash_);
178 pain.setPen(QColor(0, 0, 0));
179 qreal const fsize = fontSize();
182 qreal locscale = htextsize.toFloat(&ok);
185 QPointF const position = textPosition(false);
186 QPointF const hposition = textPosition(true);
187 QRectF const hrect(hposition, splashSize());
189 "widget pixel ratio: " << pixelRatio() <<
190 " splash pixel ratio: " << splashPixelRatio() <<
191 " version text size,position: " << fsize << "@" << position.x() << "+" << position.y());
193 // The font used to display the version info
194 font.setStyleHint(QFont::SansSerif);
195 font.setWeight(QFont::Bold);
196 font.setPointSizeF(fsize);
198 pain.drawText(position, text);
199 // The font used to display the version info
200 font.setStyleHint(QFont::SansSerif);
201 font.setWeight(QFont::Normal);
202 font.setPointSizeF(hfsize);
203 // Check how long the logo gets with the current font
204 // and adapt if the font is running wider than what
206 GuiFontMetrics fm(font);
207 // Split the title into lines to measure the longest line
208 // in the current l7n.
209 QStringList titlesegs = htext.split('\n');
211 int hline = fm.maxHeight();
212 for (QString const & seg : titlesegs) {
213 if (fm.width(seg) > wline)
214 wline = fm.width(seg);
216 // The longest line in the reference font (for English)
217 // is 180. Calculate scale factor from that.
218 double const wscale = wline > 0 ? (180.0 / wline) : 1;
219 // Now do the same for the height (necessary for condensed fonts)
220 double const hscale = (34.0 / hline);
221 // take the lower of the two scale factors.
222 double const scale = min(wscale, hscale);
223 // Now rescale. Also consider l7n's offset factor.
224 font.setPointSizeF(hfsize * scale * locscale);
227 pain.drawText(hrect, Qt::AlignLeft, htext);
228 setFocusPolicy(Qt::StrongFocus);
231 void paintEvent(QPaintEvent *) override
233 int const w = width_;
234 int const h = height_;
235 int const x = (width() - w) / 2;
236 int const y = (height() - h) / 2;
238 "widget pixel ratio: " << pixelRatio() <<
239 " splash pixel ratio: " << splashPixelRatio() <<
240 " paint pixmap: " << w << "x" << h << "@" << x << "+" << y);
242 pain.drawPixmap(x, y, w, h, splash_);
245 void keyPressEvent(QKeyEvent * ev) override
248 setKeySymbol(&sym, ev);
250 guiApp->processKeySym(sym, q_key_state(ev->modifiers()));
262 /// Current ratio between physical pixels and device-independent pixels
263 double pixelRatio() const {
264 #if QT_VERSION >= 0x050000
265 return qt_scale_factor * devicePixelRatio();
271 qreal fontSize() const {
272 return toqstr(lyxrc.font_sizes[NORMAL_SIZE]).toDouble();
275 QPointF textPosition(bool const heading) const {
276 return heading ? QPointF(width_/2 - 18, height_/2 - 45)
277 : QPointF(width_/2 - 18, height_/2 + 45);
280 QSize splashSize() const {
282 static_cast<unsigned int>(width_ * pixelRatio()),
283 static_cast<unsigned int>(height_ * pixelRatio()));
286 /// Ratio between physical pixels and device-independent pixels of splash image
287 double splashPixelRatio() const {
288 #if QT_VERSION >= 0x050000
289 return splash_.devicePixelRatio();
297 /// Toolbar store providing access to individual toolbars by name.
298 typedef map<string, GuiToolbar *> ToolbarMap;
300 typedef shared_ptr<Dialog> DialogPtr;
305 class GuiView::GuiViewPrivate
308 GuiViewPrivate(GuiViewPrivate const &);
309 void operator=(GuiViewPrivate const &);
311 GuiViewPrivate(GuiView * gv)
312 : gv_(gv), current_work_area_(nullptr), current_main_work_area_(nullptr),
313 layout_(nullptr), autosave_timeout_(5000),
316 // hardcode here the platform specific icon size
317 smallIconSize = 16; // scaling problems
318 normalIconSize = 20; // ok, default if iconsize.png is missing
319 bigIconSize = 26; // better for some math icons
320 hugeIconSize = 32; // better for hires displays
323 // if it exists, use width of iconsize.png as normal size
324 QString const dir = toqstr(addPath("images", lyxrc.icon_set));
325 FileName const fn = lyx::libFileSearch(dir, "iconsize.png");
327 QImage image(toqstr(fn.absFileName()));
328 if (image.width() < int(smallIconSize))
329 normalIconSize = smallIconSize;
330 else if (image.width() > int(giantIconSize))
331 normalIconSize = giantIconSize;
333 normalIconSize = image.width();
336 splitter_ = new QSplitter;
337 bg_widget_ = new BackgroundWidget(400, 250);
338 stack_widget_ = new QStackedWidget;
339 stack_widget_->addWidget(bg_widget_);
340 stack_widget_->addWidget(splitter_);
343 // TODO cleanup, remove the singleton, handle multiple Windows?
344 progress_ = ProgressInterface::instance();
345 if (!dynamic_cast<GuiProgress*>(progress_)) {
346 progress_ = new GuiProgress; // TODO who deletes it
347 ProgressInterface::setInstance(progress_);
350 dynamic_cast<GuiProgress*>(progress_),
351 SIGNAL(updateStatusBarMessage(QString const&)),
352 gv, SLOT(updateStatusBarMessage(QString const&)));
354 dynamic_cast<GuiProgress*>(progress_),
355 SIGNAL(clearMessageText()),
356 gv, SLOT(clearMessageText()));
363 delete stack_widget_;
368 stack_widget_->setCurrentWidget(bg_widget_);
369 bg_widget_->setUpdatesEnabled(true);
370 bg_widget_->setFocus();
373 int tabWorkAreaCount()
375 return splitter_->count();
378 TabWorkArea * tabWorkArea(int i)
380 return dynamic_cast<TabWorkArea *>(splitter_->widget(i));
383 TabWorkArea * currentTabWorkArea()
385 int areas = tabWorkAreaCount();
387 // The first TabWorkArea is always the first one, if any.
388 return tabWorkArea(0);
390 for (int i = 0; i != areas; ++i) {
391 TabWorkArea * twa = tabWorkArea(i);
392 if (current_main_work_area_ == twa->currentWorkArea())
396 // None has the focus so we just take the first one.
397 return tabWorkArea(0);
400 int countWorkAreasOf(Buffer & buf)
402 int areas = tabWorkAreaCount();
404 for (int i = 0; i != areas; ++i) {
405 TabWorkArea * twa = tabWorkArea(i);
406 if (twa->workArea(buf))
412 void setPreviewFuture(QFuture<Buffer::ExportStatus> const & f)
414 if (processing_thread_watcher_.isRunning()) {
415 // we prefer to cancel this preview in order to keep a snappy
419 processing_thread_watcher_.setFuture(f);
422 QSize iconSize(docstring const & icon_size)
425 if (icon_size == "small")
426 size = smallIconSize;
427 else if (icon_size == "normal")
428 size = normalIconSize;
429 else if (icon_size == "big")
431 else if (icon_size == "huge")
433 else if (icon_size == "giant")
434 size = giantIconSize;
436 size = icon_size.empty() ? normalIconSize : convert<int>(icon_size);
438 if (size < smallIconSize)
439 size = smallIconSize;
441 return QSize(size, size);
444 QSize iconSize(QString const & icon_size)
446 return iconSize(qstring_to_ucs4(icon_size));
449 string & iconSize(QSize const & qsize)
451 LATTEST(qsize.width() == qsize.height());
453 static string icon_size;
455 unsigned int size = qsize.width();
457 if (size < smallIconSize)
458 size = smallIconSize;
460 if (size == smallIconSize)
462 else if (size == normalIconSize)
463 icon_size = "normal";
464 else if (size == bigIconSize)
466 else if (size == hugeIconSize)
468 else if (size == giantIconSize)
471 icon_size = convert<string>(size);
476 static Buffer::ExportStatus previewAndDestroy(Buffer const * orig,
477 Buffer * buffer, string const & format);
478 static Buffer::ExportStatus exportAndDestroy(Buffer const * orig,
479 Buffer * buffer, string const & format);
480 static Buffer::ExportStatus compileAndDestroy(Buffer const * orig,
481 Buffer * buffer, string const & format);
482 static docstring autosaveAndDestroy(Buffer const * orig, Buffer * buffer);
485 static Buffer::ExportStatus runAndDestroy(const T& func,
486 Buffer const * orig, Buffer * buffer, string const & format);
488 // TODO syncFunc/previewFunc: use bind
489 bool asyncBufferProcessing(string const & argument,
490 Buffer const * used_buffer,
491 docstring const & msg,
492 Buffer::ExportStatus (*asyncFunc)(Buffer const *, Buffer *, string const &),
493 Buffer::ExportStatus (Buffer::*syncFunc)(string const &, bool) const,
494 Buffer::ExportStatus (Buffer::*previewFunc)(string const &) const,
495 bool allow_async, bool use_tmpdir = false);
497 QVector<GuiWorkArea*> guiWorkAreas();
501 GuiWorkArea * current_work_area_;
502 GuiWorkArea * current_main_work_area_;
503 QSplitter * splitter_;
504 QStackedWidget * stack_widget_;
505 BackgroundWidget * bg_widget_;
507 ToolbarMap toolbars_;
508 ProgressInterface* progress_;
509 /// The main layout box.
511 * \warning Don't Delete! The layout box is actually owned by
512 * whichever toolbar contains it. All the GuiView class needs is a
513 * means of accessing it.
515 * FIXME: replace that with a proper model so that we are not limited
516 * to only one dialog.
521 map<string, DialogPtr> dialogs_;
524 QTimer statusbar_timer_;
525 /// auto-saving of buffers
526 Timeout autosave_timeout_;
529 TocModels toc_models_;
532 QFutureWatcher<docstring> autosave_watcher_;
533 QFutureWatcher<Buffer::ExportStatus> processing_thread_watcher_;
535 string last_export_format;
536 string processing_format;
538 static QSet<Buffer const *> busyBuffers;
540 unsigned int smallIconSize;
541 unsigned int normalIconSize;
542 unsigned int bigIconSize;
543 unsigned int hugeIconSize;
544 unsigned int giantIconSize;
546 /// flag against a race condition due to multiclicks, see bug #1119
550 QSet<Buffer const *> GuiView::GuiViewPrivate::busyBuffers;
553 GuiView::GuiView(int id)
554 : d(*new GuiViewPrivate(this)), id_(id), closing_(false), busy_(0),
555 command_execute_(false), minibuffer_focus_(false), toolbarsMovable_(true),
558 connect(this, SIGNAL(bufferViewChanged()),
559 this, SLOT(onBufferViewChanged()));
561 // GuiToolbars *must* be initialised before the menu bar.
562 setIconSize(QSize(d.normalIconSize, d.normalIconSize)); // at least on Mac the default is 32 otherwise, which is huge
565 // set ourself as the current view. This is needed for the menu bar
566 // filling, at least for the static special menu item on Mac. Otherwise
567 // they are greyed out.
568 guiApp->setCurrentView(this);
570 // Fill up the menu bar.
571 guiApp->menus().fillMenuBar(menuBar(), this, true);
573 setCentralWidget(d.stack_widget_);
575 // Start autosave timer
576 if (lyxrc.autosave) {
577 // The connection is closed when this is destroyed.
578 d.autosave_timeout_.timeout.connect([this](){ autoSave();});
579 d.autosave_timeout_.setTimeout(lyxrc.autosave * 1000);
580 d.autosave_timeout_.start();
582 connect(&d.statusbar_timer_, SIGNAL(timeout()),
583 this, SLOT(clearMessage()));
585 // We don't want to keep the window in memory if it is closed.
586 setAttribute(Qt::WA_DeleteOnClose, true);
588 #if !(defined(Q_OS_WIN) || defined(Q_CYGWIN_WIN)) && !defined(Q_OS_MAC)
589 // QIcon::fromTheme was introduced in Qt 4.6
590 #if (QT_VERSION >= 0x040600)
591 // assign an icon to main form. We do not do it under Qt/Win or Qt/Mac,
592 // since the icon is provided in the application bundle. We use a themed
593 // version when available and use the bundled one as fallback.
594 setWindowIcon(QIcon::fromTheme("lyx", getPixmap("images/", "lyx", "svg,png")));
596 setWindowIcon(getPixmap("images/", "lyx", "svg,png"));
602 // use tabbed dock area for multiple docks
603 // (such as "source" and "messages")
604 setDockOptions(QMainWindow::ForceTabbedDocks);
607 // use document mode tabs on docks
608 setDocumentMode(true);
612 setAcceptDrops(true);
614 // add busy indicator to statusbar
615 search_mode mode = theGuiApp()->imageSearchMode();
616 QString fn = toqstr(lyx::libFileSearch("images", "busy", "svgz", mode).absFileName());
617 PressableSvgWidget * busySVG = new PressableSvgWidget(fn);
618 statusBar()->addPermanentWidget(busySVG);
619 // make busy indicator square with 5px margins
620 busySVG->setMaximumSize(busySVG->height() - 5, busySVG->height() - 5);
623 connect(&d.processing_thread_watcher_, SIGNAL(started()),
624 busySVG, SLOT(show()));
625 connect(&d.processing_thread_watcher_, SIGNAL(finished()),
626 busySVG, SLOT(hide()));
627 connect(busySVG, SIGNAL(pressed()), this, SLOT(checkCancelBackground()));
629 QFontMetrics const fm(statusBar()->fontMetrics());
631 zoom_slider_ = new QSlider(Qt::Horizontal, statusBar());
632 // Small size slider for macOS to prevent the status bar from enlarging
633 zoom_slider_->setAttribute(Qt::WA_MacSmallSize);
634 #if (QT_VERSION >= QT_VERSION_CHECK(5, 11, 0))
635 zoom_slider_->setFixedWidth(fm.horizontalAdvance('x') * 15);
637 zoom_slider_->setFixedWidth(fm.width('x') * 15);
639 // Make the defaultZoom center
640 zoom_slider_->setRange(10, (lyxrc.defaultZoom * 2) - 10);
641 // Initialize proper zoom value
643 zoom_ratio_ = settings.value("zoom_ratio", 1.0).toDouble();
644 // Actual zoom value: default zoom + fractional offset
645 int zoom = (int)(lyxrc.defaultZoom * zoom_ratio_);
646 if (zoom < static_cast<int>(zoom_min_))
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 if (zoom < static_cast<int>(zoom_min_))
987 setCurrentZoom(zoom);
988 devel_mode_ = settings.value("devel_mode", devel_mode_).toBool();
989 settings.beginGroup("views");
990 settings.beginGroup(QString::number(id_));
991 QString const icon_key = "icon_size";
992 if (!settings.contains(icon_key))
995 //code below is skipped when when ~/.config/LyX is (re)created
996 setIconSize(d.iconSize(settings.value(icon_key).toString()));
998 zoom_value_->setVisible(settings.value("zoom_value_visible", true).toBool());
1000 bool const show_zoom_slider = settings.value("zoom_slider_visible", true).toBool();
1001 zoom_slider_->setVisible(show_zoom_slider);
1002 zoom_in_->setVisible(show_zoom_slider);
1003 zoom_out_->setVisible(show_zoom_slider);
1005 if (guiApp->platformName() == "qt4x11" || guiApp->platformName() == "xcb") {
1006 QPoint pos = settings.value("pos", QPoint(50, 50)).toPoint();
1007 QSize size = settings.value("size", QSize(690, 510)).toSize();
1011 // Work-around for bug #6034: the window ends up in an undetermined
1012 // state when trying to restore a maximized window when it is
1013 // already maximized.
1014 if (!(windowState() & Qt::WindowMaximized))
1015 if (!restoreGeometry(settings.value("geometry").toByteArray()))
1016 setGeometry(50, 50, 690, 510);
1019 // Make sure layout is correctly oriented.
1020 setLayoutDirection(qApp->layoutDirection());
1022 // Allow the toc and view-source dock widget to be restored if needed.
1024 if ((dialog = findOrBuild("toc", true)))
1025 // see bug 5082. At least setup title and enabled state.
1026 // Visibility will be adjusted by restoreState below.
1027 dialog->prepareView();
1028 if ((dialog = findOrBuild("view-source", true)))
1029 dialog->prepareView();
1030 if ((dialog = findOrBuild("progress", true)))
1031 dialog->prepareView();
1033 if (!restoreState(settings.value("layout").toByteArray(), 0))
1036 // init the toolbars that have not been restored
1037 for (auto const & tb_p : guiApp->toolbars()) {
1038 GuiToolbar * tb = toolbar(tb_p.name);
1039 if (tb && !tb->isRestored())
1040 initToolbar(tb_p.name);
1043 // update lock (all) toolbars positions
1044 updateLockToolbars();
1051 GuiToolbar * GuiView::toolbar(string const & name)
1053 ToolbarMap::iterator it = d.toolbars_.find(name);
1054 if (it != d.toolbars_.end())
1057 LYXERR(Debug::GUI, "Toolbar::display: no toolbar named " << name);
1062 void GuiView::updateLockToolbars()
1064 toolbarsMovable_ = false;
1065 for (ToolbarInfo const & info : guiApp->toolbars()) {
1066 GuiToolbar * tb = toolbar(info.name);
1067 if (tb && tb->isMovable())
1068 toolbarsMovable_ = true;
1070 #if QT_VERSION >= 0x050200
1071 // set unified mac toolbars only when not movable as recommended:
1072 // https://doc.qt.io/qt-5/qmainwindow.html#unifiedTitleAndToolBarOnMac-prop
1073 setUnifiedTitleAndToolBarOnMac(!toolbarsMovable_);
1078 void GuiView::constructToolbars()
1080 for (auto const & tb_p : d.toolbars_)
1082 d.toolbars_.clear();
1084 // I don't like doing this here, but the standard toolbar
1085 // destroys this object when it's destroyed itself (vfr)
1086 d.layout_ = new LayoutBox(*this);
1087 d.stack_widget_->addWidget(d.layout_);
1088 d.layout_->move(0,0);
1090 // extracts the toolbars from the backend
1091 for (ToolbarInfo const & inf : guiApp->toolbars())
1092 d.toolbars_[inf.name] = new GuiToolbar(inf, *this);
1094 DynamicMenuButton::resetIconCache();
1098 void GuiView::initToolbars()
1100 // extracts the toolbars from the backend
1101 for (ToolbarInfo const & inf : guiApp->toolbars())
1102 initToolbar(inf.name);
1106 void GuiView::initToolbar(string const & name)
1108 GuiToolbar * tb = toolbar(name);
1111 int const visibility = guiApp->toolbars().defaultVisibility(name);
1112 bool newline = !(visibility & Toolbars::SAMEROW);
1113 tb->setVisible(false);
1114 tb->setVisibility(visibility);
1116 if (visibility & Toolbars::TOP) {
1118 addToolBarBreak(Qt::TopToolBarArea);
1119 addToolBar(Qt::TopToolBarArea, tb);
1122 if (visibility & Toolbars::BOTTOM) {
1124 addToolBarBreak(Qt::BottomToolBarArea);
1125 addToolBar(Qt::BottomToolBarArea, tb);
1128 if (visibility & Toolbars::LEFT) {
1130 addToolBarBreak(Qt::LeftToolBarArea);
1131 addToolBar(Qt::LeftToolBarArea, tb);
1134 if (visibility & Toolbars::RIGHT) {
1136 addToolBarBreak(Qt::RightToolBarArea);
1137 addToolBar(Qt::RightToolBarArea, tb);
1140 if (visibility & Toolbars::ON)
1141 tb->setVisible(true);
1143 tb->setMovable(true);
1147 TocModels & GuiView::tocModels()
1149 return d.toc_models_;
1153 void GuiView::setFocus()
1155 LYXERR(Debug::DEBUG, "GuiView::setFocus()" << this);
1156 QMainWindow::setFocus();
1160 bool GuiView::hasFocus() const
1162 if (currentWorkArea())
1163 return currentWorkArea()->hasFocus();
1164 if (currentMainWorkArea())
1165 return currentMainWorkArea()->hasFocus();
1166 return d.bg_widget_->hasFocus();
1170 void GuiView::focusInEvent(QFocusEvent * e)
1172 LYXERR(Debug::DEBUG, "GuiView::focusInEvent()" << this);
1173 QMainWindow::focusInEvent(e);
1174 // Make sure guiApp points to the correct view.
1175 guiApp->setCurrentView(this);
1176 if (currentWorkArea())
1177 currentWorkArea()->setFocus();
1178 else if (currentMainWorkArea())
1179 currentMainWorkArea()->setFocus();
1181 d.bg_widget_->setFocus();
1185 void GuiView::showEvent(QShowEvent * e)
1187 LYXERR(Debug::GUI, "Passed Geometry "
1188 << size().height() << "x" << size().width()
1189 << "+" << pos().x() << "+" << pos().y());
1191 if (d.splitter_->count() == 0)
1192 // No work area, switch to the background widget.
1196 QMainWindow::showEvent(e);
1200 bool GuiView::closeScheduled()
1207 bool GuiView::prepareAllBuffersForLogout()
1209 Buffer * first = theBufferList().first();
1213 // First, iterate over all buffers and ask the users if unsaved
1214 // changes should be saved.
1215 // We cannot use a for loop as the buffer list cycles.
1218 if (!saveBufferIfNeeded(*b, false))
1220 b = theBufferList().next(b);
1221 } while (b != first);
1223 // Next, save session state
1224 // When a view/window was closed before without quitting LyX, there
1225 // are already entries in the lastOpened list.
1226 theSession().lastOpened().clear();
1233 /** Destroy only all tabbed WorkAreas. Destruction of other WorkAreas
1234 ** is responsibility of the container (e.g., dialog)
1236 void GuiView::closeEvent(QCloseEvent * close_event)
1238 LYXERR(Debug::DEBUG, "GuiView::closeEvent()");
1240 if (!GuiViewPrivate::busyBuffers.isEmpty()) {
1241 Alert::warning(_("Exit LyX"),
1242 _("LyX could not be closed because documents are being processed by LyX."));
1243 close_event->setAccepted(false);
1247 // If the user pressed the x (so we didn't call closeView
1248 // programmatically), we want to clear all existing entries.
1250 theSession().lastOpened().clear();
1255 // it can happen that this event arrives without selecting the view,
1256 // e.g. when clicking the close button on a background window.
1258 if (!closeWorkAreaAll()) {
1260 close_event->ignore();
1264 // Make sure that nothing will use this to be closed View.
1265 guiApp->unregisterView(this);
1267 if (isFullScreen()) {
1268 // Switch off fullscreen before closing.
1273 // Make sure the timer time out will not trigger a statusbar update.
1274 d.statusbar_timer_.stop();
1276 // Saving fullscreen requires additional tweaks in the toolbar code.
1277 // It wouldn't also work under linux natively.
1278 if (lyxrc.allow_geometry_session) {
1283 close_event->accept();
1287 void GuiView::dragEnterEvent(QDragEnterEvent * event)
1289 if (event->mimeData()->hasUrls())
1291 /// \todo Ask lyx-devel is this is enough:
1292 /// if (event->mimeData()->hasFormat("text/plain"))
1293 /// event->acceptProposedAction();
1297 void GuiView::dropEvent(QDropEvent * event)
1299 QList<QUrl> files = event->mimeData()->urls();
1300 if (files.isEmpty())
1303 LYXERR(Debug::GUI, "GuiView::dropEvent: got URLs!");
1304 for (int i = 0; i != files.size(); ++i) {
1305 string const file = os::internal_path(fromqstr(
1306 files.at(i).toLocalFile()));
1310 string const ext = support::getExtension(file);
1311 vector<const Format *> found_formats;
1313 // Find all formats that have the correct extension.
1314 for (const Format * fmt : theConverters().importableFormats())
1315 if (fmt->hasExtension(ext))
1316 found_formats.push_back(fmt);
1319 if (!found_formats.empty()) {
1320 if (found_formats.size() > 1) {
1321 //FIXME: show a dialog to choose the correct importable format
1322 LYXERR(Debug::FILES,
1323 "Multiple importable formats found, selecting first");
1325 string const arg = found_formats[0]->name() + " " + file;
1326 cmd = FuncRequest(LFUN_BUFFER_IMPORT, arg);
1329 //FIXME: do we have to explicitly check whether it's a lyx file?
1330 LYXERR(Debug::FILES,
1331 "No formats found, trying to open it as a lyx file");
1332 cmd = FuncRequest(LFUN_FILE_OPEN, file);
1334 // add the functions to the queue
1335 guiApp->addToFuncRequestQueue(cmd);
1338 // now process the collected functions. We perform the events
1339 // asynchronously. This prevents potential problems in case the
1340 // BufferView is closed within an event.
1341 guiApp->processFuncRequestQueueAsync();
1345 void GuiView::message(docstring const & str)
1347 if (ForkedProcess::iAmAChild())
1350 // call is moved to GUI-thread by GuiProgress
1351 d.progress_->appendMessage(toqstr(str));
1355 void GuiView::clearMessageText()
1357 message(docstring());
1361 void GuiView::updateStatusBarMessage(QString const & str)
1363 statusBar()->showMessage(str);
1364 d.statusbar_timer_.stop();
1365 d.statusbar_timer_.start(3000);
1369 void GuiView::clearMessage()
1371 // FIXME: This code was introduced in r19643 to fix bug #4123. However,
1372 // the hasFocus function mostly returns false, even if the focus is on
1373 // a workarea in this view.
1377 d.statusbar_timer_.stop();
1381 void GuiView::updateWindowTitle(GuiWorkArea * wa)
1383 if (wa != d.current_work_area_
1384 || wa->bufferView().buffer().isInternal())
1386 Buffer const & buf = wa->bufferView().buffer();
1387 // Set the windows title
1388 docstring title = buf.fileName().displayName(130) + from_ascii("[*]");
1389 if (buf.notifiesExternalModification()) {
1390 title = bformat(_("%1$s (modified externally)"), title);
1391 // If the external modification status has changed, then maybe the status of
1392 // buffer-save has changed too.
1396 title += from_ascii(" - LyX");
1398 setWindowTitle(toqstr(title));
1399 // Sets the path for the window: this is used by OSX to
1400 // allow a context click on the title bar showing a menu
1401 // with the path up to the file
1402 setWindowFilePath(toqstr(buf.absFileName()));
1403 // Tell Qt whether the current document is changed
1404 setWindowModified(!buf.isClean());
1406 if (buf.params().shell_escape)
1407 shell_escape_->show();
1409 shell_escape_->hide();
1411 if (buf.hasReadonlyFlag())
1416 if (buf.lyxvc().inUse()) {
1417 version_control_->show();
1418 version_control_->setText(toqstr(buf.lyxvc().vcstatus()));
1420 version_control_->hide();
1424 void GuiView::on_currentWorkAreaChanged(GuiWorkArea * wa)
1426 if (d.current_work_area_)
1427 // disconnect the current work area from all slots
1428 QObject::disconnect(d.current_work_area_, nullptr, this, nullptr);
1430 disconnectBufferView();
1431 connectBufferView(wa->bufferView());
1432 connectBuffer(wa->bufferView().buffer());
1433 d.current_work_area_ = wa;
1434 QObject::connect(wa, SIGNAL(titleChanged(GuiWorkArea *)),
1435 this, SLOT(updateWindowTitle(GuiWorkArea *)));
1436 QObject::connect(wa, SIGNAL(busy(bool)),
1437 this, SLOT(setBusy(bool)));
1438 // connection of a signal to a signal
1439 QObject::connect(wa, SIGNAL(bufferViewChanged()),
1440 this, SIGNAL(bufferViewChanged()));
1441 Q_EMIT updateWindowTitle(wa);
1442 Q_EMIT bufferViewChanged();
1446 void GuiView::onBufferViewChanged()
1449 // Buffer-dependent dialogs must be updated. This is done here because
1450 // some dialogs require buffer()->text.
1452 zoom_slider_->setEnabled(currentBufferView());
1453 zoom_value_->setEnabled(currentBufferView());
1454 zoom_in_->setEnabled(currentBufferView());
1455 zoom_out_->setEnabled(currentBufferView());
1459 void GuiView::on_lastWorkAreaRemoved()
1462 // We already are in a close event. Nothing more to do.
1465 if (d.splitter_->count() > 1)
1466 // We have a splitter so don't close anything.
1469 // Reset and updates the dialogs.
1470 Q_EMIT bufferViewChanged();
1475 if (lyxrc.open_buffers_in_tabs)
1476 // Nothing more to do, the window should stay open.
1479 if (guiApp->viewIds().size() > 1) {
1485 // On Mac we also close the last window because the application stay
1486 // resident in memory. On other platforms we don't close the last
1487 // window because this would quit the application.
1493 void GuiView::updateStatusBar()
1495 // let the user see the explicit message
1496 if (d.statusbar_timer_.isActive())
1503 void GuiView::showMessage()
1507 QString msg = toqstr(theGuiApp()->viewStatusMessage());
1508 if (msg.isEmpty()) {
1509 BufferView const * bv = currentBufferView();
1511 msg = toqstr(bv->cursor().currentState(devel_mode_));
1513 msg = qt_("Welcome to LyX!");
1515 statusBar()->showMessage(msg);
1519 bool GuiView::event(QEvent * e)
1523 // Useful debug code:
1524 //case QEvent::ActivationChange:
1525 //case QEvent::WindowDeactivate:
1526 //case QEvent::Paint:
1527 //case QEvent::Enter:
1528 //case QEvent::Leave:
1529 //case QEvent::HoverEnter:
1530 //case QEvent::HoverLeave:
1531 //case QEvent::HoverMove:
1532 //case QEvent::StatusTip:
1533 //case QEvent::DragEnter:
1534 //case QEvent::DragLeave:
1535 //case QEvent::Drop:
1538 case QEvent::WindowStateChange: {
1539 QWindowStateChangeEvent * ev = (QWindowStateChangeEvent*)e;
1540 bool ofstate = (ev->oldState() & Qt::WindowFullScreen);
1541 bool result = QMainWindow::event(e);
1542 bool nfstate = (windowState() & Qt::WindowFullScreen);
1543 if (!ofstate && nfstate) {
1544 LYXERR(Debug::DEBUG, "GuiView: WindowStateChange(): full-screen " << nfstate);
1545 // switch to full-screen state
1546 if (lyxrc.full_screen_statusbar)
1547 statusBar()->hide();
1548 if (lyxrc.full_screen_menubar)
1550 if (lyxrc.full_screen_toolbars) {
1551 for (auto const & tb_p : d.toolbars_)
1552 if (tb_p.second->isVisibilityOn() && tb_p.second->isVisible())
1553 tb_p.second->hide();
1555 for (int i = 0; i != d.splitter_->count(); ++i)
1556 d.tabWorkArea(i)->setFullScreen(true);
1557 #if QT_VERSION > 0x050903
1558 //Qt's 5.9.4 ba44cdae38406c safe area measures won't allow us to go negative in margins
1559 setAttribute(Qt::WA_ContentsMarginsRespectsSafeArea, false);
1561 setContentsMargins(-2, -2, -2, -2);
1563 hideDialogs("prefs", nullptr);
1564 } else if (ofstate && !nfstate) {
1565 LYXERR(Debug::DEBUG, "GuiView: WindowStateChange(): full-screen " << nfstate);
1566 // switch back from full-screen state
1567 if (lyxrc.full_screen_statusbar && !statusBar()->isVisible())
1568 statusBar()->show();
1569 if (lyxrc.full_screen_menubar && !menuBar()->isVisible())
1571 if (lyxrc.full_screen_toolbars) {
1572 for (auto const & tb_p : d.toolbars_)
1573 if (tb_p.second->isVisibilityOn() && !tb_p.second->isVisible())
1574 tb_p.second->show();
1577 for (int i = 0; i != d.splitter_->count(); ++i)
1578 d.tabWorkArea(i)->setFullScreen(false);
1579 #if QT_VERSION > 0x050903
1580 setAttribute(Qt::WA_ContentsMarginsRespectsSafeArea, true);
1582 setContentsMargins(0, 0, 0, 0);
1587 case QEvent::WindowActivate: {
1588 GuiView * old_view = guiApp->currentView();
1589 if (this == old_view) {
1591 return QMainWindow::event(e);
1593 if (old_view && old_view->currentBufferView()) {
1594 // save current selection to the selection buffer to allow
1595 // middle-button paste in this window.
1596 cap::saveSelection(old_view->currentBufferView()->cursor());
1598 guiApp->setCurrentView(this);
1599 if (d.current_work_area_)
1600 on_currentWorkAreaChanged(d.current_work_area_);
1604 return QMainWindow::event(e);
1607 case QEvent::ShortcutOverride: {
1609 if (isFullScreen() && menuBar()->isHidden()) {
1610 QKeyEvent * ke = static_cast<QKeyEvent*>(e);
1611 // FIXME: we should also try to detect special LyX shortcut such as
1612 // Alt-P and Alt-M. Right now there is a hack in
1613 // GuiWorkArea::processKeySym() that hides again the menubar for
1615 if (ke->modifiers() & Qt::AltModifier && ke->key() != Qt::Key_Alt) {
1617 return QMainWindow::event(e);
1620 return QMainWindow::event(e);
1623 case QEvent::ApplicationPaletteChange: {
1624 // runtime switch from/to dark mode
1626 return QMainWindow::event(e);
1629 case QEvent::Gesture: {
1630 QGestureEvent *ge = static_cast<QGestureEvent*>(e);
1631 QGesture *gp = ge->gesture(Qt::PinchGesture);
1633 QPinchGesture *pinch = static_cast<QPinchGesture *>(gp);
1634 QPinchGesture::ChangeFlags changeFlags = pinch->changeFlags();
1635 if (changeFlags & QPinchGesture::ScaleFactorChanged) {
1636 qreal factor = lyxrc.currentZoom*pinch->scaleFactor();
1637 //factor = ceil(factor/20)*20;
1638 zoomValueChanged(factor);
1641 return QMainWindow::event(e);
1645 return QMainWindow::event(e);
1649 void GuiView::resetWindowTitle()
1651 setWindowTitle(qt_("LyX"));
1654 bool GuiView::focusNextPrevChild(bool /*next*/)
1661 bool GuiView::busy() const
1667 void GuiView::setBusy(bool busy)
1669 bool const busy_before = busy_ > 0;
1670 busy ? ++busy_ : --busy_;
1671 if ((busy_ > 0) == busy_before)
1672 // busy state didn't change
1676 QApplication::setOverrideCursor(Qt::WaitCursor);
1679 QApplication::restoreOverrideCursor();
1684 void GuiView::resetCommandExecute()
1686 command_execute_ = false;
1691 double GuiView::pixelRatio() const
1693 #if QT_VERSION >= 0x050000
1694 return qt_scale_factor * devicePixelRatio();
1701 GuiWorkArea * GuiView::workArea(int index)
1703 if (TabWorkArea * twa = d.currentTabWorkArea())
1704 if (index < twa->count())
1705 return twa->workArea(index);
1710 GuiWorkArea * GuiView::workArea(Buffer & buffer)
1712 if (currentWorkArea()
1713 && ¤tWorkArea()->bufferView().buffer() == &buffer)
1714 return currentWorkArea();
1715 if (TabWorkArea * twa = d.currentTabWorkArea())
1716 return twa->workArea(buffer);
1721 GuiWorkArea * GuiView::addWorkArea(Buffer & buffer)
1723 // Automatically create a TabWorkArea if there are none yet.
1724 TabWorkArea * tab_widget = d.splitter_->count()
1725 ? d.currentTabWorkArea() : addTabWorkArea();
1726 return tab_widget->addWorkArea(buffer, *this);
1730 TabWorkArea * GuiView::addTabWorkArea()
1732 TabWorkArea * twa = new TabWorkArea;
1733 QObject::connect(twa, SIGNAL(currentWorkAreaChanged(GuiWorkArea *)),
1734 this, SLOT(on_currentWorkAreaChanged(GuiWorkArea *)));
1735 QObject::connect(twa, SIGNAL(lastWorkAreaRemoved()),
1736 this, SLOT(on_lastWorkAreaRemoved()));
1738 d.splitter_->addWidget(twa);
1739 d.stack_widget_->setCurrentWidget(d.splitter_);
1744 GuiWorkArea const * GuiView::currentWorkArea() const
1746 return d.current_work_area_;
1750 GuiWorkArea * GuiView::currentWorkArea()
1752 return d.current_work_area_;
1756 GuiWorkArea const * GuiView::currentMainWorkArea() const
1758 if (!d.currentTabWorkArea())
1760 return d.currentTabWorkArea()->currentWorkArea();
1764 GuiWorkArea * GuiView::currentMainWorkArea()
1766 if (!d.currentTabWorkArea())
1768 return d.currentTabWorkArea()->currentWorkArea();
1772 void GuiView::setCurrentWorkArea(GuiWorkArea * wa)
1774 LYXERR(Debug::DEBUG, "Setting current wa: " << wa << endl);
1776 d.current_work_area_ = nullptr;
1778 Q_EMIT bufferViewChanged();
1782 // FIXME: I've no clue why this is here and why it accesses
1783 // theGuiApp()->currentView, which might be 0 (bug 6464).
1784 // See also 27525 (vfr).
1785 if (theGuiApp()->currentView() == this
1786 && theGuiApp()->currentView()->currentWorkArea() == wa)
1789 if (currentBufferView())
1790 cap::saveSelection(currentBufferView()->cursor());
1792 theGuiApp()->setCurrentView(this);
1793 d.current_work_area_ = wa;
1795 // We need to reset this now, because it will need to be
1796 // right if the tabWorkArea gets reset in the for loop. We
1797 // will change it back if we aren't in that case.
1798 GuiWorkArea * const old_cmwa = d.current_main_work_area_;
1799 d.current_main_work_area_ = wa;
1801 for (int i = 0; i != d.splitter_->count(); ++i) {
1802 if (d.tabWorkArea(i)->setCurrentWorkArea(wa)) {
1803 LYXERR(Debug::DEBUG, "Current wa: " << currentWorkArea()
1804 << ", Current main wa: " << currentMainWorkArea());
1809 d.current_main_work_area_ = old_cmwa;
1811 LYXERR(Debug::DEBUG, "This is not a tabbed wa");
1812 on_currentWorkAreaChanged(wa);
1813 BufferView & bv = wa->bufferView();
1814 bv.cursor().fixIfBroken();
1816 wa->setUpdatesEnabled(true);
1817 LYXERR(Debug::DEBUG, "Current wa: " << currentWorkArea() << ", Current main wa: " << currentMainWorkArea());
1821 void GuiView::removeWorkArea(GuiWorkArea * wa)
1823 LASSERT(wa, return);
1824 if (wa == d.current_work_area_) {
1826 disconnectBufferView();
1827 d.current_work_area_ = nullptr;
1828 d.current_main_work_area_ = nullptr;
1831 bool found_twa = false;
1832 for (int i = 0; i != d.splitter_->count(); ++i) {
1833 TabWorkArea * twa = d.tabWorkArea(i);
1834 if (twa->removeWorkArea(wa)) {
1835 // Found in this tab group, and deleted the GuiWorkArea.
1837 if (twa->count() != 0) {
1838 if (d.current_work_area_ == nullptr)
1839 // This means that we are closing the current GuiWorkArea, so
1840 // switch to the next GuiWorkArea in the found TabWorkArea.
1841 setCurrentWorkArea(twa->currentWorkArea());
1843 // No more WorkAreas in this tab group, so delete it.
1850 // It is not a tabbed work area (i.e., the search work area), so it
1851 // should be deleted by other means.
1852 LASSERT(found_twa, return);
1854 if (d.current_work_area_ == nullptr) {
1855 if (d.splitter_->count() != 0) {
1856 TabWorkArea * twa = d.currentTabWorkArea();
1857 setCurrentWorkArea(twa->currentWorkArea());
1859 // No more work areas, switch to the background widget.
1860 setCurrentWorkArea(nullptr);
1866 LayoutBox * GuiView::getLayoutDialog() const
1872 void GuiView::updateLayoutList()
1875 d.layout_->updateContents(false);
1879 void GuiView::updateToolbars()
1881 if (d.current_work_area_) {
1883 if (d.current_work_area_->bufferView().cursor().inMathed()
1884 && !d.current_work_area_->bufferView().cursor().inRegexped())
1885 context |= Toolbars::MATH;
1886 if (lyx::getStatus(FuncRequest(LFUN_LAYOUT_TABULAR)).enabled())
1887 context |= Toolbars::TABLE;
1888 if (currentBufferView()->buffer().areChangesPresent()
1889 || (lyx::getStatus(FuncRequest(LFUN_CHANGES_TRACK)).enabled()
1890 && lyx::getStatus(FuncRequest(LFUN_CHANGES_TRACK)).onOff(true))
1891 || (lyx::getStatus(FuncRequest(LFUN_CHANGES_OUTPUT)).enabled()
1892 && lyx::getStatus(FuncRequest(LFUN_CHANGES_OUTPUT)).onOff(true)))
1893 context |= Toolbars::REVIEW;
1894 if (lyx::getStatus(FuncRequest(LFUN_IN_MATHMACROTEMPLATE)).enabled())
1895 context |= Toolbars::MATHMACROTEMPLATE;
1896 if (lyx::getStatus(FuncRequest(LFUN_IN_IPA)).enabled())
1897 context |= Toolbars::IPA;
1898 if (command_execute_)
1899 context |= Toolbars::MINIBUFFER;
1900 if (minibuffer_focus_) {
1901 context |= Toolbars::MINIBUFFER_FOCUS;
1902 minibuffer_focus_ = false;
1905 for (auto const & tb_p : d.toolbars_)
1906 tb_p.second->update(context);
1908 for (auto const & tb_p : d.toolbars_)
1909 tb_p.second->update();
1913 void GuiView::refillToolbars()
1915 DynamicMenuButton::resetIconCache();
1916 for (auto const & tb_p : d.toolbars_)
1917 tb_p.second->refill();
1921 void GuiView::setBuffer(Buffer * newBuffer, bool switch_to)
1923 LYXERR(Debug::DEBUG, "Setting buffer: " << newBuffer << endl);
1924 LASSERT(newBuffer, return);
1926 GuiWorkArea * wa = workArea(*newBuffer);
1927 if (wa == nullptr) {
1929 newBuffer->masterBuffer()->updateBuffer();
1931 wa = addWorkArea(*newBuffer);
1932 // scroll to the position when the BufferView was last closed
1933 if (lyxrc.use_lastfilepos) {
1934 LastFilePosSection::FilePos filepos =
1935 theSession().lastFilePos().load(newBuffer->fileName());
1936 wa->bufferView().moveToPosition(filepos.pit, filepos.pos, 0, 0);
1939 //Disconnect the old buffer...there's no new one.
1942 connectBuffer(*newBuffer);
1943 connectBufferView(wa->bufferView());
1945 setCurrentWorkArea(wa);
1949 void GuiView::connectBuffer(Buffer & buf)
1951 buf.setGuiDelegate(this);
1955 void GuiView::disconnectBuffer()
1957 if (d.current_work_area_)
1958 d.current_work_area_->bufferView().buffer().setGuiDelegate(nullptr);
1962 void GuiView::connectBufferView(BufferView & bv)
1964 bv.setGuiDelegate(this);
1968 void GuiView::disconnectBufferView()
1970 if (d.current_work_area_)
1971 d.current_work_area_->bufferView().setGuiDelegate(nullptr);
1975 void GuiView::errors(string const & error_type, bool from_master)
1977 BufferView const * const bv = currentBufferView();
1981 ErrorList const & el = from_master ?
1982 bv->buffer().masterBuffer()->errorList(error_type) :
1983 bv->buffer().errorList(error_type);
1988 string err = error_type;
1990 err = "from_master|" + error_type;
1991 showDialog("errorlist", err);
1995 void GuiView::updateTocItem(string const & type, DocIterator const & dit)
1997 d.toc_models_.updateItem(toqstr(type), dit);
2001 void GuiView::structureChanged()
2003 // This is called from the Buffer, which has no way to ensure that cursors
2004 // in BufferView remain valid.
2005 if (documentBufferView())
2006 documentBufferView()->cursor().sanitize();
2007 // FIXME: This is slightly expensive, though less than the tocBackend update
2008 // (#9880). This also resets the view in the Toc Widget (#6675).
2009 d.toc_models_.reset(documentBufferView());
2010 // Navigator needs more than a simple update in this case. It needs to be
2012 updateDialog("toc", "");
2016 void GuiView::updateDialog(string const & name, string const & sdata)
2018 if (!isDialogVisible(name))
2021 map<string, DialogPtr>::const_iterator it = d.dialogs_.find(name);
2022 if (it == d.dialogs_.end())
2025 Dialog * const dialog = it->second.get();
2026 if (dialog->isVisibleView())
2027 dialog->initialiseParams(sdata);
2031 BufferView * GuiView::documentBufferView()
2033 return currentMainWorkArea()
2034 ? ¤tMainWorkArea()->bufferView()
2039 BufferView const * GuiView::documentBufferView() const
2041 return currentMainWorkArea()
2042 ? ¤tMainWorkArea()->bufferView()
2047 BufferView * GuiView::currentBufferView()
2049 return d.current_work_area_ ? &d.current_work_area_->bufferView() : nullptr;
2053 BufferView const * GuiView::currentBufferView() const
2055 return d.current_work_area_ ? &d.current_work_area_->bufferView() : nullptr;
2059 docstring GuiView::GuiViewPrivate::autosaveAndDestroy(
2060 Buffer const * orig, Buffer * clone)
2062 bool const success = clone->autoSave();
2064 busyBuffers.remove(orig);
2066 ? _("Automatic save done.")
2067 : _("Automatic save failed!");
2071 void GuiView::autoSave()
2073 LYXERR(Debug::INFO, "Running autoSave()");
2075 Buffer * buffer = documentBufferView()
2076 ? &documentBufferView()->buffer() : nullptr;
2078 resetAutosaveTimers();
2082 GuiViewPrivate::busyBuffers.insert(buffer);
2083 QFuture<docstring> f = QtConcurrent::run(GuiViewPrivate::autosaveAndDestroy,
2084 buffer, buffer->cloneBufferOnly());
2085 d.autosave_watcher_.setFuture(f);
2086 resetAutosaveTimers();
2090 void GuiView::resetAutosaveTimers()
2093 d.autosave_timeout_.restart();
2097 bool GuiView::getStatus(FuncRequest const & cmd, FuncStatus & flag)
2100 Buffer * buf = currentBufferView()
2101 ? ¤tBufferView()->buffer() : nullptr;
2102 Buffer * doc_buffer = documentBufferView()
2103 ? &(documentBufferView()->buffer()) : nullptr;
2106 /* In LyX/Mac, when a dialog is open, the menus of the
2107 application can still be accessed without giving focus to
2108 the main window. In this case, we want to disable the menu
2109 entries that are buffer-related.
2110 This code must not be used on Linux and Windows, since it
2111 would disable buffer-related entries when hovering over the
2112 menu (see bug #9574).
2114 if (cmd.origin() == FuncRequest::MENU && !hasFocus()) {
2120 // Check whether we need a buffer
2121 if (!lyxaction.funcHasFlag(cmd.action(), LyXAction::NoBuffer) && !buf) {
2122 // no, exit directly
2123 flag.message(from_utf8(N_("Command not allowed with"
2124 "out any document open")));
2125 flag.setEnabled(false);
2129 if (cmd.origin() == FuncRequest::TOC) {
2130 GuiToc * toc = static_cast<GuiToc*>(findOrBuild("toc", false));
2131 if (!toc || !toc->getStatus(documentBufferView()->cursor(), cmd, flag))
2132 flag.setEnabled(false);
2136 switch(cmd.action()) {
2137 case LFUN_BUFFER_IMPORT:
2140 case LFUN_MASTER_BUFFER_EXPORT:
2142 && (doc_buffer->parent() != nullptr
2143 || doc_buffer->hasChildren())
2144 && !d.processing_thread_watcher_.isRunning()
2145 // this launches a dialog, which would be in the wrong Buffer
2146 && !(::lyx::operator==(cmd.argument(), "custom"));
2149 case LFUN_MASTER_BUFFER_UPDATE:
2150 case LFUN_MASTER_BUFFER_VIEW:
2152 && (doc_buffer->parent() != nullptr
2153 || doc_buffer->hasChildren())
2154 && !d.processing_thread_watcher_.isRunning();
2157 case LFUN_BUFFER_UPDATE:
2158 case LFUN_BUFFER_VIEW: {
2159 if (!doc_buffer || d.processing_thread_watcher_.isRunning()) {
2163 string format = to_utf8(cmd.argument());
2164 if (cmd.argument().empty())
2165 format = doc_buffer->params().getDefaultOutputFormat();
2166 enable = doc_buffer->params().isExportable(format, true);
2170 case LFUN_BUFFER_RELOAD:
2171 enable = doc_buffer && !doc_buffer->isUnnamed()
2172 && doc_buffer->fileName().exists()
2173 && (!doc_buffer->isClean() || doc_buffer->notifiesExternalModification());
2176 case LFUN_BUFFER_RESET_EXPORT:
2177 enable = doc_buffer != nullptr;
2180 case LFUN_BUFFER_CHILD_OPEN:
2181 enable = doc_buffer != nullptr;
2184 case LFUN_MASTER_BUFFER_FORALL: {
2185 if (doc_buffer == nullptr) {
2186 flag.message(from_utf8(N_("Command not allowed without a buffer open")));
2190 FuncRequest const cmdToPass = lyxaction.lookupFunc(cmd.getLongArg(0));
2191 if (cmdToPass.action() == LFUN_UNKNOWN_ACTION) {
2192 flag.message(from_utf8(N_("Invalid argument of master-buffer-forall")));
2197 for (Buffer * buf : doc_buffer->allRelatives()) {
2198 GuiWorkArea * wa = workArea(*buf);
2201 if (wa->bufferView().getStatus(cmdToPass, flag)) {
2202 enable = flag.enabled();
2209 case LFUN_BUFFER_WRITE:
2210 enable = doc_buffer && (doc_buffer->isUnnamed()
2211 || (!doc_buffer->isClean()
2212 || cmd.argument() == "force"));
2215 //FIXME: This LFUN should be moved to GuiApplication.
2216 case LFUN_BUFFER_WRITE_ALL: {
2217 // We enable the command only if there are some modified buffers
2218 Buffer * first = theBufferList().first();
2223 // We cannot use a for loop as the buffer list is a cycle.
2225 if (!b->isClean()) {
2229 b = theBufferList().next(b);
2230 } while (b != first);
2234 case LFUN_BUFFER_EXTERNAL_MODIFICATION_CLEAR:
2235 enable = doc_buffer && doc_buffer->notifiesExternalModification();
2238 case LFUN_BUFFER_EXPORT: {
2239 if (!doc_buffer || d.processing_thread_watcher_.isRunning()) {
2243 return doc_buffer->getStatus(cmd, flag);
2246 case LFUN_BUFFER_EXPORT_AS:
2247 if (!doc_buffer || d.processing_thread_watcher_.isRunning()) {
2252 case LFUN_BUFFER_WRITE_AS:
2253 case LFUN_BUFFER_WRITE_AS_TEMPLATE:
2254 enable = doc_buffer != nullptr;
2257 case LFUN_EXPORT_CANCEL:
2258 enable = d.processing_thread_watcher_.isRunning();
2261 case LFUN_BUFFER_CLOSE:
2262 case LFUN_VIEW_CLOSE:
2263 enable = doc_buffer != nullptr;
2266 case LFUN_BUFFER_CLOSE_ALL:
2267 enable = theBufferList().last() != theBufferList().first();
2270 case LFUN_BUFFER_CHKTEX: {
2271 // hide if we have no checktex command
2272 if (lyxrc.chktex_command.empty()) {
2273 flag.setUnknown(true);
2277 if (!doc_buffer || !doc_buffer->params().isLatex()
2278 || d.processing_thread_watcher_.isRunning()) {
2279 // grey out, don't hide
2287 case LFUN_VIEW_SPLIT:
2288 if (cmd.getArg(0) == "vertical")
2289 enable = doc_buffer && (d.splitter_->count() == 1 ||
2290 d.splitter_->orientation() == Qt::Vertical);
2292 enable = doc_buffer && (d.splitter_->count() == 1 ||
2293 d.splitter_->orientation() == Qt::Horizontal);
2296 case LFUN_TAB_GROUP_CLOSE:
2297 enable = d.tabWorkAreaCount() > 1;
2300 case LFUN_DEVEL_MODE_TOGGLE:
2301 flag.setOnOff(devel_mode_);
2304 case LFUN_TOOLBAR_SET: {
2305 string const name = cmd.getArg(0);
2306 string const state = cmd.getArg(1);
2307 if (name.empty() || state.empty()) {
2309 docstring const msg =
2310 _("Function toolbar-set requires two arguments!");
2314 if (state != "on" && state != "off" && state != "auto") {
2316 docstring const msg =
2317 bformat(_("Invalid argument \"%1$s\" to function toolbar-set!"),
2322 if (GuiToolbar * t = toolbar(name)) {
2323 bool const autovis = t->visibility() & Toolbars::AUTO;
2325 flag.setOnOff(t->isVisible() && !autovis);
2326 else if (state == "off")
2327 flag.setOnOff(!t->isVisible() && !autovis);
2328 else if (state == "auto")
2329 flag.setOnOff(autovis);
2332 docstring const msg =
2333 bformat(_("Unknown toolbar \"%1$s\""), from_utf8(name));
2339 case LFUN_TOOLBAR_TOGGLE: {
2340 string const name = cmd.getArg(0);
2341 if (GuiToolbar * t = toolbar(name))
2342 flag.setOnOff(t->isVisible());
2345 docstring const msg =
2346 bformat(_("Unknown toolbar \"%1$s\""), from_utf8(name));
2352 case LFUN_TOOLBAR_MOVABLE: {
2353 string const name = cmd.getArg(0);
2354 // use negation since locked == !movable
2356 // toolbar name * locks all toolbars
2357 flag.setOnOff(!toolbarsMovable_);
2358 else if (GuiToolbar * t = toolbar(name))
2359 flag.setOnOff(!(t->isMovable()));
2362 docstring const msg =
2363 bformat(_("Unknown toolbar \"%1$s\""), from_utf8(name));
2369 case LFUN_ICON_SIZE:
2370 flag.setOnOff(d.iconSize(cmd.argument()) == iconSize());
2373 case LFUN_DROP_LAYOUTS_CHOICE:
2374 enable = buf != nullptr;
2377 case LFUN_UI_TOGGLE:
2378 if (cmd.argument() == "zoom") {
2379 flag.setOnOff(zoom_value_ ? zoom_value_->isVisible() : false);
2380 } else if (cmd.argument() == "zoomslider") {
2381 flag.setOnOff(zoom_slider_ ? zoom_slider_->isVisible() : false);
2383 flag.setOnOff(isFullScreen());
2386 case LFUN_DIALOG_DISCONNECT_INSET:
2389 case LFUN_DIALOG_HIDE:
2390 // FIXME: should we check if the dialog is shown?
2393 case LFUN_DIALOG_TOGGLE:
2394 flag.setOnOff(isDialogVisible(cmd.getArg(0)));
2397 case LFUN_DIALOG_SHOW: {
2398 string const name = cmd.getArg(0);
2400 enable = name == "aboutlyx"
2401 || name == "file" //FIXME: should be removed.
2402 || name == "lyxfiles"
2404 || name == "texinfo"
2405 || name == "progress"
2406 || name == "compare";
2407 else if (name == "character" || name == "symbols"
2408 || name == "mathdelimiter" || name == "mathmatrix") {
2409 if (!buf || buf->isReadonly())
2412 Cursor const & cur = currentBufferView()->cursor();
2413 enable = !(cur.inTexted() && cur.paragraph().isPassThru());
2416 else if (name == "latexlog")
2417 enable = FileName(doc_buffer->logName()).isReadableFile();
2418 else if (name == "spellchecker")
2419 enable = theSpellChecker()
2420 && !doc_buffer->text().empty();
2421 else if (name == "vclog")
2422 enable = doc_buffer->lyxvc().inUse();
2426 case LFUN_DIALOG_UPDATE: {
2427 string const name = cmd.getArg(0);
2429 enable = name == "prefs";
2433 case LFUN_COMMAND_EXECUTE:
2435 case LFUN_MENU_OPEN:
2436 // Nothing to check.
2439 case LFUN_COMPLETION_INLINE:
2440 if (!d.current_work_area_
2441 || !d.current_work_area_->completer().inlinePossible(
2442 currentBufferView()->cursor()))
2446 case LFUN_COMPLETION_POPUP:
2447 if (!d.current_work_area_
2448 || !d.current_work_area_->completer().popupPossible(
2449 currentBufferView()->cursor()))
2454 if (!d.current_work_area_
2455 || !d.current_work_area_->completer().inlinePossible(
2456 currentBufferView()->cursor()))
2460 case LFUN_COMPLETION_ACCEPT:
2461 if (!d.current_work_area_
2462 || (!d.current_work_area_->completer().popupVisible()
2463 && !d.current_work_area_->completer().inlineVisible()
2464 && !d.current_work_area_->completer().completionAvailable()))
2468 case LFUN_COMPLETION_CANCEL:
2469 if (!d.current_work_area_
2470 || (!d.current_work_area_->completer().popupVisible()
2471 && !d.current_work_area_->completer().inlineVisible()))
2475 case LFUN_BUFFER_ZOOM_OUT:
2476 case LFUN_BUFFER_ZOOM_IN: {
2477 // only diff between these two is that the default for ZOOM_OUT
2479 bool const neg_zoom =
2480 convert<int>(cmd.argument()) < 0 ||
2481 (cmd.action() == LFUN_BUFFER_ZOOM_OUT && cmd.argument().empty());
2482 if (lyxrc.currentZoom <= zoom_min_ && neg_zoom) {
2483 docstring const msg =
2484 bformat(_("Zoom level cannot be less than %1$d%."), zoom_min_);
2488 enable = doc_buffer;
2492 case LFUN_BUFFER_ZOOM: {
2493 bool const less_than_min_zoom =
2494 !cmd.argument().empty() && convert<int>(cmd.argument()) < zoom_min_;
2495 if (lyxrc.currentZoom <= zoom_min_ && less_than_min_zoom) {
2496 docstring const msg =
2497 bformat(_("Zoom level cannot be less than %1$d%."), zoom_min_);
2500 } else if (cmd.argument().empty() && lyxrc.currentZoom == lyxrc.defaultZoom)
2503 enable = doc_buffer;
2507 case LFUN_BUFFER_MOVE_NEXT:
2508 case LFUN_BUFFER_MOVE_PREVIOUS:
2509 // we do not cycle when moving
2510 case LFUN_BUFFER_NEXT:
2511 case LFUN_BUFFER_PREVIOUS:
2512 // because we cycle, it doesn't matter whether on first or last
2513 enable = (d.currentTabWorkArea()->count() > 1);
2515 case LFUN_BUFFER_SWITCH:
2516 // toggle on the current buffer, but do not toggle off
2517 // the other ones (is that a good idea?)
2519 && to_utf8(cmd.argument()) == doc_buffer->absFileName())
2520 flag.setOnOff(true);
2523 case LFUN_VC_REGISTER:
2524 enable = doc_buffer && !doc_buffer->lyxvc().inUse();
2526 case LFUN_VC_RENAME:
2527 enable = doc_buffer && doc_buffer->lyxvc().renameEnabled();
2530 enable = doc_buffer && doc_buffer->lyxvc().copyEnabled();
2532 case LFUN_VC_CHECK_IN:
2533 enable = doc_buffer && doc_buffer->lyxvc().checkInEnabled();
2535 case LFUN_VC_CHECK_OUT:
2536 enable = doc_buffer && doc_buffer->lyxvc().checkOutEnabled();
2538 case LFUN_VC_LOCKING_TOGGLE:
2539 enable = doc_buffer && !doc_buffer->hasReadonlyFlag()
2540 && doc_buffer->lyxvc().lockingToggleEnabled();
2541 flag.setOnOff(enable && doc_buffer->lyxvc().locking());
2543 case LFUN_VC_REVERT:
2544 enable = doc_buffer && doc_buffer->lyxvc().inUse()
2545 && !doc_buffer->hasReadonlyFlag();
2547 case LFUN_VC_UNDO_LAST:
2548 enable = doc_buffer && doc_buffer->lyxvc().undoLastEnabled();
2550 case LFUN_VC_REPO_UPDATE:
2551 enable = doc_buffer && doc_buffer->lyxvc().repoUpdateEnabled();
2553 case LFUN_VC_COMMAND: {
2554 if (cmd.argument().empty())
2556 if (!doc_buffer && contains(cmd.getArg(0), 'D'))
2560 case LFUN_VC_COMPARE:
2561 enable = doc_buffer && doc_buffer->lyxvc().prepareFileRevisionEnabled();
2564 case LFUN_SERVER_GOTO_FILE_ROW:
2565 case LFUN_LYX_ACTIVATE:
2566 case LFUN_WINDOW_RAISE:
2568 case LFUN_FORWARD_SEARCH:
2569 enable = !(lyxrc.forward_search_dvi.empty() && lyxrc.forward_search_pdf.empty());
2572 case LFUN_FILE_INSERT_PLAINTEXT:
2573 case LFUN_FILE_INSERT_PLAINTEXT_PARA:
2574 enable = documentBufferView() && documentBufferView()->cursor().inTexted();
2577 case LFUN_SPELLING_CONTINUOUSLY:
2578 flag.setOnOff(lyxrc.spellcheck_continuously);
2581 case LFUN_CITATION_OPEN:
2590 flag.setEnabled(false);
2596 static FileName selectTemplateFile()
2598 FileDialog dlg(qt_("Select template file"));
2599 dlg.setButton1(qt_("D&ocuments"), toqstr(lyxrc.document_path));
2600 dlg.setButton2(qt_("&Templates"), toqstr(lyxrc.template_path));
2602 FileDialog::Result result = dlg.open(toqstr(lyxrc.template_path),
2603 QStringList(qt_("LyX Documents (*.lyx)")));
2605 if (result.first == FileDialog::Later)
2607 if (result.second.isEmpty())
2609 return FileName(fromqstr(result.second));
2613 Buffer * GuiView::loadDocument(FileName const & filename, bool tolastfiles)
2617 Buffer * newBuffer = nullptr;
2619 newBuffer = checkAndLoadLyXFile(filename);
2620 } catch (ExceptionMessage const &) {
2627 message(_("Document not loaded."));
2631 setBuffer(newBuffer);
2632 newBuffer->errors("Parse");
2635 theSession().lastFiles().add(filename);
2636 theSession().writeFile();
2643 void GuiView::openDocument(string const & fname)
2645 string initpath = lyxrc.document_path;
2647 if (documentBufferView()) {
2648 string const trypath = documentBufferView()->buffer().filePath();
2649 // If directory is writeable, use this as default.
2650 if (FileName(trypath).isDirWritable())
2656 if (fname.empty()) {
2657 FileDialog dlg(qt_("Select document to open"));
2658 dlg.setButton1(qt_("D&ocuments"), toqstr(lyxrc.document_path));
2659 dlg.setButton2(qt_("&Examples"), toqstr(lyxrc.example_path));
2661 QStringList const filter({
2662 qt_("LyX Documents (*.lyx)"),
2663 qt_("LyX Document Backups (*.lyx~)"),
2664 qt_("All Files (*.*)")
2666 FileDialog::Result result =
2667 dlg.open(toqstr(initpath), filter);
2669 if (result.first == FileDialog::Later)
2672 filename = fromqstr(result.second);
2674 // check selected filename
2675 if (filename.empty()) {
2676 message(_("Canceled."));
2682 // get absolute path of file and add ".lyx" to the filename if
2684 FileName const fullname =
2685 fileSearch(string(), filename, "lyx", support::may_not_exist);
2686 if (!fullname.empty())
2687 filename = fullname.absFileName();
2689 if (!fullname.onlyPath().isDirectory()) {
2690 Alert::warning(_("Invalid filename"),
2691 bformat(_("The directory in the given path\n%1$s\ndoes not exist."),
2692 from_utf8(fullname.absFileName())));
2696 // if the file doesn't exist and isn't already open (bug 6645),
2697 // let the user create one
2698 if (!fullname.exists() && !theBufferList().exists(fullname) &&
2699 !LyXVC::file_not_found_hook(fullname)) {
2700 // the user specifically chose this name. Believe him.
2701 Buffer * const b = newFile(filename, string(), true);
2707 docstring const disp_fn = makeDisplayPath(filename);
2708 message(bformat(_("Opening document %1$s..."), disp_fn));
2711 Buffer * buf = loadDocument(fullname);
2713 str2 = bformat(_("Document %1$s opened."), disp_fn);
2714 if (buf->lyxvc().inUse())
2715 str2 += " " + from_utf8(buf->lyxvc().versionString()) +
2716 " " + _("Version control detected.");
2718 str2 = bformat(_("Could not open document %1$s"), disp_fn);
2723 // FIXME: clean that
2724 static bool import(GuiView * lv, FileName const & filename,
2725 string const & format, ErrorList & errorList)
2727 FileName const lyxfile(support::changeExtension(filename.absFileName(), ".lyx"));
2729 string loader_format;
2730 vector<string> loaders = theConverters().loaders();
2731 if (find(loaders.begin(), loaders.end(), format) == loaders.end()) {
2732 for (string const & loader : loaders) {
2733 if (!theConverters().isReachable(format, loader))
2736 string const tofile =
2737 support::changeExtension(filename.absFileName(),
2738 theFormats().extension(loader));
2739 if (theConverters().convert(nullptr, filename, FileName(tofile),
2740 filename, format, loader, errorList) != Converters::SUCCESS)
2742 loader_format = loader;
2745 if (loader_format.empty()) {
2746 frontend::Alert::error(_("Couldn't import file"),
2747 bformat(_("No information for importing the format %1$s."),
2748 translateIfPossible(theFormats().prettyName(format))));
2752 loader_format = format;
2754 if (loader_format == "lyx") {
2755 Buffer * buf = lv->loadDocument(lyxfile);
2759 Buffer * const b = newFile(lyxfile.absFileName(), string(), true);
2763 bool as_paragraphs = loader_format == "textparagraph";
2764 string filename2 = (loader_format == format) ? filename.absFileName()
2765 : support::changeExtension(filename.absFileName(),
2766 theFormats().extension(loader_format));
2767 lv->currentBufferView()->insertPlaintextFile(FileName(filename2),
2769 guiApp->setCurrentView(lv);
2770 lyx::dispatch(FuncRequest(LFUN_MARK_OFF));
2777 void GuiView::importDocument(string const & argument)
2780 string filename = split(argument, format, ' ');
2782 LYXERR(Debug::INFO, format << " file: " << filename);
2784 // need user interaction
2785 if (filename.empty()) {
2786 string initpath = lyxrc.document_path;
2787 if (documentBufferView()) {
2788 string const trypath = documentBufferView()->buffer().filePath();
2789 // If directory is writeable, use this as default.
2790 if (FileName(trypath).isDirWritable())
2794 docstring const text = bformat(_("Select %1$s file to import"),
2795 translateIfPossible(theFormats().prettyName(format)));
2797 FileDialog dlg(toqstr(text));
2798 dlg.setButton1(qt_("D&ocuments"), toqstr(lyxrc.document_path));
2799 dlg.setButton2(qt_("&Examples"), toqstr(lyxrc.example_path));
2801 docstring filter = translateIfPossible(theFormats().prettyName(format));
2804 filter += from_utf8(theFormats().extensions(format));
2807 FileDialog::Result result =
2808 dlg.open(toqstr(initpath), fileFilters(toqstr(filter)));
2810 if (result.first == FileDialog::Later)
2813 filename = fromqstr(result.second);
2815 // check selected filename
2816 if (filename.empty())
2817 message(_("Canceled."));
2820 if (filename.empty())
2823 // get absolute path of file
2824 FileName const fullname(support::makeAbsPath(filename));
2826 // Can happen if the user entered a path into the dialog
2828 if (fullname.onlyFileName().empty()) {
2829 docstring msg = bformat(_("The file name '%1$s' is invalid!\n"
2830 "Aborting import."),
2831 from_utf8(fullname.absFileName()));
2832 frontend::Alert::error(_("File name error"), msg);
2833 message(_("Canceled."));
2838 FileName const lyxfile(support::changeExtension(fullname.absFileName(), ".lyx"));
2840 // Check if the document already is open
2841 Buffer * buf = theBufferList().getBuffer(lyxfile);
2844 if (!closeBuffer()) {
2845 message(_("Canceled."));
2850 docstring const displaypath = makeDisplayPath(lyxfile.absFileName(), 30);
2852 // if the file exists already, and we didn't do
2853 // -i lyx thefile.lyx, warn
2854 if (lyxfile.exists() && fullname != lyxfile) {
2856 docstring text = bformat(_("The document %1$s already exists.\n\n"
2857 "Do you want to overwrite that document?"), displaypath);
2858 int const ret = Alert::prompt(_("Overwrite document?"),
2859 text, 0, 1, _("&Overwrite"), _("&Cancel"));
2862 message(_("Canceled."));
2867 message(bformat(_("Importing %1$s..."), displaypath));
2868 ErrorList errorList;
2869 if (import(this, fullname, format, errorList))
2870 message(_("imported."));
2872 message(_("file not imported!"));
2874 // FIXME (Abdel 12/08/06): Is there a need to display the error list here?
2878 void GuiView::newDocument(string const & filename, string templatefile,
2881 FileName initpath(lyxrc.document_path);
2882 if (documentBufferView()) {
2883 FileName const trypath(documentBufferView()->buffer().filePath());
2884 // If directory is writeable, use this as default.
2885 if (trypath.isDirWritable())
2889 if (from_template) {
2890 if (templatefile.empty())
2891 templatefile = selectTemplateFile().absFileName();
2892 if (templatefile.empty())
2897 if (filename.empty())
2898 b = newUnnamedFile(initpath, to_utf8(_("newfile")), templatefile);
2900 b = newFile(filename, templatefile, true);
2905 // If no new document could be created, it is unsure
2906 // whether there is a valid BufferView.
2907 if (currentBufferView())
2908 // Ensure the cursor is correctly positioned on screen.
2909 currentBufferView()->showCursor();
2913 bool GuiView::insertLyXFile(docstring const & fname, bool ignorelang)
2915 BufferView * bv = documentBufferView();
2920 FileName filename(to_utf8(fname));
2921 if (filename.empty()) {
2922 // Launch a file browser
2924 string initpath = lyxrc.document_path;
2925 string const trypath = bv->buffer().filePath();
2926 // If directory is writeable, use this as default.
2927 if (FileName(trypath).isDirWritable())
2931 FileDialog dlg(qt_("Select LyX document to insert"));
2932 dlg.setButton1(qt_("D&ocuments"), toqstr(lyxrc.document_path));
2933 dlg.setButton2(qt_("&Examples"), toqstr(lyxrc.example_path));
2935 FileDialog::Result result = dlg.open(toqstr(initpath),
2936 QStringList(qt_("LyX Documents (*.lyx)")));
2938 if (result.first == FileDialog::Later)
2942 filename.set(fromqstr(result.second));
2944 // check selected filename
2945 if (filename.empty()) {
2946 // emit message signal.
2947 message(_("Canceled."));
2952 bv->insertLyXFile(filename, ignorelang);
2953 bv->buffer().errors("Parse");
2958 string const GuiView::getTemplatesPath(Buffer & b)
2960 // We start off with the user's templates path
2961 string result = addPath(package().user_support().absFileName(), "templates");
2962 // Check for the document language
2963 string const langcode = b.params().language->code();
2964 string const shortcode = langcode.substr(0, 2);
2965 if (!langcode.empty() && shortcode != "en") {
2966 string subpath = addPath(result, shortcode);
2967 string subpath_long = addPath(result, langcode);
2968 // If we have a subdirectory for the language already,
2970 FileName sp = FileName(subpath);
2971 if (sp.isDirectory())
2973 else if (FileName(subpath_long).isDirectory())
2974 result = subpath_long;
2976 // Ask whether we should create such a subdirectory
2977 docstring const text =
2978 bformat(_("It is suggested to save the template in a subdirectory\n"
2979 "appropriate to the document language (%1$s).\n"
2980 "This subdirectory does not exists yet.\n"
2981 "Do you want to create it?"),
2982 _(b.params().language->display()));
2983 if (Alert::prompt(_("Create Language Directory?"),
2984 text, 0, 1, _("&Yes, Create"), _("&No, Save Template in Parent Directory")) == 0) {
2985 // If the user agreed, we try to create it and report if this failed.
2986 if (!sp.createDirectory(0777))
2987 Alert::error(_("Subdirectory creation failed!"),
2988 _("Could not create subdirectory.\n"
2989 "The template will be saved in the parent directory."));
2995 // Do we have a layout category?
2996 string const cat = b.params().baseClass() ?
2997 b.params().baseClass()->category()
3000 string subpath = addPath(result, cat);
3001 // If we have a subdirectory for the category already,
3003 FileName sp = FileName(subpath);
3004 if (sp.isDirectory())
3007 // Ask whether we should create such a subdirectory
3008 docstring const text =
3009 bformat(_("It is suggested to save the template in a subdirectory\n"
3010 "appropriate to the layout category (%1$s).\n"
3011 "This subdirectory does not exists yet.\n"
3012 "Do you want to create it?"),
3014 if (Alert::prompt(_("Create Category Directory?"),
3015 text, 0, 1, _("&Yes, Create"), _("&No, Save Template in Parent Directory")) == 0) {
3016 // If the user agreed, we try to create it and report if this failed.
3017 if (!sp.createDirectory(0777))
3018 Alert::error(_("Subdirectory creation failed!"),
3019 _("Could not create subdirectory.\n"
3020 "The template will be saved in the parent directory."));
3030 bool GuiView::renameBuffer(Buffer & b, docstring const & newname, RenameKind kind)
3032 FileName fname = b.fileName();
3033 FileName const oldname = fname;
3034 bool const as_template = (kind == LV_WRITE_AS_TEMPLATE);
3036 if (!newname.empty()) {
3039 fname = support::makeAbsPath(to_utf8(newname), getTemplatesPath(b));
3041 fname = support::makeAbsPath(to_utf8(newname),
3042 oldname.onlyPath().absFileName());
3044 // Switch to this Buffer.
3047 // No argument? Ask user through dialog.
3049 QString const title = as_template ? qt_("Choose a filename to save template as")
3050 : qt_("Choose a filename to save document as");
3051 FileDialog dlg(title);
3052 dlg.setButton1(qt_("D&ocuments"), toqstr(lyxrc.document_path));
3053 dlg.setButton2(qt_("&Templates"), toqstr(lyxrc.template_path));
3055 if (!isLyXFileName(fname.absFileName()))
3056 fname.changeExtension(".lyx");
3058 string const path = as_template ?
3060 : fname.onlyPath().absFileName();
3061 FileDialog::Result result =
3062 dlg.save(toqstr(path),
3063 QStringList(qt_("LyX Documents (*.lyx)")),
3064 toqstr(fname.onlyFileName()));
3066 if (result.first == FileDialog::Later)
3069 fname.set(fromqstr(result.second));
3074 if (!isLyXFileName(fname.absFileName()))
3075 fname.changeExtension(".lyx");
3078 // fname is now the new Buffer location.
3080 // if there is already a Buffer open with this name, we do not want
3081 // to have another one. (the second test makes sure we're not just
3082 // trying to overwrite ourselves, which is fine.)
3083 if (theBufferList().exists(fname) && fname != oldname
3084 && theBufferList().getBuffer(fname) != &b) {
3085 docstring const text =
3086 bformat(_("The file\n%1$s\nis already open in your current session.\n"
3087 "Please close it before attempting to overwrite it.\n"
3088 "Do you want to choose a new filename?"),
3089 from_utf8(fname.absFileName()));
3090 int const ret = Alert::prompt(_("Chosen File Already Open"),
3091 text, 0, 1, _("&Rename"), _("&Cancel"));
3093 case 0: return renameBuffer(b, docstring(), kind);
3094 case 1: return false;
3099 bool const existsLocal = fname.exists();
3100 bool const existsInVC = LyXVC::fileInVC(fname);
3101 if (existsLocal || existsInVC) {
3102 docstring const file = makeDisplayPath(fname.absFileName(), 30);
3103 if (kind != LV_WRITE_AS && existsInVC) {
3104 // renaming to a name that is already in VC
3106 docstring text = bformat(_("The document %1$s "
3107 "is already registered.\n\n"
3108 "Do you want to choose a new name?"),
3110 docstring const title = (kind == LV_VC_RENAME) ?
3111 _("Rename document?") : _("Copy document?");
3112 docstring const button = (kind == LV_VC_RENAME) ?
3113 _("&Rename") : _("&Copy");
3114 int const ret = Alert::prompt(title, text, 0, 1,
3115 button, _("&Cancel"));
3117 case 0: return renameBuffer(b, docstring(), kind);
3118 case 1: return false;
3123 docstring text = bformat(_("The document %1$s "
3124 "already exists.\n\n"
3125 "Do you want to overwrite that document?"),
3127 int const ret = Alert::prompt(_("Overwrite document?"),
3128 text, 0, 2, _("&Overwrite"),
3129 _("&Rename"), _("&Cancel"));
3132 case 1: return renameBuffer(b, docstring(), kind);
3133 case 2: return false;
3139 case LV_VC_RENAME: {
3140 string msg = b.lyxvc().rename(fname);
3143 message(from_utf8(msg));
3147 string msg = b.lyxvc().copy(fname);
3150 message(from_utf8(msg));
3154 case LV_WRITE_AS_TEMPLATE:
3157 // LyXVC created the file already in case of LV_VC_RENAME or
3158 // LV_VC_COPY, but call saveBuffer() nevertheless to get
3159 // relative paths of included stuff right if we moved e.g. from
3160 // /a/b.lyx to /a/c/b.lyx.
3162 bool const saved = saveBuffer(b, fname);
3169 bool GuiView::exportBufferAs(Buffer & b, docstring const & iformat)
3171 FileName fname = b.fileName();
3173 FileDialog dlg(qt_("Choose a filename to export the document as"));
3174 dlg.setButton1(qt_("D&ocuments"), toqstr(lyxrc.document_path));
3177 QString const anyformat = qt_("Guess from extension (*.*)");
3180 vector<Format const *> export_formats;
3181 for (Format const & f : theFormats())
3182 if (f.documentFormat())
3183 export_formats.push_back(&f);
3184 sort(export_formats.begin(), export_formats.end(), Format::formatSorter);
3185 map<QString, string> fmap;
3188 for (Format const * f : export_formats) {
3189 docstring const loc_prettyname = translateIfPossible(f->prettyname());
3190 QString const loc_filter = toqstr(bformat(from_ascii("%1$s (*.%2$s)"),
3192 from_ascii(f->extension())));
3193 types << loc_filter;
3194 fmap[loc_filter] = f->name();
3195 if (from_ascii(f->name()) == iformat) {
3196 filter = loc_filter;
3197 ext = f->extension();
3200 string ofname = fname.onlyFileName();
3202 ofname = support::changeExtension(ofname, ext);
3203 FileDialog::Result result =
3204 dlg.save(toqstr(fname.onlyPath().absFileName()),
3208 if (result.first != FileDialog::Chosen)
3212 fname.set(fromqstr(result.second));
3213 if (filter == anyformat)
3214 fmt_name = theFormats().getFormatFromExtension(fname.extension());
3216 fmt_name = fmap[filter];
3217 LYXERR(Debug::FILES, "filter=" << fromqstr(filter)
3218 << ", fmt_name=" << fmt_name << ", fname=" << fname.absFileName());
3220 if (fmt_name.empty() || fname.empty())
3223 // fname is now the new Buffer location.
3224 if (fname.exists()) {
3225 docstring const file = makeDisplayPath(fname.absFileName(), 30);
3226 docstring text = bformat(_("The document %1$s already "
3227 "exists.\n\nDo you want to "
3228 "overwrite that document?"),
3230 int const ret = Alert::prompt(_("Overwrite document?"),
3231 text, 0, 2, _("&Overwrite"), _("&Rename"), _("&Cancel"));
3234 case 1: return exportBufferAs(b, from_ascii(fmt_name));
3235 case 2: return false;
3239 FuncRequest cmd(LFUN_BUFFER_EXPORT, fmt_name + " " + fname.absFileName());
3242 return dr.dispatched();
3246 bool GuiView::saveBuffer(Buffer & b)
3248 return saveBuffer(b, FileName());
3252 bool GuiView::saveBuffer(Buffer & b, FileName const & fn)
3254 if (workArea(b) && workArea(b)->inDialogMode())
3257 if (fn.empty() && b.isUnnamed())
3258 return renameBuffer(b, docstring());
3260 bool const success = (fn.empty() ? b.save() : b.saveAs(fn));
3262 theSession().lastFiles().add(b.fileName());
3263 theSession().writeFile();
3267 // Switch to this Buffer.
3270 // FIXME: we don't tell the user *WHY* the save failed !!
3271 docstring const file = makeDisplayPath(b.absFileName(), 30);
3272 docstring text = bformat(_("The document %1$s could not be saved.\n\n"
3273 "Do you want to rename the document and "
3274 "try again?"), file);
3275 int const ret = Alert::prompt(_("Rename and save?"),
3276 text, 0, 2, _("&Rename"), _("&Retry"), _("&Cancel"));
3279 if (!renameBuffer(b, docstring()))
3288 return saveBuffer(b, fn);
3292 bool GuiView::hideWorkArea(GuiWorkArea * wa)
3294 return closeWorkArea(wa, false);
3298 // We only want to close the buffer if it is not visible in other workareas
3299 // of the same view, nor in other views, and if this is not a child
3300 bool GuiView::closeWorkArea(GuiWorkArea * wa)
3302 Buffer & buf = wa->bufferView().buffer();
3304 bool last_wa = d.countWorkAreasOf(buf) == 1
3305 && !inOtherView(buf) && !buf.parent();
3307 bool close_buffer = last_wa;
3310 if (lyxrc.close_buffer_with_last_view == "yes")
3312 else if (lyxrc.close_buffer_with_last_view == "no")
3313 close_buffer = false;
3316 if (buf.isUnnamed())
3317 file = from_utf8(buf.fileName().onlyFileName());
3319 file = buf.fileName().displayName(30);
3320 docstring const text = bformat(
3321 _("Last view on document %1$s is being closed.\n"
3322 "Would you like to close or hide the document?\n"
3324 "Hidden documents can be displayed back through\n"
3325 "the menu: View->Hidden->...\n"
3327 "To remove this question, set your preference in:\n"
3328 " Tools->Preferences->Look&Feel->UserInterface\n"
3330 int ret = Alert::prompt(_("Close or hide document?"),
3331 text, 0, 2, _("&Close"), _("&Hide"), _("&Cancel"));
3334 close_buffer = (ret == 0);
3338 return closeWorkArea(wa, close_buffer);
3342 bool GuiView::closeBuffer()
3344 GuiWorkArea * wa = currentMainWorkArea();
3345 // coverity complained about this
3346 // it seems unnecessary, but perhaps is worth the check
3347 LASSERT(wa, return false);
3349 setCurrentWorkArea(wa);
3350 Buffer & buf = wa->bufferView().buffer();
3351 return closeWorkArea(wa, !buf.parent());
3355 void GuiView::writeSession() const {
3356 GuiWorkArea const * active_wa = currentMainWorkArea();
3357 for (int i = 0; i < d.splitter_->count(); ++i) {
3358 TabWorkArea * twa = d.tabWorkArea(i);
3359 for (int j = 0; j < twa->count(); ++j) {
3360 GuiWorkArea * wa = twa->workArea(j);
3361 Buffer & buf = wa->bufferView().buffer();
3362 theSession().lastOpened().add(buf.fileName(), wa == active_wa);
3368 bool GuiView::closeBufferAll()
3371 for (auto & buf : theBufferList()) {
3372 if (!saveBufferIfNeeded(*buf, false)) {
3373 // Closing has been cancelled, so abort.
3378 // Close the workareas in all other views
3379 QList<int> const ids = guiApp->viewIds();
3380 for (int i = 0; i != ids.size(); ++i) {
3381 if (id_ != ids[i] && !guiApp->view(ids[i]).closeWorkAreaAll())
3385 // Close our own workareas
3386 if (!closeWorkAreaAll())
3393 bool GuiView::closeWorkAreaAll()
3395 setCurrentWorkArea(currentMainWorkArea());
3397 // We might be in a situation that there is still a tabWorkArea, but
3398 // there are no tabs anymore. This can happen when we get here after a
3399 // TabWorkArea::lastWorkAreaRemoved() signal. Therefore we count how
3400 // many TabWorkArea's have no documents anymore.
3403 // We have to call count() each time, because it can happen that
3404 // more than one splitter will disappear in one iteration (bug 5998).
3405 while (d.splitter_->count() > empty_twa) {
3406 TabWorkArea * twa = d.tabWorkArea(empty_twa);
3408 if (twa->count() == 0)
3411 setCurrentWorkArea(twa->currentWorkArea());
3412 if (!closeTabWorkArea(twa))
3420 bool GuiView::closeWorkArea(GuiWorkArea * wa, bool close_buffer)
3425 Buffer & buf = wa->bufferView().buffer();
3427 if (GuiViewPrivate::busyBuffers.contains(&buf)) {
3428 Alert::warning(_("Close document"),
3429 _("Document could not be closed because it is being processed by LyX."));
3434 return closeBuffer(buf);
3436 if (!inMultiTabs(wa))
3437 if (!saveBufferIfNeeded(buf, true))
3445 bool GuiView::closeBuffer(Buffer & buf)
3447 bool success = true;
3448 for (Buffer * child_buf : buf.getChildren()) {
3449 if (theBufferList().isOthersChild(&buf, child_buf)) {
3450 child_buf->setParent(nullptr);
3454 // FIXME: should we look in other tabworkareas?
3455 // ANSWER: I don't think so. I've tested, and if the child is
3456 // open in some other window, it closes without a problem.
3457 GuiWorkArea * child_wa = workArea(*child_buf);
3460 // If we are in a close_event all children will be closed in some time,
3461 // so no need to do it here. This will ensure that the children end up
3462 // in the session file in the correct order. If we close the master
3463 // buffer, we can close or release the child buffers here too.
3465 success = closeWorkArea(child_wa, true);
3469 // In this case the child buffer is open but hidden.
3470 // Even in this case, children can be dirty (e.g.,
3471 // after a label change in the master, see #11405).
3472 // Therefore, check this
3473 if (closing_ && (child_buf->isClean() || child_buf->paragraphs().empty())) {
3474 // If we are in a close_event all children will be closed in some time,
3475 // so no need to do it here. This will ensure that the children end up
3476 // in the session file in the correct order. If we close the master
3477 // buffer, we can close or release the child buffers here too.
3480 // Save dirty buffers also if closing_!
3481 if (saveBufferIfNeeded(*child_buf, false)) {
3482 child_buf->removeAutosaveFile();
3483 theBufferList().release(child_buf);
3485 // Saving of dirty children has been cancelled.
3486 // Cancel the whole process.
3493 // goto bookmark to update bookmark pit.
3494 // FIXME: we should update only the bookmarks related to this buffer!
3495 // FIXME: this is done also in LFUN_WINDOW_CLOSE!
3496 LYXERR(Debug::DEBUG, "GuiView::closeBuffer()");
3497 for (unsigned int i = 1; i < theSession().bookmarks().size(); ++i)
3498 guiApp->gotoBookmark(i, false, false);
3500 if (saveBufferIfNeeded(buf, false)) {
3501 buf.removeAutosaveFile();
3502 theBufferList().release(&buf);
3506 // open all children again to avoid a crash because of dangling
3507 // pointers (bug 6603)
3513 bool GuiView::closeTabWorkArea(TabWorkArea * twa)
3515 while (twa == d.currentTabWorkArea()) {
3516 twa->setCurrentIndex(twa->count() - 1);
3518 GuiWorkArea * wa = twa->currentWorkArea();
3519 Buffer & b = wa->bufferView().buffer();
3521 // We only want to close the buffer if the same buffer is not visible
3522 // in another view, and if this is not a child and if we are closing
3523 // a view (not a tabgroup).
3524 bool const close_buffer =
3525 !inOtherView(b) && !b.parent() && closing_;
3527 if (!closeWorkArea(wa, close_buffer))
3534 bool GuiView::saveBufferIfNeeded(Buffer & buf, bool hiding)
3536 if (buf.isClean() || buf.paragraphs().empty())
3539 // Switch to this Buffer.
3545 if (buf.isUnnamed()) {
3546 file = from_utf8(buf.fileName().onlyFileName());
3549 FileName filename = buf.fileName();
3551 file = filename.displayName(30);
3552 exists = filename.exists();
3555 // Bring this window to top before asking questions.
3560 if (hiding && buf.isUnnamed()) {
3561 docstring const text = bformat(_("The document %1$s has not been "
3562 "saved yet.\n\nDo you want to save "
3563 "the document?"), file);
3564 ret = Alert::prompt(_("Save new document?"),
3565 text, 0, 1, _("&Save"), _("&Cancel"));
3569 docstring const text = exists ?
3570 bformat(_("The document %1$s has unsaved changes."
3571 "\n\nDo you want to save the document or "
3572 "discard the changes?"), file) :
3573 bformat(_("The document %1$s has not been saved yet."
3574 "\n\nDo you want to save the document or "
3575 "discard it entirely?"), file);
3576 docstring const title = exists ?
3577 _("Save changed document?") : _("Save document?");
3578 ret = Alert::prompt(title, text, 0, 2,
3579 _("&Save"), _("&Discard"), _("&Cancel"));
3584 if (!saveBuffer(buf))
3588 // If we crash after this we could have no autosave file
3589 // but I guess this is really improbable (Jug).
3590 // Sometimes improbable things happen:
3591 // - see bug http://www.lyx.org/trac/ticket/6587 (ps)
3592 // buf.removeAutosaveFile();
3594 // revert all changes
3605 bool GuiView::inMultiTabs(GuiWorkArea * wa)
3607 Buffer & buf = wa->bufferView().buffer();
3609 for (int i = 0; i != d.splitter_->count(); ++i) {
3610 GuiWorkArea * wa_ = d.tabWorkArea(i)->workArea(buf);
3611 if (wa_ && wa_ != wa)
3614 return inOtherView(buf);
3618 bool GuiView::inOtherView(Buffer & buf)
3620 QList<int> const ids = guiApp->viewIds();
3622 for (int i = 0; i != ids.size(); ++i) {
3626 if (guiApp->view(ids[i]).workArea(buf))
3633 void GuiView::gotoNextOrPreviousBuffer(NextOrPrevious np, bool const move)
3635 if (!documentBufferView())
3638 if (TabWorkArea * twa = d.currentTabWorkArea()) {
3639 Buffer * const curbuf = &documentBufferView()->buffer();
3640 int nwa = twa->count();
3641 for (int i = 0; i < nwa; ++i) {
3642 if (&workArea(i)->bufferView().buffer() == curbuf) {
3644 if (np == NEXTBUFFER)
3645 next_index = (i == nwa - 1 ? 0 : i + 1);
3647 next_index = (i == 0 ? nwa - 1 : i - 1);
3649 twa->moveTab(i, next_index);
3651 setBuffer(&workArea(next_index)->bufferView().buffer());
3659 /// make sure the document is saved
3660 static bool ensureBufferClean(Buffer * buffer)
3662 LASSERT(buffer, return false);
3663 if (buffer->isClean() && !buffer->isUnnamed())
3666 docstring const file = buffer->fileName().displayName(30);
3669 if (!buffer->isUnnamed()) {
3670 text = bformat(_("The document %1$s has unsaved "
3671 "changes.\n\nDo you want to save "
3672 "the document?"), file);
3673 title = _("Save changed document?");
3676 text = bformat(_("The document %1$s has not been "
3677 "saved yet.\n\nDo you want to save "
3678 "the document?"), file);
3679 title = _("Save new document?");
3681 int const ret = Alert::prompt(title, text, 0, 1, _("&Save"), _("&Cancel"));
3684 dispatch(FuncRequest(LFUN_BUFFER_WRITE));
3686 return buffer->isClean() && !buffer->isUnnamed();
3690 bool GuiView::reloadBuffer(Buffer & buf)
3692 currentBufferView()->cursor().reset();
3693 Buffer::ReadStatus status = buf.reload();
3694 return status == Buffer::ReadSuccess;
3698 void GuiView::checkExternallyModifiedBuffers()
3700 for (Buffer * buf : theBufferList()) {
3701 if (buf->fileName().exists() && buf->isChecksumModified()) {
3702 docstring text = bformat(_("Document \n%1$s\n has been externally modified."
3703 " Reload now? Any local changes will be lost."),
3704 from_utf8(buf->absFileName()));
3705 int const ret = Alert::prompt(_("Reload externally changed document?"),
3706 text, 0, 1, _("&Reload"), _("&Cancel"));
3714 void GuiView::dispatchVC(FuncRequest const & cmd, DispatchResult & dr)
3716 Buffer * buffer = documentBufferView()
3717 ? &(documentBufferView()->buffer()) : nullptr;
3719 switch (cmd.action()) {
3720 case LFUN_VC_REGISTER:
3721 if (!buffer || !ensureBufferClean(buffer))
3723 if (!buffer->lyxvc().inUse()) {
3724 if (buffer->lyxvc().registrer()) {
3725 reloadBuffer(*buffer);
3726 dr.clearMessageUpdate();
3731 case LFUN_VC_RENAME:
3732 case LFUN_VC_COPY: {
3733 if (!buffer || !ensureBufferClean(buffer))
3735 if (buffer->lyxvc().inUse() && !buffer->hasReadonlyFlag()) {
3736 if (buffer->lyxvc().isCheckInWithConfirmation()) {
3737 // Some changes are not yet committed.
3738 // We test here and not in getStatus(), since
3739 // this test is expensive.
3741 LyXVC::CommandResult ret =
3742 buffer->lyxvc().checkIn(log);
3744 if (ret == LyXVC::ErrorCommand ||
3745 ret == LyXVC::VCSuccess)
3746 reloadBuffer(*buffer);
3747 if (buffer->lyxvc().isCheckInWithConfirmation()) {
3748 frontend::Alert::error(
3749 _("Revision control error."),
3750 _("Document could not be checked in."));
3754 RenameKind const kind = (cmd.action() == LFUN_VC_RENAME) ?
3755 LV_VC_RENAME : LV_VC_COPY;
3756 renameBuffer(*buffer, cmd.argument(), kind);
3761 case LFUN_VC_CHECK_IN:
3762 if (!buffer || !ensureBufferClean(buffer))
3764 if (buffer->lyxvc().inUse() && !buffer->hasReadonlyFlag()) {
3766 LyXVC::CommandResult ret = buffer->lyxvc().checkIn(log);
3768 // Only skip reloading if the checkin was cancelled or
3769 // an error occurred before the real checkin VCS command
3770 // was executed, since the VCS might have changed the
3771 // file even if it could not checkin successfully.
3772 if (ret == LyXVC::ErrorCommand || ret == LyXVC::VCSuccess)
3773 reloadBuffer(*buffer);
3777 case LFUN_VC_CHECK_OUT:
3778 if (!buffer || !ensureBufferClean(buffer))
3780 if (buffer->lyxvc().inUse()) {
3781 dr.setMessage(buffer->lyxvc().checkOut());
3782 reloadBuffer(*buffer);
3786 case LFUN_VC_LOCKING_TOGGLE:
3787 if (!buffer || !ensureBufferClean(buffer) || buffer->hasReadonlyFlag())
3789 if (buffer->lyxvc().inUse()) {
3790 string res = buffer->lyxvc().lockingToggle();
3792 frontend::Alert::error(_("Revision control error."),
3793 _("Error when setting the locking property."));
3796 reloadBuffer(*buffer);
3801 case LFUN_VC_REVERT:
3804 if (buffer->lyxvc().revert()) {
3805 reloadBuffer(*buffer);
3806 dr.clearMessageUpdate();
3810 case LFUN_VC_UNDO_LAST:
3813 buffer->lyxvc().undoLast();
3814 reloadBuffer(*buffer);
3815 dr.clearMessageUpdate();
3818 case LFUN_VC_REPO_UPDATE:
3821 if (ensureBufferClean(buffer)) {
3822 dr.setMessage(buffer->lyxvc().repoUpdate());
3823 checkExternallyModifiedBuffers();
3827 case LFUN_VC_COMMAND: {
3828 string flag = cmd.getArg(0);
3829 if (buffer && contains(flag, 'R') && !ensureBufferClean(buffer))
3832 if (contains(flag, 'M')) {
3833 if (!Alert::askForText(message, _("LyX VC: Log Message")))
3836 string path = cmd.getArg(1);
3837 if (contains(path, "$$p") && buffer)
3838 path = subst(path, "$$p", buffer->filePath());
3839 LYXERR(Debug::LYXVC, "Directory: " << path);
3841 if (!pp.isReadableDirectory()) {
3842 lyxerr << _("Directory is not accessible.") << endl;
3845 support::PathChanger p(pp);
3847 string command = cmd.getArg(2);
3848 if (command.empty())
3851 command = subst(command, "$$i", buffer->absFileName());
3852 command = subst(command, "$$p", buffer->filePath());
3854 command = subst(command, "$$m", to_utf8(message));
3855 LYXERR(Debug::LYXVC, "Command: " << command);
3857 one.startscript(Systemcall::Wait, command);
3861 if (contains(flag, 'I'))
3862 buffer->markDirty();
3863 if (contains(flag, 'R'))
3864 reloadBuffer(*buffer);
3869 case LFUN_VC_COMPARE: {
3870 if (cmd.argument().empty()) {
3871 lyx::dispatch(FuncRequest(LFUN_DIALOG_SHOW, "comparehistory"));
3877 string rev1 = cmd.getArg(0);
3881 if (!buffer->lyxvc().prepareFileRevision(rev1, f1))
3884 if (isStrInt(rev1) && convert<int>(rev1) <= 0) {
3885 f2 = buffer->absFileName();
3887 string rev2 = cmd.getArg(1);
3891 if (!buffer->lyxvc().prepareFileRevision(rev2, f2))
3895 LYXERR(Debug::LYXVC, "Launching comparison for fetched revisions:\n" <<
3896 f1 << "\n" << f2 << "\n" );
3897 string par = "compare run " + quoteName(f1) + " " + quoteName(f2);
3898 lyx::dispatch(FuncRequest(LFUN_DIALOG_SHOW, par));
3908 void GuiView::openChildDocument(string const & fname)
3910 LASSERT(documentBufferView(), return);
3911 Buffer & buffer = documentBufferView()->buffer();
3912 FileName const filename = support::makeAbsPath(fname, buffer.filePath());
3913 documentBufferView()->saveBookmark(false);
3914 Buffer * child = nullptr;
3915 if (theBufferList().exists(filename)) {
3916 child = theBufferList().getBuffer(filename);
3919 message(bformat(_("Opening child document %1$s..."),
3920 makeDisplayPath(filename.absFileName())));
3921 child = loadDocument(filename, false);
3923 // Set the parent name of the child document.
3924 // This makes insertion of citations and references in the child work,
3925 // when the target is in the parent or another child document.
3927 child->setParent(&buffer);
3931 bool GuiView::goToFileRow(string const & argument)
3935 size_t i = argument.find_last_of(' ');
3936 if (i != string::npos) {
3937 file_name = os::internal_path(FileName(trim(argument.substr(0, i))).realPath());
3938 istringstream is(argument.substr(i + 1));
3943 if (i == string::npos) {
3944 LYXERR0("Wrong argument: " << argument);
3947 Buffer * buf = nullptr;
3948 string const realtmp = package().temp_dir().realPath();
3949 // We have to use os::path_prefix_is() here, instead of
3950 // simply prefixIs(), because the file name comes from
3951 // an external application and may need case adjustment.
3952 if (os::path_prefix_is(file_name, realtmp, os::CASE_ADJUSTED)) {
3953 buf = theBufferList().getBufferFromTmp(file_name, true);
3954 LYXERR(Debug::FILES, "goToFileRow: buffer lookup for " << file_name
3955 << (buf ? " success" : " failed"));
3957 // Must replace extension of the file to be .lyx
3958 // and get full path
3959 FileName const s = fileSearch(string(),
3960 support::changeExtension(file_name, ".lyx"), "lyx");
3961 // Either change buffer or load the file
3962 if (theBufferList().exists(s))
3963 buf = theBufferList().getBuffer(s);
3964 else if (s.exists()) {
3965 buf = loadDocument(s);
3970 _("File does not exist: %1$s"),
3971 makeDisplayPath(file_name)));
3977 _("No buffer for file: %1$s."),
3978 makeDisplayPath(file_name))
3983 bool success = documentBufferView()->setCursorFromRow(row);
3985 LYXERR(Debug::LATEX,
3986 "setCursorFromRow: invalid position for row " << row);
3987 frontend::Alert::error(_("Inverse Search Failed"),
3988 _("Invalid position requested by inverse search.\n"
3989 "You may need to update the viewed document."));
3995 void GuiView::toolBarPopup(const QPoint & /*pos*/)
3997 QMenu * menu = guiApp->menus().menu(toqstr("context-toolbars"), * this);
3998 menu->exec(QCursor::pos());
4003 Buffer::ExportStatus GuiView::GuiViewPrivate::runAndDestroy(const T& func,
4004 Buffer const * orig, Buffer * clone, string const & format)
4006 Buffer::ExportStatus const status = func(format);
4008 // the cloning operation will have produced a clone of the entire set of
4009 // documents, starting from the master. so we must delete those.
4010 Buffer * mbuf = const_cast<Buffer *>(clone->masterBuffer());
4012 busyBuffers.remove(orig);
4017 Buffer::ExportStatus GuiView::GuiViewPrivate::compileAndDestroy(
4018 Buffer const * orig, Buffer * clone, string const & format)
4020 Buffer::ExportStatus (Buffer::* mem_func)(std::string const &, bool) const =
4022 return runAndDestroy(lyx::bind(mem_func, clone, _1, true), orig, clone, format);
4026 Buffer::ExportStatus GuiView::GuiViewPrivate::exportAndDestroy(
4027 Buffer const * orig, Buffer * clone, string const & format)
4029 Buffer::ExportStatus (Buffer::* mem_func)(std::string const &, bool) const =
4031 return runAndDestroy(lyx::bind(mem_func, clone, _1, false), orig, clone, format);
4035 Buffer::ExportStatus GuiView::GuiViewPrivate::previewAndDestroy(
4036 Buffer const * orig, Buffer * clone, string const & format)
4038 Buffer::ExportStatus (Buffer::* mem_func)(std::string const &) const =
4040 return runAndDestroy(lyx::bind(mem_func, clone, _1), orig, clone, format);
4044 bool GuiView::GuiViewPrivate::asyncBufferProcessing(string const & argument,
4045 Buffer const * used_buffer,
4046 docstring const & msg,
4047 Buffer::ExportStatus (*asyncFunc)(Buffer const *, Buffer *, string const &),
4048 Buffer::ExportStatus (Buffer::*syncFunc)(string const &, bool) const,
4049 Buffer::ExportStatus (Buffer::*previewFunc)(string const &) const,
4050 bool allow_async, bool use_tmpdir)
4055 string format = argument;
4057 format = used_buffer->params().getDefaultOutputFormat();
4058 processing_format = format;
4060 progress_->clearMessages();
4063 #if EXPORT_in_THREAD
4065 GuiViewPrivate::busyBuffers.insert(used_buffer);
4066 Buffer * cloned_buffer = used_buffer->cloneWithChildren();
4067 if (!cloned_buffer) {
4068 Alert::error(_("Export Error"),
4069 _("Error cloning the Buffer."));
4072 QFuture<Buffer::ExportStatus> f = QtConcurrent::run(
4077 setPreviewFuture(f);
4078 last_export_format = used_buffer->params().bufferFormat();
4081 // We are asynchronous, so we don't know here anything about the success
4084 Buffer::ExportStatus status;
4086 status = (used_buffer->*syncFunc)(format, use_tmpdir);
4087 } else if (previewFunc) {
4088 status = (used_buffer->*previewFunc)(format);
4091 handleExportStatus(gv_, status, format);
4093 return (status == Buffer::ExportSuccess
4094 || status == Buffer::PreviewSuccess);
4098 Buffer::ExportStatus status;
4100 status = (used_buffer->*syncFunc)(format, true);
4101 } else if (previewFunc) {
4102 status = (used_buffer->*previewFunc)(format);
4105 handleExportStatus(gv_, status, format);
4107 return (status == Buffer::ExportSuccess
4108 || status == Buffer::PreviewSuccess);
4112 void GuiView::dispatchToBufferView(FuncRequest const & cmd, DispatchResult & dr)
4114 BufferView * bv = currentBufferView();
4115 LASSERT(bv, return);
4117 // Let the current BufferView dispatch its own actions.
4118 bv->dispatch(cmd, dr);
4119 if (dr.dispatched()) {
4120 if (cmd.action() == LFUN_REDO || cmd.action() == LFUN_UNDO)
4121 updateDialog("document", "");
4125 // Try with the document BufferView dispatch if any.
4126 BufferView * doc_bv = documentBufferView();
4127 if (doc_bv && doc_bv != bv) {
4128 doc_bv->dispatch(cmd, dr);
4129 if (dr.dispatched()) {
4130 if (cmd.action() == LFUN_REDO || cmd.action() == LFUN_UNDO)
4131 updateDialog("document", "");
4136 // Then let the current Cursor dispatch its own actions.
4137 bv->cursor().dispatch(cmd);
4139 // update completion. We do it here and not in
4140 // processKeySym to avoid another redraw just for a
4141 // changed inline completion
4142 if (cmd.origin() == FuncRequest::KEYBOARD) {
4143 if (cmd.action() == LFUN_SELF_INSERT
4144 || (cmd.action() == LFUN_ERT_INSERT && bv->cursor().inMathed()))
4145 updateCompletion(bv->cursor(), true, true);
4146 else if (cmd.action() == LFUN_CHAR_DELETE_BACKWARD)
4147 updateCompletion(bv->cursor(), false, true);
4149 updateCompletion(bv->cursor(), false, false);
4152 dr = bv->cursor().result();
4156 void GuiView::dispatch(FuncRequest const & cmd, DispatchResult & dr)
4158 BufferView * bv = currentBufferView();
4159 // By default we won't need any update.
4160 dr.screenUpdate(Update::None);
4161 // assume cmd will be dispatched
4162 dr.dispatched(true);
4164 Buffer * doc_buffer = documentBufferView()
4165 ? &(documentBufferView()->buffer()) : nullptr;
4167 if (cmd.origin() == FuncRequest::TOC) {
4168 GuiToc * toc = static_cast<GuiToc*>(findOrBuild("toc", false));
4169 toc->doDispatch(bv->cursor(), cmd, dr);
4173 string const argument = to_utf8(cmd.argument());
4175 switch(cmd.action()) {
4176 case LFUN_BUFFER_CHILD_OPEN:
4177 openChildDocument(to_utf8(cmd.argument()));
4180 case LFUN_BUFFER_IMPORT:
4181 importDocument(to_utf8(cmd.argument()));
4184 case LFUN_MASTER_BUFFER_EXPORT:
4186 doc_buffer = const_cast<Buffer *>(doc_buffer->masterBuffer());
4188 case LFUN_BUFFER_EXPORT: {
4191 // GCC only sees strfwd.h when building merged
4192 if (::lyx::operator==(cmd.argument(), "custom")) {
4193 // LFUN_MASTER_BUFFER_EXPORT is not enabled for this case,
4194 // so the following test should not be needed.
4195 // In principle, we could try to switch to such a view...
4196 // if (cmd.action() == LFUN_BUFFER_EXPORT)
4197 dispatch(FuncRequest(LFUN_DIALOG_SHOW, "sendto"), dr);
4201 string const dest = cmd.getArg(1);
4202 FileName target_dir;
4203 if (!dest.empty() && FileName::isAbsolute(dest))
4204 target_dir = FileName(support::onlyPath(dest));
4206 target_dir = doc_buffer->fileName().onlyPath();
4208 string const format = (argument.empty() || argument == "default") ?
4209 doc_buffer->params().getDefaultOutputFormat() : argument;
4211 if ((dest.empty() && doc_buffer->isUnnamed())
4212 || !target_dir.isDirWritable()) {
4213 exportBufferAs(*doc_buffer, from_utf8(format));
4216 /* TODO/Review: Is it a problem to also export the children?
4217 See the update_unincluded flag */
4218 d.asyncBufferProcessing(format,
4221 &GuiViewPrivate::exportAndDestroy,
4223 nullptr, cmd.allowAsync());
4224 // TODO Inform user about success
4228 case LFUN_BUFFER_EXPORT_AS: {
4229 LASSERT(doc_buffer, break);
4230 docstring f = cmd.argument();
4232 f = from_ascii(doc_buffer->params().getDefaultOutputFormat());
4233 exportBufferAs(*doc_buffer, f);
4237 case LFUN_BUFFER_UPDATE: {
4238 d.asyncBufferProcessing(argument,
4241 &GuiViewPrivate::compileAndDestroy,
4243 nullptr, cmd.allowAsync(), true);
4246 case LFUN_BUFFER_VIEW: {
4247 d.asyncBufferProcessing(argument,
4249 _("Previewing ..."),
4250 &GuiViewPrivate::previewAndDestroy,
4252 &Buffer::preview, cmd.allowAsync());
4255 case LFUN_MASTER_BUFFER_UPDATE: {
4256 d.asyncBufferProcessing(argument,
4257 (doc_buffer ? doc_buffer->masterBuffer() : nullptr),
4259 &GuiViewPrivate::compileAndDestroy,
4261 nullptr, cmd.allowAsync(), true);
4264 case LFUN_MASTER_BUFFER_VIEW: {
4265 d.asyncBufferProcessing(argument,
4266 (doc_buffer ? doc_buffer->masterBuffer() : nullptr),
4268 &GuiViewPrivate::previewAndDestroy,
4269 nullptr, &Buffer::preview, cmd.allowAsync());
4272 case LFUN_EXPORT_CANCEL: {
4273 Systemcall::killscript();
4276 case LFUN_BUFFER_SWITCH: {
4277 string const file_name = to_utf8(cmd.argument());
4278 if (!FileName::isAbsolute(file_name)) {
4280 dr.setMessage(_("Absolute filename expected."));
4284 Buffer * buffer = theBufferList().getBuffer(FileName(file_name));
4287 dr.setMessage(_("Document not loaded"));
4291 // Do we open or switch to the buffer in this view ?
4292 if (workArea(*buffer)
4293 || lyxrc.open_buffers_in_tabs || !documentBufferView()) {
4298 // Look for the buffer in other views
4299 QList<int> const ids = guiApp->viewIds();
4301 for (; i != ids.size(); ++i) {
4302 GuiView & gv = guiApp->view(ids[i]);
4303 if (gv.workArea(*buffer)) {
4305 gv.activateWindow();
4307 gv.setBuffer(buffer);
4312 // If necessary, open a new window as a last resort
4313 if (i == ids.size()) {
4314 lyx::dispatch(FuncRequest(LFUN_WINDOW_NEW));
4320 case LFUN_BUFFER_NEXT:
4321 gotoNextOrPreviousBuffer(NEXTBUFFER, false);
4324 case LFUN_BUFFER_MOVE_NEXT:
4325 gotoNextOrPreviousBuffer(NEXTBUFFER, true);
4328 case LFUN_BUFFER_PREVIOUS:
4329 gotoNextOrPreviousBuffer(PREVBUFFER, false);
4332 case LFUN_BUFFER_MOVE_PREVIOUS:
4333 gotoNextOrPreviousBuffer(PREVBUFFER, true);
4336 case LFUN_BUFFER_CHKTEX:
4337 LASSERT(doc_buffer, break);
4338 doc_buffer->runChktex();
4341 case LFUN_COMMAND_EXECUTE: {
4342 command_execute_ = true;
4343 minibuffer_focus_ = true;
4346 case LFUN_DROP_LAYOUTS_CHOICE:
4347 d.layout_->showPopup();
4350 case LFUN_MENU_OPEN:
4351 if (QMenu * menu = guiApp->menus().menu(toqstr(cmd.argument()), *this))
4352 menu->exec(QCursor::pos());
4355 case LFUN_FILE_INSERT: {
4356 bool const ignore_lang = cmd.getArg(1) == "ignorelang";
4357 if (insertLyXFile(from_utf8(cmd.getArg(0)), ignore_lang)) {
4358 dr.forceBufferUpdate();
4359 dr.screenUpdate(Update::Force);
4364 case LFUN_FILE_INSERT_PLAINTEXT:
4365 case LFUN_FILE_INSERT_PLAINTEXT_PARA: {
4366 string const fname = to_utf8(cmd.argument());
4367 if (!fname.empty() && !FileName::isAbsolute(fname)) {
4368 dr.setMessage(_("Absolute filename expected."));
4372 FileName filename(fname);
4373 if (fname.empty()) {
4374 FileDialog dlg(qt_("Select file to insert"));
4376 FileDialog::Result result = dlg.open(toqstr(bv->buffer().filePath()),
4377 QStringList(qt_("All Files (*)")));
4379 if (result.first == FileDialog::Later || result.second.isEmpty()) {
4380 dr.setMessage(_("Canceled."));
4384 filename.set(fromqstr(result.second));
4388 FuncRequest const new_cmd(cmd, filename.absoluteFilePath());
4389 bv->dispatch(new_cmd, dr);
4394 case LFUN_BUFFER_RELOAD: {
4395 LASSERT(doc_buffer, break);
4398 bool drop = (cmd.argument() == "dump");
4401 if (!drop && !doc_buffer->isClean()) {
4402 docstring const file =
4403 makeDisplayPath(doc_buffer->absFileName(), 20);
4404 if (doc_buffer->notifiesExternalModification()) {
4405 docstring text = _("The current version will be lost. "
4406 "Are you sure you want to load the version on disk "
4407 "of the document %1$s?");
4408 ret = Alert::prompt(_("Reload saved document?"),
4409 bformat(text, file), 1, 1,
4410 _("&Reload"), _("&Cancel"));
4412 docstring text = _("Any changes will be lost. "
4413 "Are you sure you want to revert to the saved version "
4414 "of the document %1$s?");
4415 ret = Alert::prompt(_("Revert to saved document?"),
4416 bformat(text, file), 1, 1,
4417 _("&Revert"), _("&Cancel"));
4422 doc_buffer->markClean();
4423 reloadBuffer(*doc_buffer);
4424 dr.forceBufferUpdate();
4429 case LFUN_BUFFER_RESET_EXPORT:
4430 LASSERT(doc_buffer, break);
4431 doc_buffer->requireFreshStart(true);
4432 dr.setMessage(_("Buffer export reset."));
4435 case LFUN_BUFFER_WRITE:
4436 LASSERT(doc_buffer, break);
4437 saveBuffer(*doc_buffer);
4440 case LFUN_BUFFER_WRITE_AS:
4441 LASSERT(doc_buffer, break);
4442 renameBuffer(*doc_buffer, cmd.argument());
4445 case LFUN_BUFFER_WRITE_AS_TEMPLATE:
4446 LASSERT(doc_buffer, break);
4447 renameBuffer(*doc_buffer, cmd.argument(),
4448 LV_WRITE_AS_TEMPLATE);
4451 case LFUN_BUFFER_WRITE_ALL: {
4452 Buffer * first = theBufferList().first();
4455 message(_("Saving all documents..."));
4456 // We cannot use a for loop as the buffer list cycles.
4459 if (!b->isClean()) {
4461 LYXERR(Debug::ACTION, "Saved " << b->absFileName());
4463 b = theBufferList().next(b);
4464 } while (b != first);
4465 dr.setMessage(_("All documents saved."));
4469 case LFUN_MASTER_BUFFER_FORALL: {
4473 FuncRequest funcToRun = lyxaction.lookupFunc(cmd.getLongArg(0));
4474 funcToRun.allowAsync(false);
4476 for (Buffer const * buf : doc_buffer->allRelatives()) {
4477 // Switch to other buffer view and resend cmd
4478 lyx::dispatch(FuncRequest(
4479 LFUN_BUFFER_SWITCH, buf->absFileName()));
4480 lyx::dispatch(funcToRun);
4483 lyx::dispatch(FuncRequest(
4484 LFUN_BUFFER_SWITCH, doc_buffer->absFileName()));
4488 case LFUN_BUFFER_EXTERNAL_MODIFICATION_CLEAR:
4489 LASSERT(doc_buffer, break);
4490 doc_buffer->clearExternalModification();
4493 case LFUN_BUFFER_CLOSE:
4497 case LFUN_BUFFER_CLOSE_ALL:
4501 case LFUN_DEVEL_MODE_TOGGLE:
4502 devel_mode_ = !devel_mode_;
4504 dr.setMessage(_("Developer mode is now enabled."));
4506 dr.setMessage(_("Developer mode is now disabled."));
4509 case LFUN_TOOLBAR_SET: {
4510 string const name = cmd.getArg(0);
4511 string const state = cmd.getArg(1);
4512 if (GuiToolbar * t = toolbar(name))
4517 case LFUN_TOOLBAR_TOGGLE: {
4518 string const name = cmd.getArg(0);
4519 if (GuiToolbar * t = toolbar(name))
4524 case LFUN_TOOLBAR_MOVABLE: {
4525 string const name = cmd.getArg(0);
4527 // toggle (all) toolbars movablility
4528 toolbarsMovable_ = !toolbarsMovable_;
4529 for (ToolbarInfo const & ti : guiApp->toolbars()) {
4530 GuiToolbar * tb = toolbar(ti.name);
4531 if (tb && tb->isMovable() != toolbarsMovable_)
4532 // toggle toolbar movablity if it does not fit lock
4533 // (all) toolbars positions state silent = true, since
4534 // status bar notifications are slow
4537 if (toolbarsMovable_)
4538 dr.setMessage(_("Toolbars unlocked."));
4540 dr.setMessage(_("Toolbars locked."));
4541 } else if (GuiToolbar * tb = toolbar(name))
4542 // toggle current toolbar movablity
4544 // update lock (all) toolbars positions
4545 updateLockToolbars();
4549 case LFUN_ICON_SIZE: {
4550 QSize size = d.iconSize(cmd.argument());
4552 dr.setMessage(bformat(_("Icon size set to %1$dx%2$d."),
4553 size.width(), size.height()));
4557 case LFUN_DIALOG_UPDATE: {
4558 string const name = to_utf8(cmd.argument());
4559 if (name == "prefs" || name == "document")
4560 updateDialog(name, string());
4561 else if (name == "paragraph")
4562 lyx::dispatch(FuncRequest(LFUN_PARAGRAPH_UPDATE));
4563 else if (currentBufferView()) {
4564 Inset * inset = currentBufferView()->editedInset(name);
4565 // Can only update a dialog connected to an existing inset
4567 // FIXME: get rid of this indirection; GuiView ask the inset
4568 // if he is kind enough to update itself...
4569 FuncRequest fr(LFUN_INSET_DIALOG_UPDATE, cmd.argument());
4570 //FIXME: pass DispatchResult here?
4571 inset->dispatch(currentBufferView()->cursor(), fr);
4577 case LFUN_DIALOG_TOGGLE: {
4578 FuncCode const func_code = isDialogVisible(cmd.getArg(0))
4579 ? LFUN_DIALOG_HIDE : LFUN_DIALOG_SHOW;
4580 dispatch(FuncRequest(func_code, cmd.argument()), dr);
4584 case LFUN_DIALOG_DISCONNECT_INSET:
4585 disconnectDialog(to_utf8(cmd.argument()));
4588 case LFUN_DIALOG_HIDE: {
4589 guiApp->hideDialogs(to_utf8(cmd.argument()), nullptr);
4593 case LFUN_DIALOG_SHOW: {
4594 string const name = cmd.getArg(0);
4595 string sdata = trim(to_utf8(cmd.argument()).substr(name.size()));
4597 if (name == "latexlog") {
4598 // getStatus checks that
4599 LASSERT(doc_buffer, break);
4600 Buffer::LogType type;
4601 string const logfile = doc_buffer->logName(&type);
4603 case Buffer::latexlog:
4606 case Buffer::buildlog:
4607 sdata = "literate ";
4610 sdata += Lexer::quoteString(logfile);
4611 showDialog("log", sdata);
4612 } else if (name == "vclog") {
4613 // getStatus checks that
4614 LASSERT(doc_buffer, break);
4615 string const sdata2 = "vc " +
4616 Lexer::quoteString(doc_buffer->lyxvc().getLogFile());
4617 showDialog("log", sdata2);
4618 } else if (name == "symbols") {
4619 sdata = bv->cursor().getEncoding()->name();
4621 showDialog("symbols", sdata);
4622 } else if (name == "findreplace") {
4623 sdata = to_utf8(bv->cursor().selectionAsString(false));
4624 showDialog(name, sdata);
4626 } else if (name == "prefs" && isFullScreen()) {
4627 lfunUiToggle("fullscreen");
4628 showDialog("prefs", sdata);
4630 showDialog(name, sdata);
4635 dr.setMessage(cmd.argument());
4638 case LFUN_UI_TOGGLE: {
4639 string arg = cmd.getArg(0);
4640 if (!lfunUiToggle(arg)) {
4641 docstring const msg = "ui-toggle " + _("%1$s unknown command!");
4642 dr.setMessage(bformat(msg, from_utf8(arg)));
4644 // Make sure the keyboard focus stays in the work area.
4649 case LFUN_VIEW_SPLIT: {
4650 LASSERT(doc_buffer, break);
4651 string const orientation = cmd.getArg(0);
4652 d.splitter_->setOrientation(orientation == "vertical"
4653 ? Qt::Vertical : Qt::Horizontal);
4654 TabWorkArea * twa = addTabWorkArea();
4655 GuiWorkArea * wa = twa->addWorkArea(*doc_buffer, *this);
4656 setCurrentWorkArea(wa);
4659 case LFUN_TAB_GROUP_CLOSE:
4660 if (TabWorkArea * twa = d.currentTabWorkArea()) {
4661 closeTabWorkArea(twa);
4662 d.current_work_area_ = nullptr;
4663 twa = d.currentTabWorkArea();
4664 // Switch to the next GuiWorkArea in the found TabWorkArea.
4666 // Make sure the work area is up to date.
4667 setCurrentWorkArea(twa->currentWorkArea());
4669 setCurrentWorkArea(nullptr);
4674 case LFUN_VIEW_CLOSE:
4675 if (TabWorkArea * twa = d.currentTabWorkArea()) {
4676 closeWorkArea(twa->currentWorkArea());
4677 d.current_work_area_ = nullptr;
4678 twa = d.currentTabWorkArea();
4679 // Switch to the next GuiWorkArea in the found TabWorkArea.
4681 // Make sure the work area is up to date.
4682 setCurrentWorkArea(twa->currentWorkArea());
4684 setCurrentWorkArea(nullptr);
4689 case LFUN_COMPLETION_INLINE:
4690 if (d.current_work_area_)
4691 d.current_work_area_->completer().showInline();
4694 case LFUN_COMPLETION_POPUP:
4695 if (d.current_work_area_)
4696 d.current_work_area_->completer().showPopup();
4701 if (d.current_work_area_)
4702 d.current_work_area_->completer().tab();
4705 case LFUN_COMPLETION_CANCEL:
4706 if (d.current_work_area_) {
4707 if (d.current_work_area_->completer().popupVisible())
4708 d.current_work_area_->completer().hidePopup();
4710 d.current_work_area_->completer().hideInline();
4714 case LFUN_COMPLETION_ACCEPT:
4715 if (d.current_work_area_)
4716 d.current_work_area_->completer().activate();
4719 case LFUN_BUFFER_ZOOM_IN:
4720 case LFUN_BUFFER_ZOOM_OUT:
4721 case LFUN_BUFFER_ZOOM: {
4722 if (cmd.argument().empty()) {
4723 if (cmd.action() == LFUN_BUFFER_ZOOM)
4725 else if (cmd.action() == LFUN_BUFFER_ZOOM_IN)
4730 if (cmd.action() == LFUN_BUFFER_ZOOM)
4731 zoom_ratio_ = convert<int>(cmd.argument()) / double(lyxrc.defaultZoom);
4732 else if (cmd.action() == LFUN_BUFFER_ZOOM_IN)
4733 zoom_ratio_ += convert<int>(cmd.argument()) / 100.0;
4735 zoom_ratio_ -= convert<int>(cmd.argument()) / 100.0;
4738 // Actual zoom value: default zoom + fractional extra value
4739 int zoom = (int)(lyxrc.defaultZoom * zoom_ratio_);
4740 if (zoom < static_cast<int>(zoom_min_))
4743 setCurrentZoom(zoom);
4745 dr.setMessage(bformat(_("Zoom level is now %1$d% (default value: %2$d%)"),
4746 lyxrc.currentZoom, lyxrc.defaultZoom));
4748 guiApp->fontLoader().update();
4749 dr.screenUpdate(Update::ForceAll | Update::FitCursor);
4753 case LFUN_VC_REGISTER:
4754 case LFUN_VC_RENAME:
4756 case LFUN_VC_CHECK_IN:
4757 case LFUN_VC_CHECK_OUT:
4758 case LFUN_VC_REPO_UPDATE:
4759 case LFUN_VC_LOCKING_TOGGLE:
4760 case LFUN_VC_REVERT:
4761 case LFUN_VC_UNDO_LAST:
4762 case LFUN_VC_COMMAND:
4763 case LFUN_VC_COMPARE:
4764 dispatchVC(cmd, dr);
4767 case LFUN_SERVER_GOTO_FILE_ROW:
4768 if(goToFileRow(to_utf8(cmd.argument())))
4769 dr.screenUpdate(Update::Force | Update::FitCursor);
4772 case LFUN_LYX_ACTIVATE:
4776 case LFUN_WINDOW_RAISE:
4782 case LFUN_FORWARD_SEARCH: {
4783 // it seems safe to assume we have a document buffer, since
4784 // getStatus wants one.
4785 LASSERT(doc_buffer, break);
4786 Buffer const * doc_master = doc_buffer->masterBuffer();
4787 FileName const path(doc_master->temppath());
4788 string const texname = doc_master->isChild(doc_buffer)
4789 ? DocFileName(changeExtension(
4790 doc_buffer->absFileName(),
4791 "tex")).mangledFileName()
4792 : doc_buffer->latexName();
4793 string const fulltexname =
4794 support::makeAbsPath(texname, doc_master->temppath()).absFileName();
4795 string const mastername =
4796 removeExtension(doc_master->latexName());
4797 FileName const dviname(addName(path.absFileName(),
4798 addExtension(mastername, "dvi")));
4799 FileName const pdfname(addName(path.absFileName(),
4800 addExtension(mastername, "pdf")));
4801 bool const have_dvi = dviname.exists();
4802 bool const have_pdf = pdfname.exists();
4803 if (!have_dvi && !have_pdf) {
4804 dr.setMessage(_("Please, preview the document first."));
4807 string outname = dviname.onlyFileName();
4808 string command = lyxrc.forward_search_dvi;
4809 if (!have_dvi || (have_pdf &&
4810 pdfname.lastModified() > dviname.lastModified())) {
4811 outname = pdfname.onlyFileName();
4812 command = lyxrc.forward_search_pdf;
4815 DocIterator cur = bv->cursor();
4816 int row = doc_buffer->texrow().rowFromDocIterator(cur).first;
4817 LYXERR(Debug::ACTION, "Forward search: row:" << row
4819 if (row == -1 || command.empty()) {
4820 dr.setMessage(_("Couldn't proceed."));
4823 string texrow = convert<string>(row);
4825 command = subst(command, "$$n", texrow);
4826 command = subst(command, "$$f", fulltexname);
4827 command = subst(command, "$$t", texname);
4828 command = subst(command, "$$o", outname);
4830 volatile PathChanger p(path);
4832 one.startscript(Systemcall::DontWait, command);
4836 case LFUN_SPELLING_CONTINUOUSLY:
4837 lyxrc.spellcheck_continuously = !lyxrc.spellcheck_continuously;
4838 dr.screenUpdate(Update::Force);
4841 case LFUN_CITATION_OPEN: {
4843 if (theFormats().getFormat("pdf"))
4844 pdfv = theFormats().getFormat("pdf")->viewer();
4845 if (theFormats().getFormat("ps"))
4846 psv = theFormats().getFormat("ps")->viewer();
4847 frontend::showTarget(argument, pdfv, psv);
4852 // The LFUN must be for one of BufferView, Buffer or Cursor;
4854 dispatchToBufferView(cmd, dr);
4858 // Need to update bv because many LFUNs here might have destroyed it
4859 bv = currentBufferView();
4861 // Clear non-empty selections
4862 // (e.g. from a "char-forward-select" followed by "char-backward-select")
4864 Cursor & cur = bv->cursor();
4865 if ((cur.selection() && cur.selBegin() == cur.selEnd())) {
4866 cur.clearSelection();
4872 bool GuiView::lfunUiToggle(string const & ui_component)
4874 if (ui_component == "scrollbar") {
4875 // hide() is of no help
4876 if (d.current_work_area_->verticalScrollBarPolicy() ==
4877 Qt::ScrollBarAlwaysOff)
4879 d.current_work_area_->setVerticalScrollBarPolicy(
4880 Qt::ScrollBarAsNeeded);
4882 d.current_work_area_->setVerticalScrollBarPolicy(
4883 Qt::ScrollBarAlwaysOff);
4884 } else if (ui_component == "statusbar") {
4885 statusBar()->setVisible(!statusBar()->isVisible());
4886 } else if (ui_component == "menubar") {
4887 menuBar()->setVisible(!menuBar()->isVisible());
4888 } else if (ui_component == "zoom") {
4889 zoom_value_->setVisible(!zoom_value_->isVisible());
4890 } else if (ui_component == "zoomslider") {
4891 zoom_slider_->setVisible(!zoom_slider_->isVisible());
4892 zoom_in_->setVisible(zoom_slider_->isVisible());
4893 zoom_out_->setVisible(zoom_slider_->isVisible());
4894 } else if (ui_component == "frame") {
4895 int const l = contentsMargins().left();
4897 //are the frames in default state?
4898 d.current_work_area_->setFrameStyle(QFrame::NoFrame);
4900 #if QT_VERSION > 0x050903
4901 setAttribute(Qt::WA_ContentsMarginsRespectsSafeArea, false);
4903 setContentsMargins(-2, -2, -2, -2);
4905 #if QT_VERSION > 0x050903
4906 setAttribute(Qt::WA_ContentsMarginsRespectsSafeArea, true);
4908 setContentsMargins(0, 0, 0, 0);
4911 if (ui_component == "fullscreen") {
4919 void GuiView::toggleFullScreen()
4921 setWindowState(windowState() ^ Qt::WindowFullScreen);
4925 Buffer const * GuiView::updateInset(Inset const * inset)
4930 Buffer const * inset_buffer = &(inset->buffer());
4932 for (int i = 0; i != d.splitter_->count(); ++i) {
4933 GuiWorkArea * wa = d.tabWorkArea(i)->currentWorkArea();
4936 Buffer const * buffer = &(wa->bufferView().buffer());
4937 if (inset_buffer == buffer)
4938 wa->scheduleRedraw(true);
4940 return inset_buffer;
4944 void GuiView::restartCaret()
4946 /* When we move around, or type, it's nice to be able to see
4947 * the caret immediately after the keypress.
4949 if (d.current_work_area_)
4950 d.current_work_area_->startBlinkingCaret();
4952 // Take this occasion to update the other GUI elements.
4958 void GuiView::updateCompletion(Cursor & cur, bool start, bool keep)
4960 if (d.current_work_area_)
4961 d.current_work_area_->completer().updateVisibility(cur, start, keep);
4966 // This list should be kept in sync with the list of insets in
4967 // src/insets/Inset.cpp. I.e., if a dialog goes with an inset, the
4968 // dialog should have the same name as the inset.
4969 // Changes should be also recorded in LFUN_DIALOG_SHOW doxygen
4970 // docs in LyXAction.cpp.
4972 char const * const dialognames[] = {
4974 "aboutlyx", "bibitem", "bibtex", "box", "branch", "changes", "character",
4975 "citation", "compare", "comparehistory", "counter", "document", "errorlist", "ert",
4976 "external", "file", "findreplace", "findreplaceadv", "float", "graphics",
4977 "href", "include", "index", "index_print", "info", "listings", "label", "line",
4978 "log", "lyxfiles", "mathdelimiter", "mathmatrix", "mathspace", "nomenclature",
4979 "nomencl_print", "note", "paragraph", "phantom", "prefs", "ref",
4980 "sendto", "space", "spellchecker", "symbols", "tabular", "tabularcreate",
4981 "thesaurus", "texinfo", "toc", "view-source", "vspace", "wrap", "progress"};
4983 char const * const * const end_dialognames =
4984 dialognames + (sizeof(dialognames) / sizeof(char *));
4988 cmpCStr(char const * name) : name_(name) {}
4989 bool operator()(char const * other) {
4990 return strcmp(other, name_) == 0;
4997 bool isValidName(string const & name)
4999 return find_if(dialognames, end_dialognames,
5000 cmpCStr(name.c_str())) != end_dialognames;
5006 void GuiView::resetDialogs()
5008 // Make sure that no LFUN uses any GuiView.
5009 guiApp->setCurrentView(nullptr);
5013 constructToolbars();
5014 guiApp->menus().fillMenuBar(menuBar(), this, false);
5015 d.layout_->updateContents(true);
5016 // Now update controls with current buffer.
5017 guiApp->setCurrentView(this);
5023 void GuiView::flatGroupBoxes(const QObject * widget, bool flag)
5025 for (QObject * child: widget->children()) {
5026 if (child->inherits("QGroupBox")) {
5027 QGroupBox * box = (QGroupBox*) child;
5030 flatGroupBoxes(child, flag);
5036 Dialog * GuiView::findOrBuild(string const & name, bool hide_it)
5038 if (!isValidName(name))
5041 map<string, DialogPtr>::iterator it = d.dialogs_.find(name);
5043 if (it != d.dialogs_.end()) {
5045 it->second->hideView();
5046 return it->second.get();
5049 Dialog * dialog = build(name);
5050 d.dialogs_[name].reset(dialog);
5051 #if (QT_VERSION >= QT_VERSION_CHECK(5, 0, 0))
5052 // Force a uniform style for group boxes
5053 // On Mac non-flat works better, on Linux flat is standard
5054 flatGroupBoxes(dialog->asQWidget(), guiApp->platformName() != "cocoa");
5056 if (lyxrc.allow_geometry_session)
5057 dialog->restoreSession();
5064 void GuiView::showDialog(string const & name, string const & sdata,
5067 triggerShowDialog(toqstr(name), toqstr(sdata), inset);
5071 void GuiView::doShowDialog(QString const & qname, QString const & qdata,
5077 const string name = fromqstr(qname);
5078 const string sdata = fromqstr(qdata);
5082 Dialog * dialog = findOrBuild(name, false);
5084 bool const visible = dialog->isVisibleView();
5085 dialog->showData(sdata);
5086 if (currentBufferView())
5087 currentBufferView()->editInset(name, inset);
5088 // We only set the focus to the new dialog if it was not yet
5089 // visible in order not to change the existing previous behaviour
5091 // activateWindow is needed for floating dockviews
5092 dialog->asQWidget()->raise();
5093 dialog->asQWidget()->activateWindow();
5094 if (dialog->wantInitialFocus())
5095 dialog->asQWidget()->setFocus();
5099 catch (ExceptionMessage const &) {
5107 bool GuiView::isDialogVisible(string const & name) const
5109 map<string, DialogPtr>::const_iterator it = d.dialogs_.find(name);
5110 if (it == d.dialogs_.end())
5112 return it->second.get()->isVisibleView() && !it->second.get()->isClosing();
5116 void GuiView::hideDialog(string const & name, Inset * inset)
5118 map<string, DialogPtr>::const_iterator it = d.dialogs_.find(name);
5119 if (it == d.dialogs_.end())
5123 if (!currentBufferView())
5125 if (inset != currentBufferView()->editedInset(name))
5129 Dialog * const dialog = it->second.get();
5130 if (dialog->isVisibleView())
5132 if (currentBufferView())
5133 currentBufferView()->editInset(name, nullptr);
5137 void GuiView::disconnectDialog(string const & name)
5139 if (!isValidName(name))
5141 if (currentBufferView())
5142 currentBufferView()->editInset(name, nullptr);
5146 void GuiView::hideAll() const
5148 for(auto const & dlg_p : d.dialogs_)
5149 dlg_p.second->hideView();
5153 void GuiView::updateDialogs()
5155 for(auto const & dlg_p : d.dialogs_) {
5156 Dialog * dialog = dlg_p.second.get();
5158 if (dialog->needBufferOpen() && !documentBufferView())
5159 hideDialog(fromqstr(dialog->name()), nullptr);
5160 else if (dialog->isVisibleView())
5161 dialog->checkStatus();
5169 Dialog * GuiView::build(string const & name)
5171 return createDialog(*this, name);
5175 SEMenu::SEMenu(QWidget * parent)
5177 QAction * action = addAction(qt_("Disable Shell Escape"));
5178 connect(action, SIGNAL(triggered()),
5179 parent, SLOT(disableShellEscape()));
5183 void PressableSvgWidget::mousePressEvent(QMouseEvent * event)
5185 if (event->button() == Qt::LeftButton) {
5190 } // namespace frontend
5193 #include "moc_GuiView.cpp"