]> git.lyx.org Git - features.git/blobdiff - src/frontends/qt/GuiView.cpp
#12434 add event handler for pinch-to-zoom gesture
[features.git] / src / frontends / qt / GuiView.cpp
index 7dd05f06aa13aaf5934e37fad0473f3b9077fb3e..791a2c2b42a52bc34d29c82eb02425608deb904f 100644 (file)
@@ -96,7 +96,6 @@
 #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
@@ -208,7 +209,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);
@@ -612,20 +612,19 @@ GuiView::GuiView(int id)
        setAcceptDrops(true);
 
        // 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();
 
        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(busySVG, SIGNAL(pressed()), this, SLOT(checkCancelBackground()));
 
        QFontMetrics const fm(statusBar()->fontMetrics());
 
@@ -650,14 +649,19 @@ GuiView::GuiView(int id)
        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_out_->setText(QString(0x2212));
-       zoom_out_->setFixedSize(QSize(fm.height(), fm.height()));
-       zoom_out_->setFlat(true);
+       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());
@@ -672,39 +676,28 @@ GuiView::GuiView(int id)
        connect(zoom_in_, SIGNAL(clicked()), this, SLOT(zoomInPressed()));
        connect(zoom_out_, SIGNAL(clicked()), this, SLOT(zoomOutPressed()));
 
-       zoom_value_ = new QToolButton(statusBar());
-       zoom_value_->setText(toqstr(bformat(_("[[ZOOM]]%1$d%"), zoom)));
-       zoom_value_->setToolButtonStyle(Qt::ToolButtonTextOnly);
-       zoom_value_->setAutoRaise(true);
-       zoom_value_->setPopupMode(QToolButton::InstantPopup);
+       // QPalette palette = statusBar()->palette();
+
+       zoom_value_ = new QLabel(statusBar());
+       // 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());
 
-       act_zoom_default_ = new QAction(toqstr(bformat(_("&Reset to default (%1$d%)"),
-                                                      lyxrc.defaultZoom)), this);
-       act_zoom_in_ = new QAction(qt_("Zoom &in"), this);
-       act_zoom_out_ = new QAction(qt_("Zoom &out"), this);
-       act_zoom_show_ = new QAction(qt_("Show zoom slider"), this);
-       act_zoom_show_->setCheckable(true);
-       zoom_value_->addAction(act_zoom_default_);
-       zoom_value_->addAction(act_zoom_in_);
-       zoom_value_->addAction(act_zoom_out_);
-       zoom_value_->addAction(act_zoom_show_);
-       enableZoomOptions();
-       connect(act_zoom_default_, SIGNAL(triggered()),
-                       this, SLOT(resetDefaultZoom()));
-       connect(act_zoom_in_, SIGNAL(triggered()),
-                       this, SLOT(zoomInPressed()));
-       connect(act_zoom_out_, SIGNAL(triggered()),
-                       this, SLOT(zoomOutPressed()));
-       connect(act_zoom_show_, SIGNAL(triggered()),
-                       this, SLOT(toogleZoomSlider()));
+       statusBar()->setContextMenuPolicy(Qt::CustomContextMenu);
+       connect(statusBar(), SIGNAL(customContextMenuRequested(QPoint)),
+               this, SLOT(showZoomContextMenu()));
+
+       // enable pinch to zoom
+       grabGesture(Qt::PinchGesture);
 
        int const iconheight = max(int(d.normalIconSize), fm.height());
        QSize const iconsize(iconheight, iconheight);
@@ -803,20 +796,11 @@ void GuiView::checkCancelBackground()
 }
 
 
-void GuiView::enableZoomOptions()
-{
-       act_zoom_default_->setEnabled(zoom_slider_->value() != lyxrc.defaultZoom);
-       FuncStatus status;
-       act_zoom_in_->setEnabled(getStatus(FuncRequest(LFUN_BUFFER_ZOOM_IN), status));
-       act_zoom_out_->setEnabled(getStatus(FuncRequest(LFUN_BUFFER_ZOOM_OUT), status));
-}
-
-
 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)));
 }
 
@@ -825,7 +809,6 @@ void GuiView::zoomValueChanged(int value)
 {
        if (value != lyxrc.currentZoom)
                zoomSliderMoved(value);
-       enableZoomOptions();
 }
 
 
@@ -833,7 +816,7 @@ void GuiView::zoomInPressed()
 {
        DispatchResult dr;
        dispatch(FuncRequest(LFUN_BUFFER_ZOOM_IN), dr);
-       currentWorkArea()->scheduleRedraw(true);
+       scheduleRedrawWorkAreas();
 }
 
 
