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