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
45 QT_BEGIN_NAMESPACE
46
47 static const int BG_ALPHA = 32;
48 static const int LINE_PROXIMITY_RADIUS = 3;
49 static const int LOOP_MARGIN = 20;
50 static const int VLABEL_MARGIN = 1;
51 static const int HLABEL_MARGIN = 3;
52 static const int GROUND_W = 20;
53 static const int GROUND_H = 25;
54
55 /*******************************************************************************
56 ** Tools
57 */
58
fixRect(const QRect & r)59 static QRect fixRect(const QRect &r)
60 {
61 return QRect(r.x(), r.y(), r.width() - 1, r.height() - 1);
62 }
63
expand(const QRect & r,int i)64 static 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
endPointRectHelper(const QPoint & pos)69 static 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
paintGround(QPainter * p,QRect r)76 static void paintGround(QPainter *p, QRect r)
77 {
78 const QPoint mid = r.center();
79 p->drawLine(mid.x(), r.top(), mid.x(), mid.y());
80 p->drawLine(r.left(), mid.y(), r.right(), mid.y());
81 int y = r.top() + 4*r.height()/6;
82 int x = GROUND_W/6;
83 p->drawLine(r.left() + x, y, r.right() - x, y);
84 y = r.top() + 5*r.height()/6;
85 x = 2*GROUND_W/6;
86 p->drawLine(r.left() + x, y, r.right() - x, y);
87 p->drawLine(mid.x(), r.bottom(), mid.x() + 1, r.bottom());
88 }
89
paintEndPoint(QPainter * p,const QPoint & pos)90 static 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), p->pen().color());
95 }
96
classifyLine(const QPoint & p1,const QPoint & p2)97 static 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
pointInsideRect(const QRect & r,QPoint p)105 static 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
120 namespace qdesigner_internal {
121
122 /*******************************************************************************
123 ** Commands
124 */
125
AddConnectionCommand(ConnectionEdit * edit,Connection * con)126 AddConnectionCommand::AddConnectionCommand(ConnectionEdit *edit, Connection *con)
127 : CECommand(edit), m_con(con)
128 {
129 setText(QApplication::translate("Command", "Add connection"));
130 }
131
redo()132 void AddConnectionCommand::redo()
133 {
134 edit()->selectNone();
135 emit edit()->aboutToAddConnection(edit()->m_con_list.size());
136 edit()->m_con_list.append(m_con);
137 m_con->inserted();
138 emit edit()->connectionAdded(m_con);
139 edit()->setSelected(m_con, true);
140 }
141
undo()142 void AddConnectionCommand::undo()
143 {
144 const int idx = edit()->indexOfConnection(m_con);
145 emit edit()->aboutToRemoveConnection(m_con);
146 edit()->setSelected(m_con, false);
147 m_con->update();
148 m_con->removed();
149 edit()->m_con_list.removeAll(m_con);
150 emit edit()->connectionRemoved(idx);
151 }
152
153 class AdjustConnectionCommand : public CECommand
154 {
155 public:
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;
163 private:
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
AdjustConnectionCommand(ConnectionEdit * edit,Connection * con,const QPoint & old_source_pos,const QPoint & old_target_pos,const QPoint & new_source_pos,const QPoint & new_target_pos)171 AdjustConnectionCommand::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("Command", "Adjust connection"));
184 }
185
undo()186 void AdjustConnectionCommand::undo()
187 {
188 m_con->setEndPoint(EndPoint::Source, m_con->widget(EndPoint::Source), m_old_source_pos);
189 m_con->setEndPoint(EndPoint::Target, m_con->widget(EndPoint::Target), m_old_target_pos);
190 }
191
redo()192 void AdjustConnectionCommand::redo()
193 {
194 m_con->setEndPoint(EndPoint::Source, m_con->widget(EndPoint::Source), m_new_source_pos);
195 m_con->setEndPoint(EndPoint::Target, m_con->widget(EndPoint::Target), m_new_target_pos);
196 }
197
DeleteConnectionsCommand(ConnectionEdit * edit,const ConnectionList & con_list)198 DeleteConnectionsCommand::DeleteConnectionsCommand(ConnectionEdit *edit,
199 const ConnectionList &con_list)
200 : CECommand(edit), m_con_list(con_list)
201 {
202 setText(QApplication::translate("Command", "Delete connections"));
203 }
204
redo()205 void DeleteConnectionsCommand::redo()
206 {
207 for (Connection *con : qAsConst(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, false);
212 con->update();
213 con->removed();
214 edit()->m_con_list.removeAll(con);
215 emit edit()->connectionRemoved(idx);
216 }
217 }
218
undo()219 void DeleteConnectionsCommand::undo()
220 {
221 for (Connection *con : qAsConst(m_con_list)) {
222 Q_ASSERT(!edit()->m_con_list.contains(con));
223 emit edit()->aboutToAddConnection(edit()->m_con_list.size());
224 edit()->m_con_list.append(con);
225 edit()->selectNone();
226 con->update();
227 con->inserted();
228 emit edit()->connectionAdded(con);
229 edit()->setSelected(con, true);
230 }
231 }
232
233 class SetEndPointCommand : public CECommand
234 {
235 public:
236 SetEndPointCommand(ConnectionEdit *edit, Connection *con, EndPoint::Type type, QObject *object);
237 void redo() override;
238 void undo() override;
239 private:
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
SetEndPointCommand(ConnectionEdit * edit,Connection * con,EndPoint::Type type,QObject * object)247 SetEndPointCommand::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*>(object)) {
257 m_new_pos = edit->widgetRect(widget).center();
258 }
259
260 if (m_type == EndPoint::Source)
261 setText(QApplication::translate("Command", "Change source"));
262 else
263 setText(QApplication::translate("Command", "Change target"));
264 }
265
redo()266 void SetEndPointCommand::redo()
267 {
268 m_con->setEndPoint(m_type, m_new_widget, m_new_pos);
269 emit edit()->connectionChanged(m_con);
270 }
271
undo()272 void SetEndPointCommand::undo()
273 {
274 m_con->setEndPoint(m_type, m_old_widget, m_old_pos);
275 emit edit()->connectionChanged(m_con);
276 }
277
278 /*******************************************************************************
279 ** Connection
280 */
281
Connection(ConnectionEdit * edit)282 Connection::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
Connection(ConnectionEdit * edit,QObject * source,QObject * target)293 Connection::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
setVisible(bool b)303 void Connection::setVisible(bool b)
304 {
305 m_visible = b;
306 }
307
updateVisibility()308 void Connection::updateVisibility()
309 {
310 QWidget *source = widget(EndPoint::Source);
311 QWidget *target = widget(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
isVisible() const339 bool Connection::isVisible() const
340 {
341 return m_visible;
342 }
343
ground() const344 bool Connection::ground() const
345 {
346 return m_target != nullptr && m_target == m_edit->m_bg_widget;
347 }
348
endPointPos(EndPoint::Type type) const349 QPoint Connection::endPointPos(EndPoint::Type type) const
350 {
351 return type == EndPoint::Source ? m_source_pos : m_target_pos;
352 }
353
lineEntryPos(const QPoint & p1,const QPoint & p2,const QRect & rect)354 static 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
arrowHead(const QPoint & p1,const QPoint & p2)376 static 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(p2 + QPoint(0, 1));
383 result.append(p2 + QPoint(LINE_PROXIMITY_RADIUS, LINE_PROXIMITY_RADIUS*2 + 1));
384 result.append(p2 + QPoint(-LINE_PROXIMITY_RADIUS, LINE_PROXIMITY_RADIUS*2 + 1));
385 break;
386 case CETypes::DownDir:
387 result.append(p2);
388 result.append(p2 + QPoint(LINE_PROXIMITY_RADIUS, -LINE_PROXIMITY_RADIUS*2));
389 result.append(p2 + QPoint(-LINE_PROXIMITY_RADIUS, -LINE_PROXIMITY_RADIUS*2));
390 break;
391 case CETypes::LeftDir:
392 result.append(p2 + QPoint(1, 0));
393 result.append(p2 + QPoint(2*LINE_PROXIMITY_RADIUS + 1, -LINE_PROXIMITY_RADIUS));
394 result.append(p2 + QPoint(2*LINE_PROXIMITY_RADIUS + 1, LINE_PROXIMITY_RADIUS));
395 break;
396 case CETypes::RightDir:
397 result.append(p2);
398 result.append(p2 + QPoint(-2*LINE_PROXIMITY_RADIUS, -LINE_PROXIMITY_RADIUS));
399 result.append(p2 + QPoint(-2*LINE_PROXIMITY_RADIUS, LINE_PROXIMITY_RADIUS));
400 break;
401 }
402
403 return result;
404 }
405
closestEdge(const QPoint & p,const QRect & r)406 static 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
pointAboveLine(const QPoint & l1,const QPoint & l2,const QPoint & p)432 static 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
updateKneeList()439 void Connection::updateKneeList()
440 {
441 const LineDir old_source_label_dir = labelDir(EndPoint::Source);
442 const LineDir old_target_label_dir = labelDir(EndPoint::Target);
443
444 QPoint s = endPointPos(EndPoint::Source);
445 QPoint t = endPointPos(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(s);
458 if (m_target == nullptr) {
459 m_knee_list.append(QPoint(t.x(), s.y()));
460 } else if (m_target == m_edit->m_bg_widget) {
461 m_knee_list.append(QPoint(s.x(), t.y()));
462 } else if (tr.contains(sr) || sr.contains(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(t, tr);
477 switch (dir) {
478 case UpDir:
479 m_knee_list.append(QPoint(s.x(), r.top() - LOOP_MARGIN));
480 m_knee_list.append(QPoint(t.x(), r.top() - LOOP_MARGIN));
481 break;
482 case DownDir:
483 m_knee_list.append(QPoint(s.x(), r.bottom() + LOOP_MARGIN));
484 m_knee_list.append(QPoint(t.x(), r.bottom() + LOOP_MARGIN));
485 break;
486 case LeftDir:
487 m_knee_list.append(QPoint(r.left() - LOOP_MARGIN, s.y()));
488 m_knee_list.append(QPoint(r.left() - LOOP_MARGIN, t.y()));
489 break;
490 case RightDir:
491 m_knee_list.append(QPoint(r.right() + LOOP_MARGIN, s.y()));
492 m_knee_list.append(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(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(p);
519 if (sr.contains(p))
520 s = m_source_pos = p;
521 }
522 } else {
523 m_knee_list.append(QPoint(s.x(), t.y()));
524 }
525 } else {
526 /*
527 +--------+
528 | o----+-------+
529 | | +---|----+
530 +--------+ | | |
531 | x |
532 +--------+
533 */
534 m_knee_list.append(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(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(p);
560 if (sr.contains(p))
561 s = m_source_pos = p;
562 }
563 } else {
564 m_knee_list.append(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(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(tr.topLeft(), tr.bottomRight(), t))
595 m_knee_list.append(QPoint(t.x(), s.y()));
596 else
597 m_knee_list.append(QPoint(s.x(), t.y()));
598 } else if (sr.topRight() == r.topRight()) {
599 if (pointAboveLine(tr.bottomLeft(), tr.topRight(), t))
600 m_knee_list.append(QPoint(t.x(), s.y()));
601 else
602 m_knee_list.append(QPoint(s.x(), t.y()));
603 } else if (sr.bottomRight() == r.bottomRight()) {
604 if (pointAboveLine(tr.topLeft(), tr.bottomRight(), t))
605 m_knee_list.append(QPoint(s.x(), t.y()));
606 else
607 m_knee_list.append(QPoint(t.x(), s.y()));
608 } else {
609 if (pointAboveLine(tr.bottomLeft(), tr.topRight(), t))
610 m_knee_list.append(QPoint(s.x(), t.y()));
611 else
612 m_knee_list.append(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(EndPoint::Source);
624 const LineDir new_target_label_dir = labelDir(EndPoint::Target);
625 if (new_source_label_dir != old_source_label_dir)
626 updatePixmap(EndPoint::Source);
627 if (new_target_label_dir != old_target_label_dir)
628 updatePixmap(EndPoint::Target);
629 }
630
trimLine()631 void 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(m_knee_list.at(1)))
643 m_knee_list.removeFirst();
644
645 cnt = m_knee_list.size();
646 if (cnt < 2)
647 return;
648
649 if (!tr.contains(sr) && tr.contains(m_knee_list.at(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(m_knee_list.at(0)) && !sr.contains(m_knee_list.at(1)))
657 m_knee_list[0] = lineEntryPos(m_knee_list.at(1), m_knee_list.at(0), sr);
658
659 if (tr.contains(m_knee_list.at(cnt - 1)) && !tr.contains(m_knee_list.at(cnt - 2))) {
660 m_knee_list[cnt - 1]
661 = lineEntryPos(m_knee_list.at(cnt - 2), m_knee_list.at(cnt - 1), tr);
662 m_arrow_head = arrowHead(m_knee_list.at(cnt - 2), m_knee_list.at(cnt - 1));
663 }
664 }
665
setSource(QObject * source,const QPoint & pos)666 void Connection::setSource(QObject *source, const QPoint &pos)
667 {
668 if (source == m_source && m_source_pos == pos)
669 return;
670
671 update(false);
672
673 m_source = source;
674 if (QWidget *widget = qobject_cast<QWidget*>(source)) {
675 m_source_pos = pos;
676 m_source_rect = m_edit->widgetRect(widget);
677 updateKneeList();
678 }
679
680 update(false);
681 }
682
setTarget(QObject * target,const QPoint & pos)683 void Connection::setTarget(QObject *target, const QPoint &pos)
684 {
685 if (target == m_target && m_target_pos == pos)
686 return;
687
688 update(false);
689
690 m_target = target;
691 if (QWidget *widget = qobject_cast<QWidget*>(target)) {
692 m_target_pos = pos;
693 m_target_rect = m_edit->widgetRect(widget);
694 updateKneeList();
695 }
696
697 update(false);
698 }
699
lineRect(const QPoint & a,const QPoint & b)700 static QRect lineRect(const QPoint &a, const QPoint &b)
701 {
702 const QPoint c(qMin(a.x(), b.x()), qMin(a.y(), b.y()));
703 const QPoint d(qMax(a.x(), b.x()), qMax(a.y(), b.y()));
704
705 QRect result(c, d);
706 return expand(result, LINE_PROXIMITY_RADIUS);
707 }
708
groundRect() const709 QRect 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
region() const720 QRegion Connection::region() const
721 {
722 QRegion result;
723
724 for (int i = 0; i < m_knee_list.size() - 1; ++i)
725 result = result.united(lineRect(m_knee_list.at(i), m_knee_list.at(i + 1)));
726
727 if (!m_arrow_head.isEmpty()) {
728 QRect r = m_arrow_head.boundingRect().toRect();
729 r = expand(r, 1);
730 result = result.united(r);
731 } else if (ground()) {
732 result = result.united(groundRect());
733 }
734
735 result = result.united(labelRect(EndPoint::Source));
736 result = result.united(labelRect(EndPoint::Target));
737
738 return result;
739 }
740
update(bool update_widgets) const741 void 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
paint(QPainter * p) const755 void Connection::paint(QPainter *p) const
756 {
757 for (int i = 0; i < m_knee_list.size() - 1; ++i)
758 p->drawLine(m_knee_list.at(i), m_knee_list.at(i + 1));
759
760 if (!m_arrow_head.isEmpty()) {
761 p->save();
762 p->setBrush(p->pen().color());
763 p->drawPolygon(m_arrow_head);
764 p->restore();
765 } else if (ground()) {
766 paintGround(p, groundRect());
767 }
768 }
769
contains(const QPoint & pos) const770 bool Connection::contains(const QPoint &pos) const
771 {
772 return region().contains(pos);
773 }
774
endPointRect(EndPoint::Type type) const775 QRect Connection::endPointRect(EndPoint::Type type) const
776 {
777 if (type == EndPoint::Source) {
778 if (m_source_pos != QPoint(-1, -1))
779 return endPointRectHelper(m_source_pos);
780 } else {
781 if (m_target_pos != QPoint(-1, -1))
782 return endPointRectHelper(m_target_pos);
783 }
784 return QRect();
785 }
786
labelDir(EndPoint::Type type) const787 CETypes::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(m_knee_list.at(0), m_knee_list.at(1));
796 else
797 dir = classifyLine(m_knee_list.at(cnt - 2), m_knee_list.at(cnt - 1));
798
799 if (dir == LeftDir)
800 dir = RightDir;
801 if (dir == UpDir)
802 dir = DownDir;
803
804 return dir;
805 }
806
labelRect(EndPoint::Type type) const807 QRect 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(0);
820 p2 = m_knee_list.at(1);
821 } else {
822 p1 = m_knee_list.at(cnt - 1);
823 p2 = m_knee_list.at(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
setLabel(EndPoint::Type type,const QString & text)846 void 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
updatePixmap(EndPoint::Type type)859 void 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(Qt::TextSingleLine, text) + QSize(HLABEL_MARGIN*2, VLABEL_MARGIN*2);
871 *pm = QPixmap(size);
872 QColor color = m_edit->palette().color(QPalette::Normal, QPalette::Base);
873 color.setAlpha(190);
874 pm->fill(color);
875
876 QPainter p(pm);
877 p.setPen(m_edit->palette().color(QPalette::Normal, QPalette::Text));
878 p.drawText(-fm.leftBearing(text.at(0)) + HLABEL_MARGIN, fm.ascent() + VLABEL_MARGIN, 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
checkWidgets()887 void Connection::checkWidgets()
888 {
889 bool changed = false;
890
891 if (QWidget *sourceWidget = qobject_cast<QWidget*>(m_source)) {
892 const QRect r = m_edit->widgetRect(sourceWidget);
893 if (r != m_source_rect) {
894 if (m_source_pos != QPoint(-1, -1) && !r.contains(m_source_pos)) {
895 QPoint offset = m_source_pos - m_source_rect.topLeft();
896 m_source_pos = pointInsideRect(r, 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*>(m_target)) {
905 const QRect r = m_edit->widgetRect(targetWidget);
906 if (r != m_target_rect) {
907 if (m_target_pos != QPoint(-1, -1) && !r.contains(m_target_pos)) {
908 const QPoint offset = m_target_pos - m_target_rect.topLeft();
909 m_target_pos = pointInsideRect(r, 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
ConnectionEdit(QWidget * parent,QDesignerFormWindowInterface * form)928 ConnectionEdit::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, true);
940 setFocusPolicy(Qt::ClickFocus);
941
942 connect(form, &QDesignerFormWindowInterface::widgetRemoved, this, &ConnectionEdit::widgetRemoved);
943 connect(form, &QDesignerFormWindowInterface::objectRemoved, this, &ConnectionEdit::objectRemoved);
944 }
945
~ConnectionEdit()946 ConnectionEdit::~ConnectionEdit()
947 {
948 qDeleteAll(m_con_list);
949 }
950
clear()951 void 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
setBackground(QWidget * background)960 void 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
enableUpdateBackground(bool enable)971 void ConnectionEdit::enableUpdateBackground(bool enable)
972 {
973 m_enable_update_background = enable;
974 if (enable)
975 updateBackground();
976 }
977
updateBackground()978 void 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(m_con_list))
988 c->updateVisibility();
989
990 updateLines();
991 update();
992 }
993
widgetAt(const QPoint & pos) const994 QWidget *ConnectionEdit::widgetAt(const QPoint &pos) const
995 {
996 if (m_bg_widget == nullptr)
997 return nullptr;
998 QWidget *widget = m_bg_widget->childAt(pos);
999 if (widget == nullptr)
1000 widget = m_bg_widget;
1001
1002 return widget;
1003 }
1004
1005
widgetRect(QWidget * w) const1006 QRect 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(pos);
1014 return r;
1015 }
1016
state() const1017 ConnectionEdit::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
paintLabel(QPainter * p,EndPoint::Type type,Connection * con)1026 void 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(r.topLeft(), con->labelPixmap(type));
1036 p->drawRect(fixRect(r));
1037 }
1038
paintConnection(QPainter * p,Connection * con,WidgetSet * heavy_highlight_set,WidgetSet * light_highlight_set) const1039 void ConnectionEdit::paintConnection(QPainter *p, Connection *con,
1040 WidgetSet *heavy_highlight_set,
1041 WidgetSet *light_highlight_set) const
1042 {
1043 QWidget *source = con->widget(EndPoint::Source);
1044 QWidget *target = con->widget(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(source, source);
1053
1054 if (target != nullptr && target != m_bg_widget)
1055 set->insert(target, target);
1056 }
1057
paintEvent(QPaintEvent * e)1058 void 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(m_con_list)) {
1066 if (!con->isVisible())
1067 continue;
1068
1069 paintConnection(&p, con, &heavy_highlight_set, &light_highlight_set);
1070 }
1071
1072 if (m_tmp_con != nullptr)
1073 paintConnection(&p, m_tmp_con, &heavy_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(m_widget_under_mouse, 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(heavy_highlight_set)) {
1084 p.drawRect(fixRect(widgetRect(w)));
1085 light_highlight_set.remove(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(light_highlight_set))
1094 p.drawRect(fixRect(widgetRect(w)));
1095
1096 p.setBrush(palette().color(QPalette::Base));
1097 p.setPen(palette().color(QPalette::Text));
1098 for (Connection *con : qAsConst(m_con_list)) {
1099 if (con->isVisible()) {
1100 paintLabel(&p, EndPoint::Source, con);
1101 paintLabel(&p, EndPoint::Target, con);
1102 }
1103 }
1104
1105 p.setPen(m_active_color);
1106 p.setBrush(m_active_color);
1107
1108 for (Connection *con : qAsConst(m_con_list)) {
1109 if (!selected(con) || !con->isVisible())
1110 continue;
1111
1112 paintEndPoint(&p, con->endPointPos(EndPoint::Source));
1113
1114 if (con->widget(EndPoint::Target) != nullptr)
1115 paintEndPoint(&p, con->endPointPos(EndPoint::Target));
1116 }
1117 }
1118
abortConnection()1119 void 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
mousePressEvent(QMouseEvent * e)1131 void 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(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(e->pos());
1147
1148 m_start_connection_on_drag = false;
1149 const bool toggleSelection = e->modifiers().testFlag(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(m_end_point_under_mouse, e->pos());
1161 } else if (con_under_mouse != nullptr) {
1162 if (toggleSelection) {
1163 setSelected(con_under_mouse, !selected(con_under_mouse));
1164 } else {
1165 selectNone();
1166 setSelected(con_under_mouse, 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
mouseDoubleClickEvent(QMouseEvent * e)1179 void ConnectionEdit::mouseDoubleClickEvent(QMouseEvent *e)
1180 {
1181 if (e->button() != Qt::LeftButton) {
1182 QWidget::mouseDoubleClickEvent(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(m_widget_under_mouse);
1196 else if (m_sel_con_set.size() == 1)
1197 modifyConnection(m_sel_con_set.constBegin().key());
1198 break;
1199 }
1200
1201 }
1202
mouseReleaseEvent(QMouseEvent * e)1203 void ConnectionEdit::mouseReleaseEvent(QMouseEvent *e)
1204 {
1205 if (e->button() != Qt::LeftButton) {
1206 QWidget::mouseReleaseEvent(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(m_widget_under_mouse, e->pos());
1217 #if QT_CONFIG(cursor)
1218 setCursor(QCursor());
1219 #endif
1220 break;
1221 case Editing:
1222 break;
1223 case Dragging:
1224 endDrag(e->pos());
1225 break;
1226 }
1227 }
1228
1229
findObjectsUnderMouse(const QPoint & pos)1230 void 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(m_widget_under_mouse));
1245 m_widget_under_mouse = w;
1246 if (!m_widget_under_mouse.isNull())
1247 update(widgetRect(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
mouseMoveEvent(QMouseEvent * e)1262 void ConnectionEdit::mouseMoveEvent(QMouseEvent *e)
1263 {
1264 findObjectsUnderMouse(e->pos());
1265 switch (state()) {
1266 case Connecting:
1267 continueConnection(m_widget_under_mouse, 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(m_widget_under_mouse, e->pos());
1275 #if QT_CONFIG(cursor)
1276 setCursor(Qt::CrossCursor);
1277 #endif
1278 }
1279 break;
1280 case Dragging:
1281 continueDrag(e->pos());
1282 break;
1283 }
1284
1285 e->accept();
1286 }
1287
keyPressEvent(QKeyEvent * e)1288 void 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
startConnection(QWidget * source,const QPoint & pos)1304 void 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(EndPoint::Source, source, pos);
1310 }
1311
endConnection(QWidget * target,const QPoint & pos)1312 void ConnectionEdit::endConnection(QWidget *target, const QPoint &pos)
1313 {
1314 Q_ASSERT(m_tmp_con != nullptr);
1315
1316 m_tmp_con->setEndPoint(EndPoint::Target, target, pos);
1317
1318 QWidget *source = m_tmp_con->widget(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(EndPoint::Source, source, m_tmp_con->endPointPos(EndPoint::Source));
1326 new_con->setEndPoint(EndPoint::Target, target, m_tmp_con->endPointPos(EndPoint::Target));
1327 m_undo_stack->push(new AddConnectionCommand(this, new_con));
1328 emit connectionChanged(new_con);
1329 }
1330
1331 delete m_tmp_con;
1332 m_tmp_con = nullptr;
1333
1334 findObjectsUnderMouse(mapFromGlobal(QCursor::pos()));
1335 }
1336
continueConnection(QWidget * target,const QPoint & pos)1337 void ConnectionEdit::continueConnection(QWidget *target, const QPoint &pos)
1338 {
1339 Q_ASSERT(m_tmp_con != nullptr);
1340
1341 m_tmp_con->setEndPoint(EndPoint::Target, target, pos);
1342 }
1343
modifyConnection(Connection *)1344 void ConnectionEdit::modifyConnection(Connection *)
1345 {
1346 }
1347
createConnection(QWidget * source,QWidget * target)1348 Connection *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
1355 template <class ObjectIterator>
findConnectionsOf(const ConnectionEdit::ConnectionList & cl,ObjectIterator oi1,const ObjectIterator & oi2)1356 static 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(ConnectionEdit::EndPoint::Source) == *oi1 || con->object(ConnectionEdit::EndPoint::Target) == *oi1)
1365 rc.insert(con, con);
1366 }
1367 }
1368 return rc;
1369 }
1370
widgetRemoved(QWidget * widget)1371 void 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(widget);
1379
1380 const ConnectionSet remove_set = findConnectionsOf(m_con_list, child_list.constBegin(), 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
objectRemoved(QObject * o)1390 void 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(o);
1398 const ConnectionSet remove_set = findConnectionsOf(m_con_list, child_list.constBegin(), 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
setSelected(Connection * con,bool sel)1407 void ConnectionEdit::setSelected(Connection *con, bool sel)
1408 {
1409 if (!con || sel == m_sel_con_set.contains(con))
1410 return;
1411
1412 if (sel) {
1413 m_sel_con_set.insert(con, con);
1414 emit connectionSelected(con);
1415 } else {
1416 m_sel_con_set.remove(con);
1417 }
1418
1419 con->update();
1420 }
1421
selected(const Connection * con) const1422 bool ConnectionEdit::selected(const Connection *con) const
1423 {
1424 return m_sel_con_set.contains(const_cast<Connection*>(con));
1425 }
1426
selectNone()1427 void ConnectionEdit::selectNone()
1428 {
1429 for (Connection *con : qAsConst(m_sel_con_set))
1430 con->update();
1431
1432 m_sel_con_set.clear();
1433 }
1434
selectAll()1435 void ConnectionEdit::selectAll()
1436 {
1437 if (m_sel_con_set.size() == m_con_list.size())
1438 return;
1439 for (Connection *con : qAsConst(m_con_list))
1440 setSelected(con, true);
1441 }
1442
connectionAt(const QPoint & pos) const1443 Connection *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
endPointAt(const QPoint & pos) const1452 CETypes::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(EndPoint::Source);
1458 const QRect tr = con->endPointRect(EndPoint::Target);
1459
1460 if (sr.contains(pos))
1461 return EndPoint(con, EndPoint::Source);
1462 if (tr.contains(pos))
1463 return EndPoint(con, EndPoint::Target);
1464 }
1465 return EndPoint();
1466 }
1467
startDrag(const EndPoint & end_point,const QPoint & pos)1468 void 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(EndPoint::Source);
1473 m_old_target_pos = m_drag_end_point.con->endPointPos(EndPoint::Target);
1474 adjustHotSopt(m_drag_end_point, pos);
1475 }
1476
continueDrag(const QPoint & pos)1477 void ConnectionEdit::continueDrag(const QPoint &pos)
1478 {
1479 Q_ASSERT(!m_drag_end_point.isNull());
1480 adjustHotSopt(m_drag_end_point, pos);
1481 }
1482
endDrag(const QPoint & pos)1483 void ConnectionEdit::endDrag(const QPoint &pos)
1484 {
1485 Q_ASSERT(!m_drag_end_point.isNull());
1486 adjustHotSopt(m_drag_end_point, pos);
1487
1488 Connection *con = m_drag_end_point.con;
1489 const QPoint new_source_pos = con->endPointPos(EndPoint::Source);
1490 const QPoint new_target_pos = con->endPointPos(EndPoint::Target);
1491 m_undo_stack->push(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
adjustHotSopt(const EndPoint & end_point,const QPoint & pos)1497 void ConnectionEdit::adjustHotSopt(const EndPoint &end_point, const QPoint &pos)
1498 {
1499 QWidget *w = end_point.con->widget(end_point.type);
1500 end_point.con->setEndPoint(end_point.type, w, pointInsideRect(widgetRect(w), pos));
1501 }
1502
deleteSelected()1503 void 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
addConnection(Connection * con)1511 void ConnectionEdit::addConnection(Connection *con)
1512 {
1513 m_con_list.append(con);
1514 }
1515
updateLines()1516 void ConnectionEdit::updateLines()
1517 {
1518 for (Connection *con : qAsConst(m_con_list))
1519 con->checkWidgets();
1520 }
1521
resizeEvent(QResizeEvent * e)1522 void ConnectionEdit::resizeEvent(QResizeEvent *e)
1523 {
1524 updateBackground();
1525 QWidget::resizeEvent(e);
1526 }
1527
setSource(Connection * con,const QString & obj_name)1528 void 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*>(obj_name);
1533 if (object == nullptr && m_bg_widget->objectName() == obj_name)
1534 object = m_bg_widget;
1535
1536 if (object == con->object(EndPoint::Source))
1537 return;
1538 }
1539 m_undo_stack->push(new SetEndPointCommand(this, con, EndPoint::Source, object));
1540 }
1541
setTarget(Connection * con,const QString & obj_name)1542 void 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*>(obj_name);
1547 if (object == nullptr && m_bg_widget->objectName() == obj_name)
1548 object = m_bg_widget;
1549
1550 if (object == con->object(EndPoint::Target))
1551 return;
1552 }
1553 m_undo_stack->push(new SetEndPointCommand(this, con, EndPoint::Target, object));
1554 }
1555
takeConnection(Connection * con)1556 Connection *ConnectionEdit::takeConnection(Connection *con)
1557 {
1558 if (!m_con_list.contains(con))
1559 return nullptr;
1560 m_con_list.removeAll(con);
1561 return con;
1562 }
1563
clearNewlyAddedConnection()1564 void ConnectionEdit::clearNewlyAddedConnection()
1565 {
1566 delete m_tmp_con;
1567 m_tmp_con = nullptr;
1568 }
1569
createContextMenu(QMenu & menu)1570 void ConnectionEdit::createContextMenu(QMenu &menu)
1571 {
1572 // Select
1573 QAction *selectAllAction = menu.addAction(tr("Select All"));
1574 selectAllAction->setEnabled(!connectionList().isEmpty());
1575 connect(selectAllAction, &QAction::triggered, this, &ConnectionEdit::selectAll);
1576 QAction *deselectAllAction = menu.addAction(tr("Deselect All"));
1577 deselectAllAction->setEnabled(!selection().isEmpty());
1578 connect(deselectAllAction, &QAction::triggered, this, &ConnectionEdit::selectNone);
1579 menu.addSeparator();
1580 // Delete
1581 QAction *deleteAction = menu.addAction(tr("Delete"));
1582 deleteAction->setShortcut(QKeySequence::Delete);
1583 deleteAction->setEnabled(!selection().isEmpty());
1584 connect(deleteAction, &QAction::triggered, this, &ConnectionEdit::deleteSelected);
1585 }
1586
contextMenuEvent(QContextMenuEvent * event)1587 void ConnectionEdit::contextMenuEvent(QContextMenuEvent * event)
1588 {
1589 QMenu menu;
1590 createContextMenu(menu);
1591 menu.exec(event->globalPos());
1592 }
1593
1594 } // namespace qdesigner_internal
1595
1596 QT_END_NAMESPACE
1597