@@ -841,21 +824,27 @@ void GuiView::zoomOutPressed()
 {
        DispatchResult dr;
        dispatch(FuncRequest(LFUN_BUFFER_ZOOM_OUT), dr);
-       currentWorkArea()->scheduleRedraw(true);
+       scheduleRedrawWorkAreas();
 }
 
 
-void GuiView::toogleZoomSlider()
+void GuiView::showZoomContextMenu()
 {
-       DispatchResult dr;
-       dispatch(FuncRequest(LFUN_UI_TOGGLE, "zoomslider"), dr);
+       QMenu * menu = guiApp->menus().menu(toqstr("context-statusbar"), * this);
+       if (!menu)
+               return;
+       menu->exec(QCursor::pos());
 }
 
 
-void GuiView::resetDefaultZoom()
+void GuiView::scheduleRedrawWorkAreas()
 {
-       zoomValueChanged(lyxrc.defaultZoom);
-       enableZoomOptions();
+       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);
+               }
+       }
 }
 
 
@@ -961,6 +950,7 @@ 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());
 }
 
@@ -1005,9 +995,10 @@ 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);
-       act_zoom_show_->setChecked(show_zoom_slider);
        zoom_in_->setVisible(show_zoom_slider);
        zoom_out_->setVisible(show_zoom_slider);
 
@@ -1076,6 +1067,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
 }
 
 
@@ -1094,6 +1090,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();
 }
 
 
@@ -1217,7 +1215,7 @@ bool GuiView::prepareAllBuffersForLogout()
        // 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);
@@ -1551,7 +1549,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)
@@ -1572,7 +1570,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();
                        }
@@ -1584,7 +1582,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) {
@@ -1627,6 +1626,21 @@ bool GuiView::event(QEvent * e)
                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();
+                       if (changeFlags & QPinchGesture::ScaleFactorChanged) {
+                               qreal factor = lyxrc.currentZoom*pinch->scaleFactor();
+                               //factor = ceil(factor/20)*20;
+                               zoomValueChanged(factor);
+                       }
+               }
+               return QMainWindow::event(e);
+       }
+
        default:
                return QMainWindow::event(e);
        }
@@ -1898,6 +1912,7 @@ void GuiView::updateToolbars()
 
 void GuiView::refillToolbars()
 {
+       DynamicMenuButton::resetIconCache();
        for (auto const & tb_p : d.toolbars_)
                tb_p.second->refill();
 }
@@ -2360,9 +2375,10 @@ bool GuiView::getStatus(FuncRequest const & cmd, FuncStatus & flag)
                break;
 
        case LFUN_UI_TOGGLE:
-               if (cmd.argument() == "zoomslider") {
-                       enable = doc_buffer;
-                       flag.setOnOff(zoom_slider_->isVisible());
+               if (cmd.argument() == "zoom") {
+                       flag.setOnOff(zoom_value_ ? zoom_value_->isVisible() : false);
+               } else if (cmd.argument() == "zoomslider") {
+                       flag.setOnOff(zoom_slider_ ? zoom_slider_->isVisible() : false);
                } else
                        flag.setOnOff(isFullScreen());
                break;
@@ -2481,7 +2497,8 @@ bool GuiView::getStatus(FuncRequest const & cmd, FuncStatus & flag)
                                bformat(_("Zoom level cannot be less than %1$d%."), zoom_min_);
                        flag.message(msg);
                        enable = false;
-               }
+               } else if (cmd.argument().empty() && lyxrc.currentZoom == lyxrc.defaultZoom)
+                       enable = false;
                else
                        enable = doc_buffer;
                break;
@@ -2641,7 +2658,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);
 
@@ -3200,7 +3221,7 @@ bool GuiView::exportBufferAs(Buffer & b, docstring const & iformat)
                return false;
 
        // 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 "
@@ -4517,12 +4538,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;
                }
 
@@ -4865,11 +4885,12 @@ bool GuiView::lfunUiToggle(string const & ui_component)
                statusBar()->setVisible(!statusBar()->isVisible());
        } else if (ui_component == "menubar") {
                menuBar()->setVisible(!menuBar()->isVisible());
+       } else if (ui_component == "zoom") {
+               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());
-               act_zoom_show_->setChecked(zoom_slider_->isVisible());
        } else if (ui_component == "frame") {
                int const l = contentsMargins().left();
 
@@ -5158,6 +5179,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