From kde-commits Wed Apr 30 22:16:27 2014 From: Dominik Haumann Date: Wed, 30 Apr 2014 22:16:27 +0000 To: kde-commits Subject: [tikzkit] src/ui: add proper Grid implementation and grid snapping Message-Id: X-MARC-Message: https://marc.info/?l=kde-commits&m=139889619800817 Git commit c754c3f290d07075ad14bf672ac9da098f4bc0c8 by Dominik Haumann. Committed on 30/04/2014 at 22:15. Pushed by dhaumann into branch 'master'. add proper Grid implementation and grid snapping M +1 -0 src/ui/CMakeLists.txt M +48 -16 src/ui/TikzView.cpp M +20 -0 src/ui/TikzView.h M +9 -26 src/ui/tools/EllipseTool.cpp M +8 -5 src/ui/tools/LineTool.cpp M +8 -25 src/ui/tools/NodeTool.cpp A +168 -0 src/ui/utils/Grid.cpp [License: LGPL (v2+)] A +75 -0 src/ui/utils/Grid.h [License: LGPL (v2+)] http://commits.kde.org/tikzkit/c754c3f290d07075ad14bf672ac9da098f4bc0c8 diff --git a/src/ui/CMakeLists.txt b/src/ui/CMakeLists.txt index 4508428..60d638a 100644 --- a/src/ui/CMakeLists.txt +++ b/src/ui/CMakeLists.txt @@ -52,6 +52,7 @@ set(qtikzui_SOURCES = utils/AnchorManager.cpp utils/Ruler.cpp + utils/Grid.cpp = TikzScene.cpp TikzView.cpp diff --git a/src/ui/TikzView.cpp b/src/ui/TikzView.cpp index 73aeb8c..227ee54 100644 --- a/src/ui/TikzView.cpp +++ b/src/ui/TikzView.cpp @@ -19,10 +19,12 @@ = #include "TikzView.h" #include "Ruler.h" +#include "Grid.h" = #include = #include +#include #include #include = @@ -35,8 +37,9 @@ class TikzViewPrivate { public: TikzDocument * doc; - tikz::ui::Ruler * m_hRuler; - tikz::ui::Ruler * m_vRuler; + tikz::ui::Grid * grid; + tikz::ui::Ruler * hRuler; + tikz::ui::Ruler * vRuler; QPointF lastMousePos; bool handTool; }; @@ -54,18 +57,20 @@ TikzView::TikzView(TikzDocument * doc, QWidget * parent) gridLayout->setSpacing(0); gridLayout->setMargin(0); = - d->m_hRuler =3D new tikz::ui::Ruler(Qt::Horizontal, this); - d->m_vRuler =3D new tikz::ui::Ruler(Qt::Vertical, this); + d->grid =3D new tikz::ui::Grid(this); = - d->m_hRuler->setUnit(tikz::Centimeter); - d->m_vRuler->setUnit(tikz::Centimeter); + d->hRuler =3D new tikz::ui::Ruler(Qt::Horizontal, this); + d->vRuler =3D new tikz::ui::Ruler(Qt::Vertical, this); + + d->hRuler->setUnit(tikz::Centimeter); + d->vRuler->setUnit(tikz::Centimeter); = QWidget* top =3D new QWidget(); top->setBackgroundRole(QPalette::Window); top->setFixedSize(s_ruler_size, s_ruler_size); gridLayout->addWidget(top, 0, 0); - gridLayout->addWidget(d->m_hRuler, 0, 1); - gridLayout->addWidget(d->m_vRuler, 1, 0); + gridLayout->addWidget(d->hRuler, 0, 1); + gridLayout->addWidget(d->vRuler, 1, 0); gridLayout->addWidget(viewport(), 1, 1); = setLayout(gridLayout); @@ -81,6 +86,24 @@ TikzDocument * TikzView::document() const return d->doc; } = +tikz::Value TikzView::snapValue(const tikz::Value & value) const +{ + const bool snap =3D QApplication::keyboardModifiers() ^ Qt::ShiftModif= ier; + return snap ? d->grid->snapValue(value) : value; +} + +tikz::Pos TikzView::snapPos(const tikz::Pos & pos) const +{ + const bool snap =3D QApplication::keyboardModifiers() ^ Qt::ShiftModif= ier; + return snap ? d->grid->snapPos(pos) : pos; +} + +qreal TikzView::snapAngle(qreal angle) const +{ + const bool snap =3D QApplication::keyboardModifiers() ^ Qt::ShiftModif= ier; + return snap ? (qRound(angle / 15) * 15) : angle; +} + void TikzView::mousePressEvent(QMouseEvent* event) { d->lastMousePos =3D event->pos(); @@ -111,11 +134,11 @@ void TikzView::mouseMoveEvent(QMouseEvent* event) } = // update mouse indicator on rulers - d->m_hRuler->setOrigin(d->m_hRuler->mapFromGlobal(viewport()->mapToGlo= bal(mapFromScene(QPointF(0, 0)))).x()); - d->m_vRuler->setOrigin(d->m_vRuler->mapFromGlobal(viewport()->mapToGlo= bal(mapFromScene(QPointF(0, 0)))).y()); + d->hRuler->setOrigin(d->hRuler->mapFromGlobal(viewport()->mapToGlobal(= mapFromScene(QPointF(0, 0)))).x()); + d->vRuler->setOrigin(d->vRuler->mapFromGlobal(viewport()->mapToGlobal(= mapFromScene(QPointF(0, 0)))).y()); = - d->m_hRuler->setMousePos(event->globalPos()); - d->m_vRuler->setMousePos(event->globalPos()); + d->hRuler->setMousePos(event->globalPos()); + d->vRuler->setMousePos(event->globalPos()); = // track last mouse position d->lastMousePos =3D event->pos(); @@ -149,15 +172,24 @@ void TikzView::wheelEvent(QWheelEvent* event) = bool TikzView::viewportEvent(QEvent * event) { - d->m_hRuler->setOrigin(d->m_hRuler->mapFromGlobal(viewport()->mapToGlo= bal(mapFromScene(QPointF(0, 0)))).x()); - d->m_vRuler->setOrigin(d->m_vRuler->mapFromGlobal(viewport()->mapToGlo= bal(mapFromScene(QPointF(0, 0)))).y()); + d->hRuler->setOrigin(d->hRuler->mapFromGlobal(viewport()->mapToGlobal(= mapFromScene(QPointF(0, 0)))).x()); + d->vRuler->setOrigin(d->vRuler->mapFromGlobal(viewport()->mapToGlobal(= mapFromScene(QPointF(0, 0)))).y()); const qreal s =3D tikz::Value(1, tikz::Inch).toPoint(); - d->m_hRuler->setZoom(transform().m11() / physicalDpiX() * s); - d->m_vRuler->setZoom(qAbs(transform().m22()) / physicalDpiY() * s); + d->hRuler->setZoom(transform().m11() / physicalDpiX() * s); + d->vRuler->setZoom(qAbs(transform().m22()) / physicalDpiY() * s); = return QGraphicsView::viewportEvent(event); } = +void TikzView::drawBackground(QPainter * painter, const QRectF & rect) +{ + // draw default background (typically nothing) + QGraphicsView::drawBackground(painter, rect); + + // draw raster on top + d->grid->draw(painter, rect); +} + } } = diff --git a/src/ui/TikzView.h b/src/ui/TikzView.h index 0ca758d..61f7fe4 100644 --- a/src/ui/TikzView.h +++ b/src/ui/TikzView.h @@ -24,6 +24,9 @@ = #include "tikzgui_export.h" = +#include +#include + = namespace tikz { namespace ui { @@ -51,6 +54,22 @@ class TIKZUI_EXPORT TikzView : public QGraphicsView */ TikzDocument * document() const; = + /** + * Snap @p value to the grid. + */ + tikz::Value snapValue(const tikz::Value & value) const; + + /** + * Snap x/y components of @p pos to the grid. + */ + tikz::Pos snapPos(const tikz::Pos & pos) const; + + /** + * Snap @p angle in degrees to a 15=C2=B0 raster. + */ + qreal snapAngle(qreal angle) const; + + protected: void mousePressEvent(QMouseEvent* event) override; void mouseMoveEvent(QMouseEvent* event) override; @@ -58,6 +77,7 @@ class TIKZUI_EXPORT TikzView : public QGraphicsView void wheelEvent(QWheelEvent* event) override; bool viewportEvent(QEvent * event) override; = + void drawBackground(QPainter * painter, const QRectF & rect) overr= ide; = private: TikzViewPrivate * const d; diff --git a/src/ui/tools/EllipseTool.cpp b/src/ui/tools/EllipseTool.cpp index a35da53..8350758 100644 --- a/src/ui/tools/EllipseTool.cpp +++ b/src/ui/tools/EllipseTool.cpp @@ -24,6 +24,7 @@ #include "EllipsePathItem.h" #include "AnchorManager.h" #include "TikzDocument.h" +#include "TikzView.h" #include #include = @@ -136,26 +137,9 @@ QPointF EllipseTool::handlePos(Handle::Position pos) return c + t.map(p); } = -static void snapPos(tikz::Pos & pos) -{ - const bool snap =3D QApplication::keyboardModifiers() ^ Qt::ShiftModif= ier; - if (snap) { - pos =3D tikz::Pos(qRound(pos.x().value() / 0.2) * 0.2, - qRound(pos.y().value() / 0.2) * 0.2, pos.x().unit(= )); - } -} - -static void snapValue(tikz::Value & val) -{ - const bool snap =3D QApplication::keyboardModifiers() ^ Qt::ShiftModif= ier; - if (snap) { - val =3D tikz::Value(qRound(val.value() / 0.2) * 0.2, val.unit()); - } -} - void EllipseTool::handleMoved(Handle * handle, const QPointF & scenePos, Q= GraphicsView * view) { - const bool snap =3D QApplication::keyboardModifiers() ^ Qt::ShiftModif= ier; + TikzView * tikzView =3D qobject_cast(view); = // // rotate @@ -163,8 +147,7 @@ void EllipseTool::handleMoved(Handle * handle, const QP= ointF & scenePos, QGraphi if (handle->handleType() =3D=3D Handle::RotateHandle) { const QPointF delta =3D m_path->pos() - scenePos; const qreal rad =3D atan2(-delta.y(), -delta.x()); - qreal deg =3D rad * 180 / M_PI + 90; - if (snap) deg =3D qRound(deg / 15) * 15; + const qreal deg =3D tikzView->snapAngle(rad * 180 / M_PI + 90); tikz::core::EdgeStyle s; s.setStyle(*m_path->style()); s.setRotation(deg); @@ -183,9 +166,9 @@ void EllipseTool::handleMoved(Handle * handle, const QP= ointF & scenePos, QGraphi // try to attach to anchor tikz::core::EllipsePath * ep =3D m_path->ellipsePath(); tikz::core::MetaPos metaPos =3D m_anchorManager->anchorAt(scenePos= , view); - if (snap && ! metaPos.node()) { + if (! metaPos.node()) { tikz::Pos p =3D tikz::Pos(scenePos).convertTo(unit); - snapPos(p); + p =3D tikzView->snapPos(p); metaPos.setPos(p); } = @@ -213,8 +196,8 @@ void EllipseTool::handleMoved(Handle * handle, const QP= ointF & scenePos, QGraphi h =3D delta.y(); = // snap to raster - snapValue(w); - snapValue(h); + w =3D tikzView->snapValue(w); + h =3D tikzView->snapValue(h); = break; } @@ -222,14 +205,14 @@ void EllipseTool::handleMoved(Handle * handle, const = QPointF & scenePos, QGraphi case Handle::BottomBorder: { h =3D delta.y(); // snap to raster - snapValue(h); + h =3D tikzView->snapValue(h); break; } case Handle::LeftBorder: case Handle::RightBorder: { w =3D delta.x(); // snap to raster - snapValue(w); + w =3D tikzView->snapValue(w); break; } case Handle::Center: Q_ASSERT(false); diff --git a/src/ui/tools/LineTool.cpp b/src/ui/tools/LineTool.cpp index aa76fdb..6b648fa 100644 --- a/src/ui/tools/LineTool.cpp +++ b/src/ui/tools/LineTool.cpp @@ -24,6 +24,7 @@ #include "EdgePathItem.h" #include "AnchorManager.h" #include "TikzDocument.h" +#include "TikzView.h" = #include #include @@ -114,15 +115,17 @@ QPointF LineTool::handlePos(Handle::Position pos) = void LineTool::handleMoved(Handle * handle, const QPointF & scenePos, QGra= phicsView * view) { - const bool snap =3D QApplication::keyboardModifiers() ^ Qt::ShiftModif= ier; + TikzView * tikzView =3D qobject_cast(view); + + // later: preferred unit + const tikz::Unit unit =3D tikz::Centimeter; = // try to attach to anchor tikz::core::MetaPos metaPos =3D m_anchorManager->anchorAt(scenePos, vi= ew); = - if (snap && ! metaPos.node()) { - QPointF p =3D scenePos; - p.rx() =3D qRound(p.x() / 0.2) * 0.2; - p.ry() =3D qRound(p.y() / 0.2) * 0.2; + if (! metaPos.node()) { + tikz::Pos p =3D tikz::Pos(scenePos).convertTo(unit); + p =3D tikzView->snapPos(p); metaPos.setPos(p); } = diff --git a/src/ui/tools/NodeTool.cpp b/src/ui/tools/NodeTool.cpp index 32ea756..868eb27 100644 --- a/src/ui/tools/NodeTool.cpp +++ b/src/ui/tools/NodeTool.cpp @@ -23,6 +23,7 @@ #include "MoveHandle.h" #include "NodeItem.h" #include "TikzDocument.h" +#include "TikzView.h" #include = #include @@ -135,26 +136,9 @@ QPointF NodeTool::handlePos(Handle::Position pos) return c + t.map(p); } = -static void snapPos(tikz::Pos & pos) -{ - const bool snap =3D QApplication::keyboardModifiers() ^ Qt::ShiftModif= ier; - if (snap) { - pos =3D tikz::Pos(qRound(pos.x().value() / 0.2) * 0.2, - qRound(pos.y().value() / 0.2) * 0.2, pos.x().unit(= )); - } -} - -static void snapValue(tikz::Value & val) -{ - const bool snap =3D QApplication::keyboardModifiers() ^ Qt::ShiftModif= ier; - if (snap) { - val =3D tikz::Value(qRound(val.value() / 0.2) * 0.2, val.unit()); - } -} - void NodeTool::handleMoved(Handle * handle, const QPointF & scenePos, QGra= phicsView * view) { - const bool snap =3D QApplication::keyboardModifiers() ^ Qt::ShiftModif= ier; + TikzView * tikzView =3D qobject_cast(view); = // later: preferred unit const tikz::Unit unit =3D tikz::Centimeter; @@ -165,8 +149,7 @@ void NodeTool::handleMoved(Handle * handle, const QPoin= tF & scenePos, QGraphicsV if (handle->handleType() =3D=3D Handle::RotateHandle) { const QPointF delta =3D m_node->node()->pos() - tikz::Pos(scenePos= ); const qreal rad =3D atan2(-delta.y(), -delta.x()); - qreal deg =3D rad * 180 / M_PI + 90; - if (snap) deg =3D qRound(deg / 15) * 15; + const qreal deg =3D tikzView->snapAngle(rad * 180 / M_PI + 90); tikz::core::NodeStyle s; s.setStyle(*m_node->style()); s.setRotation(deg); @@ -180,7 +163,7 @@ void NodeTool::handleMoved(Handle * handle, const QPoin= tF & scenePos, QGraphicsV // if (handle->handlePos() =3D=3D Handle::Center) { tikz::Pos p =3D tikz::Pos(scenePos).convertTo(unit); - snapPos(p); + p =3D tikzView->snapPos(p); m_node->node()->setPos(p); return; } @@ -205,8 +188,8 @@ void NodeTool::handleMoved(Handle * handle, const QPoin= tF & scenePos, QGraphicsV h =3D delta.y(); = // snap to raster - snapValue(w); - snapValue(h); + w =3D tikzView->snapValue(w); + h =3D tikzView->snapValue(h); = break; } @@ -214,14 +197,14 @@ void NodeTool::handleMoved(Handle * handle, const QPo= intF & scenePos, QGraphicsV case Handle::BottomBorder: { h =3D delta.y(); // snap to raster - snapValue(h); + h =3D tikzView->snapValue(h); break; } case Handle::LeftBorder: case Handle::RightBorder: { w =3D delta.x(); // snap to raster - snapValue(w); + w =3D tikzView->snapValue(w); break; } case Handle::Center: Q_ASSERT(false); diff --git a/src/ui/utils/Grid.cpp b/src/ui/utils/Grid.cpp new file mode 100644 index 0000000..6365221 --- /dev/null +++ b/src/ui/utils/Grid.cpp @@ -0,0 +1,168 @@ +/* This file is part of the TikZKit project. + * + * Copyright (C) 2014 Dominik Haumann + * + * This library is free software; you can redistribute it and/or modify + * it under the terms of the GNU Library General Public License as publish= ed + * by the Free Software Foundation, either version 2 of the License, or + * (at your option) any later version. + * + * This library 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 Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public Licen= se + * along with this library; see the file COPYING.LIB. If not, see + * . + */ + +#include "Grid.h" + +#include + +#include +#include + +namespace tikz { +namespace ui { + +class GridPrivate +{ +public: + QGraphicsView * view; + qreal zoom =3D 1000000000; + QRectF rect; + + QVarLengthArray majorLines; + QVarLengthArray minorLines; + + /** + * Returns the number of lines that are visible per unit: + * - 1 equals only major lines + * - 2 means each unit is subdivided by one minor line + * - 3, ... + * The return value is guaranteed to be greater or equal to 1. + */ + int linesPerUnit(tikz::Unit unit) const + { + const qreal s =3D tikz::Value(1, tikz::Inch).toPoint(); + const qreal zoom =3D view->transform().m11() / view->physicalDpiX(= ) * s; + + // how much space does one unit have on screen [cm] ? + const tikz::Value oneUnitOnScreen =3D tikz::Value(zoom, unit);//.c= onvertTo(tikz::Centimeter); + + // we want a line each 5 mm + int linesPerUnit =3D 1; + while ((oneUnitOnScreen / linesPerUnit) >=3D tikz::Value(10, tikz:= :Millimeter)) { + linesPerUnit *=3D 2; + } + + Q_ASSERT(linesPerUnit > 0); + return linesPerUnit; + } + + /** + * Update the cached major and minor lines. + */ + void updateCache(const QRectF & r) + { + const qreal s =3D tikz::Value(1, tikz::Inch).toPoint(); + const qreal z =3D view->transform().m11() / view->physicalDpiX() *= s; + + if (rect !=3D r || zoom !=3D z) { + rect =3D r; + zoom =3D z; + + // TODO (later): make this unit configurable + const tikz::Unit unit =3D tikz::Centimeter; + + // we want a line each 5 mm + const int lpu =3D linesPerUnit(unit); + qDebug() << "lines per unit" << lpu; + + majorLines.clear(); + minorLines.clear(); + + const Value one(1, unit); + + tikz::Value left =3D tikz::Value(tikz::Value(rect.left()).conv= ertTo(unit).value()); + left =3D tikz::Value(std::floor(left.value()), unit); + + tikz::Value top =3D tikz::Value(tikz::Value(rect.top()).conver= tTo(unit).value()); + top =3D tikz::Value(std::ceil(top.value()), unit); + + QVarLengthArray lines; + int i =3D 0; + for (tikz::Value x =3D left; x.toPoint() < rect.right(); x += =3D one / lpu) { + if ((i % lpu) =3D=3D 0) { + majorLines.append(QLineF(x.toPoint(), rect.top(), x.to= Point(), rect.bottom())); + } else { + minorLines.append(QLineF(x.toPoint(), rect.top(), x.to= Point(), rect.bottom())); + } + ++i; + } + + i =3D 0; + for (tikz::Value y =3D top; y.toPoint() < rect.bottom(); y += =3D one / lpu) { + if ((i % lpu) =3D=3D 0) { + majorLines.append(QLineF(rect.left(), y.toPoint(), rec= t.right(), y.toPoint())); + } else { + minorLines.append(QLineF(rect.left(), y.toPoint(), rec= t.right(), y.toPoint())); + } + ++i; + } + + } + } +}; + +Grid::Grid(QGraphicsView * view) + : d(new GridPrivate()) +{ + d->view =3D view; +} + +Grid::~Grid() +{ + delete d; +} + +void Grid::draw(QPainter * p, const QRectF & rect) +{ + d->updateCache(rect); + + p->save(); +// QPen pen(QColor(243, 243, 243)); + QPen pen(QColor(230, 230, 230)); + pen.setWidth(0); + p->setPen(pen); + p->drawLines(d->majorLines.data(), d->majorLines.size()); + + pen.setDashPattern(QVector() << 5 << 5); + p->setPen(pen); + p->drawLines(d->minorLines.data(), d->minorLines.size()); + p->restore(); +} + +tikz::Value Grid::snapValue(const tikz::Value & value) const +{ + // TODO (later): make this unit configurable + const tikz::Unit unit =3D tikz::Centimeter; + const int lpu =3D d->linesPerUnit(unit); + const Value one(1.0 / lpu, unit); + + const auto snappedValue =3D tikz::Value(qRound(value.convertTo(unit).v= alue() / one.value()) * one.value(), unit).convertTo(value.unit()); +// qDebug() << value << "converts to:" << snappedValue; + return snappedValue; +} + +tikz::Pos Grid::snapPos(const tikz::Pos & pos) const +{ + return tikz::Pos(snapValue(pos.x()), snapValue(pos.y())); +} + +} +} + +// kate: indent-width 4; replace-tabs on; diff --git a/src/ui/utils/Grid.h b/src/ui/utils/Grid.h new file mode 100644 index 0000000..ed56835 --- /dev/null +++ b/src/ui/utils/Grid.h @@ -0,0 +1,75 @@ +/* This file is part of the TikZKit project. + * + * Copyright (C) 2014 Dominik Haumann + * + * This library is free software; you can redistribute it and/or modify + * it under the terms of the GNU Library General Public License as publish= ed + * by the Free Software Foundation, either version 2 of the License, or + * (at your option) any later version. + * + * This library 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 Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public Licen= se + * along with this library; see the file COPYING.LIB. If not, see + * . + */ + +#ifndef TIKZ_UI_GRID_H +#define TIKZ_UI_GRID_H + +#include +#include +#include + +#include +#include + +class QGraphicsView; +class QPainter; + +namespace tikz { +namespace ui { + +class GridPrivate; + +class Grid +{ +public: + /** + * Constructor with required @p view. + */ + Grid(QGraphicsView * view); + + /** + * Destructor. + */ + ~Grid(); + + /** + * Draw the grid using the painter @p p. + */ + void draw(QPainter * p, const QRectF & rect); + + /** + * Snap @p value to the grid. + */ + tikz::Value snapValue(const tikz::Value & value) const; + + /** + * Snap x/y components of @p pos to the grid. + */ + tikz::Pos snapPos(const tikz::Pos & pos) const; + +private: + GridPrivate * const d; +}; + +} +} + +#endif // TIKZ_UI_GRID_H + +// kate: indent-width 4; replace-tabs on;