[Spreadsheet] Enable zoom in Spreadsheet (#16130)

* [Spreadsheet] Enable zoom in Spreadsheet

Closes #6094. This commit also fixes page tab order of Spreadsheet
settings in Preferences.

* Spreadsheet: apply clang-format

---------

Co-authored-by: Chris Hennes <chennes@pioneerlibrarysystem.org>
This commit is contained in:
xtemp09
2024-12-13 23:52:51 +07:00
committed by GitHub
parent 0416ec1b4e
commit b1f93bc51e
9 changed files with 576 additions and 62 deletions

View File

@@ -86,6 +86,8 @@ SET(SpreadsheetGui_SRCS
DlgBindSheet.cpp
DlgSheetConf.h
DlgSheetConf.cpp
ZoomableView.h
ZoomableView.cpp
${SpreadsheetGui_UIC_HDRS}
)

File diff suppressed because one or more lines are too long

View File

@@ -57,6 +57,7 @@ void DlgSettingsImp::saveSettings()
ui->quoteCharLineEdit->onSave();
ui->escapeCharLineEdit->onSave();
ui->formatString->onSave();
ui->dZLSpinBox->onSave();
ui->checkBoxShowAlias->onSave();
}
@@ -94,6 +95,7 @@ void DlgSettingsImp::loadSettings()
ui->quoteCharLineEdit->onRestore();
ui->escapeCharLineEdit->onRestore();
ui->formatString->onRestore();
ui->dZLSpinBox->onRestore();
ui->checkBoxShowAlias->onRestore();
}

View File

@@ -57,7 +57,154 @@ Spreadsheet.my_alias_name instead of Spreadsheet.B1</string>
</layout>
</item>
<item>
<widget class="SpreadsheetGui::SheetTableView" name="cells"/>
<layout class="QHBoxLayout" name="horizontalLayout_2">
<item>
<widget class="SpreadsheetGui::SheetTableView" name="cells"/>
</item>
<item>
<widget class="QScrollBar" name="realSB_v">
<property name="sizePolicy">
<sizepolicy hsizetype="Fixed" vsizetype="Expanding">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="singleStep">
<number>5</number>
</property>
<property name="pageStep">
<number>20</number>
</property>
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
</widget>
</item>
</layout>
</item>
<item>
<layout class="QHBoxLayout" name="horizontalLayout">
<item>
<widget class="QScrollBar" name="realSB_h">
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="singleStep">
<number>5</number>
</property>
<property name="pageStep">
<number>20</number>
</property>
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
</widget>
</item>
<item>
<spacer name="horizontalSpacer">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>40</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
<item>
<widget class="QToolButton" name="zoomTB">
<property name="sizePolicy">
<sizepolicy hsizetype="Minimum" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="focusPolicy">
<enum>Qt::NoFocus</enum>
</property>
<property name="text">
<string>Zoom</string>
</property>
<property name="toolButtonStyle">
<enum>Qt::ToolButtonTextOnly</enum>
</property>
<property name="autoRaise">
<bool>true</bool>
</property>
</widget>
</item>
<item>
<widget class="QToolButton" name="zoomMinus">
<property name="focusPolicy">
<enum>Qt::ClickFocus</enum>
</property>
<property name="text">
<string>-</string>
</property>
<property name="toolButtonStyle">
<enum>Qt::ToolButtonTextOnly</enum>
</property>
<property name="autoRaise">
<bool>true</bool>
</property>
</widget>
</item>
<item>
<widget class="QSlider" name="zoomSlider">
<property name="sizePolicy">
<sizepolicy hsizetype="MinimumExpanding" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="focusPolicy">
<enum>Qt::ClickFocus</enum>
</property>
<property name="minimum">
<number>60</number>
</property>
<property name="maximum">
<number>160</number>
</property>
<property name="singleStep">
<number>10</number>
</property>
<property name="pageStep">
<number>20</number>
</property>
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="tickPosition">
<enum>QSlider::TicksBelow</enum>
</property>
<property name="tickInterval">
<number>10</number>
</property>
</widget>
</item>
<item>
<widget class="QToolButton" name="zoomPlus">
<property name="focusPolicy">
<enum>Qt::ClickFocus</enum>
</property>
<property name="text">
<string>+</string>
</property>
<property name="toolButtonStyle">
<enum>Qt::ToolButtonTextOnly</enum>
</property>
<property name="autoRaise">
<bool>true</bool>
</property>
</widget>
</item>
</layout>
</item>
</layout>
</widget>

