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