1 /*
2 Copyright (c) 2008-2021, Benoit AUTHEMAN All rights reserved.
3
4 Redistribution and use in source and binary forms, with or without
5 modification, are permitted provided that the following conditions are met:
6 * Redistributions of source code must retain the above copyright
7 notice, this list of conditions and the following disclaimer.
8 * Redistributions in binary form must reproduce the above copyright
9 notice, this list of conditions and the following disclaimer in the
10 documentation and/or other materials provided with the distribution.
11 * Neither the name of the author or Destrat.io nor the
12 names of its contributors may be used to endorse or promote products
13 derived from this software without specific prior written permission.
14
15 THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
16 ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
17 WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
18 DISCLAIMED. IN NO EVENT SHALL AUTHOR BE LIABLE FOR ANY
19 DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
20 (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
21 LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
22 ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
23 (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
24 SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
25 */
26
27 //-----------------------------------------------------------------------------
28 // This file is a part of the QuickQanava software library.
29 //
30 // \file qanLineGrid.cpp
31 // \author benoit@destrat.io
32 // \date 2019 11 09 (initial code 2017 01 29)
33 //-----------------------------------------------------------------------------
34
35 // Std headers
36 #include <math.h> // std::round
37 #include <iostream>
38
39 // Qt headers
40
41 // QuickQanava headers
42 #include "./qanLineGrid.h"
43
44 namespace qan { // ::qan
45
46 /* OrthoGrid Object Management *///---------------------------------------------
OrthoGrid(QQuickItem * parent)47 OrthoGrid::OrthoGrid(QQuickItem* parent) :
48 Grid{parent}
49 { /* Nil */ }
50 //-----------------------------------------------------------------------------
51
52 /* Grid Management *///--------------------------------------------------------
updateGrid(const QRectF & viewRect,const QQuickItem & container,const QQuickItem & navigable)53 bool OrthoGrid::updateGrid(const QRectF& viewRect,
54 const QQuickItem& container,
55 const QQuickItem& navigable ) noexcept
56 {
57 // PRECONDITIONS:
58 // Grid item must be visible
59 // View rect must be valid
60 // _geometryComponent must have been set.
61 if ( !isVisible() ) // Do not update an invisible Grid
62 return false;
63 if ( !viewRect.isValid() )
64 return false;
65
66 _viewRectCache = viewRect; // Cache all arguments for an eventual updateGrid() call with
67 _containerCache = const_cast<QQuickItem*>(&container); // no arguments (for example on a qan::Grid property change)
68 _navigableCache = const_cast<QQuickItem*>(&navigable);
69
70 return true;
71 }
72
updateGrid()73 bool OrthoGrid::updateGrid() noexcept
74 {
75 if ( _containerCache &&
76 _navigableCache &&
77 _viewRectCache.isValid() ) // Update the grid with the last correct cached settings
78 return updateGrid( _viewRectCache, *_containerCache, *_navigableCache );
79 return false;
80 }
81 //-----------------------------------------------------------------------------
82
83
84 /* LineGrid Object Management *///---------------------------------------------
LineGrid(QQuickItem * parent)85 LineGrid::LineGrid(QQuickItem* parent) :
86 OrthoGrid{parent} { /* Nil */ }
87
~LineGrid()88 LineGrid::~LineGrid()
89 {
90 for ( auto line : _minorLines ) {
91 if ( line != nullptr &&
92 QQmlEngine::objectOwnership(line) == QQmlEngine::CppOwnership )
93 line->deleteLater();
94 }
95 _minorLines.clear();
96 for ( auto line : _majorLines ) {
97 if ( line != nullptr &&
98 QQmlEngine::objectOwnership(line) == QQmlEngine::CppOwnership )
99 line->deleteLater();
100 }
101 _majorLines.clear();
102 }
103 //-----------------------------------------------------------------------------
104
105 /* Grid Management *///--------------------------------------------------------
updateGrid(const QRectF & viewRect,const QQuickItem & container,const QQuickItem & navigable)106 bool LineGrid::updateGrid(const QRectF& viewRect,
107 const QQuickItem& container,
108 const QQuickItem& navigable) noexcept
109 {
110 // PRECONDITIONS:
111 // Base implementation should return true
112 // gridShape property should have been set
113 if (!OrthoGrid::updateGrid(viewRect, container, navigable))
114 return false;
115
116 const unsigned int gridMajor{static_cast<unsigned int>(getGridMajor())}; // On major thick every 10 minor thicks
117 const qreal gridScale{getGridScale()};
118
119 qreal containerZoom = container.scale();
120 if ( qFuzzyCompare(1.0 + containerZoom, 1.0) ) // Protect against 0 zoom
121 containerZoom = 1.0;
122 const qreal adaptativeScale = containerZoom < 1. ? gridScale / containerZoom : gridScale;
123 QPointF rectifiedTopLeft{ std::floor(viewRect.topLeft().x() / adaptativeScale) * adaptativeScale,
124 std::floor(viewRect.topLeft().y() / adaptativeScale) * adaptativeScale };
125 QPointF rectifiedBottomRight{ ( std::ceil(viewRect.bottomRight().x() / adaptativeScale) * adaptativeScale ),
126 ( std::ceil(viewRect.bottomRight().y() / adaptativeScale) * adaptativeScale )};
127 QRectF rectified{rectifiedTopLeft, rectifiedBottomRight };
128
129 const int numLinesX = static_cast<int>(round(rectified.width() / adaptativeScale));
130 const int numLinesY = static_cast<int>(round(rectified.height() / adaptativeScale));
131
132 // Update points cache size
133 const int linesCount = numLinesX + numLinesY;
134 if ( _minorLines.size() < linesCount )
135 _minorLines.resize(linesCount);
136 if ( _minorLines.size() < linesCount )
137 return false;
138 if ( _majorLines.size() < linesCount )
139 _majorLines.resize(linesCount);
140 if ( _majorLines.size() < linesCount )
141 return false;
142
143 const auto navigableRectified = container.mapRectToItem(&navigable, rectified);
144 int minorL = 0;
145 int majorL = 0;
146 for (int nlx = 0; nlx < numLinesX; ++nlx ){ // Generate HORIZONTAL lines
147 auto lx = rectifiedTopLeft.x() + (nlx * adaptativeScale);
148 auto navigablePoint = container.mapToItem(&navigable, QPointF{lx, 0.});
149
150 const QPointF p1{navigablePoint.x(), navigableRectified.top()};
151 const QPointF p2{navigablePoint.x(), navigableRectified.bottom()};
152
153 const bool isMajorColumn = qFuzzyCompare( 1. + std::fmod(lx, gridMajor * adaptativeScale), 1. );
154 if (isMajorColumn) {
155 if (_majorLines[majorL] != nullptr ) {
156 _majorLines[majorL]->getP1() = p1;
157 _majorLines[majorL]->getP2() = p2;
158 } else {
159 impl::GridLine* line = new impl::GridLine{std::move(p1), std::move(p2)};
160 _majorLines[majorL] = line;
161 }
162 ++majorL;
163 } else {
164 if (_minorLines[minorL] != nullptr ) {
165 _minorLines[minorL]->getP1() = p1;
166 _minorLines[minorL]->getP2() = p2;
167 } else {
168 impl::GridLine* line = new impl::GridLine{std::move(p1), std::move(p2)};
169 _minorLines[minorL] = line;
170 }
171 ++minorL;
172 }
173 }
174 for (int nly = 0; nly < numLinesY; ++nly) { // Generate VERTICAL lines
175 auto py = rectifiedTopLeft.y() + (nly * adaptativeScale);
176 auto navigablePoint = container.mapToItem(&navigable, QPointF{0., py});
177
178 const QPointF p1{navigableRectified.left(), navigablePoint.y()};
179 const QPointF p2{navigableRectified.right(), navigablePoint.y()};
180
181 const bool isMajorRow = qFuzzyCompare( 1. + std::fmod(py, gridMajor * adaptativeScale), 1. );
182 if (isMajorRow) {
183 if (_majorLines[majorL] == nullptr) {
184 impl::GridLine* line = new impl::GridLine{std::move(p1), std::move(p2)};
185 _majorLines[majorL] = line;
186 } else {
187 _majorLines[majorL]->getP1() = p1;
188 _majorLines[majorL]->getP2() = p2;
189 }
190 ++majorL;
191 } else {
192 if (_minorLines[minorL] == nullptr) {
193 impl::GridLine* line = new impl::GridLine{std::move(p1), std::move(p2)};
194 _minorLines[minorL] = line;
195 } else {
196 _minorLines[minorL]->getP1() = p1;
197 _minorLines[minorL]->getP2() = p2;
198 }
199 ++minorL;
200 }
201 }
202
203 // Note: Lines when l < _lines.size() are left ignored, they won't be drawn since we
204 // emit the number of lines to draw in redrawLines signal.
205 emit redrawLines(minorL, majorL);
206 return true;
207 }
208
getMinorLines()209 QQmlListProperty<impl::GridLine> LineGrid::getMinorLines() {
210 return QQmlListProperty<impl::GridLine>(this, this,
211 &LineGrid::callMinorLinesCount,
212 &LineGrid::callMinorLinesAt);
213 }
getMajorLines()214 QQmlListProperty<impl::GridLine> LineGrid::getMajorLines() {
215 return QQmlListProperty<impl::GridLine>(this, this,
216 &LineGrid::callMajorLinesCount,
217 &LineGrid::callMajorLinesAt);
218 }
219
minorLinesCount() const220 LineGrid::size_type LineGrid::minorLinesCount() const { return _minorLines.size(); }
minorLinesAt(size_type index) const221 impl::GridLine* LineGrid::minorLinesAt(size_type index) const { return _minorLines.at(index); }
222
majorLinesCount() const223 LineGrid::size_type LineGrid::majorLinesCount() const { return _majorLines.size(); }
majorLinesAt(size_type index) const224 impl::GridLine* LineGrid::majorLinesAt(size_type index) const { return _majorLines.at(index); }
225
callMinorLinesCount(QQmlListProperty<impl::GridLine> * list)226 LineGrid::size_type LineGrid::callMinorLinesCount(QQmlListProperty<impl::GridLine>* list) { return reinterpret_cast<LineGrid*>(list->data)->minorLinesCount(); }
callMinorLinesAt(QQmlListProperty<impl::GridLine> * list,size_type index)227 impl::GridLine* LineGrid::callMinorLinesAt(QQmlListProperty<impl::GridLine>* list, size_type index) { return reinterpret_cast<LineGrid*>(list->data)->minorLinesAt(index); }
228
callMajorLinesCount(QQmlListProperty<impl::GridLine> * list)229 LineGrid::size_type LineGrid::callMajorLinesCount(QQmlListProperty<impl::GridLine>* list) { return reinterpret_cast<LineGrid*>(list->data)->majorLinesCount(); }
callMajorLinesAt(QQmlListProperty<impl::GridLine> * list,size_type index)230 impl::GridLine* LineGrid::callMajorLinesAt(QQmlListProperty<impl::GridLine>* list, size_type index) { return reinterpret_cast<LineGrid*>(list->data)->majorLinesAt(index); }
231 //-----------------------------------------------------------------------------
232
233 } // ::qan
234