Line data Source code
1 : /// Including QT Headers
2 : #include <QPainter>
3 : #include <QPen>
4 : #include <QToolTip>
5 : /// Including Schema Editor
6 : #include "dbe/SchemaGraphicSegmentedArrow.hpp"
7 : #include "dbe/SchemaKernelWrapper.hpp"
8 : #include "dbe/SchemaStyle.hpp"
9 :
10 : /// Including Oks Headers
11 : #include "oks/class.hpp"
12 :
13 : /// Including C++ Headers
14 : #include <cmath>
15 :
16 : namespace dbse {
17 :
18 0 : SchemaGraphicSegmentedArrow::SchemaGraphicSegmentedArrow ( SchemaGraphicObject * start_item,
19 : SchemaGraphicObject * end_item,
20 : int connection_count,
21 : bool is_inheritance,
22 : bool is_composite, QString arrow_name,
23 0 : QString arrow_cardinality, QGraphicsItem * parent )
24 : : QGraphicsPathItem ( parent ),
25 0 : m_start_item ( start_item ),
26 0 : m_end_item ( end_item ),
27 0 : m_connection_count ( connection_count),
28 0 : m_inheritance ( is_inheritance ),
29 0 : m_composite ( is_composite ),
30 0 : m_name ( arrow_name ),
31 0 : m_cardinality ( arrow_cardinality ),
32 0 : LastDegree ( 0 ),
33 0 : LastRotation ( 0 )
34 : {
35 0 : if (!is_inheritance) {
36 0 : setAcceptHoverEvents(true);
37 : }
38 : //setPen(QPen(Qt::black,2,Qt::SolidLine,Qt::RoundCap,Qt::RoundJoin));
39 0 : setFlag ( ItemIsSelectable, true );
40 0 : m_arrow_size = 20;
41 :
42 0 : }
43 :
44 0 : SchemaGraphicSegmentedArrow::~SchemaGraphicSegmentedArrow()
45 : {
46 0 : }
47 :
48 0 : void dbse::SchemaGraphicSegmentedArrow::hoverEnterEvent ( QGraphicsSceneHoverEvent* he) {
49 0 : if (!m_name.isEmpty()) {
50 0 : auto info = m_start_item->GetClass();
51 :
52 0 : auto rel = info->find_relationship(m_name.toStdString());
53 :
54 0 : QToolTip::showText( he->screenPos(),
55 0 : QString::fromStdString(rel->get_description()) );
56 : }
57 0 : he->ignore();
58 0 : }
59 0 : void dbse::SchemaGraphicSegmentedArrow::hoverLeaveEvent ( QGraphicsSceneHoverEvent* he) {
60 0 : QToolTip::hideText();
61 0 : he->ignore();
62 0 : }
63 :
64 0 : QRectF SchemaGraphicSegmentedArrow::boundingRect() const
65 : {
66 0 : if ( m_start_item->collidesWithItem ( m_end_item ) )
67 : {
68 0 : return QRectF();
69 : }
70 :
71 0 : qreal extra = ( pen().width() + 20 ) / 2.0 + 10;
72 0 : auto br = QRectF ( p1(),
73 0 : QSizeF ( p2().x() - p1().x(),
74 0 : p2().y() - p1().y() ) ).normalized().adjusted (
75 : -extra, -extra, extra, extra );
76 0 : br = br.united(m_rel_label_br);
77 0 : br = br.united(m_rel_cardinality_br);
78 0 : return br;
79 : }
80 :
81 0 : QPainterPath SchemaGraphicSegmentedArrow::shape() const
82 : {
83 0 : QPainterPath path = QGraphicsPathItem::shape();
84 0 : path.addRect(m_rel_label_br);
85 0 : path.addRect(m_rel_cardinality_br);
86 0 : path.addPolygon ( m_marker );
87 : // path.addText ( p2() + QPoint ( 10, 10 ), QFont ( "Helvetica [Cronyx]", 10 ),
88 : // m_cardinality );
89 : // path.addText ( p1() + QPoint ( -10, -10 ), QFont ( "Helvetica [Cronyx]", 10 ),
90 : // m_cardinality );
91 0 : return path;
92 0 : }
93 :
94 0 : void SchemaGraphicSegmentedArrow::UpdatePosition()
95 : {
96 0 : if ( m_start_item->collidesWithItem ( m_end_item ) ) {
97 : // this->setPath(QPainterPath());
98 : // m_marker = QPolygonF();
99 0 : return;
100 : }
101 :
102 0 : std::vector<QLineF> norms = {
103 : {0.,0., 0., 1.}, // up
104 : {0.,0., 1., 0.}, // right
105 : {0.,0., 0, -1.}, // down
106 : {0.,0., -1., 0.}, // left
107 0 : };
108 :
109 0 : qreal xoffset;
110 0 : qreal yoffset;
111 0 : const qreal xfactor = 23.0;
112 0 : const qreal yfactor = 17.0;
113 0 : if (m_start_item->boundingRect().x() < m_end_item->boundingRect().x()) {
114 0 : xoffset = m_connection_count*xfactor;
115 : }
116 : else {
117 0 : xoffset = m_connection_count*-xfactor;
118 : }
119 :
120 0 : if (m_start_item->boundingRect().y() > m_end_item->boundingRect().y()) {
121 0 : yoffset = m_connection_count*yfactor;
122 : }
123 : else {
124 0 : yoffset = m_connection_count*-yfactor;
125 : }
126 :
127 0 : QPointF start_offset(xoffset, yoffset);
128 0 : QPointF end_offset(0, 0);
129 :
130 0 : QLineF center_line ( m_start_item->mapToScene ( m_start_item->boundingRect().center()+start_offset ),
131 0 : m_end_item->mapToScene ( m_end_item->boundingRect().center()+end_offset ) );
132 0 : QPolygonF start_polygon = QPolygonF ( m_start_item->boundingRect() );
133 0 : QPolygonF end_polygon = QPolygonF ( m_end_item->boundingRect() );
134 0 : QPointF intersect_point_start, intersect_point_end;
135 0 : QLineF intersect_norm_start, intersect_norm_end;
136 :
137 : // Iterate on the sides starting from top-left
138 0 : QPointF p1 = end_polygon.first() + m_end_item->pos(), p2;
139 0 : for ( int i = 1; i < end_polygon.count(); ++i )
140 : {
141 :
142 0 : p2 = end_polygon.at ( i ) + m_end_item->pos();
143 0 : QLineF item_side = QLineF ( p1, p2 );
144 0 : QLineF::IntersectType intersect_type = item_side.intersects ( center_line, &intersect_point_end );
145 :
146 0 : if ( intersect_type == QLineF::BoundedIntersection ) {
147 0 : intersect_norm_end = norms[i-1];
148 0 : break;
149 : }
150 :
151 0 : p1 = p2;
152 : }
153 :
154 0 : p1 = start_polygon.first() + m_start_item->pos();
155 0 : QPointF start_sp;
156 0 : for ( int i = 1; i < start_polygon.count(); ++i )
157 : {
158 0 : p2 = start_polygon.at ( i ) + m_start_item->pos();
159 0 : QLineF item_side = QLineF ( p1, p2 );
160 0 : QLineF::IntersectType intersect_type = item_side.intersects ( center_line,
161 : &intersect_point_start );
162 0 : if ( intersect_type == QLineF::BoundedIntersection ) {
163 0 : switch (i) {
164 0 : case 1:
165 0 : start_sp=QPointF {m_start_item->pos().x() + (m_start_item->boundingRect().width() / 2) + xoffset,
166 : intersect_point_start.y()};
167 0 : break;
168 0 : case 3:
169 0 : start_sp=QPointF {m_start_item->pos().x() + (m_start_item->boundingRect().width() / 2) - xoffset,
170 : intersect_point_start.y()};
171 0 : break;
172 :
173 0 : case 2:
174 0 : case 4: start_sp={intersect_point_start.x(),
175 0 : m_start_item->pos().y()+m_start_item->boundingRect().height() / 2+yoffset};
176 0 : break;
177 : }
178 0 : intersect_norm_start = norms[i-1];
179 0 : break;
180 : }
181 :
182 0 : p1 = p2;
183 : }
184 :
185 0 : QLineF direct_line(start_sp, intersect_point_end);
186 0 : auto font = QFont(SchemaStyle::get_font("line"));
187 0 : auto label_br = QRectF(QFontMetrics ( font ).boundingRect ( m_name ));
188 : // Center rectangle on origin
189 0 : label_br.translate(-label_br.width()/2, label_br.height()/2);
190 :
191 :
192 0 : auto cardinality_br = QRectF(QFontMetrics ( font ).boundingRect ( m_cardinality ));
193 : // Center rectangle on origin
194 0 : cardinality_br.translate(-cardinality_br.width()/2, cardinality_br.height()/2);
195 :
196 :
197 0 : qreal label_x_padding = 2;
198 0 : qreal label_y_padding = -2;
199 0 : qreal card_x_padding = 2;
200 0 : qreal card_y_padding = -2;
201 :
202 0 : QPainterPath path( intersect_point_start );
203 :
204 0 : if ((intersect_norm_start.dx() == 0) && (intersect_norm_end.dx() == 0)) {
205 : // Three segments, starting and ending vertically
206 0 : QPointF wp1 = QPointF(direct_line.x1(),
207 0 : direct_line.center().y()+yoffset);
208 0 : QPointF wp2 = QPointF(direct_line.x2(),
209 0 : direct_line.center().y()+yoffset);
210 0 : path.moveTo(direct_line.x1(), direct_line.y1());
211 0 : path.lineTo(wp1);
212 0 : path.lineTo(wp2);
213 :
214 : // Place the label
215 0 : label_br.translate( wp2
216 0 : + QPointF(
217 0 : (direct_line.dx() < 0 ? 1 : -1) * (label_x_padding+label_br.width()/2),
218 0 : (direct_line.dy() > 0 ? 1 : -1) * (label_br.height()+label_y_padding)
219 0 : )
220 : );
221 : // Place cardinality
222 0 : cardinality_br.translate( intersect_point_start
223 0 : + QPointF(
224 0 : (direct_line.dx() < 0 ? 1 : -1) * (card_x_padding+cardinality_br.width()/2),
225 0 : (direct_line.dy() > 0 ? 1 : -1) * (cardinality_br.height()+card_y_padding)
226 0 : )
227 : );
228 :
229 0 : } else if ((intersect_norm_start.dy() == 0) && (intersect_norm_end.dy() == 0)) {
230 : // Three segments, starting and ending horizontally
231 0 : QPointF wp1 = QPointF(direct_line.center().x()-xoffset,
232 0 : direct_line.y1());
233 0 : QPointF wp2 = QPointF(direct_line.center().x()-xoffset,
234 0 : direct_line.y2());
235 :
236 0 : path.moveTo(direct_line.x1(), direct_line.y1());
237 0 : path.lineTo(wp1);
238 0 : path.lineTo(wp2);
239 :
240 : // Place the label
241 0 : label_br.translate( wp2
242 0 : + QPointF(
243 0 : (direct_line.dx() > 0 ? 1 : -1) * (label_x_padding+label_br.width()/2),
244 0 : (direct_line.dy() < 0 ? 1 : -1) * (label_br.height()+label_y_padding)
245 0 : )
246 : );
247 0 : cardinality_br.translate( intersect_point_start
248 0 : + QPointF(
249 0 : (direct_line.dx() > 0 ? 1 : -1) * (card_x_padding+cardinality_br.width()/2),
250 0 : (direct_line.dy() < 0 ? 1 : -1) * (cardinality_br.height()+card_y_padding)
251 0 : )
252 : );
253 :
254 0 : } else if (intersect_norm_start.dx() == 0) {
255 : // Two segments, starting vertically, ending horizontally
256 0 : QPointF wp1 = QPointF(direct_line.x1(), direct_line.y2());
257 :
258 0 : path.moveTo(direct_line.x1(), direct_line.y1());
259 0 : path.lineTo(wp1);
260 :
261 0 : label_br.translate( wp1
262 0 : + QPointF(
263 0 : (direct_line.dx() > 0 ? 1 : -1) * (label_x_padding+label_br.width()/2),
264 0 : (direct_line.dy() < 0 ? 1 : -1) * (label_br.height()+label_y_padding)
265 0 : )
266 : );
267 0 : cardinality_br.translate( intersect_point_start
268 0 : + QPointF(
269 0 : (direct_line.dx() < 0 ? 1 : -1) * (card_x_padding+cardinality_br.width()/2),
270 0 : (direct_line.dy() > 0 ? 1 : -1) * (cardinality_br.height()+card_y_padding)
271 0 : )
272 : );
273 :
274 0 : } else if (intersect_norm_start.dy() == 0) {
275 : // Two segments, starting horizontally, ending vertically
276 0 : QPointF wp1 = QPointF(direct_line.x2(), direct_line.y1() );
277 0 : path.moveTo(direct_line.x1(), direct_line.y1());
278 :
279 0 : path.lineTo(wp1);
280 :
281 :
282 0 : label_br.translate( wp1
283 0 : + QPointF(
284 0 : (direct_line.dx() > 0 ? 1 : -1) * (label_x_padding+label_br.width()/2),
285 0 : (direct_line.dy() < 0 ? 1 : -1) * (label_br.height()+label_y_padding)
286 0 : )
287 : );
288 0 : cardinality_br.translate( intersect_point_start
289 0 : + QPointF(
290 0 : (direct_line.dx() < 0 ? 1 : -1) * (card_x_padding+cardinality_br.width()/2),
291 0 : (direct_line.dy() > 0 ? 1 : -1) * (cardinality_br.height()+card_y_padding)
292 0 : )
293 : );
294 :
295 : }
296 :
297 0 : path.lineTo( intersect_point_end );
298 0 : setPath(path);
299 :
300 0 : m_rel_label_br = label_br;
301 0 : m_rel_cardinality_br = cardinality_br;
302 :
303 : // Why does a function called UpdatePosition add decorations to a line??
304 : // =====================================================================
305 0 : if ( m_inheritance )
306 : {
307 0 : m_marker = this->make_arrow_head(-intersect_norm_end.angle()/180*M_PI).translated(this->p2());
308 : } else {
309 0 : m_marker = this->make_rhombus(-intersect_norm_start.angle()/180*M_PI).translated(this->p1());
310 : }
311 0 : }
312 :
313 0 : SchemaGraphicObject * SchemaGraphicSegmentedArrow::GetStartItem() const
314 : {
315 0 : return m_start_item;
316 : }
317 :
318 0 : SchemaGraphicObject * SchemaGraphicSegmentedArrow::GetEndItem() const
319 : {
320 0 : return m_end_item;
321 : }
322 :
323 0 : bool SchemaGraphicSegmentedArrow::GetInheritanceMode()
324 : {
325 0 : return m_inheritance;
326 : }
327 :
328 0 : void SchemaGraphicSegmentedArrow::RemoveArrow()
329 : {
330 0 : if ( m_inheritance )
331 : {
332 : /// Remove Super class
333 : }
334 : else
335 : {
336 : /// Remove relationship
337 : }
338 0 : }
339 :
340 0 : QPolygonF SchemaGraphicSegmentedArrow::make_arrow_head(qreal rotation) const {
341 :
342 0 : float opening_angle = atan2(1, 0.5);
343 0 : qreal size = m_arrow_size;
344 0 : QPointF a1 = QPointF ( sin ( rotation + opening_angle ) * size, cos ( rotation + opening_angle ) * size );
345 0 : QPointF a2 = QPointF ( sin ( rotation + M_PI - opening_angle ) * size, cos ( rotation + M_PI - opening_angle ) * size );
346 :
347 0 : QPolygonF arrow;
348 0 : arrow << QPointF(0,0) << a1 << a2;
349 0 : return arrow;
350 0 : }
351 :
352 0 : QPolygonF SchemaGraphicSegmentedArrow::make_rhombus(qreal rotation) const {
353 :
354 0 : float opening_angle = M_PI / 3;
355 0 : qreal size = 0.6*m_arrow_size;
356 :
357 0 : QPointF a1 = QPointF ( sin ( rotation + opening_angle ) * size, cos ( rotation + opening_angle ) * size );
358 0 : QPointF a2 = QPointF ( sin ( rotation + M_PI - opening_angle ) * size, cos ( rotation + M_PI - opening_angle ) * size );
359 :
360 0 : QPointF m = QPointF ( ( a1.x() + a2.x() ) / 2, ( a1.y() + a2.y() ) / 2 );
361 0 : QPointF a3 = QPointF ( 2 * m.x(), 2 * m.y() );
362 :
363 0 : QPolygonF rhombus;
364 0 : rhombus << QPointF(0,0) << a1 << a3 << a2;
365 0 : return rhombus;
366 0 : }
367 :
368 :
369 :
370 0 : void SchemaGraphicSegmentedArrow::paint ( QPainter * painter,
371 : const QStyleOptionGraphicsItem * option,
372 : QWidget * widget )
373 : {
374 0 : Q_UNUSED ( option )
375 0 : Q_UNUSED ( widget )
376 :
377 0 : if ( m_start_item->collidesWithItem ( m_end_item ) )
378 : {
379 0 : return;
380 : }
381 :
382 0 : const QPen line_pen = QPen(SchemaStyle::get_color("foreground", "line"), 1);
383 0 : const QPen arrow_pen = QPen(SchemaStyle::get_color("foreground", "line"), 1);
384 :
385 0 : painter->setFont ( QFont(SchemaStyle::get_font("line")) );
386 0 : painter->setPen ( line_pen );
387 0 : painter->setBrush ( {} );
388 0 : painter->setRenderHint(QPainter::Antialiasing);
389 :
390 :
391 0 : painter->drawPath ( this->path());
392 :
393 0 : painter->setPen ( arrow_pen );
394 0 : if ( !m_inheritance ) {
395 0 : painter->drawText(m_rel_label_br, Qt::AlignTop | Qt::AlignLeft, QString(m_name));
396 0 : painter->drawText(m_rel_cardinality_br, Qt::AlignTop | Qt::AlignLeft, QString(m_cardinality));
397 : }
398 :
399 0 : if ( m_inheritance )
400 : {
401 0 : painter->setBrush ( Qt::white );
402 : }
403 0 : else if ( m_composite )
404 : {
405 : /// Draw Rhombus
406 0 : painter->setBrush ( SchemaStyle::get_color("foreground", "line") );
407 :
408 :
409 : } else {
410 : /// Draw Rhombus
411 0 : painter->setBrush ( Qt::white );
412 : }
413 0 : painter->drawPolygon ( m_marker );
414 :
415 0 : }
416 :
417 : } // namespace dbse
|