View File

@@ -119,7 +119,7 @@ SheetTableView::SheetTableView(QWidget* parent)
setHorizontalScrollMode(QAbstractItemView::ScrollPerPixel);
connect(verticalHeader(), &QWidget::customContextMenuRequested, [this](const QPoint& point) {
QMenu menu(this);
QMenu menu {nullptr};
const auto selection = selectionModel()->selectedRows();
const auto& [min, max] = selectedMinMaxRows(selection);
if (bool isContiguous = max - min == selection.size() - 1) {
@@ -141,11 +141,11 @@ SheetTableView::SheetTableView(QWidget* parent)
}
auto remove = menu.addAction(tr("Remove row(s)", "", selection.size()));
connect(remove, &QAction::triggered, this, &SheetTableView::removeRows);
menu.exec(verticalHeader()->mapToGlobal(point));
menu.exec(QCursor::pos());
});
connect(horizontalHeader(), &QWidget::customContextMenuRequested, [this](const QPoint& point) {
QMenu menu(this);
QMenu menu {nullptr};
const auto selection = selectionModel()->selectedColumns();
const auto& [min, max] = selectedMinMaxColumns(selection);
if (bool isContiguous = max - min == selection.size() - 1) {
@@ -171,7 +171,7 @@ SheetTableView::SheetTableView(QWidget* parent)
}
auto remove = menu.addAction(tr("Remove column(s)", "", selection.size()));
connect(remove, &QAction::triggered, this, &SheetTableView::removeColumns);
menu.exec(horizontalHeader()->mapToGlobal(point));
menu.exec(QCursor::pos());
});
actionProperties = new QAction(tr("Properties..."), this);
@@ -180,41 +180,40 @@ SheetTableView::SheetTableView(QWidget* parent)
horizontalHeader()->setContextMenuPolicy(Qt::CustomContextMenu);
verticalHeader()->setContextMenuPolicy(Qt::CustomContextMenu);
contextMenu = new QMenu(this);
contextMenu->addAction(actionProperties);
contextMenu.addAction(actionProperties);
connect(actionProperties, &QAction::triggered, this, &SheetTableView::cellProperties);
contextMenu->addSeparator();
contextMenu.addSeparator();
actionRecompute = new QAction(tr("Recompute"), this);
connect(actionRecompute, &QAction::triggered, this, &SheetTableView::onRecompute);
contextMenu->addAction(actionRecompute);
contextMenu.addAction(actionRecompute);
actionBind = new QAction(tr("Bind..."), this);
connect(actionBind, &QAction::triggered, this, &SheetTableView::onBind);
contextMenu->addAction(actionBind);
contextMenu.addAction(actionBind);
actionConf = new QAction(tr("Configuration table..."), this);
connect(actionConf, &QAction::triggered, this, &SheetTableView::onConfSetup);
contextMenu->addAction(actionConf);
contextMenu.addAction(actionConf);
horizontalHeader()->addAction(actionBind);
verticalHeader()->addAction(actionBind);
contextMenu->addSeparator();
actionMerge = contextMenu->addAction(tr("Merge cells"));
contextMenu.addSeparator();
actionMerge = contextMenu.addAction(tr("Merge cells"));
connect(actionMerge, &QAction::triggered, this, &SheetTableView::mergeCells);
actionSplit = contextMenu->addAction(tr("Split cells"));
actionSplit = contextMenu.addAction(tr("Split cells"));
connect(actionSplit, &QAction::triggered, this, &SheetTableView::splitCell);
contextMenu->addSeparator();
actionCut = contextMenu->addAction(tr("Cut"));
contextMenu.addSeparator();
actionCut = contextMenu.addAction(tr("Cut"));
connect(actionCut, &QAction::triggered, this, &SheetTableView::cutSelection);
actionCopy = contextMenu->addAction(tr("Copy"));
actionCopy = contextMenu.addAction(tr("Copy"));
connect(actionCopy, &QAction::triggered, this, &SheetTableView::copySelection);
actionPaste = contextMenu->addAction(tr("Paste"));
actionPaste = contextMenu.addAction(tr("Paste"));
connect(actionPaste, &QAction::triggered, this, &SheetTableView::pasteClipboard);
actionDel = contextMenu->addAction(tr("Delete"));
actionDel = contextMenu.addAction(tr("Delete"));
connect(actionDel, &QAction::triggered, this, &SheetTableView::deleteSelection);
setTabKeyNavigation(false);
@@ -241,7 +240,7 @@ void SheetTableView::onBind()
{
auto ranges = selectedRanges();
if (!ranges.empty() && ranges.size() <= 2) {
DlgBindSheet dlg(sheet, ranges, this);
DlgBindSheet dlg {sheet, ranges};
dlg.exec();
}
}
@@ -252,16 +251,16 @@ void SheetTableView::onConfSetup()
if (ranges.empty()) {
return;
}
DlgSheetConf dlg(sheet, ranges.back(), this);
DlgSheetConf dlg {sheet, ranges.back()};
dlg.exec();
}
void SheetTableView::cellProperties()
{
std::unique_ptr<PropertiesDialog> dialog(new PropertiesDialog(sheet, selectedRanges(), this));
PropertiesDialog dialog {sheet, selectedRanges()};
if (dialog->exec() == QDialog::Accepted) {
dialog->apply();
if (dialog.exec() == QDialog::Accepted) {
dialog.apply();
}
}
@@ -1125,7 +1124,7 @@ void SheetTableView::contextMenuEvent(QContextMenuEvent*)
auto ranges = selectedRanges();
actionBind->setEnabled(!ranges.empty() && ranges.size() <= 2);
contextMenu->exec(QCursor::pos());
contextMenu.exec(QCursor::pos());
}
QString SheetTableView::toHtml() const

