#include "DialogFactory.h"
#include "DispatchResult.h"
#include "FileDialog.h"
+#include "FindAndReplace.h"
#include "FontLoader.h"
#include "GuiApplication.h"
#include "GuiClickableLabel.h"
#include "Toolbars.h"
#include "version.h"
+#include "graphics/PreviewLoader.h"
+
#include "support/convert.h"
#include "support/debug.h"
#include "support/ExceptionMessage.h"
#include <QMenu>
#include <QMenuBar>
#include <QMimeData>
-#include <QMovie>
#include <QPainter>
#include <QPixmap>
#include <QPoint>
#include <QTimer>
#include <QUrl>
#include <QWindowStateChangeEvent>
+#include <QGestureEvent>
+#include <QPinchGesture>
// sync with GuiAlert.cpp
return;
/// The text to be written on top of the pixmap
QString const htext = qt_("The Document\nProcessor[[welcome banner]]");
- QString const htextsize = qt_("1.0[[possibly scale the welcome banner text size]]");
+ QString const htextsize = qt_("1.0[[translating this to different value scales the welcome banner text size for your language]]");
/// The text to be written on top of the pixmap
QString const text = lyx_version ?
qt_("version ") + lyx_version : qt_("unknown version");
-#if QT_VERSION >= 0x050000
QString imagedir = "images/";
FileName fname = imageLibFileSearch(imagedir, "banner", "svgz");
QSvgRenderer svgRenderer(toqstr(fname.absFileName()));
} else {
splash_ = getPixmap("images/", "banner", "png");
}
-#else
- splash_ = getPixmap("images/", "banner", "svgz,png");
-#endif
QPainter pain(&splash_);
pain.setPen(QColor(0, 0, 0));
QStringList titlesegs = htext.split('\n');
int wline = 0;
int hline = fm.maxHeight();
- QStringList::const_iterator sit;
for (QString const & seg : titlesegs) {
if (fm.width(seg) > wline)
wline = fm.width(seg);
/// Current ratio between physical pixels and device-independent pixels
double pixelRatio() const {
-#if QT_VERSION >= 0x050000
return qt_scale_factor * devicePixelRatio();
-#else
- return 1.0;
-#endif
}
qreal fontSize() const {
/// Ratio between physical pixels and device-independent pixels of splash image
double splashPixelRatio() const {
-#if QT_VERSION >= 0x050000
return splash_.devicePixelRatio();
-#else
- return 1.0;
-#endif
}
};
///
QTimer statusbar_timer_;
+ QTimer statusbar_stats_timer_;
/// auto-saving of buffers
Timeout autosave_timeout_;
/// flag against a race condition due to multiclicks, see bug #1119
bool in_show_;
+
+ // Timers for statistic updates in buffer
+ /// Current time left to the nearest info update
+ int time_to_update = 1000;
+ ///Basic step for timer in ms. Basically reaction time for short selections
+ int const timer_rate = 500;
+ /// Real stats updates infrequently. First they take long time for big buffers, second
+ /// they are visible for fast-repeat keyboards even for mid documents.
+ int const default_stats_rate = 5000;
+ /// Detection of new selection, so we can react fast
+ bool already_in_selection_ = false;
+ /// Maximum size of "short" selection for which we can update with faster timer_rate
+ int const max_sel_chars = 5000;
+
};
QSet<Buffer const *> GuiView::GuiViewPrivate::busyBuffers;
GuiView::GuiView(int id)
: d(*new GuiViewPrivate(this)), id_(id), closing_(false), busy_(0),
- command_execute_(false), minibuffer_focus_(false), toolbarsMovable_(true),
- devel_mode_(false)
+ command_execute_(false), minibuffer_focus_(false), word_count_enabled_(true),
+ char_count_enabled_(true), char_nb_count_enabled_(false),
+ toolbarsMovable_(true), devel_mode_(false)
{
connect(this, SIGNAL(bufferViewChanged()),
this, SLOT(onBufferViewChanged()));
}
connect(&d.statusbar_timer_, SIGNAL(timeout()),
this, SLOT(clearMessage()));
+ connect(&d.statusbar_stats_timer_, SIGNAL(timeout()),
+ this, SLOT(showStats()));
+ d.statusbar_stats_timer_.start(d.timer_rate);
// We don't want to keep the window in memory if it is closed.
setAttribute(Qt::WA_DeleteOnClose, true);
#if !(defined(Q_OS_WIN) || defined(Q_CYGWIN_WIN)) && !defined(Q_OS_MAC)
- // QIcon::fromTheme was introduced in Qt 4.6
-#if (QT_VERSION >= 0x040600)
// assign an icon to main form. We do not do it under Qt/Win or Qt/Mac,
// since the icon is provided in the application bundle. We use a themed
// version when available and use the bundled one as fallback.
setWindowIcon(QIcon::fromTheme("lyx", getPixmap("images/", "lyx", "svg,png")));
-#else
- setWindowIcon(getPixmap("images/", "lyx", "svg,png"));
-#endif
#endif
resetWindowTitle();
// For Drag&Drop.
setAcceptDrops(true);
+ QFontMetrics const fm(statusBar()->fontMetrics());
+ int const iconheight = max(int(d.normalIconSize), fm.height());
+ QSize const iconsize(iconheight, iconheight);
+
// add busy indicator to statusbar
- GuiClickableLabel * busylabel = new GuiClickableLabel(statusBar());
- statusBar()->addPermanentWidget(busylabel);
search_mode mode = theGuiApp()->imageSearchMode();
- QString fn = toqstr(lyx::libFileSearch("images", "busy", "gif", mode).absFileName());
- QMovie * busyanim = new QMovie(fn, QByteArray(), busylabel);
- busylabel->setMovie(busyanim);
- busyanim->start();
- busylabel->hide();
+ QString fn = toqstr(lyx::libFileSearch("images", "busy", "svgz", mode).absFileName());
+ PressableSvgWidget * busySVG = new PressableSvgWidget(fn);
+ statusBar()->addPermanentWidget(busySVG);
+ // make busy indicator square with 5px margins
+ busySVG->setMaximumSize(busySVG->height() - 5, busySVG->height() - 5);
+ busySVG->hide();
+ // Add cancel button
+ QPixmap ps = QIcon(getPixmap("images/", "process-stop", "svgz")).pixmap(iconsize);
+ GuiClickableLabel * processStop = new GuiClickableLabel(statusBar());
+ processStop->setPixmap(ps);
+ processStop->setToolTip(qt_("Click here to stop export/output process"));
+ processStop->hide();
+ statusBar()->addPermanentWidget(processStop);
connect(&d.processing_thread_watcher_, SIGNAL(started()),
- busylabel, SLOT(show()));
+ busySVG, SLOT(show()));
+ connect(&d.processing_thread_watcher_, SIGNAL(finished()),
+ busySVG, SLOT(hide()));
+ connect(&d.processing_thread_watcher_, SIGNAL(started()),
+ processStop, SLOT(show()));
connect(&d.processing_thread_watcher_, SIGNAL(finished()),
- busylabel, SLOT(hide()));
- connect(busylabel, SIGNAL(clicked()), this, SLOT(checkCancelBackground()));
+ processStop, SLOT(hide()));
+ connect(processStop, SIGNAL(clicked()), this, SLOT(checkCancelBackground()));
- QFontMetrics const fm(statusBar()->fontMetrics());
+ connect(this, SIGNAL(scriptKilled()), busySVG, SLOT(hide()));
+ connect(this, SIGNAL(scriptKilled()), processStop, SLOT(hide()));
+
+ stat_counts_ = new GuiClickableLabel(statusBar());
+ stat_counts_->setAlignment(Qt::AlignCenter);
+ stat_counts_->hide();
+ statusBar()->addPermanentWidget(stat_counts_);
+
+ connect(stat_counts_, SIGNAL(clicked()), this, SLOT(statsPressed()));
zoom_slider_ = new QSlider(Qt::Horizontal, statusBar());
- // Small size slider for macOS to prevent the status bar from enlarging
- zoom_slider_->setAttribute(Qt::WA_MacSmallSize);
#if (QT_VERSION >= QT_VERSION_CHECK(5, 11, 0))
zoom_slider_->setFixedWidth(fm.horizontalAdvance('x') * 15);
#else
zoom_ratio_ = settings.value("zoom_ratio", 1.0).toDouble();
// Actual zoom value: default zoom + fractional offset
int zoom = (int)(lyxrc.defaultZoom * zoom_ratio_);
- if (zoom < static_cast<int>(zoom_min_))
- zoom = zoom_min_;
+ zoom = min(max(zoom, zoom_min_), zoom_max_);
zoom_slider_->setValue(zoom);
zoom_slider_->setToolTip(qt_("Workarea zoom level. Drag, use Ctrl-+/- or Shift-Mousewheel to adjust."));
// Buttons to change zoom stepwise
- zoom_in_ = new QPushButton(statusBar());
+#if (QT_VERSION >= QT_VERSION_CHECK(5, 11, 0))
+ QSize s(fm.horizontalAdvance('+'), fm.height());
+#else
+ QSize s(fm.width('+'), fm.height());
+#endif
+ zoom_in_ = new GuiClickableLabel(statusBar());
zoom_in_->setText("+");
- zoom_in_->setFlat(true);
- zoom_in_->setFixedSize(QSize(fm.height(), fm.height()));
- zoom_out_ = new QPushButton(statusBar());
+ zoom_in_->setFixedSize(s);
+ zoom_in_->setAlignment(Qt::AlignCenter);
+ zoom_out_ = new GuiClickableLabel(statusBar());
zoom_out_->setText(QString(QChar(0x2212)));
- zoom_out_->setFixedSize(QSize(fm.height(), fm.height()));
- zoom_out_->setFlat(true);
-
- statusBar()->addPermanentWidget(zoom_out_);
- zoom_out_->setEnabled(currentBufferView());
- statusBar()->addPermanentWidget(zoom_slider_);
+ zoom_out_->setFixedSize(s);
+ zoom_out_->setAlignment(Qt::AlignCenter);
+
+
+ zoom_widget_ = new QWidget(statusBar());
+ zoom_widget_->setAttribute(Qt::WA_MacSmallSize);
+ zoom_widget_->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Preferred);
+ zoom_widget_->setLayout(new QHBoxLayout());
+ zoom_widget_->layout()->setSpacing(5);
+ zoom_widget_->layout()->setContentsMargins(0,0,0,0);
+ zoom_widget_->layout()->addWidget(zoom_out_);
+ zoom_widget_->layout()->addWidget(zoom_slider_);
+ zoom_widget_->layout()->addWidget(zoom_in_);
+ statusBar()->addPermanentWidget(zoom_widget_);
+ zoom_out_->setEnabled(currentBufferView()
+ && zoom_slider_->value() > zoom_slider_->minimum());
zoom_slider_->setEnabled(currentBufferView());
- zoom_in_->setEnabled(currentBufferView());
- statusBar()->addPermanentWidget(zoom_in_);
+ zoom_in_->setEnabled(currentBufferView()
+ && zoom_slider_->value() < zoom_slider_->maximum());
connect(zoom_slider_, SIGNAL(sliderMoved(int)), this, SLOT(zoomSliderMoved(int)));
connect(zoom_slider_, SIGNAL(valueChanged(int)), this, SLOT(zoomValueChanged(int)));
connect(zoom_in_, SIGNAL(clicked()), this, SLOT(zoomInPressed()));
connect(zoom_out_, SIGNAL(clicked()), this, SLOT(zoomOutPressed()));
- zoom_value_ = new QLabel(statusBar());
+ // QPalette palette = statusBar()->palette();
+
+ zoom_value_ = new GuiClickableLabel(statusBar());
+ connect(zoom_value_, SIGNAL(pressed()), this, SLOT(showZoomContextMenu()));
+ // zoom_value_->setPalette(palette);
+ zoom_value_->setForegroundRole(statusBar()->foregroundRole());
zoom_value_->setFixedHeight(fm.height());
#if (QT_VERSION >= QT_VERSION_CHECK(5, 11, 0))
- zoom_value_->setMinimumWidth(fm.horizontalAdvance("000%"));
+ zoom_value_->setMinimumWidth(fm.horizontalAdvance("444\%"));
#else
- zoom_value_->setMinimumWidth(fm.width("000%"));
+ zoom_value_->setMinimumWidth(fm.width("444\%"));
#endif
+ zoom_value_->setAlignment(Qt::AlignCenter);
zoom_value_->setText(toqstr(bformat(_("[[ZOOM]]%1$d%"), zoom)));
statusBar()->addPermanentWidget(zoom_value_);
zoom_value_->setEnabled(currentBufferView());
- zoom_value_->setContextMenuPolicy(Qt::CustomContextMenu);
- connect(zoom_value_, SIGNAL(customContextMenuRequested(QPoint)),
- this, SLOT(showZoomContextMenu()));
+ statusBar()->setContextMenuPolicy(Qt::CustomContextMenu);
+ connect(statusBar(), SIGNAL(customContextMenuRequested(QPoint)),
+ this, SLOT(showStatusBarContextMenu()));
- int const iconheight = max(int(d.normalIconSize), fm.height());
- QSize const iconsize(iconheight, iconheight);
+ // enable pinch to zoom
+ grabGesture(Qt::PinchGesture);
QPixmap shellescape = QIcon(getPixmap("images/", "emblem-shellescape", "svgz,png")).pixmap(iconsize);
shell_escape_ = new QLabel(statusBar());
connect(this, SIGNAL(triggerShowDialog(QString const &, QString const &, Inset *)),
SLOT(doShowDialog(QString const &, QString const &, Inset *)));
- // set custom application bars context menu, e.g. tool bar and menu bar
- setContextMenuPolicy(Qt::CustomContextMenu);
- connect(this, SIGNAL(customContextMenuRequested(const QPoint &)),
- SLOT(toolBarPopup(const QPoint &)));
-
// Forbid too small unresizable window because it can happen
// with some window manager under X11.
setMinimumSize(300, 200);
int const ret =
Alert::prompt(ttl, msg, 1, 1,
_("&Cancel export"), _("Co&ntinue"));
- if (ret == 0)
- Systemcall::killscript();
+ if (ret == 0) {
+ cancelExport();
+ }
}
+void GuiView::statsPressed()
+{
+ DispatchResult dr;
+ dispatch(FuncRequest(LFUN_STATISTICS), dr);
+}
void GuiView::zoomSliderMoved(int value)
{
DispatchResult dr;
dispatch(FuncRequest(LFUN_BUFFER_ZOOM, convert<string>(value)), dr);
- currentWorkArea()->scheduleRedraw(true);
+ scheduleRedrawWorkAreas();
zoom_value_->setText(toqstr(bformat(_("[[ZOOM]]%1$d%"), value)));
+ zoom_in_->setEnabled(currentBufferView()
+ && value < zoom_slider_->maximum());
+ zoom_out_->setEnabled(currentBufferView()
+ && value > zoom_slider_->minimum());
}
{
DispatchResult dr;
dispatch(FuncRequest(LFUN_BUFFER_ZOOM_IN), dr);
- currentWorkArea()->scheduleRedraw(true);
+ scheduleRedrawWorkAreas();
}
{
DispatchResult dr;
dispatch(FuncRequest(LFUN_BUFFER_ZOOM_OUT), dr);
- currentWorkArea()->scheduleRedraw(true);
+ scheduleRedrawWorkAreas();
}
}
+void GuiView::showStatusBarContextMenu()
+{
+ QMenu * menu = guiApp->menus().menu(toqstr("context-statusbar"), * this);
+ if (!menu)
+ return;
+ menu->exec(QCursor::pos());
+}
+
+
+void GuiView::scheduleRedrawWorkAreas()
+{
+ for (int i = 0; i < d.tabWorkAreaCount(); i++) {
+ TabWorkArea* ta = d.tabWorkArea(i);
+ for (int u = 0; u < ta->count(); u++) {
+ ta->workArea(u)->scheduleRedraw(true);
+ }
+ }
+}
+
+
QVector<GuiWorkArea*> GuiView::GuiViewPrivate::guiWorkAreas()
{
QVector<GuiWorkArea*> areas;
settings.setValue("devel_mode", devel_mode_);
settings.beginGroup("views");
settings.beginGroup(QString::number(id_));
- if (guiApp->platformName() == "qt4x11" || guiApp->platformName() == "xcb") {
+ if (guiApp->platformName() == "xcb") {
settings.setValue("pos", pos());
settings.setValue("size", size());
} else
settings.setValue("geometry", saveGeometry());
settings.setValue("layout", saveState(0));
settings.setValue("icon_size", toqstr(d.iconSize(iconSize())));
- settings.setValue("zoom_slider_visible", zoom_slider_->isVisible());
+ settings.setValue("zoom_value_visible", zoom_value_->isVisible());
+ settings.setValue("zoom_slider_visible", zoom_widget_->isVisible());
+ settings.setValue("word_count_enabled", word_count_enabled_);
+ settings.setValue("char_count_enabled", char_count_enabled_);
+ settings.setValue("char_nb_count_enabled", char_nb_count_enabled_);
}
void GuiView::setCurrentZoom(const int v)
{
+ Q_EMIT currentZoomChanged(v);
lyxrc.currentZoom = v;
zoom_value_->setText(toqstr(bformat(_("[[ZOOM]]%1$d%"), v)));
- Q_EMIT currentZoomChanged(v);
+ zoom_in_->setEnabled(currentBufferView() && v < zoom_slider_->maximum());
+ zoom_out_->setEnabled(currentBufferView() && v > zoom_slider_->minimum());
}
zoom_ratio_ = settings.value("zoom_ratio", 1.0).toDouble();
// Actual zoom value: default zoom + fractional offset
int zoom = (int)(lyxrc.defaultZoom * zoom_ratio_);
- if (zoom < static_cast<int>(zoom_min_))
- zoom = zoom_min_;
+ zoom = min(max(zoom, zoom_min_), zoom_max_);
setCurrentZoom(zoom);
devel_mode_ = settings.value("devel_mode", devel_mode_).toBool();
settings.beginGroup("views");
//code below is skipped when when ~/.config/LyX is (re)created
setIconSize(d.iconSize(settings.value(icon_key).toString()));
+ zoom_value_->setVisible(settings.value("zoom_value_visible", true).toBool());
+
bool const show_zoom_slider = settings.value("zoom_slider_visible", true).toBool();
- zoom_slider_->setVisible(show_zoom_slider);
- zoom_in_->setVisible(show_zoom_slider);
- zoom_out_->setVisible(show_zoom_slider);
+ zoom_widget_->setVisible(show_zoom_slider);
+
+ word_count_enabled_ = settings.value("word_count_enabled", true).toBool();
+ char_count_enabled_ = settings.value("char_count_enabled", true).toBool();
+ char_nb_count_enabled_ = settings.value("char_nb_count_enabled", true).toBool();
+ stat_counts_->setVisible(word_count_enabled_ || char_count_enabled_ || char_nb_count_enabled_);
- if (guiApp->platformName() == "qt4x11" || guiApp->platformName() == "xcb") {
+ if (guiApp->platformName() == "xcb") {
QPoint pos = settings.value("pos", QPoint(50, 50)).toPoint();
QSize size = settings.value("size", QSize(690, 510)).toSize();
resize(size);
if (tb && tb->isMovable())
toolbarsMovable_ = true;
}
+ // set unified mac toolbars only when not movable as recommended:
+ // https://doc.qt.io/qt-5/qmainwindow.html#unifiedTitleAndToolBarOnMac-prop
+ setUnifiedTitleAndToolBarOnMac(!toolbarsMovable_);
}
// extracts the toolbars from the backend
for (ToolbarInfo const & inf : guiApp->toolbars())
d.toolbars_[inf.name] = new GuiToolbar(inf, *this);
+
+ DynamicMenuButton::resetIconCache();
}
// We cannot use a for loop as the buffer list cycles.
Buffer * b = first;
do {
- if (!saveBufferIfNeeded(const_cast<Buffer &>(*b), false))
+ if (!saveBufferIfNeeded(*b, false))
return false;
b = theBufferList().next(b);
} while (b != first);
{
LYXERR(Debug::DEBUG, "GuiView::closeEvent()");
+ // FIXME Bug #12828 bites here. If there is some other View open, then
+ // we really should only refuse to close if one of the Buffers open here
+ // is being processed.
if (!GuiViewPrivate::busyBuffers.isEmpty()) {
Alert::warning(_("Exit LyX"),
_("LyX could not be closed because documents are being processed by LyX."));
// Make sure the timer time out will not trigger a statusbar update.
d.statusbar_timer_.stop();
+ d.statusbar_stats_timer_.stop();
// Saving fullscreen requires additional tweaks in the toolbar code.
// It wouldn't also work under linux natively.
d.statusbar_timer_.stop();
}
+void GuiView::showStats()
+{
+ if (!statsEnabled())
+ return;
+
+ d.time_to_update -= d.timer_rate;
+
+ BufferView * bv = currentBufferView();
+ Buffer * buf = bv ? &bv->buffer() : nullptr;
+ if (!buf) {
+ stat_counts_->hide();
+ return;
+ }
+
+ Cursor const & cur = bv->cursor();
+
+ // we start new selection and need faster update
+ if (!d.already_in_selection_ && cur.selection())
+ d.time_to_update = 0;
+
+ if (d.time_to_update > 0)
+ return;
+
+ DocIterator from, to;
+ if (cur.selection()) {
+ from = cur.selectionBegin();
+ to = cur.selectionEnd();
+ d.already_in_selection_ = true;
+ } else {
+ from = doc_iterator_begin(buf);
+ to = doc_iterator_end(buf);
+ d.already_in_selection_ = false;
+ }
+
+ buf->updateStatistics(from, to);
+
+ QStringList stats;
+ if (word_count_enabled_) {
+ int const words = buf->wordCount();
+ if (words == 1)
+ stats << toqstr(bformat(_("%1$d Word"), words));
+ else
+ stats << toqstr(bformat(_("%1$d Words"), words));
+ }
+ int const chars_with_blanks = buf->charCount(true);
+ if (char_count_enabled_) {
+ if (chars_with_blanks == 1)
+ stats << toqstr(bformat(_("%1$d Character"), chars_with_blanks));
+ else
+ stats << toqstr(bformat(_("%1$d Characters"), chars_with_blanks));
+ }
+ if (char_nb_count_enabled_) {
+ int const chars = buf->charCount(false);
+ if (chars == 1)
+ stats << toqstr(bformat(_("%1$d Character (no Blanks)"), chars));
+ else
+ stats << toqstr(bformat(_("%1$d Characters (no Blanks)"), chars));
+ }
+ stat_counts_->setText(stats.join(qt_(", [[stats separator]]")));
+ stat_counts_->show();
+
+ d.time_to_update = d.default_stats_rate;
+ // fast updates for small selections
+ if (chars_with_blanks < d.max_sel_chars && cur.selection())
+ d.time_to_update = d.timer_rate;
+}
+
void GuiView::updateWindowTitle(GuiWorkArea * wa)
{
// buffer-save has changed too.
updateToolbars();
}
-#ifndef Q_WS_MAC
title += from_ascii(" - LyX");
-#endif
setWindowTitle(toqstr(title));
// Sets the path for the window: this is used by OSX to
// allow a context click on the title bar showing a menu
updateDialogs();
zoom_slider_->setEnabled(currentBufferView());
zoom_value_->setEnabled(currentBufferView());
- zoom_in_->setEnabled(currentBufferView());
- zoom_out_->setEnabled(currentBufferView());
+ zoom_in_->setEnabled(currentBufferView()
+ && zoom_slider_->value() < zoom_slider_->maximum());
+ zoom_out_->setEnabled(currentBufferView()
+ && zoom_slider_->value() > zoom_slider_->minimum());
}
}
+bool GuiView::statsEnabled() const
+{
+ return word_count_enabled_ || char_count_enabled_ || char_nb_count_enabled_;
+}
+
+
bool GuiView::event(QEvent * e)
{
switch (e->type())
menuBar()->hide();
if (lyxrc.full_screen_toolbars) {
for (auto const & tb_p : d.toolbars_)
- if (tb_p.second->isVisibiltyOn() && tb_p.second->isVisible())
+ if (tb_p.second->isVisibilityOn() && tb_p.second->isVisible())
tb_p.second->hide();
}
for (int i = 0; i != d.splitter_->count(); ++i)
menuBar()->show();
if (lyxrc.full_screen_toolbars) {
for (auto const & tb_p : d.toolbars_)
- if (tb_p.second->isVisibiltyOn() && !tb_p.second->isVisible())
+ if (tb_p.second->isVisibilityOn() && !tb_p.second->isVisible())
tb_p.second->show();
//updateToolbars();
}
setContentsMargins(0, 0, 0, 0);
}
return result;
- }
+ }
+
case QEvent::WindowActivate: {
GuiView * old_view = guiApp->currentView();
if (this == old_view) {
return QMainWindow::event(e);
}
+ case QEvent::Gesture: {
+ QGestureEvent *ge = static_cast<QGestureEvent*>(e);
+ QGesture *gp = ge->gesture(Qt::PinchGesture);
+ if (gp) {
+ QPinchGesture *pinch = static_cast<QPinchGesture *>(gp);
+ QPinchGesture::ChangeFlags changeFlags = pinch->changeFlags();
+ qreal totalScaleFactor = pinch->totalScaleFactor();
+ LYXERR(Debug::GUI, "totalScaleFactor: " << totalScaleFactor);
+ if (pinch->state() == Qt::GestureStarted) {
+ initialZoom_ = lyxrc.currentZoom;
+ LYXERR(Debug::GUI, "initialZoom_: " << initialZoom_);
+ }
+ if (changeFlags & QPinchGesture::ScaleFactorChanged) {
+ qreal factor = initialZoom_ * totalScaleFactor;
+ LYXERR(Debug::GUI, "scaleFactor: " << factor);
+ zoomValueChanged(factor);
+ }
+ }
+ return QMainWindow::event(e);
+ }
+
+ // dark/light mode runtime switch support, OS-dependent.
+ // 1. Mac OS X
+ // Limit to Q_OS_MAC as this unnecessarily would also
+ // trigger on Linux with grave performance issues
+#ifdef Q_OS_MAC
case QEvent::ApplicationPaletteChange: {
- // runtime switch from/to dark mode
+ // We need to update metrics here to avoid a crash (#12786)
+ theBufferList().changed(true);
refillToolbars();
return QMainWindow::event(e);
}
+#endif
+ // 2. Linux
+ case QEvent::StyleChange: {
+ // We need to update metrics here to avoid a crash (#12786)
+ theBufferList().changed(true);
+ return QMainWindow::event(e);
+ }
default:
return QMainWindow::event(e);
double GuiView::pixelRatio() const
{
-#if QT_VERSION >= 0x050000
return qt_scale_factor * devicePixelRatio();
-#else
- return 1.0;
-#endif
}
QObject::connect(twa, SIGNAL(lastWorkAreaRemoved()),
this, SLOT(on_lastWorkAreaRemoved()));
- d.splitter_->addWidget(twa);
+ d.splitter_->insertWidget(d.splitter_->indexOf(d.currentTabWorkArea()) + 1,
+ twa);
d.stack_widget_->setCurrentWidget(d.splitter_);
return twa;
}
}
+bool GuiView::hasVisibleWorkArea(GuiWorkArea * wa) const
+{
+ for (int i = 0; i < d.splitter_->count(); ++i)
+ if (d.tabWorkArea(i)->currentWorkArea() == wa)
+ return true;
+
+ FindAndReplace * fr = static_cast<FindAndReplace*>(find("findreplaceadv", false));
+ return fr->isVisible() && fr->hasWorkArea(wa);
+}
+
+
LayoutBox * GuiView::getLayoutDialog() const
{
return d.layout_;
void GuiView::refillToolbars()
{
+ DynamicMenuButton::resetIconCache();
for (auto const & tb_p : d.toolbars_)
tb_p.second->refill();
}
}
+namespace {
+
+double zoomRatio(FuncRequest const & cmd, double const zr)
+{
+ if (cmd.argument().empty()) {
+ if (cmd.action() == LFUN_BUFFER_ZOOM)
+ return 1.0;
+ else if (cmd.action() == LFUN_BUFFER_ZOOM_IN)
+ return zr + 0.1;
+ else // cmd.action() == LFUN_BUFFER_ZOOM_OUT
+ return zr - 0.1;
+ } else {
+ if (cmd.action() == LFUN_BUFFER_ZOOM)
+ return convert<int>(cmd.argument()) / double(lyxrc.defaultZoom);
+ else if (cmd.action() == LFUN_BUFFER_ZOOM_IN)
+ return zr + convert<int>(cmd.argument()) / 100.0;
+ else // cmd.action() == LFUN_BUFFER_ZOOM_OUT
+ return zr - convert<int>(cmd.argument()) / 100.0;
+ }
+}
+
+}
+
+
bool GuiView::getStatus(FuncRequest const & cmd, FuncStatus & flag)
{
bool enable = true;
break;
}
+ case LFUN_CHANGES_TRACK: {
+ if (!doc_buffer) {
+ enable = false;
+ break;
+ }
+ return doc_buffer->getStatus(cmd, flag);
+ }
+
case LFUN_VIEW_SPLIT:
if (cmd.getArg(0) == "vertical")
enable = doc_buffer && (d.splitter_->count() == 1 ||
d.splitter_->orientation() == Qt::Horizontal);
break;
+ case LFUN_TAB_GROUP_NEXT:
+ case LFUN_TAB_GROUP_PREVIOUS:
+ enable = (d.splitter_->count() > 1);
+ break;
+
case LFUN_TAB_GROUP_CLOSE:
enable = d.tabWorkAreaCount() > 1;
break;
break;
case LFUN_UI_TOGGLE:
- if (cmd.argument() == "zoomslider") {
- enable = doc_buffer;
- flag.setOnOff(zoom_slider_ ? zoom_slider_->isVisible() : false);
+ if (cmd.argument() == "zoomlevel") {
+ flag.setOnOff(zoom_value_ ? zoom_value_->isVisible() : false);
+ } else if (cmd.argument() == "zoomslider") {
+ flag.setOnOff(zoom_widget_ ? zoom_widget_->isVisible() : false);
+ } else if (cmd.argument() == "statistics-w") {
+ flag.setOnOff(word_count_enabled_);
+ } else if (cmd.argument() == "statistics-cb") {
+ flag.setOnOff(char_count_enabled_);
+ } else if (cmd.argument() == "statistics-c") {
+ flag.setOnOff(char_nb_count_enabled_);
} else
flag.setOnOff(isFullScreen());
break;
break;
case LFUN_BUFFER_ZOOM_OUT:
- case LFUN_BUFFER_ZOOM_IN: {
- // only diff between these two is that the default for ZOOM_OUT
- // is a neg. number
- bool const neg_zoom =
- convert<int>(cmd.argument()) < 0 ||
- (cmd.action() == LFUN_BUFFER_ZOOM_OUT && cmd.argument().empty());
- if (lyxrc.currentZoom <= zoom_min_ && neg_zoom) {
+ case LFUN_BUFFER_ZOOM_IN:
+ case LFUN_BUFFER_ZOOM: {
+ int const zoom = (int)(lyxrc.defaultZoom * zoomRatio(cmd, zoom_ratio_));
+ if (zoom < zoom_min_) {
docstring const msg =
bformat(_("Zoom level cannot be less than %1$d%."), zoom_min_);
flag.message(msg);
enable = false;
- } else
- enable = doc_buffer;
- break;
- }
-
- case LFUN_BUFFER_ZOOM: {
- bool const less_than_min_zoom =
- !cmd.argument().empty() && convert<int>(cmd.argument()) < zoom_min_;
- if (lyxrc.currentZoom <= zoom_min_ && less_than_min_zoom) {
+ } else if (zoom > zoom_max_) {
docstring const msg =
- bformat(_("Zoom level cannot be less than %1$d%."), zoom_min_);
+ bformat(_("Zoom level cannot be more than %1$d%."), zoom_max_);
flag.message(msg);
enable = false;
- } else if (cmd.argument().empty() && lyxrc.currentZoom == lyxrc.defaultZoom)
- enable = false;
- else
+ } else
enable = doc_buffer;
break;
}
+
case LFUN_BUFFER_MOVE_NEXT:
case LFUN_BUFFER_MOVE_PREVIOUS:
// we do not cycle when moving
case LFUN_WINDOW_RAISE:
break;
case LFUN_FORWARD_SEARCH:
- enable = !(lyxrc.forward_search_dvi.empty() && lyxrc.forward_search_pdf.empty());
+ enable = !(lyxrc.forward_search_dvi.empty() && lyxrc.forward_search_pdf.empty()) &&
+ doc_buffer && doc_buffer->isSyncTeXenabled();
break;
case LFUN_FILE_INSERT_PLAINTEXT:
}
-void GuiView::openDocument(string const & fname)
+void GuiView::openDocuments(string const & fname, int origin)
{
string initpath = lyxrc.document_path;
initpath = trypath;
}
- string filename;
+ QStringList files;
if (fname.empty()) {
- FileDialog dlg(qt_("Select document to open"));
+ FileDialog dlg(qt_("Select documents to open"));
dlg.setButton1(qt_("D&ocuments"), toqstr(lyxrc.document_path));
dlg.setButton2(qt_("&Examples"), toqstr(lyxrc.example_path));
- QStringList const filter(qt_("LyX Documents (*.lyx)"));
- FileDialog::Result result =
- dlg.open(toqstr(initpath), filter);
+ QStringList const filter({
+ qt_("LyX Documents (*.lyx)"),
+ qt_("LyX Document Backups (*.lyx~)"),
+ qt_("All Files") + " " + wildcardAllFiles()
+ });
+ FileDialog::Results results =
+ dlg.openMulti(toqstr(initpath), filter);
- if (result.first == FileDialog::Later)
+ if (results.first == FileDialog::Later)
return;
- filename = fromqstr(result.second);
+ files = results.second;
// check selected filename
- if (filename.empty()) {
+ if (files.isEmpty()) {
message(_("Canceled."));
return;
}
} else
- filename = fname;
-
- // get absolute path of file and add ".lyx" to the filename if
- // necessary.
- FileName const fullname =
- fileSearch(string(), filename, "lyx", support::may_not_exist);
- if (!fullname.empty())
- filename = fullname.absFileName();
-
- if (!fullname.onlyPath().isDirectory()) {
- Alert::warning(_("Invalid filename"),
- bformat(_("The directory in the given path\n%1$s\ndoes not exist."),
- from_utf8(fullname.absFileName())));
- return;
- }
+ files << toqstr(fname);
+
+ // iterate over all selected files
+ for (auto const & file : files) {
+ string filename = fromqstr(file);
+
+ // get absolute path of file and add ".lyx" to the filename if
+ // necessary.
+ FileName const fullname =
+ fileSearch(string(), filename, "lyx", support::may_not_exist);
+ if (!fullname.empty())
+ filename = fullname.absFileName();
+
+ if (!fullname.onlyPath().isDirectory()) {
+ Alert::warning(_("Invalid filename"),
+ bformat(_("The directory in the given path\n%1$s\ndoes not exist."),
+ from_utf8(fullname.absFileName())));
+ continue;
+ }
- // if the file doesn't exist and isn't already open (bug 6645),
- // let the user create one
- if (!fullname.exists() && !theBufferList().exists(fullname) &&
- !LyXVC::file_not_found_hook(fullname)) {
- // the user specifically chose this name. Believe him.
- Buffer * const b = newFile(filename, string(), true);
- if (b)
- setBuffer(b);
- return;
- }
+ // if the file doesn't exist and isn't already open (bug 6645),
+ // let the user create one
+ if (!fullname.exists() && !theBufferList().exists(fullname) &&
+ !LyXVC::file_not_found_hook(fullname)) {
+ // see bug #12609
+ if (origin == FuncRequest::MENU) {
+ docstring const & msg =
+ bformat(_("File\n"
+ "%1$s\n"
+ "does not exist. Create empty file?"),
+ from_utf8(filename));
+ int ret = Alert::prompt(_("File does not exist"),
+ msg, 0, 1,
+ _("Create &File"),
+ _("&Cancel"));
+ if (ret == 1)
+ continue;
+ }
+ Buffer * const b = newFile(filename, string(), true);
+ if (b)
+ setBuffer(b);
+ continue;
+ }
- docstring const disp_fn = makeDisplayPath(filename);
- message(bformat(_("Opening document %1$s..."), disp_fn));
+ docstring const disp_fn = makeDisplayPath(filename);
+ message(bformat(_("Opening document %1$s..."), disp_fn));
- docstring str2;
- Buffer * buf = loadDocument(fullname);
- if (buf) {
- str2 = bformat(_("Document %1$s opened."), disp_fn);
- if (buf->lyxvc().inUse())
- str2 += " " + from_utf8(buf->lyxvc().versionString()) +
- " " + _("Version control detected.");
- } else {
- str2 = bformat(_("Could not open document %1$s"), disp_fn);
+ docstring str2;
+ Buffer * buf = loadDocument(fullname);
+ if (buf) {
+ str2 = bformat(_("Document %1$s opened."), disp_fn);
+ if (buf->lyxvc().inUse())
+ str2 += " " + from_utf8(buf->lyxvc().versionString()) +
+ " " + _("Version control detected.");
+ } else {
+ str2 = bformat(_("Could not open document %1$s"), disp_fn);
+ }
+ message(str2);
}
- message(str2);
}
// FIXME: clean that
dlg.setButton1(qt_("D&ocuments"), toqstr(lyxrc.document_path));
dlg.setButton2(qt_("&Templates"), toqstr(lyxrc.template_path));
- if (!isLyXFileName(fname.absFileName()))
- fname.changeExtension(".lyx");
+ fname.ensureExtension(".lyx");
string const path = as_template ?
getTemplatesPath(b)
if (fname.empty())
return false;
- if (!isLyXFileName(fname.absFileName()))
- fname.changeExtension(".lyx");
+ fname.ensureExtension(".lyx");
}
// fname is now the new Buffer location.
if (fmt_name.empty() || fname.empty())
return false;
+ fname.ensureExtension(theFormats().extension(fmt_name));
+
// fname is now the new Buffer location.
- if (FileName(fname).exists()) {
+ if (fname.exists()) {
docstring const file = makeDisplayPath(fname.absFileName(), 30);
docstring text = bformat(_("The document %1$s already "
"exists.\n\nDo you want to "
for (int i = 0; i < nwa; ++i) {
if (&workArea(i)->bufferView().buffer() == curbuf) {
int next_index;
- if (np == NEXTBUFFER)
+ if (np == NEXT)
next_index = (i == nwa - 1 ? 0 : i + 1);
else
next_index = (i == 0 ? nwa - 1 : i - 1);
}
+void GuiView::gotoNextTabWorkArea(NextOrPrevious np)
+{
+ int count = d.splitter_->count();
+ for (int i = 0; i < count; ++i) {
+ if (d.tabWorkArea(i) == d.currentTabWorkArea()) {
+ int new_index;
+ if (np == NEXT)
+ new_index = (i == count - 1 ? 0 : i + 1);
+ else
+ new_index = (i == 0 ? count - 1 : i - 1);
+ setCurrentWorkArea(d.tabWorkArea(new_index)->currentWorkArea());
+ break;
+ }
+ }
+}
+
+
/// make sure the document is saved
static bool ensureBufferClean(Buffer * buffer)
{
setBuffer(buf);
bool success = documentBufferView()->setCursorFromRow(row);
if (!success) {
- LYXERR(Debug::LATEX,
+ LYXERR(Debug::OUTFILE,
"setCursorFromRow: invalid position for row " << row);
frontend::Alert::error(_("Inverse Search Failed"),
_("Invalid position requested by inverse search.\n"
}
-void GuiView::toolBarPopup(const QPoint & /*pos*/)
-{
- QMenu * menu = guiApp->menus().menu(toqstr("context-toolbars"), * this);
- menu->exec(QCursor::pos());
-}
-
-
template<class T>
Buffer::ExportStatus GuiView::GuiViewPrivate::runAndDestroy(const T& func,
Buffer const * orig, Buffer * clone, string const & format)
break;
}
case LFUN_EXPORT_CANCEL: {
- Systemcall::killscript();
+ cancelExport();
break;
}
case LFUN_BUFFER_SWITCH: {
}
case LFUN_BUFFER_NEXT:
- gotoNextOrPreviousBuffer(NEXTBUFFER, false);
+ gotoNextOrPreviousBuffer(NEXT, false);
break;
case LFUN_BUFFER_MOVE_NEXT:
- gotoNextOrPreviousBuffer(NEXTBUFFER, true);
+ gotoNextOrPreviousBuffer(NEXT, true);
break;
case LFUN_BUFFER_PREVIOUS:
- gotoNextOrPreviousBuffer(PREVBUFFER, false);
+ gotoNextOrPreviousBuffer(PREV, false);
break;
case LFUN_BUFFER_MOVE_PREVIOUS:
- gotoNextOrPreviousBuffer(PREVBUFFER, true);
+ gotoNextOrPreviousBuffer(PREV, true);
break;
case LFUN_BUFFER_CHKTEX:
doc_buffer->runChktex();
break;
+ case LFUN_CHANGES_TRACK: {
+ // the actual dispatch is done in Buffer
+ dispatchToBufferView(cmd, dr);
+ // but we inform the GUI (document settings) if this is toggled
+ LASSERT(doc_buffer, break);
+ Q_EMIT changeTrackingToggled(doc_buffer->params().track_changes);
+ break;
+ }
+
case LFUN_COMMAND_EXECUTE: {
command_execute_ = true;
minibuffer_focus_ = true;
FileDialog dlg(qt_("Select file to insert"));
FileDialog::Result result = dlg.open(toqstr(bv->buffer().filePath()),
- QStringList(qt_("All Files (*)")));
+ QStringList(qt_("All Files")+ " " + wildcardAllFiles()));
if (result.first == FileDialog::Later || result.second.isEmpty()) {
dr.setMessage(_("Canceled."));
dr.setMessage(_("Toolbars unlocked."));
else
dr.setMessage(_("Toolbars locked."));
- } else if (GuiToolbar * t = toolbar(name)) {
+ } else if (GuiToolbar * tb = toolbar(name))
// toggle current toolbar movablity
- t->movable();
- // update lock (all) toolbars positions
- updateLockToolbars();
- }
+ tb->movable();
+ // update lock (all) toolbars positions
+ updateLockToolbars();
break;
}
? Qt::Vertical : Qt::Horizontal);
TabWorkArea * twa = addTabWorkArea();
GuiWorkArea * wa = twa->addWorkArea(*doc_buffer, *this);
+
+ wa->bufferView().copySettingsFrom(*bv);
+ dr.screenUpdate(Update::ForceAll);
setCurrentWorkArea(wa);
break;
}
+
+ case LFUN_TAB_GROUP_NEXT:
+ gotoNextTabWorkArea(NEXT);
+ break;
+
+ case LFUN_TAB_GROUP_PREVIOUS:
+ gotoNextTabWorkArea(PREV);
+ break;
+
case LFUN_TAB_GROUP_CLOSE:
if (TabWorkArea * twa = d.currentTabWorkArea()) {
closeTabWorkArea(twa);
case LFUN_BUFFER_ZOOM_IN:
case LFUN_BUFFER_ZOOM_OUT:
case LFUN_BUFFER_ZOOM: {
- if (cmd.argument().empty()) {
- if (cmd.action() == LFUN_BUFFER_ZOOM)
- zoom_ratio_ = 1.0;
- else if (cmd.action() == LFUN_BUFFER_ZOOM_IN)
- zoom_ratio_ += 0.1;
- else
- zoom_ratio_ -= 0.1;
- } else {
- if (cmd.action() == LFUN_BUFFER_ZOOM)
- zoom_ratio_ = convert<int>(cmd.argument()) / double(lyxrc.defaultZoom);
- else if (cmd.action() == LFUN_BUFFER_ZOOM_IN)
- zoom_ratio_ += convert<int>(cmd.argument()) / 100.0;
- else
- zoom_ratio_ -= convert<int>(cmd.argument()) / 100.0;
- }
+ zoom_ratio_ = zoomRatio(cmd, zoom_ratio_);
// Actual zoom value: default zoom + fractional extra value
int zoom = (int)(lyxrc.defaultZoom * zoom_ratio_);
- if (zoom < static_cast<int>(zoom_min_))
- zoom = zoom_min_;
+ zoom = min(max(zoom, zoom_min_), zoom_max_);
setCurrentZoom(zoom);
lyxrc.currentZoom, lyxrc.defaultZoom));
guiApp->fontLoader().update();
+ // Regenerate instant previews
+ if (lyxrc.preview != LyXRC::PREVIEW_OFF
+ && doc_buffer && doc_buffer->loader())
+ doc_buffer->loader()->refreshPreviews();
dr.screenUpdate(Update::ForceAll | Update::FitCursor);
break;
}
dr.setMessage(_("Please, preview the document first."));
break;
}
+ bool const goto_dvi = have_dvi && !lyxrc.forward_search_dvi.empty();
+ bool const goto_pdf = have_pdf && !lyxrc.forward_search_pdf.empty();
string outname = dviname.onlyFileName();
string command = lyxrc.forward_search_dvi;
- if (!have_dvi || (have_pdf &&
- pdfname.lastModified() > dviname.lastModified())) {
+ if ((!goto_dvi || goto_pdf) &&
+ pdfname.lastModified() > dviname.lastModified()) {
outname = pdfname.onlyFileName();
command = lyxrc.forward_search_pdf;
}
break;
case LFUN_CITATION_OPEN: {
- string pdfv, psv;
- if (theFormats().getFormat("pdf"))
- pdfv = theFormats().getFormat("pdf")->viewer();
- if (theFormats().getFormat("ps"))
- psv = theFormats().getFormat("ps")->viewer();
- frontend::showTarget(argument, pdfv, psv);
+ LASSERT(doc_buffer, break);
+ frontend::showTarget(argument, *doc_buffer);
break;
}
statusBar()->setVisible(!statusBar()->isVisible());
} else if (ui_component == "menubar") {
menuBar()->setVisible(!menuBar()->isVisible());
+ } else if (ui_component == "zoomlevel") {
+ zoom_value_->setVisible(!zoom_value_->isVisible());
} else if (ui_component == "zoomslider") {
- zoom_slider_->setVisible(!zoom_slider_->isVisible());
- zoom_in_->setVisible(zoom_slider_->isVisible());
- zoom_out_->setVisible(zoom_slider_->isVisible());
+ zoom_widget_->setVisible(!zoom_widget_->isVisible());
+ } else if (ui_component == "statistics-w") {
+ word_count_enabled_ = !word_count_enabled_;
+ if (statsEnabled())
+ showStats();
+ } else if (ui_component == "statistics-cb") {
+ char_count_enabled_ = !char_count_enabled_;
+ if (statsEnabled())
+ showStats();
+ } else if (ui_component == "statistics-c") {
+ char_nb_count_enabled_ = !char_nb_count_enabled_;
+ if (statsEnabled())
+ showStats();
} else if (ui_component == "frame") {
int const l = contentsMargins().left();
toggleFullScreen();
} else
return false;
+ stat_counts_->setVisible(statsEnabled());
return true;
}
+void GuiView::cancelExport()
+{
+ Systemcall::killscript();
+ // stop busy signal immediately so that in the subsequent
+ // "Export canceled" prompt the status bar icons are accurate.
+ Q_EMIT scriptKilled();
+}
+
+
void GuiView::toggleFullScreen()
{
setWindowState(windowState() ^ Qt::WindowFullScreen);
}
-Dialog * GuiView::findOrBuild(string const & name, bool hide_it)
+Dialog * GuiView::find(string const & name, bool hide_it) const
{
if (!isValidName(name))
return nullptr;
it->second->hideView();
return it->second.get();
}
+ return nullptr;
+}
- Dialog * dialog = build(name);
- d.dialogs_[name].reset(dialog);
-#if (QT_VERSION >= QT_VERSION_CHECK(5, 0, 0))
- // Force a uniform style for group boxes
- // On Mac non-flat works better, on Linux flat is standard
- flatGroupBoxes(dialog->asQWidget(), guiApp->platformName() != "cocoa");
-#endif
- if (lyxrc.allow_geometry_session)
- dialog->restoreSession();
- if (hide_it)
- dialog->hideView();
+
+Dialog * GuiView::findOrBuild(string const & name, bool hide_it)
+{
+ Dialog * dialog = find(name, hide_it);
+ if (dialog != nullptr)
+ return dialog;
+
+ dialog = build(name);
+ if (dialog) {
+
+ d.dialogs_[name].reset(dialog);
+ // Force a uniform style for group boxes
+ // On Mac non-flat works better, on Linux flat is standard
+ flatGroupBoxes(dialog->asQWidget(), guiApp->platformName() != "cocoa");
+ if (lyxrc.allow_geometry_session)
+ dialog->restoreSession();
+ if (hide_it)
+ dialog->hideView();
+ }
return dialog;
}
parent, SLOT(disableShellEscape()));
}
+
+void PressableSvgWidget::mousePressEvent(QMouseEvent * event)
+{
+ if (event->button() == Qt::LeftButton) {
+ Q_EMIT pressed();
+ }
+}
+
} // namespace frontend
} // namespace lyx