X-Git-Url: https://git.lyx.org/gitweb/?a=blobdiff_plain;ds=sidebyside;f=src%2Ffrontends%2Fqt%2FGuiView.cpp;h=8269ff09cceec5164a8882c2045b17d6992678c4;hb=01abab9a;hp=5e68603f3f5e19caadd2600d8178479a26549acc;hpb=128346d03d85ac4b6b758caa6b7c4e61b7513703;p=features.git diff --git a/src/frontends/qt/GuiView.cpp b/src/frontends/qt/GuiView.cpp index 5e68603f3f..8269ff09cc 100644 --- a/src/frontends/qt/GuiView.cpp +++ b/src/frontends/qt/GuiView.cpp @@ -18,6 +18,7 @@ #include "DialogFactory.h" #include "DispatchResult.h" #include "FileDialog.h" +#include "FindAndReplace.h" #include "FontLoader.h" #include "GuiApplication.h" #include "GuiClickableLabel.h" @@ -68,6 +69,8 @@ #include "Toolbars.h" #include "version.h" +#include "graphics/PreviewLoader.h" + #include "support/convert.h" #include "support/debug.h" #include "support/ExceptionMessage.h" @@ -96,7 +99,6 @@ #include #include #include -#include #include #include #include @@ -111,6 +113,8 @@ #include #include #include +#include +#include // sync with GuiAlert.cpp @@ -208,7 +212,6 @@ public: 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); @@ -522,6 +525,7 @@ public: /// QTimer statusbar_timer_; + QTimer statusbar_stats_timer_; /// auto-saving of buffers Timeout autosave_timeout_; @@ -545,6 +549,20 @@ public: /// 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 GuiView::GuiViewPrivate::busyBuffers; @@ -552,8 +570,9 @@ QSet 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())); @@ -581,6 +600,9 @@ GuiView::GuiView(int id) } 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); @@ -611,49 +633,121 @@ GuiView::GuiView(int id) // 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()), - busylabel, SLOT(hide())); - connect(busylabel, SIGNAL(clicked()), this, SLOT(checkCancelBackground())); + busySVG, SLOT(hide())); + connect(&d.processing_thread_watcher_, SIGNAL(started()), + processStop, SLOT(show())); + connect(&d.processing_thread_watcher_, SIGNAL(finished()), + processStop, SLOT(hide())); + connect(processStop, SIGNAL(pressed()), this, SLOT(checkCancelBackground())); - QFontMetrics const fm(statusBar()->fontMetrics()); + connect(this, SIGNAL(scriptKilled()), busySVG, SLOT(hide())); + connect(this, SIGNAL(scriptKilled()), processStop, SLOT(hide())); - QSlider * zoomslider = new QSlider(Qt::Horizontal, statusBar()); + stat_counts_ = new GuiClickableLabel(statusBar()); + stat_counts_->setAlignment(Qt::AlignCenter); + stat_counts_->setFrameStyle(QFrame::StyledPanel); + 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)) - zoomslider->setFixedWidth(fm.horizontalAdvance('x') * 15); + zoom_slider_->setFixedWidth(fm.horizontalAdvance('x') * 15); #else - zoomslider->setFixedWidth(fm.width('x') * 15); + zoom_slider_->setFixedWidth(fm.width('x') * 15); #endif // Make the defaultZoom center - zoomslider->setRange(10, (lyxrc.defaultZoom * 2) - 10); - zoomslider->setValue(lyxrc.currentZoom); - zoomslider->setToolTip(qt_("Workarea zoom level. Drag, use Ctrl-+/- or Shift-Mousewheel to adjust.")); - zoomslider->setTickPosition(QSlider::TicksBelow); - zoomslider->setTickInterval(lyxrc.defaultZoom - 10); - statusBar()->addPermanentWidget(zoomslider); + zoom_slider_->setRange(10, (lyxrc.defaultZoom * 2) - 10); + // Initialize proper zoom value + QSettings settings; + zoom_ratio_ = settings.value("zoom_ratio", 1.0).toDouble(); + // Actual zoom value: default zoom + fractional offset + int zoom = (int)(lyxrc.defaultZoom * zoom_ratio_); + 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 +#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_->setFixedSize(s); + zoom_in_->setAlignment(Qt::AlignCenter); + zoom_out_ = new GuiClickableLabel(statusBar()); + zoom_out_->setText(QString(QChar(0x2212))); + zoom_out_->setFixedSize(s); + zoom_out_->setAlignment(Qt::AlignCenter); + + statusBar()->addPermanentWidget(zoom_out_); + zoom_out_->setEnabled(currentBufferView()); + statusBar()->addPermanentWidget(zoom_slider_); + zoom_slider_->setEnabled(currentBufferView()); + zoom_in_->setEnabled(currentBufferView()); + statusBar()->addPermanentWidget(zoom_in_); + + connect(zoom_slider_, SIGNAL(sliderMoved(int)), this, SLOT(zoomSliderMoved(int))); + connect(zoom_slider_, SIGNAL(valueChanged(int)), this, SLOT(zoomValueChanged(int))); + connect(this, SIGNAL(currentZoomChanged(int)), zoom_slider_, SLOT(setValue(int))); + connect(zoom_in_, SIGNAL(clicked()), this, SLOT(zoomInPressed())); + connect(zoom_out_, SIGNAL(clicked()), this, SLOT(zoomOutPressed())); + + // 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("444\%")); +#else + 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()); - connect(zoomslider, SIGNAL(sliderMoved(int)), this, SLOT(zoomSliderMoved(int))); - connect(zoomslider, SIGNAL(valueChanged(int)), this, SLOT(zoomValueChanged(int))); - connect(this, SIGNAL(currentZoomChanged(int)), zoomslider, SLOT(setValue(int))); + 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()); shell_escape_->setPixmap(shellescape); - shell_escape_->setScaledContents(true); shell_escape_->setAlignment(Qt::AlignCenter); shell_escape_->setContextMenuPolicy(Qt::CustomContextMenu); shell_escape_->setToolTip(qt_("WARNING: LaTeX is allowed to execute " @@ -668,7 +762,6 @@ GuiView::GuiView(int id) QPixmap readonly = QIcon(getPixmap("images/", "emblem-readonly", "svgz,png")).pixmap(iconsize); read_only_ = new QLabel(statusBar()); read_only_->setPixmap(readonly); - read_only_->setScaledContents(true); read_only_->setAlignment(Qt::AlignCenter); read_only_->hide(); statusBar()->addPermanentWidget(read_only_); @@ -713,7 +806,6 @@ GuiView::GuiView(int id) initToolbars(); // clear session data if any. - QSettings settings; settings.remove("views"); } @@ -742,18 +834,26 @@ void GuiView::checkCancelBackground() int const ret = Alert::prompt(ttl, msg, 1, 1, _("&Cancel export"), _("Co&ntinue")); - if (ret == 0) + if (ret == 0) { 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::statsPressed() +{ + DispatchResult dr; + dispatch(FuncRequest(LFUN_STATISTICS), dr); +} void GuiView::zoomSliderMoved(int value) { DispatchResult dr; dispatch(FuncRequest(LFUN_BUFFER_ZOOM, convert(value)), dr); - currentWorkArea()->scheduleRedraw(true); - message(bformat(_("Zoom level is now %1$d% (default value: %2$d%)"), - value, lyxrc.defaultZoom)); + scheduleRedrawWorkAreas(); + zoom_value_->setText(toqstr(bformat(_("[[ZOOM]]%1$d%"), value))); } @@ -764,6 +864,51 @@ void GuiView::zoomValueChanged(int value) } +void GuiView::zoomInPressed() +{ + DispatchResult dr; + dispatch(FuncRequest(LFUN_BUFFER_ZOOM_IN), dr); + scheduleRedrawWorkAreas(); +} + + +void GuiView::zoomOutPressed() +{ + DispatchResult dr; + dispatch(FuncRequest(LFUN_BUFFER_ZOOM_OUT), dr); + scheduleRedrawWorkAreas(); +} + + +void GuiView::showZoomContextMenu() +{ + QMenu * menu = guiApp->menus().menu(toqstr("context-zoom"), * this); + if (!menu) + return; + menu->exec(QCursor::pos()); +} + + +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 GuiView::GuiViewPrivate::guiWorkAreas() { QVector areas; @@ -866,6 +1011,11 @@ void GuiView::saveLayout() const settings.setValue("geometry", saveGeometry()); settings.setValue("layout", saveState(0)); settings.setValue("icon_size", toqstr(d.iconSize(iconSize()))); + settings.setValue("zoom_value_visible", zoom_value_->isVisible()); + settings.setValue("zoom_slider_visible", zoom_slider_->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_); } @@ -885,6 +1035,7 @@ void GuiView::saveUISettings() const void GuiView::setCurrentZoom(const int v) { lyxrc.currentZoom = v; + zoom_value_->setText(toqstr(bformat(_("[[ZOOM]]%1$d%"), v))); Q_EMIT currentZoomChanged(v); } @@ -895,8 +1046,7 @@ bool GuiView::restoreLayout() 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(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"); @@ -908,6 +1058,18 @@ bool GuiView::restoreLayout() //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); + + 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") { QPoint pos = settings.value("pos", QPoint(50, 50)).toPoint(); QSize size = settings.value("size", QSize(690, 510)).toSize(); @@ -973,6 +1135,11 @@ void GuiView::updateLockToolbars() if (tb && tb->isMovable()) toolbarsMovable_ = true; } +#if QT_VERSION >= 0x050200 + // set unified mac toolbars only when not movable as recommended: + // https://doc.qt.io/qt-5/qmainwindow.html#unifiedTitleAndToolBarOnMac-prop + setUnifiedTitleAndToolBarOnMac(!toolbarsMovable_); +#endif } @@ -991,6 +1158,8 @@ void GuiView::constructToolbars() // extracts the toolbars from the backend for (ToolbarInfo const & inf : guiApp->toolbars()) d.toolbars_[inf.name] = new GuiToolbar(inf, *this); + + DynamicMenuButton::resetIconCache(); } @@ -1114,7 +1283,7 @@ bool GuiView::prepareAllBuffersForLogout() // We cannot use a for loop as the buffer list cycles. Buffer * b = first; do { - if (!saveBufferIfNeeded(const_cast(*b), false)) + if (!saveBufferIfNeeded(*b, false)) return false; b = theBufferList().next(b); } while (b != first); @@ -1171,6 +1340,7 @@ void GuiView::closeEvent(QCloseEvent * close_event) // 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. @@ -1276,6 +1446,73 @@ void GuiView::clearMessage() 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) { @@ -1348,6 +1585,10 @@ void GuiView::onBufferViewChanged() // Buffer-dependent dialogs must be updated. This is done here because // some dialogs require buffer()->text. updateDialogs(); + zoom_slider_->setEnabled(currentBufferView()); + zoom_value_->setEnabled(currentBufferView()); + zoom_in_->setEnabled(currentBufferView()); + zoom_out_->setEnabled(currentBufferView()); } @@ -1411,6 +1652,12 @@ void GuiView::showMessage() } +bool GuiView::statsEnabled() const +{ + return word_count_enabled_ || char_count_enabled_ || char_nb_count_enabled_; +} + + bool GuiView::event(QEvent * e) { switch (e->type()) @@ -1444,7 +1691,7 @@ bool GuiView::event(QEvent * e) 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) @@ -1465,7 +1712,7 @@ bool GuiView::event(QEvent * e) 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(); } @@ -1477,7 +1724,8 @@ bool GuiView::event(QEvent * e) setContentsMargins(0, 0, 0, 0); } return result; - } + } + case QEvent::WindowActivate: { GuiView * old_view = guiApp->currentView(); if (this == old_view) { @@ -1520,6 +1768,27 @@ bool GuiView::event(QEvent * e) return QMainWindow::event(e); } + case QEvent::Gesture: { + QGestureEvent *ge = static_cast(e); + QGesture *gp = ge->gesture(Qt::PinchGesture); + if (gp) { + QPinchGesture *pinch = static_cast(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); + } + default: return QMainWindow::event(e); } @@ -1742,6 +2011,17 @@ void GuiView::removeWorkArea(GuiWorkArea * wa) } +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(find("findreplaceadv", false)); + return fr->isVisible() && fr->hasWorkArea(wa); +} + + LayoutBox * GuiView::getLayoutDialog() const { return d.layout_; @@ -1791,6 +2071,7 @@ void GuiView::updateToolbars() void GuiView::refillToolbars() { + DynamicMenuButton::resetIconCache(); for (auto const & tb_p : d.toolbars_) tb_p.second->refill(); } @@ -1972,6 +2253,30 @@ void GuiView::resetAutosaveTimers() } +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(cmd.argument()) / double(lyxrc.defaultZoom); + else if (cmd.action() == LFUN_BUFFER_ZOOM_IN) + return zr + convert(cmd.argument()) / 100.0; + else // cmd.action() == LFUN_BUFFER_ZOOM_OUT + return zr - convert(cmd.argument()) / 100.0; + } +} + +} + + bool GuiView::getStatus(FuncRequest const & cmd, FuncStatus & flag) { bool enable = true; @@ -2253,7 +2558,18 @@ bool GuiView::getStatus(FuncRequest const & cmd, FuncStatus & flag) break; case LFUN_UI_TOGGLE: - flag.setOnOff(isFullScreen()); + if (cmd.argument() == "zoomlevel") { + flag.setOnOff(zoom_value_ ? zoom_value_->isVisible() : false); + } else if (cmd.argument() == "zoomslider") { + flag.setOnOff(zoom_slider_ ? zoom_slider_->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; case LFUN_DIALOG_DISCONNECT_INSET: @@ -2346,36 +2662,25 @@ bool GuiView::getStatus(FuncRequest const & cmd, FuncStatus & flag) 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(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(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 + } else enable = doc_buffer; break; } + case LFUN_BUFFER_MOVE_NEXT: case LFUN_BUFFER_MOVE_PREVIOUS: // we do not cycle when moving @@ -2438,7 +2743,8 @@ bool GuiView::getStatus(FuncRequest const & cmd, FuncStatus & flag) 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: @@ -2530,7 +2836,11 @@ void GuiView::openDocument(string const & fname) dlg.setButton1(qt_("D&ocuments"), toqstr(lyxrc.document_path)); dlg.setButton2(qt_("&Examples"), toqstr(lyxrc.example_path)); - QStringList const filter(qt_("LyX Documents (*.lyx)")); + QStringList const filter({ + qt_("LyX Documents (*.lyx)"), + qt_("LyX Document Backups (*.lyx~)"), + qt_("All Files (*.*)") + }); FileDialog::Result result = dlg.open(toqstr(initpath), filter); @@ -2920,8 +3230,7 @@ bool GuiView::renameBuffer(Buffer & b, docstring const & newname, RenameKind kin 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) @@ -2939,8 +3248,7 @@ bool GuiView::renameBuffer(Buffer & b, docstring const & newname, RenameKind kin if (fname.empty()) return false; - if (!isLyXFileName(fname.absFileName())) - fname.changeExtension(".lyx"); + fname.ensureExtension(".lyx"); } // fname is now the new Buffer location. @@ -3088,8 +3396,10 @@ bool GuiView::exportBufferAs(Buffer & b, docstring const & iformat) 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 " @@ -3850,7 +4160,7 @@ bool GuiView::goToFileRow(string const & argument) 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" @@ -4139,6 +4449,7 @@ void GuiView::dispatch(FuncRequest const & cmd, DispatchResult & dr) } case LFUN_EXPORT_CANCEL: { Systemcall::killscript(); + Q_EMIT scriptKilled(); break; } case LFUN_BUFFER_SWITCH: { @@ -4406,12 +4717,11 @@ void GuiView::dispatch(FuncRequest const & cmd, DispatchResult & dr) 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; } @@ -4588,26 +4898,11 @@ void GuiView::dispatch(FuncRequest const & cmd, DispatchResult & dr) 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(cmd.argument()) / double(lyxrc.defaultZoom); - else if (cmd.action() == LFUN_BUFFER_ZOOM_IN) - zoom_ratio_ += convert(cmd.argument()) / 100.0; - else - zoom_ratio_ -= convert(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(zoom_min_)) - zoom = zoom_min_; + zoom = min(max(zoom, zoom_min_), zoom_max_); setCurrentZoom(zoom); @@ -4615,6 +4910,10 @@ void GuiView::dispatch(FuncRequest const & cmd, DispatchResult & dr) 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; } @@ -4673,10 +4972,12 @@ void GuiView::dispatch(FuncRequest const & cmd, DispatchResult & dr) 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; } @@ -4754,8 +5055,25 @@ bool GuiView::lfunUiToggle(string const & ui_component) statusBar()->setVisible(!statusBar()->isVisible()); } else if (ui_component == "menubar") { menuBar()->setVisible(!menuBar()->isVisible()); - } else - if (ui_component == "frame") { + } 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()); + } 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(); //are the frames in default state? @@ -4776,6 +5094,7 @@ bool GuiView::lfunUiToggle(string const & ui_component) toggleFullScreen(); } else return false; + stat_counts_->setVisible(statsEnabled()); return true; } @@ -4897,7 +5216,7 @@ void GuiView::flatGroupBoxes(const QObject * widget, bool flag) } -Dialog * GuiView::findOrBuild(string const & name, bool hide_it) +Dialog * GuiView::find(string const & name, bool hide_it) const { if (!isValidName(name)) return nullptr; @@ -4909,8 +5228,17 @@ Dialog * GuiView::findOrBuild(string const & name, bool hide_it) it->second->hideView(); return it->second.get(); } + return nullptr; +} + + +Dialog * GuiView::findOrBuild(string const & name, bool hide_it) +{ + Dialog * dialog = find(name, hide_it); + if (dialog != nullptr) + return dialog; - Dialog * dialog = build(name); + dialog = build(name); d.dialogs_[name].reset(dialog); #if (QT_VERSION >= QT_VERSION_CHECK(5, 0, 0)) // Force a uniform style for group boxes @@ -5043,6 +5371,14 @@ SEMenu::SEMenu(QWidget * parent) parent, SLOT(disableShellEscape())); } + +void PressableSvgWidget::mousePressEvent(QMouseEvent * event) +{ + if (event->button() == Qt::LeftButton) { + Q_EMIT pressed(); + } +} + } // namespace frontend } // namespace lyx