View File

@@ -26,7 +26,7 @@
#include <QHeaderView>
#include <QTableView>
#include <QTimer>
#include <QMenu>
#include <Mod/Spreadsheet/App/Sheet.h>
@@ -109,7 +109,7 @@ protected:
Spreadsheet::Sheet* sheet;
int tabCounter;
QMenu* contextMenu;
QMenu contextMenu;
QAction* actionProperties;
QAction* actionRecompute;

View File

@@ -28,7 +28,6 @@
#include <QPrintPreviewDialog>
#include <QPrinter>
#include <QTextDocument>
#include <cmath>
#endif
#include <App/DocumentObject.h>
@@ -47,6 +46,7 @@
#include "LineEdit.h"
#include "SpreadsheetDelegate.h"
#include "SpreadsheetView.h"
#include "ZoomableView.h"
#include "qtcolorpicker.h"
#include "ui_Sheet.h"
@@ -74,6 +74,8 @@ SheetView::SheetView(Gui::Document* pcDocument, App::DocumentObject* docObj, QWi
ui->setupUi(w);
setCentralWidget(w);
new ZoomableView(ui);
delegate = new SpreadsheetDelegate(sheet);
ui->cells->setModel(model);
ui->cells->setItemDelegate(delegate);

View File

@@ -0,0 +1,231 @@
// SPDX-License-Identifier: LGPL-2.1-or-later
/****************************************************************************
* *
* Copyright (c) 2024 xtemp09 <xtemp09@gmail.com> *
* *
* This file is part of FreeCAD. *
* *
* FreeCAD is free software: you can redistribute it and/or modify it *
* under the terms of the GNU Lesser General Public License as *
* published by the Free Software Foundation, either version 2.1 of the *
* License, or (at your option) any later version. *
* *
* FreeCAD is distributed in the hope that it will be useful, but *
* WITHOUT ANY WARRANTY; without even the implied warranty of *
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU *
* Lesser General Public License for more details. *
* *
* You should have received a copy of the GNU Lesser General Public *
* License along with FreeCAD. If not, see *
* <https://www.gnu.org/licenses/>. *
* *
***************************************************************************/
#include <QInputDialog>
#include <QGraphicsProxyWidget>
#include "ZoomableView.h"
#include "ui_Sheet.h"
ZoomableView::ZoomableView(Ui::Sheet* ui)
: QGraphicsView {}
, stv {ui->cells}
{
if (!stv) {
Base::Console().DeveloperWarning("ZoomableView", "Failed to find a SheetTableView object");
deleteLater();
return;
}
else {
QLayoutItem* li_stv = stv->parentWidget()->layout()->replaceWidget(stv, this);
if (li_stv == nullptr) {
Base::Console().DeveloperWarning("ZoomableView",
"Failed to replace the SheetTableView object");
deleteLater();
return;
}
delete li_stv;
}
stv->setParent(nullptr);
qpw = m_scene.addWidget(stv);
setScene(&m_scene);
setBackgroundBrush(Qt::transparent);
setFrameStyle(QFrame::NoFrame);
setSizePolicy(QSizePolicy {QSizePolicy::Expanding, QSizePolicy::Expanding});
setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
stv->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
stv->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
QPointer<QScrollBar> dummySB_h {stv->horizontalScrollBar()},
dummySB_v {stv->verticalScrollBar()}, realSB_h {ui->realSB_h}, realSB_v {ui->realSB_v};
if (!dummySB_h || !dummySB_v || !realSB_h || !realSB_v) {
Base::Console().DeveloperWarning("ZoomableView", "Failed to identify the scrollbars");
deleteLater();
return;
}
realSB_h->setRange(dummySB_h->minimum(), dummySB_h->maximum());
realSB_v->setRange(dummySB_v->minimum(), dummySB_v->maximum());
realSB_h->setPageStep(dummySB_h->pageStep());
realSB_v->setPageStep(dummySB_v->pageStep());
connect(realSB_h, &QAbstractSlider::valueChanged, dummySB_h, &QAbstractSlider::setValue);
connect(realSB_v, &QAbstractSlider::valueChanged, dummySB_v, &QAbstractSlider::setValue);
connect(dummySB_h, &QAbstractSlider::rangeChanged, realSB_h, &QAbstractSlider::setRange);
connect(dummySB_v, &QAbstractSlider::rangeChanged, realSB_v, &QAbstractSlider::setRange);
connect(dummySB_h, &QAbstractSlider::valueChanged, this, &ZoomableView::updateView);
connect(dummySB_v, &QAbstractSlider::valueChanged, this, &ZoomableView::updateView);
connect(this,
&ZoomableView::zoomLevelChanged,
ui->zoomTB,
[zoomTB = ui->zoomTB](int new_zoomLevel) {
zoomTB->setText(QStringLiteral("%1%").arg(new_zoomLevel));
});
connect(this,
&ZoomableView::zoomLevelChanged,
ui->zoomSlider,
[zoomSlider = ui->zoomSlider](int new_zoomLevel) {
zoomSlider->blockSignals(true);
zoomSlider->setValue(new_zoomLevel);
zoomSlider->blockSignals(false);
});
connect(ui->zoomPlus, &QToolButton::clicked, this, &ZoomableView::zoomIn);
connect(ui->zoomSlider, &QSlider::valueChanged, this, &ZoomableView::setZoomLevel);
connect(ui->zoomMinus, &QToolButton::clicked, this, &ZoomableView::zoomOut);
connect(ui->zoomTB, &QToolButton::clicked, ui->zoomSlider, [zoomSlider = ui->zoomSlider]() {
const QString title = tr("Zoom level"), label = tr("New zoom level:");
constexpr int min = ZoomableView::min, max = ZoomableView::max, step = 10;
const int val = zoomSlider->value();
bool ok;
const int new_val =
QInputDialog::getInt(zoomSlider, title, label, val, min, max, step, &ok);
if (ok) {
zoomSlider->setValue(new_val);
}
});
resetZoom();
}
int ZoomableView::zoomLevel() const
{
return m_zoomLevel;
}
void ZoomableView::setZoomLevel(int new_zoomLevel)
{
checkLimits(new_zoomLevel);
if (m_zoomLevel == new_zoomLevel) {
return;
}
m_zoomLevel = new_zoomLevel;
updateView();
Q_EMIT zoomLevelChanged(m_zoomLevel);
}
inline void ZoomableView::checkLimits(int& zoom_level)
{
zoom_level = qBound(ZoomableView::min, zoom_level, ZoomableView::max);
}
void ZoomableView::zoomIn(void)
{
setZoomLevel(m_zoomLevel + zoom_step_kb);
}
void ZoomableView::zoomOut(void)
{
setZoomLevel(m_zoomLevel - zoom_step_kb);
}
void ZoomableView::resetZoom(void)
{
constexpr const char* path = "User parameter:BaseApp/Preferences/Mod/Spreadsheet";
ParameterGrp::handle hGrp = App::GetApplication().GetParameterGroupByPath(path);
const int defaultZoomLevel = static_cast<int>(hGrp->GetInt("DefaultZoomLevel", 100));
setZoomLevel(defaultZoomLevel);
}
void ZoomableView::updateView(void)
{
/* QGraphicsView has hardcoded margins therefore we have to avoid fitInView
* Find more information at https://bugreports.qt.io/browse/QTBUG-42331 */
const qreal scale_factor = static_cast<qreal>(m_zoomLevel) / 100.0,
new_w = static_cast<qreal>(viewport()->rect().width()) / scale_factor,
new_h = static_cast<qreal>(viewport()->rect().height()) / scale_factor;
const QRectF new_geometry {0.0, 0.0, new_w, new_h};
const QRect old_geometry {stv->geometry()};
stv->setGeometry(1, 1, old_geometry.width() - 1, old_geometry.height() - 1);
resetTransform();
qpw->setGeometry(new_geometry);
setSceneRect(new_geometry);
scale(scale_factor, scale_factor);
centerOn(new_geometry.center());
}
void ZoomableView::keyPressEvent(QKeyEvent* event)
{
if (event->modifiers() & Qt::ControlModifier) {
switch (event->key()) {
case Qt::Key_Plus:
zoomIn();
event->accept();
break;
case Qt::Key_Minus:
zoomOut();
event->accept();
break;
case Qt::Key_0:
resetZoom();
event->accept();
break;
default:
QGraphicsView::keyPressEvent(event);
}
}
else {
QGraphicsView::keyPressEvent(event);
}
}
void ZoomableView::resizeEvent(QResizeEvent* event)
{
QGraphicsView::resizeEvent(event);
updateView();
}
void ZoomableView::wheelEvent(QWheelEvent* event)
{
if (event->modifiers() & Qt::ControlModifier) {
const int y = event->angleDelta().y();
setZoomLevel(m_zoomLevel + (y > 0 ? zoom_step_mwheel : -zoom_step_mwheel));
event->accept();
}
else {
QGraphicsView::wheelEvent(event);
}
}

View File

@@ -0,0 +1,83 @@
// SPDX-License-Identifier: LGPL-2.1-or-later
/****************************************************************************
* *
* Copyright (c) 2024 xtemp09 <xtemp09@gmail.com> *
* *
* This file is part of FreeCAD. *
* *
* FreeCAD is free software: you can redistribute it and/or modify it *
* under the terms of the GNU Lesser General Public License as *
* published by the Free Software Foundation, either version 2.1 of the *
* License, or (at your option) any later version. *
* *
* FreeCAD is distributed in the hope that it will be useful, but *
* WITHOUT ANY WARRANTY; without even the implied warranty of *
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU *
* Lesser General Public License for more details. *
* *
* You should have received a copy of the GNU Lesser General Public *
* License along with FreeCAD. If not, see *
* <https://www.gnu.org/licenses/>. *
* *
***************************************************************************/
#ifndef ZOOMABLEVIEW_H
#define ZOOMABLEVIEW_H
#include <QGraphicsView>
#include "SpreadsheetView.h"
namespace SpreadsheetGui
{
class SheetTableView;
}
class ZoomableView: public QGraphicsView
{
Q_OBJECT
Q_PROPERTY(int zoomLevel READ zoomLevel() WRITE setZoomLevel NOTIFY zoomLevelChanged)
public:
/*!
* \brief A descendant of QGraphicsView to show a SheetTableView object in its viewport,
* allowing magnification. \param ui \details The object replaces SheetTableView in layout,
* handling mouse and keyboard events.
*/
explicit ZoomableView(Ui::Sheet* ui);
~ZoomableView() override = default;
int zoomLevel() const;
void setZoomLevel(int new_scale);
static constexpr int min {60}, max {160};
static void checkLimits(int& zoom_level);
Q_SIGNALS:
void zoomLevelChanged(int); /// This signal is emitted whenever zoom level is changed. It is
/// used to show the zoom level in the zoom button.
public Q_SLOTS:
void zoomIn(void); /// This function is the slot for the zoomIn button and a keyboard shortcut
void
zoomOut(void); /// This function is the slot for the zoomOut button and a keyboard shortcut
void resetZoom(void); /// This function is the slot for a keyboard shortcut
private:
void updateView(void);
QPointer<SpreadsheetGui::SheetTableView> stv;
QGraphicsScene m_scene;
QGraphicsProxyWidget* qpw;
int m_zoomLevel;
protected:
void keyPressEvent(QKeyEvent* event) override;
void resizeEvent(QResizeEvent* event) override;
void wheelEvent(QWheelEvent* event) override;
static constexpr int zoom_step_mwheel {5}, zoom_step_kb {10};
};
#endif // ZOOMABLEVIEW_H