1/****************************************************************************
2**
3** Copyright (C) 2016 The Qt Company Ltd.
4** Contact: https://www.qt.io/licensing/
5**
6** This file is part of the Qt Designer of the Qt Toolkit.
7**
8** $QT_BEGIN_LICENSE:GPL-EXCEPT$
9** Commercial License Usage
10** Licensees holding valid commercial Qt licenses may use this file in
11** accordance with the commercial license agreement provided with the
12** Software or, alternatively, in accordance with the terms contained in
13** a written agreement between you and The Qt Company. For licensing terms
14** and conditions see https://www.qt.io/terms-conditions. For further
15** information use the contact form at https://www.qt.io/contact-us.
16**
17** GNU General Public License Usage
18** Alternatively, this file may be used under the terms of the GNU
19** General Public License version 3 as published by the Free Software
20** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
21** included in the packaging of this file. Please review the following
22** information to ensure the GNU General Public License requirements will
23** be met: https://www.gnu.org/licenses/gpl-3.0.html.
24**
25** $QT_END_LICENSE$
26**
27****************************************************************************/
28
29
30#include "connectionedit_p.h"
31
32#include <QtDesigner/abstractformwindow.h>
33
34#include <QtGui/qpainter.h>
35#include <QtGui/qevent.h>
36#include <QtGui/qfontmetrics.h>
37#include <QtGui/qpixmap.h>
38#include <QtGui/qtransform.h>
39#include <QtWidgets/qapplication.h>
40#include <QtWidgets/qmenu.h>
41#include <QtWidgets/qaction.h>
42
43#include <QtCore/qmap.h>
44
45QT_BEGIN_NAMESPACE
46
47static const int BG_ALPHA = 32;
48static const int LINE_PROXIMITY_RADIUS = 3;
49static const int LOOP_MARGIN = 20;
50static const int VLABEL_MARGIN = 1;
51static const int HLABEL_MARGIN = 3;
52static const int GROUND_W = 20;
53static const int GROUND_H = 25;
54
55/*******************************************************************************
56** Tools
57*/
58
59static QRect fixRect(const QRect &r)
60{
61 return QRect(r.x(), r.y(), r.width() - 1, r.height() - 1);
62}
63
64static QRect expand(const QRect &r, int i)
65{
66 return QRect(r.x() - i, r.y() - i, r.width() + 2*i, r.height() + 2*i);
67}
68
69static QRect endPointRectHelper(const QPoint &pos)
70{
71 const QRect r(pos + QPoint(-LINE_PROXIMITY_RADIUS, -LINE_PROXIMITY_RADIUS),
72 QSize(2*LINE_PROXIMITY_RADIUS, 2*LINE_PROXIMITY_RADIUS));
73 return r;
74}
75
76static void paintGround(QPainter *p, QRect r)
77{
78 const QPoint mid = r.center();
79 p->drawLine(x1: mid.x(), y1: r.top(), x2: mid.x(), y2: mid.y());
80 p->drawLine(x1: r.left(), y1: mid.y(), x2: r.right(), y2: mid.y());
81 int y = r.top() + 4*r.height()/6;
82 int x = GROUND_W/6;
83 p->drawLine(x1: r.left() + x, y1: y, x2: r.right() - x, y2: y);
84 y = r.top() + 5*r.height()/6;
85 x = 2*GROUND_W/6;
86 p->drawLine(x1: r.left() + x, y1: y, x2: r.right() - x, y2: y);
87 p->drawLine(x1: mid.x(), y1: r.bottom(), x2: mid.x() + 1, y2: r.bottom());
88}
89
90static void paintEndPoint(QPainter *p, const QPoint &pos)
91{
92 const QRect r(pos + QPoint(-LINE_PROXIMITY_RADIUS, -LINE_PROXIMITY_RADIUS),
93 QSize(2*LINE_PROXIMITY_RADIUS, 2*LINE_PROXIMITY_RADIUS));
94 p->fillRect(fixRect(r), color: p->pen().color());
95}
96
97static qdesigner_internal::CETypes::LineDir classifyLine(const QPoint &p1, const QPoint &p2)
98{
99 if (p1.x() == p2.x())
100 return p1.y() < p2.y() ? qdesigner_internal::CETypes::DownDir : qdesigner_internal::CETypes::UpDir;
101 Q_ASSERT(p1.y() == p2.y());
102 return p1.x() < p2.x() ? qdesigner_internal::CETypes::RightDir : qdesigner_internal::CETypes::LeftDir;
103}
104
105static QPoint pointInsideRect(const QRect &r, QPoint p)
106{
107 if (p.x() < r.left())
108 p.setX(r.left());
109 else if (p.x() > r.right())
110 p.setX(r.right());
111
112 if (p.y() < r.top())
113 p.setY(r.top());
114 else if (p.y() > r.bottom())
115 p.setY(r.bottom());
116
117 return p;
118}
119
120namespace qdesigner_internal {
121
122/*******************************************************************************
123** Commands
124*/
125
126AddConnectionCommand::AddConnectionCommand(ConnectionEdit *edit, Connection *con)
127 : CECommand(edit), m_con(con)
128{
129 setText(QApplication::translate(context: "Command", key: "Add connection"));
130}
131
132void AddConnectionCommand::redo()
133{
134 edit()->selectNone();
135 emit edit()->aboutToAddConnection(idx: edit()->m_con_list.size());
136 edit()->m_con_list.append(t: m_con);
137 m_con->inserted();
138 emit edit()->connectionAdded(con: m_con);
139 edit()->setSelected(con: m_con, sel: true);
140}
141
142void AddConnectionCommand::undo()
143{
144 const int idx = edit()->indexOfConnection(con: m_con);
145 emit edit()->aboutToRemoveConnection(con: m_con);
146 edit()->setSelected(con: m_con, sel: false);
147 m_con->update();
148 m_con->removed();
149 edit()->m_con_list.removeAll(t: m_con);
150 emit edit()->connectionRemoved(idx);
151}
152
153class AdjustConnectionCommand : public CECommand
154{
155public:
156 AdjustConnectionCommand(ConnectionEdit *edit, Connection *con,
157 const QPoint &old_source_pos,
158 const QPoint &old_target_pos,
159 const QPoint &new_source_pos,
160 const QPoint &new_target_pos);
161 void redo() override;
162 void undo() override;
163private:
164 Connection *m_con;
165 const QPoint m_old_source_pos;
166 const QPoint m_old_target_pos;
167 const QPoint m_new_source_pos;
168 const QPoint m_new_target_pos;
169};
170
171AdjustConnectionCommand::AdjustConnectionCommand(ConnectionEdit *edit, Connection *con,
172 const QPoint &old_source_pos,
173 const QPoint &old_target_pos,
174 const QPoint &new_source_pos,
175 const QPoint &new_target_pos) :
176 CECommand(edit),
177 m_con(con),
178 m_old_source_pos(old_source_pos),
179 m_old_target_pos(old_target_pos),
180 m_new_source_pos(new_source_pos),
181 m_new_target_pos(new_target_pos)
182{
183 setText(QApplication::translate(context: "Command", key: "Adjust connection"));
184}
185
186void AdjustConnectionCommand::undo()
187{
188 m_con->setEndPoint(type: EndPoint::Source, w: m_con->widget(type: EndPoint::Source), pos: m_old_source_pos);
189 m_con->setEndPoint(type: EndPoint::Target, w: m_con->widget(type: EndPoint::Target), pos: m_old_target_pos);
190}
191
192void AdjustConnectionCommand::redo()
193{
194 m_con->setEndPoint(type: EndPoint::Source, w: m_con->widget(type: EndPoint::Source), pos: m_new_source_pos);
195 m_con->setEndPoint(type: EndPoint::Target, w: m_con->widget(type: EndPoint::Target), pos: m_new_target_pos);
196}
197
198DeleteConnectionsCommand::DeleteConnectionsCommand(ConnectionEdit *edit,
199 const ConnectionList &con_list)
200 : CECommand(edit), m_con_list(con_list)
201{
202 setText(QApplication::translate(context: "Command", key: "Delete connections"));
203}
204
205void DeleteConnectionsCommand::redo()
206{
207 for (Connection *con : qAsConst(t&: m_con_list)) {
208 const int idx = edit()->indexOfConnection(con);
209 emit edit()->aboutToRemoveConnection(con);
210 Q_ASSERT(edit()->m_con_list.contains(con));
211 edit()->setSelected(con, sel: false);
212 con->update();
213 con->removed();
214 edit()->m_con_list.removeAll(t: con);
215 emit edit()->connectionRemoved(idx);
216 }
217}
218
219void DeleteConnectionsCommand::undo()
220{
221 for (Connection *con : qAsConst(t&: m_con_list)) {
222 Q_ASSERT(!edit()->m_con_list.contains(con));
223 emit edit()->aboutToAddConnection(idx: edit()->m_con_list.size());
224 edit()->m_con_list.append(t: con);
225 edit()->selectNone();
226 con->update();
227 con->inserted();
228 emit edit()->connectionAdded(con);
229 edit()->setSelected(con, sel: true);
230 }
231}
232
233class SetEndPointCommand : public CECommand
234{
235public:
236 SetEndPointCommand(ConnectionEdit *edit, Connection *con, EndPoint::Type type, QObject *object);
237 void redo() override;
238 void undo() override;
239private:
240 Connection *m_con;
241 const EndPoint::Type m_type;
242 QObject *m_old_widget, *m_new_widget;
243 const QPoint m_old_pos;
244 QPoint m_new_pos;
245};
246
247SetEndPointCommand::SetEndPointCommand(ConnectionEdit *edit, Connection *con,
248 EndPoint::Type type, QObject *object) :
249 CECommand(edit),
250 m_con(con),
251 m_type(type),
252 m_old_widget(con->object(type)),
253 m_new_widget(object),
254 m_old_pos(con->endPointPos(type))
255{
256 if (QWidget *widget = qobject_cast<QWidget*>(o: object)) {
257 m_new_pos = edit->widgetRect(w: widget).center();
258 }
259
260 if (m_type == EndPoint::Source)
261 setText(QApplication::translate(context: "Command", key: "Change source"));
262 else
263 setText(QApplication::translate(context: "Command", key: "Change target"));
264}
265
266void SetEndPointCommand::redo()
267{
268 m_con->setEndPoint(type: m_type, w: m_new_widget, pos: m_new_pos);
269 emit edit()->connectionChanged(con: m_con);
270}
271
272void SetEndPointCommand::undo()
273{
274 m_con->setEndPoint(type: m_type, w: m_old_widget, pos: m_old_pos);
275 emit edit()->connectionChanged(con: m_con);
276}
277
278/*******************************************************************************
279** Connection
280*/
281
282Connection::Connection(ConnectionEdit *edit) :
283 m_source_pos(QPoint(-1, -1)),
284 m_target_pos(QPoint(-1, -1)),
285 m_source(nullptr),
286 m_target(nullptr),
287 m_edit(edit),
288 m_visible(true)
289{
290
291}
292
293Connection::Connection(ConnectionEdit *edit, QObject *source, QObject *target) :
294 m_source_pos(QPoint(-1, -1)),
295 m_target_pos(QPoint(-1, -1)),
296 m_source(source),
297 m_target(target),
298 m_edit(edit),
299 m_visible(true)
300{
301}
302
303void Connection::setVisible(bool b)
304{
305 m_visible = b;
306}
307
308void Connection::updateVisibility()
309{
310 QWidget *source = widget(type: EndPoint::Source);
311 QWidget *target = widget(type: EndPoint::Target);
312
313 if (source == nullptr || target == nullptr) {
314 setVisible(false);
315 return;
316 }
317
318 QWidget *w = source;
319 while (w && w->parentWidget()) {
320 if (!w->isVisibleTo(w->parentWidget())) {
321 setVisible(false);
322 return;
323 }
324 w = w->parentWidget();
325 }
326
327 w = target;
328 while (w && w->parentWidget()) {
329 if (!w->isVisibleTo(w->parentWidget())) {
330 setVisible(false);
331 return;
332 }
333 w = w->parentWidget();
334 }
335
336 setVisible(true);
337}
338
339bool Connection::isVisible() const
340{
341 return m_visible;
342}
343
344bool Connection::ground() const
345{
346 return m_target != nullptr && m_target == m_edit->m_bg_widget;
347}
348
349QPoint Connection::endPointPos(EndPoint::Type type) const
350{
351 return type == EndPoint::Source ? m_source_pos : m_target_pos;
352}
353
354static QPoint lineEntryPos(const QPoint &p1, const QPoint &p2, const QRect &rect)
355{
356 QPoint result;
357
358 switch (classifyLine(p1, p2)) {
359 case CETypes::UpDir:
360 result = QPoint(p1.x(), rect.bottom());
361 break;
362 case CETypes::DownDir:
363 result = QPoint(p1.x(), rect.top());
364 break;
365 case CETypes::LeftDir:
366 result = QPoint(rect.right(), p1.y());
367 break;
368 case CETypes::RightDir:
369 result = QPoint(rect.left(), p1.y());
370 break;
371 }
372
373 return result;
374}
375
376static QPolygonF arrowHead(const QPoint &p1, const QPoint &p2)
377{
378 QPolygonF result;
379
380 switch (classifyLine(p1, p2)) {
381 case CETypes::UpDir:
382 result.append(t: p2 + QPoint(0, 1));
383 result.append(t: p2 + QPoint(LINE_PROXIMITY_RADIUS, LINE_PROXIMITY_RADIUS*2 + 1));
384 result.append(t: p2 + QPoint(-LINE_PROXIMITY_RADIUS, LINE_PROXIMITY_RADIUS*2 + 1));
385 break;
386 case CETypes::DownDir:
387 result.append(t: p2);
388 result.append(t: p2 + QPoint(LINE_PROXIMITY_RADIUS, -LINE_PROXIMITY_RADIUS*2));
389 result.append(t: p2 + QPoint(-LINE_PROXIMITY_RADIUS, -LINE_PROXIMITY_RADIUS*2));
390 break;
391 case CETypes::LeftDir:
392 result.append(t: p2 + QPoint(1, 0));
393 result.append(t: p2 + QPoint(2*LINE_PROXIMITY_RADIUS + 1, -LINE_PROXIMITY_RADIUS));
394 result.append(t: p2 + QPoint(2*LINE_PROXIMITY_RADIUS + 1, LINE_PROXIMITY_RADIUS));
395 break;
396 case CETypes::RightDir:
397 result.append(t: p2);
398 result.append(t: p2 + QPoint(-2*LINE_PROXIMITY_RADIUS, -LINE_PROXIMITY_RADIUS));
399 result.append(t: p2 + QPoint(-2*LINE_PROXIMITY_RADIUS, LINE_PROXIMITY_RADIUS));
400 break;
401 }
402
403 return result;
404}
405
406static CETypes::LineDir closestEdge(const QPoint &p, const QRect &r)
407{
408 CETypes::LineDir result = CETypes::UpDir;
409 int min = p.y() - r.top();
410
411 int d = p.x() - r.left();
412 if (d < min) {
413 min = d;
414 result = CETypes::LeftDir;
415 }
416
417 d = r.bottom() - p.y();
418 if (d < min) {
419 min = d;
420 result = CETypes::DownDir;
421 }
422
423 d = r.right() - p.x();
424 if (d < min) {
425 min = d;
426 result = CETypes::RightDir;
427 }
428
429 return result;
430}
431
432static bool pointAboveLine(const QPoint &l1, const QPoint &l2, const QPoint &p)
433{
434 if (l1.x() == l2.x())
435 return p.x() >= l1.x();
436 return p.y() <= l1.y() + (p.x() - l1.x())*(l2.y() - l1.y())/(l2.x() - l1.x());
437}
438
439void Connection::updateKneeList()
440{
441 const LineDir old_source_label_dir = labelDir(type: EndPoint::Source);
442 const LineDir old_target_label_dir = labelDir(type: EndPoint::Target);
443
444 QPoint s = endPointPos(type: EndPoint::Source);
445 QPoint t = endPointPos(type: EndPoint::Target);
446 const QRect sr = m_source_rect;
447 const QRect tr = m_target_rect;
448
449 m_knee_list.clear();
450 m_arrow_head.clear();
451
452 if (m_source == nullptr || s == QPoint(-1, -1) || t == QPoint(-1, -1))
453 return;
454
455 const QRect r = sr | tr;
456
457 m_knee_list.append(t: s);
458 if (m_target == nullptr) {
459 m_knee_list.append(t: QPoint(t.x(), s.y()));
460 } else if (m_target == m_edit->m_bg_widget) {
461 m_knee_list.append(t: QPoint(s.x(), t.y()));
462 } else if (tr.contains(r: sr) || sr.contains(r: tr)) {
463/*
464 +------------------+
465 | +----------+ |
466 | | | |
467 | | o | |
468 | +---|------+ |
469 | | x |
470 +-----|-----|------+
471 +-----+
472
473 We find out which edge of the outer rectangle is closest to the target
474 point, and make a loop which exits and re-enters through that edge.
475*/
476 const LineDir dir = closestEdge(p: t, r: tr);
477 switch (dir) {
478 case UpDir:
479 m_knee_list.append(t: QPoint(s.x(), r.top() - LOOP_MARGIN));
480 m_knee_list.append(t: QPoint(t.x(), r.top() - LOOP_MARGIN));
481 break;
482 case DownDir:
483 m_knee_list.append(t: QPoint(s.x(), r.bottom() + LOOP_MARGIN));
484 m_knee_list.append(t: QPoint(t.x(), r.bottom() + LOOP_MARGIN));
485 break;
486 case LeftDir:
487 m_knee_list.append(t: QPoint(r.left() - LOOP_MARGIN, s.y()));
488 m_knee_list.append(t: QPoint(r.left() - LOOP_MARGIN, t.y()));
489 break;
490 case RightDir:
491 m_knee_list.append(t: QPoint(r.right() + LOOP_MARGIN, s.y()));
492 m_knee_list.append(t: QPoint(r.right() + LOOP_MARGIN, t.y()));
493 break;
494 }
495 } else {
496 if (r.height() < sr.height() + tr.height()) {
497 if ((s.y() >= tr.top() && s.y() <= tr.bottom()) || (t.y() >= sr.bottom() || t.y() <= sr.top())) {
498/*
499 +--------+
500 | | +--------+
501 | o--+---+--x |
502 | o | | |
503 +-----|--+ | |
504 +------+--x |
505 +--------+
506
507 When dragging one end point, move the other end point to the same y position,
508 if that does not cause it to exit it's rectangle.
509*/
510 if (m_edit->state() == ConnectionEdit::Dragging) {
511 if (m_edit->m_drag_end_point.type == EndPoint::Source) {
512 const QPoint p(t.x(), s.y());
513 m_knee_list.append(t: p);
514 if (tr.contains(p))
515 t = m_target_pos = p;
516 } else {
517 const QPoint p(s.x(), t.y());
518 m_knee_list.append(t: p);
519 if (sr.contains(p))
520 s = m_source_pos = p;
521 }
522 } else {
523 m_knee_list.append(t: QPoint(s.x(), t.y()));
524 }
525 } else {
526/*
527 +--------+
528 | o----+-------+
529 | | +---|----+
530 +--------+ | | |
531 | x |
532 +--------+
533*/
534 m_knee_list.append(t: QPoint(t.x(), s.y()));
535 }
536 } else if (r.width() < sr.width() + tr.width()) {
537 if ((s.x() >= tr.left() && s.x() <= tr.right()) || t.x() >= sr.right() || t.x() <= sr.left()) {
538/*
539 +--------+
540 | |
541 | o o+--+
542 +----|---+ |
543 +-|------|-+
544 | x x |
545 | |
546 +----------+
547
548 When dragging one end point, move the other end point to the same x position,
549 if that does not cause it to exit it's rectangle.
550*/
551 if (m_edit->state() == ConnectionEdit::Dragging) {
552 if (m_edit->m_drag_end_point.type == EndPoint::Source) {
553 const QPoint p(s.x(), t.y());
554 m_knee_list.append(t: p);
555 if (tr.contains(p))
556 t = m_target_pos = p;
557 } else {
558 const QPoint p(t.x(), s.y());
559 m_knee_list.append(t: p);
560 if (sr.contains(p))
561 s = m_source_pos = p;
562 }
563 } else {
564 m_knee_list.append(t: QPoint(t.x(), s.y()));
565 }
566 } else {
567/*
568 +--------+
569 | |
570 | o |
571 +--|-----+
572 | +--------+
573 +---+-x |
574 | |
575 +--------+
576
577*/
578 m_knee_list.append(t: QPoint(s.x(), t.y()));
579 }
580 } else {
581/*
582 +--------+
583 | |
584 | o o-+--------+
585 +--|-----+ |
586 | +-----|--+
587 | | x |
588 +--------+-x |
589 +--------+
590
591 The line enters the target rectangle through the closest edge.
592*/
593 if (sr.topLeft() == r.topLeft()) {
594 if (pointAboveLine(l1: tr.topLeft(), l2: tr.bottomRight(), p: t))
595 m_knee_list.append(t: QPoint(t.x(), s.y()));
596 else
597 m_knee_list.append(t: QPoint(s.x(), t.y()));
598 } else if (sr.topRight() == r.topRight()) {
599 if (pointAboveLine(l1: tr.bottomLeft(), l2: tr.topRight(), p: t))
600 m_knee_list.append(t: QPoint(t.x(), s.y()));
601 else
602 m_knee_list.append(t: QPoint(s.x(), t.y()));
603 } else if (sr.bottomRight() == r.bottomRight()) {
604 if (pointAboveLine(l1: tr.topLeft(), l2: tr.bottomRight(), p: t))
605 m_knee_list.append(t: QPoint(s.x(), t.y()));
606 else
607 m_knee_list.append(t: QPoint(t.x(), s.y()));
608 } else {
609 if (pointAboveLine(l1: tr.bottomLeft(), l2: tr.topRight(), p: t))
610 m_knee_list.append(t: QPoint(s.x(), t.y()));
611 else
612 m_knee_list.append(t: QPoint(t.x(), s.y()));
613 }
614 }
615 }
616 m_knee_list.append(t);
617
618 if (m_knee_list.size() == 2)
619 m_knee_list.clear();
620
621 trimLine();
622
623 const LineDir new_source_label_dir = labelDir(type: EndPoint::Source);
624 const LineDir new_target_label_dir = labelDir(type: EndPoint::Target);
625 if (new_source_label_dir != old_source_label_dir)
626 updatePixmap(type: EndPoint::Source);
627 if (new_target_label_dir != old_target_label_dir)
628 updatePixmap(type: EndPoint::Target);
629}
630
631void Connection::trimLine()
632{
633 if (m_source == nullptr || m_source_pos == QPoint(-1, -1) || m_target_pos == QPoint(-1, -1))
634 return;
635 int cnt = m_knee_list.size();
636 if (cnt < 2)
637 return;
638
639 const QRect sr = m_source_rect;
640 const QRect tr = m_target_rect;
641
642 if (sr.contains(p: m_knee_list.at(i: 1)))
643 m_knee_list.removeFirst();
644
645 cnt = m_knee_list.size();
646 if (cnt < 2)
647 return;
648
649 if (!tr.contains(r: sr) && tr.contains(p: m_knee_list.at(i: cnt - 2)))
650 m_knee_list.removeLast();
651
652 cnt = m_knee_list.size();
653 if (cnt < 2)
654 return;
655
656 if (sr.contains(p: m_knee_list.at(i: 0)) && !sr.contains(p: m_knee_list.at(i: 1)))
657 m_knee_list[0] = lineEntryPos(p1: m_knee_list.at(i: 1), p2: m_knee_list.at(i: 0), rect: sr);
658
659 if (tr.contains(p: m_knee_list.at(i: cnt - 1)) && !tr.contains(p: m_knee_list.at(i: cnt - 2))) {
660 m_knee_list[cnt - 1]
661 = lineEntryPos(p1: m_knee_list.at(i: cnt - 2), p2: m_knee_list.at(i: cnt - 1), rect: tr);
662 m_arrow_head = arrowHead(p1: m_knee_list.at(i: cnt - 2), p2: m_knee_list.at(i: cnt - 1));
663 }
664}
665
666void Connection::setSource(QObject *source, const QPoint &pos)
667{
668 if (source == m_source && m_source_pos == pos)
669 return;
670
671 update(update_widgets: false);
672
673 m_source = source;
674 if (QWidget *widget = qobject_cast<QWidget*>(o: source)) {
675 m_source_pos = pos;
676 m_source_rect = m_edit->widgetRect(w: widget);
677 updateKneeList();
678 }
679
680 update(update_widgets: false);
681}
682
683void Connection::setTarget(QObject *target, const QPoint &pos)
684{
685 if (target == m_target && m_target_pos == pos)
686 return;
687
688 update(update_widgets: false);
689
690 m_target = target;
691 if (QWidget *widget = qobject_cast<QWidget*>(o: target)) {
692 m_target_pos = pos;
693 m_target_rect = m_edit->widgetRect(w: widget);
694 updateKneeList();
695 }
696
697 update(update_widgets: false);
698}
699
700static QRect lineRect(const QPoint &a, const QPoint &b)
701{
702 const QPoint c(qMin(a: a.x(), b: b.x()), qMin(a: a.y(), b: b.y()));
703 const QPoint d(qMax(a: a.x(), b: b.x()), qMax(a: a.y(), b: b.y()));
704
705 QRect result(c, d);
706 return expand(r: result, i: LINE_PROXIMITY_RADIUS);
707}
708
709QRect Connection::groundRect() const
710{
711 if (!ground())
712 return QRect();
713 if (m_knee_list.isEmpty())
714 return QRect();
715
716 const QPoint p = m_knee_list.last();
717 return QRect(p.x() - GROUND_W/2, p.y(), GROUND_W, GROUND_H);
718}
719
720QRegion Connection::region() const
721{
722 QRegion result;
723
724 for (int i = 0; i < m_knee_list.size() - 1; ++i)
725 result = result.united(r: lineRect(a: m_knee_list.at(i), b: m_knee_list.at(i: i + 1)));
726
727 if (!m_arrow_head.isEmpty()) {
728 QRect r = m_arrow_head.boundingRect().toRect();
729 r = expand(r, i: 1);
730 result = result.united(r);
731 } else if (ground()) {
732 result = result.united(r: groundRect());
733 }
734
735 result = result.united(r: labelRect(type: EndPoint::Source));
736 result = result.united(r: labelRect(type: EndPoint::Target));
737
738 return result;
739}
740
741void Connection::update(bool update_widgets) const
742{
743 m_edit->update(region());
744 if (update_widgets) {
745 if (m_source != nullptr)
746 m_edit->update(m_source_rect);
747 if (m_target != nullptr)
748 m_edit->update(m_target_rect);
749 }
750
751 m_edit->update(endPointRect(EndPoint::Source));
752 m_edit->update(endPointRect(EndPoint::Target));
753}
754
755void Connection::paint(QPainter *p) const
756{
757 for (int i = 0; i < m_knee_list.size() - 1; ++i)
758 p->drawLine(p1: m_knee_list.at(i), p2: m_knee_list.at(i: i + 1));
759
760 if (!m_arrow_head.isEmpty()) {
761 p->save();
762 p->setBrush(p->pen().color());
763 p->drawPolygon(polygon: m_arrow_head);
764 p->restore();
765 } else if (ground()) {
766 paintGround(p, r: groundRect());
767 }
768}
769
770bool Connection::contains(const QPoint &pos) const
771{
772 return region().contains(p: pos);
773}
774
775QRect Connection::endPointRect(EndPoint::Type type) const
776{
777 if (type == EndPoint::Source) {
778 if (m_source_pos != QPoint(-1, -1))
779 return endPointRectHelper(pos: m_source_pos);
780 } else {
781 if (m_target_pos != QPoint(-1, -1))
782 return endPointRectHelper(pos: m_target_pos);
783 }
784 return QRect();
785}
786
787CETypes::LineDir Connection::labelDir(EndPoint::Type type) const
788{
789 const int cnt = m_knee_list.size();
790 if (cnt < 2)
791 return RightDir;
792
793 LineDir dir;
794 if (type == EndPoint::Source)
795 dir = classifyLine(p1: m_knee_list.at(i: 0), p2: m_knee_list.at(i: 1));
796 else
797 dir = classifyLine(p1: m_knee_list.at(i: cnt - 2), p2: m_knee_list.at(i: cnt - 1));
798
799 if (dir == LeftDir)
800 dir = RightDir;
801 if (dir == UpDir)
802 dir = DownDir;
803
804 return dir;
805}
806
807QRect Connection::labelRect(EndPoint::Type type) const
808{
809 const int cnt = m_knee_list.size();
810 if (cnt < 2)
811 return QRect();
812 const QString text = label(type);
813 if (text.isEmpty())
814 return QRect();
815
816 const QSize size = labelPixmap(type).size();
817 QPoint p1, p2;
818 if (type == EndPoint::Source) {
819 p1 = m_knee_list.at(i: 0);
820 p2 = m_knee_list.at(i: 1);
821 } else {
822 p1 = m_knee_list.at(i: cnt - 1);
823 p2 = m_knee_list.at(i: cnt - 2);
824 }
825 const LineDir dir = classifyLine(p1, p2);
826
827 QRect result;
828 switch (dir) {
829 case UpDir:
830 result = QRect(p1 + QPoint(-size.width()/2, 0), size);
831 break;
832 case DownDir:
833 result = QRect(p1 + QPoint(-size.width()/2, -size.height()), size);
834 break;
835 case LeftDir:
836 result = QRect(p1 + QPoint(0, -size.height()/2), size);
837 break;
838 case RightDir:
839 result = QRect(p1 + QPoint(-size.width(), -size.height()/2), size);
840 break;
841 }
842
843 return result;
844}
845
846void Connection::setLabel(EndPoint::Type type, const QString &text)
847{
848 if (text == label(type))
849 return;
850
851 if (type == EndPoint::Source)
852 m_source_label = text;
853 else
854 m_target_label = text;
855
856 updatePixmap(type);
857}
858
859void Connection::updatePixmap(EndPoint::Type type)
860{
861 QPixmap *pm = type == EndPoint::Source ? &m_source_label_pm : &m_target_label_pm;
862
863 const QString text = label(type);
864 if (text.isEmpty()) {
865 *pm = QPixmap();
866 return;
867 }
868
869 const QFontMetrics fm = m_edit->fontMetrics();
870 const QSize size = fm.size(flags: Qt::TextSingleLine, str: text) + QSize(HLABEL_MARGIN*2, VLABEL_MARGIN*2);
871 *pm = QPixmap(size);
872 QColor color = m_edit->palette().color(cg: QPalette::Normal, cr: QPalette::Base);
873 color.setAlpha(190);
874 pm->fill(fillColor: color);
875
876 QPainter p(pm);
877 p.setPen(m_edit->palette().color(cg: QPalette::Normal, cr: QPalette::Text));
878 p.drawText(x: -fm.leftBearing(text.at(i: 0)) + HLABEL_MARGIN, y: fm.ascent() + VLABEL_MARGIN, s: text);
879 p.end();
880
881 const LineDir dir = labelDir(type);
882
883 if (dir == DownDir)
884 *pm = pm->transformed(QTransform(0.0, -1.0, 1.0, 0.0, 0.0, 0.0));
885}
886
887void Connection::checkWidgets()
888{
889 bool changed = false;
890
891 if (QWidget *sourceWidget = qobject_cast<QWidget*>(o: m_source)) {
892 const QRect r = m_edit->widgetRect(w: sourceWidget);
893 if (r != m_source_rect) {
894 if (m_source_pos != QPoint(-1, -1) && !r.contains(p: m_source_pos)) {
895 QPoint offset = m_source_pos - m_source_rect.topLeft();
896 m_source_pos = pointInsideRect(r, p: r.topLeft() + offset);
897 }
898 m_edit->update(m_source_rect);
899 m_source_rect = r;
900 changed = true;
901 }
902 }
903
904 if (QWidget *targetWidget = qobject_cast<QWidget*>(o: m_target)) {
905 const QRect r = m_edit->widgetRect(w: targetWidget);
906 if (r != m_target_rect) {
907 if (m_target_pos != QPoint(-1, -1) && !r.contains(p: m_target_pos)) {
908 const QPoint offset = m_target_pos - m_target_rect.topLeft();
909 m_target_pos = pointInsideRect(r, p: r.topLeft() + offset);
910 }
911 m_edit->update(m_target_rect);
912 m_target_rect = r;
913 changed = true;
914 }
915 }
916
917 if (changed) {
918 update();
919 updateKneeList();
920 update();
921 }
922}
923
924/*******************************************************************************
925** ConnectionEdit
926*/
927
928ConnectionEdit::ConnectionEdit(QWidget *parent, QDesignerFormWindowInterface *form) :
929 QWidget(parent),
930 m_bg_widget(nullptr),
931 m_undo_stack(form->commandHistory()),
932 m_enable_update_background(false),
933 m_tmp_con(nullptr),
934 m_start_connection_on_drag(true),
935 m_widget_under_mouse(nullptr),
936 m_inactive_color(Qt::blue),
937 m_active_color(Qt::red)
938{
939 setAttribute(Qt::WA_MouseTracking, on: true);
940 setFocusPolicy(Qt::ClickFocus);
941
942 connect(sender: form, signal: &QDesignerFormWindowInterface::widgetRemoved, receiver: this, slot: &ConnectionEdit::widgetRemoved);
943 connect(sender: form, signal: &QDesignerFormWindowInterface::objectRemoved, receiver: this, slot: &ConnectionEdit::objectRemoved);
944}
945
946ConnectionEdit::~ConnectionEdit()
947{
948 qDeleteAll(c: m_con_list);
949}
950
951void ConnectionEdit::clear()
952{
953 m_con_list.clear();
954 m_sel_con_set.clear();
955 m_bg_widget = nullptr;
956 m_widget_under_mouse = nullptr;
957 m_tmp_con = nullptr;
958}
959
960void ConnectionEdit::setBackground(QWidget *background)
961{
962 if (background == m_bg_widget) {
963 // nothing to do
964 return;
965 }
966
967 m_bg_widget = background;
968 updateBackground();
969}
970
971void ConnectionEdit::enableUpdateBackground(bool enable)
972{
973 m_enable_update_background = enable;
974 if (enable)
975 updateBackground();
976}
977
978void ConnectionEdit::updateBackground()
979{
980 // Might happen while reloading a form.
981 if (m_bg_widget == nullptr)
982 return;
983
984 if (!m_enable_update_background)
985 return;
986
987 for (Connection *c : qAsConst(t&: m_con_list))
988 c->updateVisibility();
989
990 updateLines();
991 update();
992}
993
994QWidget *ConnectionEdit::widgetAt(const QPoint &pos) const
995{
996 if (m_bg_widget == nullptr)
997 return nullptr;
998 QWidget *widget = m_bg_widget->childAt(p: pos);
999 if (widget == nullptr)
1000 widget = m_bg_widget;
1001
1002 return widget;
1003}
1004
1005
1006QRect ConnectionEdit::widgetRect(QWidget *w) const
1007{
1008 if (w == nullptr)
1009 return QRect();
1010 QRect r = w->geometry();
1011 QPoint pos = w->mapToGlobal(QPoint(0, 0));
1012 pos = mapFromGlobal(pos);
1013 r.moveTopLeft(p: pos);
1014 return r;
1015}
1016
1017ConnectionEdit::State ConnectionEdit::state() const
1018{
1019 if (m_tmp_con != nullptr)
1020 return Connecting;
1021 if (!m_drag_end_point.isNull())
1022 return Dragging;
1023 return Editing;
1024}
1025
1026void ConnectionEdit::paintLabel(QPainter *p, EndPoint::Type type, Connection *con)
1027{
1028 if (con->label(type).isEmpty())
1029 return;
1030
1031 const bool heavy = selected(con) || con == m_tmp_con;
1032 p->setPen(heavy ? m_active_color : m_inactive_color);
1033 p->setBrush(Qt::NoBrush);
1034 const QRect r = con->labelRect(type);
1035 p->drawPixmap(p: r.topLeft(), pm: con->labelPixmap(type));
1036 p->drawRect(r: fixRect(r));
1037}
1038
1039void ConnectionEdit::paintConnection(QPainter *p, Connection *con,
1040 WidgetSet *heavy_highlight_set,
1041 WidgetSet *light_highlight_set) const
1042{
1043 QWidget *source = con->widget(type: EndPoint::Source);
1044 QWidget *target = con->widget(type: EndPoint::Target);
1045
1046 const bool heavy = selected(con) || con == m_tmp_con;
1047 WidgetSet *set = heavy ? heavy_highlight_set : light_highlight_set;
1048 p->setPen(heavy ? m_active_color : m_inactive_color);
1049 con->paint(p);
1050
1051 if (source != nullptr && source != m_bg_widget)
1052 set->insert(akey: source, avalue: source);
1053
1054 if (target != nullptr && target != m_bg_widget)
1055 set->insert(akey: target, avalue: target);
1056}
1057
1058void ConnectionEdit::paintEvent(QPaintEvent *e)
1059{
1060 QPainter p(this);
1061 p.setClipRegion(e->region());
1062
1063 WidgetSet heavy_highlight_set, light_highlight_set;
1064
1065 for (Connection *con : qAsConst(t&: m_con_list)) {
1066 if (!con->isVisible())
1067 continue;
1068
1069 paintConnection(p: &p, con, heavy_highlight_set: &heavy_highlight_set, light_highlight_set: &light_highlight_set);
1070 }
1071
1072 if (m_tmp_con != nullptr)
1073 paintConnection(p: &p, con: m_tmp_con, heavy_highlight_set: &heavy_highlight_set, light_highlight_set: &light_highlight_set);
1074
1075 if (!m_widget_under_mouse.isNull() && m_widget_under_mouse != m_bg_widget)
1076 heavy_highlight_set.insert(akey: m_widget_under_mouse, avalue: m_widget_under_mouse);
1077
1078 QColor c = m_active_color;
1079 p.setPen(c);
1080 c.setAlpha(BG_ALPHA);
1081 p.setBrush(c);
1082
1083 for (QWidget *w : qAsConst(t&: heavy_highlight_set)) {
1084 p.drawRect(r: fixRect(r: widgetRect(w)));
1085 light_highlight_set.remove(akey: w);
1086 }
1087
1088 c = m_inactive_color;
1089 p.setPen(c);
1090 c.setAlpha(BG_ALPHA);
1091 p.setBrush(c);
1092
1093 for (QWidget *w : qAsConst(t&: light_highlight_set))
1094 p.drawRect(r: fixRect(r: widgetRect(w)));
1095
1096 p.setBrush(palette().color(cr: QPalette::Base));
1097 p.setPen(palette().color(cr: QPalette::Text));
1098 for (Connection *con : qAsConst(t&: m_con_list)) {
1099 if (con->isVisible()) {
1100 paintLabel(p: &p, type: EndPoint::Source, con);
1101 paintLabel(p: &p, type: EndPoint::Target, con);
1102 }
1103 }
1104
1105 p.setPen(m_active_color);
1106 p.setBrush(m_active_color);
1107
1108 for (Connection *con : qAsConst(t&: m_con_list)) {
1109 if (!selected(con) || !con->isVisible())
1110 continue;
1111
1112 paintEndPoint(p: &p, pos: con->endPointPos(type: EndPoint::Source));
1113
1114 if (con->widget(type: EndPoint::Target) != nullptr)
1115 paintEndPoint(p: &p, pos: con->endPointPos(type: EndPoint::Target));
1116 }
1117}
1118
1119void ConnectionEdit::abortConnection()
1120{
1121 m_tmp_con->update();
1122 delete m_tmp_con;
1123 m_tmp_con = nullptr;
1124#if QT_CONFIG(cursor)
1125 setCursor(QCursor());
1126#endif
1127 if (m_widget_under_mouse == m_bg_widget)
1128 m_widget_under_mouse = nullptr;
1129}
1130
1131void ConnectionEdit::mousePressEvent(QMouseEvent *e)
1132{
1133 // Right click only to cancel
1134 const Qt::MouseButton button = e->button();
1135 const State cstate = state();
1136 if (button != Qt::LeftButton && !(button == Qt::RightButton && cstate == Connecting)) {
1137 QWidget::mousePressEvent(event: e);
1138 return;
1139 }
1140
1141 e->accept();
1142 // Prefer a non-background widget over the connection,
1143 // otherwise, widgets covered by the connection labels cannot be accessed
1144 Connection *con_under_mouse = nullptr;
1145 if (!m_widget_under_mouse || m_widget_under_mouse == m_bg_widget)
1146 con_under_mouse = connectionAt(pos: e->pos());
1147
1148 m_start_connection_on_drag = false;
1149 const bool toggleSelection = e->modifiers().testFlag(flag: Qt::ControlModifier);
1150 switch (cstate) {
1151 case Connecting:
1152 if (button == Qt::RightButton)
1153 abortConnection();
1154 break;
1155 case Dragging:
1156 break;
1157 case Editing:
1158 if (!m_end_point_under_mouse.isNull()) {
1159 if (!toggleSelection)
1160 startDrag(end_point: m_end_point_under_mouse, pos: e->pos());
1161 } else if (con_under_mouse != nullptr) {
1162 if (toggleSelection) {
1163 setSelected(con: con_under_mouse, sel: !selected(con: con_under_mouse));
1164 } else {
1165 selectNone();
1166 setSelected(con: con_under_mouse, sel: true);
1167 }
1168 } else {
1169 if (!toggleSelection) {
1170 selectNone();
1171 if (!m_widget_under_mouse.isNull())
1172 m_start_connection_on_drag = true;
1173 }
1174 }
1175 break;
1176 }
1177}
1178
1179void ConnectionEdit::mouseDoubleClickEvent(QMouseEvent *e)
1180{
1181 if (e->button() != Qt::LeftButton) {
1182 QWidget::mouseDoubleClickEvent(event: e);
1183 return;
1184 }
1185
1186 e->accept();
1187 switch (state()) {
1188 case Connecting:
1189 abortConnection();
1190 break;
1191 case Dragging:
1192 break;
1193 case Editing:
1194 if (!m_widget_under_mouse.isNull())
1195 emit widgetActivated(wgt: m_widget_under_mouse);
1196 else if (m_sel_con_set.size() == 1)
1197 modifyConnection(con: m_sel_con_set.constBegin().key());
1198 break;
1199 }
1200
1201}
1202
1203void ConnectionEdit::mouseReleaseEvent(QMouseEvent *e)
1204{
1205 if (e->button() != Qt::LeftButton) {
1206 QWidget::mouseReleaseEvent(event: e);
1207 return;
1208 }
1209 e->accept();
1210
1211 switch (state()) {
1212 case Connecting:
1213 if (m_widget_under_mouse.isNull())
1214 abortConnection();
1215 else
1216 endConnection(target: m_widget_under_mouse, pos: e->pos());
1217#if QT_CONFIG(cursor)
1218 setCursor(QCursor());
1219#endif
1220 break;
1221 case Editing:
1222 break;
1223 case Dragging:
1224 endDrag(pos: e->pos());
1225 break;
1226 }
1227}
1228
1229
1230void ConnectionEdit::findObjectsUnderMouse(const QPoint &pos)
1231{
1232 Connection *con_under_mouse = connectionAt(pos);
1233
1234 QWidget *w = widgetAt(pos);
1235 // Prefer a non-background widget over the connection,
1236 // otherwise, widgets covered by the connection labels cannot be accessed
1237 if (w == m_bg_widget && con_under_mouse)
1238 w = nullptr;
1239 else
1240 con_under_mouse = nullptr;
1241
1242 if (w != m_widget_under_mouse) {
1243 if (!m_widget_under_mouse.isNull())
1244 update(widgetRect(w: m_widget_under_mouse));
1245 m_widget_under_mouse = w;
1246 if (!m_widget_under_mouse.isNull())
1247 update(widgetRect(w: m_widget_under_mouse));
1248 }
1249
1250 const EndPoint hs = endPointAt(pos);
1251 if (hs != m_end_point_under_mouse) {
1252#if QT_CONFIG(cursor)
1253 if (m_end_point_under_mouse.isNull())
1254 setCursor(Qt::PointingHandCursor);
1255 else
1256 setCursor(QCursor());
1257#endif
1258 m_end_point_under_mouse = hs;
1259 }
1260}
1261
1262void ConnectionEdit::mouseMoveEvent(QMouseEvent *e)
1263{
1264 findObjectsUnderMouse(pos: e->pos());
1265 switch (state()) {
1266 case Connecting:
1267 continueConnection(target: m_widget_under_mouse, pos: e->pos());
1268 break;
1269 case Editing:
1270 if ((e->buttons() & Qt::LeftButton)
1271 && m_start_connection_on_drag
1272 && !m_widget_under_mouse.isNull()) {
1273 m_start_connection_on_drag = false;
1274 startConnection(source: m_widget_under_mouse, pos: e->pos());
1275#if QT_CONFIG(cursor)
1276 setCursor(Qt::CrossCursor);
1277#endif
1278 }
1279 break;
1280 case Dragging:
1281 continueDrag(pos: e->pos());
1282 break;
1283 }
1284
1285 e->accept();
1286}
1287
1288void ConnectionEdit::keyPressEvent(QKeyEvent *e)
1289{
1290 switch (e->key()) {
1291 case Qt::Key_Delete:
1292 if (state() == Editing)
1293 deleteSelected();
1294 break;
1295 case Qt::Key_Escape:
1296 if (state() == Connecting)
1297 abortConnection();
1298 break;
1299 }
1300
1301 e->accept();
1302}
1303
1304void ConnectionEdit::startConnection(QWidget *source, const QPoint &pos)
1305{
1306 Q_ASSERT(m_tmp_con == nullptr);
1307
1308 m_tmp_con = new Connection(this);
1309 m_tmp_con->setEndPoint(type: EndPoint::Source, w: source, pos);
1310}
1311
1312void ConnectionEdit::endConnection(QWidget *target, const QPoint &pos)
1313{
1314 Q_ASSERT(m_tmp_con != nullptr);
1315
1316 m_tmp_con->setEndPoint(type: EndPoint::Target, w: target, pos);
1317
1318 QWidget *source = m_tmp_con->widget(type: EndPoint::Source);
1319 Q_ASSERT(source != nullptr);
1320 Q_ASSERT(target != nullptr);
1321 setEnabled(false);
1322 Connection *new_con = createConnection(source, target);
1323 setEnabled(true);
1324 if (new_con != nullptr) {
1325 new_con->setEndPoint(type: EndPoint::Source, w: source, pos: m_tmp_con->endPointPos(type: EndPoint::Source));
1326 new_con->setEndPoint(type: EndPoint::Target, w: target, pos: m_tmp_con->endPointPos(type: EndPoint::Target));
1327 m_undo_stack->push(cmd: new AddConnectionCommand(this, new_con));
1328 emit connectionChanged(con: new_con);
1329 }
1330
1331 delete m_tmp_con;
1332 m_tmp_con = nullptr;
1333
1334 findObjectsUnderMouse(pos: mapFromGlobal(QCursor::pos()));
1335}
1336
1337void ConnectionEdit::continueConnection(QWidget *target, const QPoint &pos)
1338{
1339 Q_ASSERT(m_tmp_con != nullptr);
1340
1341 m_tmp_con->setEndPoint(type: EndPoint::Target, w: target, pos);
1342}
1343
1344void ConnectionEdit::modifyConnection(Connection *)
1345{
1346}
1347
1348Connection *ConnectionEdit::createConnection(QWidget *source, QWidget *target)
1349{
1350 Connection *con = new Connection(this, source, target);
1351 return con;
1352}
1353
1354// Find all connections which in which a sequence of objects is involved
1355template <class ObjectIterator>
1356static ConnectionEdit::ConnectionSet findConnectionsOf(const ConnectionEdit::ConnectionList &cl, ObjectIterator oi1, const ObjectIterator &oi2)
1357{
1358 ConnectionEdit::ConnectionSet rc;
1359
1360 const ConnectionEdit::ConnectionList::const_iterator ccend = cl.constEnd();
1361 for ( ; oi1 != oi2; ++oi1) {
1362 for (ConnectionEdit::ConnectionList::const_iterator cit = cl.constBegin(); cit != ccend; ++cit) {
1363 Connection *con = *cit;
1364 if (con->object(type: ConnectionEdit::EndPoint::Source) == *oi1 || con->object(type: ConnectionEdit::EndPoint::Target) == *oi1)
1365 rc.insert(akey: con, avalue: con);
1366 }
1367 }
1368 return rc;
1369}
1370
1371void ConnectionEdit::widgetRemoved(QWidget *widget)
1372{
1373 // Remove all connections of that widget and its children.
1374 if (m_con_list.isEmpty())
1375 return;
1376
1377 QWidgetList child_list = widget->findChildren<QWidget*>();
1378 child_list.prepend(t: widget);
1379
1380 const ConnectionSet remove_set = findConnectionsOf(cl: m_con_list, oi1: child_list.constBegin(), oi2: child_list.constEnd());
1381
1382 if (!remove_set.isEmpty()) {
1383 auto cmd = new DeleteConnectionsCommand(this, ConnectionList(remove_set.cbegin(), remove_set.cend()));
1384 m_undo_stack->push(cmd);
1385 }
1386
1387 updateBackground();
1388}
1389
1390void ConnectionEdit::objectRemoved(QObject *o)
1391{
1392 // Remove all connections of that object and its children (in case of action groups).
1393 if (m_con_list.isEmpty())
1394 return;
1395
1396 QObjectList child_list = o->children();
1397 child_list.prepend(t: o);
1398 const ConnectionSet remove_set = findConnectionsOf(cl: m_con_list, oi1: child_list.constBegin(), oi2: child_list.constEnd());
1399 if (!remove_set.isEmpty()) {
1400 auto cmd = new DeleteConnectionsCommand(this, ConnectionList(remove_set.cbegin(), remove_set.cend()));
1401 m_undo_stack->push(cmd);
1402 }
1403
1404 updateBackground();
1405}
1406
1407void ConnectionEdit::setSelected(Connection *con, bool sel)
1408{
1409 if (!con || sel == m_sel_con_set.contains(akey: con))
1410 return;
1411
1412 if (sel) {
1413 m_sel_con_set.insert(akey: con, avalue: con);
1414 emit connectionSelected(con);
1415 } else {
1416 m_sel_con_set.remove(akey: con);
1417 }
1418
1419 con->update();
1420}
1421
1422bool ConnectionEdit::selected(const Connection *con) const
1423{
1424 return m_sel_con_set.contains(akey: const_cast<Connection*>(con));
1425}
1426
1427void ConnectionEdit::selectNone()
1428{
1429 for (Connection *con : qAsConst(t&: m_sel_con_set))
1430 con->update();
1431
1432 m_sel_con_set.clear();
1433}
1434
1435void ConnectionEdit::selectAll()
1436{
1437 if (m_sel_con_set.size() == m_con_list.size())
1438 return;
1439 for (Connection *con : qAsConst(t&: m_con_list))
1440 setSelected(con, sel: true);
1441}
1442
1443Connection *ConnectionEdit::connectionAt(const QPoint &pos) const
1444{
1445 for (Connection *con : m_con_list) {
1446 if (con->contains(pos))
1447 return con;
1448 }
1449 return nullptr;
1450}
1451
1452CETypes::EndPoint ConnectionEdit::endPointAt(const QPoint &pos) const
1453{
1454 for (Connection *con : m_con_list) {
1455 if (!selected(con))
1456 continue;
1457 const QRect sr = con->endPointRect(type: EndPoint::Source);
1458 const QRect tr = con->endPointRect(type: EndPoint::Target);
1459
1460 if (sr.contains(p: pos))
1461 return EndPoint(con, EndPoint::Source);
1462 if (tr.contains(p: pos))
1463 return EndPoint(con, EndPoint::Target);
1464 }
1465 return EndPoint();
1466}
1467
1468void ConnectionEdit::startDrag(const EndPoint &end_point, const QPoint &pos)
1469{
1470 Q_ASSERT(m_drag_end_point.isNull());
1471 m_drag_end_point = end_point;
1472 m_old_source_pos = m_drag_end_point.con->endPointPos(type: EndPoint::Source);
1473 m_old_target_pos = m_drag_end_point.con->endPointPos(type: EndPoint::Target);
1474 adjustHotSopt(end_point: m_drag_end_point, pos);
1475}
1476
1477void ConnectionEdit::continueDrag(const QPoint &pos)
1478{
1479 Q_ASSERT(!m_drag_end_point.isNull());
1480 adjustHotSopt(end_point: m_drag_end_point, pos);
1481}
1482
1483void ConnectionEdit::endDrag(const QPoint &pos)
1484{
1485 Q_ASSERT(!m_drag_end_point.isNull());
1486 adjustHotSopt(end_point: m_drag_end_point, pos);
1487
1488 Connection *con = m_drag_end_point.con;
1489 const QPoint new_source_pos = con->endPointPos(type: EndPoint::Source);
1490 const QPoint new_target_pos = con->endPointPos(type: EndPoint::Target);
1491 m_undo_stack->push(cmd: new AdjustConnectionCommand(this, con, m_old_source_pos, m_old_target_pos,
1492 new_source_pos, new_target_pos));
1493
1494 m_drag_end_point = EndPoint();
1495}
1496
1497void ConnectionEdit::adjustHotSopt(const EndPoint &end_point, const QPoint &pos)
1498{
1499 QWidget *w = end_point.con->widget(type: end_point.type);
1500 end_point.con->setEndPoint(type: end_point.type, w, pos: pointInsideRect(r: widgetRect(w), p: pos));
1501}
1502
1503void ConnectionEdit::deleteSelected()
1504{
1505 if (m_sel_con_set.isEmpty())
1506 return;
1507 auto cmd = new DeleteConnectionsCommand(this, ConnectionList(m_sel_con_set.cbegin(), m_sel_con_set.cend()));
1508 m_undo_stack->push(cmd);
1509}
1510
1511void ConnectionEdit::addConnection(Connection *con)
1512{
1513 m_con_list.append(t: con);
1514}
1515
1516void ConnectionEdit::updateLines()
1517{
1518 for (Connection *con : qAsConst(t&: m_con_list))
1519 con->checkWidgets();
1520}
1521
1522void ConnectionEdit::resizeEvent(QResizeEvent *e)
1523{
1524 updateBackground();
1525 QWidget::resizeEvent(event: e);
1526}
1527
1528void ConnectionEdit::setSource(Connection *con, const QString &obj_name)
1529{
1530 QObject *object = nullptr;
1531 if (!obj_name.isEmpty()) {
1532 object = m_bg_widget->findChild<QObject*>(aName: obj_name);
1533 if (object == nullptr && m_bg_widget->objectName() == obj_name)
1534 object = m_bg_widget;
1535
1536 if (object == con->object(type: EndPoint::Source))
1537 return;
1538 }
1539 m_undo_stack->push(cmd: new SetEndPointCommand(this, con, EndPoint::Source, object));
1540}
1541
1542void ConnectionEdit::setTarget(Connection *con, const QString &obj_name)
1543{
1544 QObject *object = nullptr;
1545 if (!obj_name.isEmpty()) {
1546 object = m_bg_widget->findChild<QObject*>(aName: obj_name);
1547 if (object == nullptr && m_bg_widget->objectName() == obj_name)
1548 object = m_bg_widget;
1549
1550 if (object == con->object(type: EndPoint::Target))
1551 return;
1552 }
1553 m_undo_stack->push(cmd: new SetEndPointCommand(this, con, EndPoint::Target, object));
1554}
1555
1556Connection *ConnectionEdit::takeConnection(Connection *con)
1557{
1558 if (!m_con_list.contains(t: con))
1559 return nullptr;
1560 m_con_list.removeAll(t: con);
1561 return con;
1562}
1563
1564void ConnectionEdit::clearNewlyAddedConnection()
1565{
1566 delete m_tmp_con;
1567 m_tmp_con = nullptr;
1568}
1569
1570void ConnectionEdit::createContextMenu(QMenu &menu)
1571{
1572 // Select
1573 QAction *selectAllAction = menu.addAction(text: tr(s: "Select All"));
1574 selectAllAction->setEnabled(!connectionList().isEmpty());
1575 connect(sender: selectAllAction, signal: &QAction::triggered, receiver: this, slot: &ConnectionEdit::selectAll);
1576 QAction *deselectAllAction = menu.addAction(text: tr(s: "Deselect All"));
1577 deselectAllAction->setEnabled(!selection().isEmpty());
1578 connect(sender: deselectAllAction, signal: &QAction::triggered, receiver: this, slot: &ConnectionEdit::selectNone);
1579 menu.addSeparator();
1580 // Delete
1581 QAction *deleteAction = menu.addAction(text: tr(s: "Delete"));
1582 deleteAction->setShortcut(QKeySequence::Delete);
1583 deleteAction->setEnabled(!selection().isEmpty());
1584 connect(sender: deleteAction, signal: &QAction::triggered, receiver: this, slot: &ConnectionEdit::deleteSelected);
1585}
1586
1587void ConnectionEdit::contextMenuEvent(QContextMenuEvent * event)
1588{
1589 QMenu menu;
1590 createContextMenu(menu);
1591 menu.exec(pos: event->globalPos());
1592}
1593
1594} // namespace qdesigner_internal
1595
1596QT_END_NAMESPACE
1597

source code of qttools/src/designer/src/lib/shared/connectionedit.cpp