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