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 | |
59 | static QRect fixRect(const QRect &r) |
60 | { |
61 | return QRect(r.x(), r.y(), r.width() - 1, r.height() - 1); |
62 | } |
63 | |
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 | |
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 | |
76 | static void paintGround(QPainter *p, QRect r) |
77 | { |
78 | const QPoint mid = r.center(); |
79 | p->drawLine(x1: mid.x(), y1: r.top(), x2: mid.x(), y2: mid.y()); |
80 | p->drawLine(x1: r.left(), y1: mid.y(), x2: r.right(), y2: mid.y()); |
81 | int y = r.top() + 4*r.height()/6; |
82 | int x = GROUND_W/6; |
83 | p->drawLine(x1: r.left() + x, y1: y, x2: r.right() - x, y2: y); |
84 | y = r.top() + 5*r.height()/6; |
85 | x = 2*GROUND_W/6; |
86 | p->drawLine(x1: r.left() + x, y1: y, x2: r.right() - x, y2: y); |
87 | p->drawLine(x1: mid.x(), y1: r.bottom(), x2: mid.x() + 1, y2: r.bottom()); |
88 | } |
89 | |
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), color: p->pen().color()); |
95 | } |
96 | |
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 | |
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 | |
126 | AddConnectionCommand::AddConnectionCommand(ConnectionEdit *edit, Connection *con) |
127 | : CECommand(edit), m_con(con) |
128 | { |
129 | setText(QApplication::translate(context: "Command" , key: "Add connection" )); |
130 | } |
131 | |
132 | void AddConnectionCommand::redo() |
133 | { |
134 | edit()->selectNone(); |
135 | emit edit()->aboutToAddConnection(idx: edit()->m_con_list.size()); |
136 | edit()->m_con_list.append(t: m_con); |
137 | m_con->inserted(); |
138 | emit edit()->connectionAdded(con: m_con); |
139 | edit()->setSelected(con: m_con, sel: true); |
140 | } |
141 | |
142 | void AddConnectionCommand::undo() |
143 | { |
144 | const int idx = edit()->indexOfConnection(con: m_con); |
145 | emit edit()->aboutToRemoveConnection(con: m_con); |
146 | edit()->setSelected(con: m_con, sel: false); |
147 | m_con->update(); |
148 | m_con->removed(); |
149 | edit()->m_con_list.removeAll(t: m_con); |
150 | emit edit()->connectionRemoved(idx); |
151 | } |
152 | |
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 | |
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(context: "Command" , key: "Adjust connection" )); |
184 | } |
185 | |
186 | void AdjustConnectionCommand::undo() |
187 | { |
188 | m_con->setEndPoint(type: EndPoint::Source, w: m_con->widget(type: EndPoint::Source), pos: m_old_source_pos); |
189 | m_con->setEndPoint(type: EndPoint::Target, w: m_con->widget(type: EndPoint::Target), pos: m_old_target_pos); |
190 | } |
191 | |
192 | void AdjustConnectionCommand::redo() |
193 | { |
194 | m_con->setEndPoint(type: EndPoint::Source, w: m_con->widget(type: EndPoint::Source), pos: m_new_source_pos); |
195 | m_con->setEndPoint(type: EndPoint::Target, w: m_con->widget(type: EndPoint::Target), pos: m_new_target_pos); |
196 | } |
197 | |
198 | DeleteConnectionsCommand::DeleteConnectionsCommand(ConnectionEdit *edit, |
199 | const ConnectionList &con_list) |
200 | : CECommand(edit), m_con_list(con_list) |
201 | { |
202 | setText(QApplication::translate(context: "Command" , key: "Delete connections" )); |
203 | } |
204 | |
205 | void DeleteConnectionsCommand::redo() |
206 | { |
207 | for (Connection *con : qAsConst(t&: m_con_list)) { |
208 | const int idx = edit()->indexOfConnection(con); |
209 | emit edit()->aboutToRemoveConnection(con); |
210 | Q_ASSERT(edit()->m_con_list.contains(con)); |
211 | edit()->setSelected(con, sel: false); |
212 | con->update(); |
213 | con->removed(); |
214 | edit()->m_con_list.removeAll(t: con); |
215 | emit edit()->connectionRemoved(idx); |
216 | } |
217 | } |
218 | |
219 | void DeleteConnectionsCommand::undo() |
220 | { |
221 | for (Connection *con : qAsConst(t&: m_con_list)) { |
222 | Q_ASSERT(!edit()->m_con_list.contains(con)); |
223 | emit edit()->aboutToAddConnection(idx: edit()->m_con_list.size()); |
224 | edit()->m_con_list.append(t: con); |
225 | edit()->selectNone(); |
226 | con->update(); |
227 | con->inserted(); |
228 | emit edit()->connectionAdded(con); |
229 | edit()->setSelected(con, sel: true); |
230 | } |
231 | } |
232 | |
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 | |
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*>(o: object)) { |
257 | m_new_pos = edit->widgetRect(w: widget).center(); |
258 | } |
259 | |
260 | if (m_type == EndPoint::Source) |
261 | setText(QApplication::translate(context: "Command" , key: "Change source" )); |
262 | else |
263 | setText(QApplication::translate(context: "Command" , key: "Change target" )); |
264 | } |
265 | |
266 | void SetEndPointCommand::redo() |
267 | { |
268 | m_con->setEndPoint(type: m_type, w: m_new_widget, pos: m_new_pos); |
269 | emit edit()->connectionChanged(con: m_con); |
270 | } |
271 | |
272 | void SetEndPointCommand::undo() |
273 | { |
274 | m_con->setEndPoint(type: m_type, w: m_old_widget, pos: m_old_pos); |
275 | emit edit()->connectionChanged(con: m_con); |
276 | } |
277 | |
278 | /******************************************************************************* |
279 | ** Connection |
280 | */ |
281 | |
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 | |
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 | |
303 | void Connection::setVisible(bool b) |
304 | { |
305 | m_visible = b; |
306 | } |
307 | |
308 | void Connection::updateVisibility() |
309 | { |
310 | QWidget *source = widget(type: EndPoint::Source); |
311 | QWidget *target = widget(type: EndPoint::Target); |
312 | |
313 | if (source == nullptr || target == nullptr) { |
314 | setVisible(false); |
315 | return; |
316 | } |
317 | |
318 | QWidget *w = source; |
319 | while (w && w->parentWidget()) { |
320 | if (!w->isVisibleTo(w->parentWidget())) { |
321 | setVisible(false); |
322 | return; |
323 | } |
324 | w = w->parentWidget(); |
325 | } |
326 | |
327 | w = target; |
328 | while (w && w->parentWidget()) { |
329 | if (!w->isVisibleTo(w->parentWidget())) { |
330 | setVisible(false); |
331 | return; |
332 | } |
333 | w = w->parentWidget(); |
334 | } |
335 | |
336 | setVisible(true); |
337 | } |
338 | |
339 | bool Connection::isVisible() const |
340 | { |
341 | return m_visible; |
342 | } |
343 | |
344 | bool Connection::ground() const |
345 | { |
346 | return m_target != nullptr && m_target == m_edit->m_bg_widget; |
347 | } |
348 | |
349 | QPoint Connection::endPointPos(EndPoint::Type type) const |
350 | { |
351 | return type == EndPoint::Source ? m_source_pos : m_target_pos; |
352 | } |
353 | |
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 | |
376 | static QPolygonF (const QPoint &p1, const QPoint &p2) |
377 | { |
378 | QPolygonF result; |
379 | |
380 | switch (classifyLine(p1, p2)) { |
381 | case CETypes::UpDir: |
382 | result.append(t: p2 + QPoint(0, 1)); |
383 | result.append(t: p2 + QPoint(LINE_PROXIMITY_RADIUS, LINE_PROXIMITY_RADIUS*2 + 1)); |
384 | result.append(t: p2 + QPoint(-LINE_PROXIMITY_RADIUS, LINE_PROXIMITY_RADIUS*2 + 1)); |
385 | break; |
386 | case CETypes::DownDir: |
387 | result.append(t: p2); |
388 | result.append(t: p2 + QPoint(LINE_PROXIMITY_RADIUS, -LINE_PROXIMITY_RADIUS*2)); |
389 | result.append(t: p2 + QPoint(-LINE_PROXIMITY_RADIUS, -LINE_PROXIMITY_RADIUS*2)); |
390 | break; |
391 | case CETypes::LeftDir: |
392 | result.append(t: p2 + QPoint(1, 0)); |
393 | result.append(t: p2 + QPoint(2*LINE_PROXIMITY_RADIUS + 1, -LINE_PROXIMITY_RADIUS)); |
394 | result.append(t: p2 + QPoint(2*LINE_PROXIMITY_RADIUS + 1, LINE_PROXIMITY_RADIUS)); |
395 | break; |
396 | case CETypes::RightDir: |
397 | result.append(t: p2); |
398 | result.append(t: p2 + QPoint(-2*LINE_PROXIMITY_RADIUS, -LINE_PROXIMITY_RADIUS)); |
399 | result.append(t: p2 + QPoint(-2*LINE_PROXIMITY_RADIUS, LINE_PROXIMITY_RADIUS)); |
400 | break; |
401 | } |
402 | |
403 | return result; |
404 | } |
405 | |
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 | |
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 | |
439 | void Connection::updateKneeList() |
440 | { |
441 | const LineDir old_source_label_dir = labelDir(type: EndPoint::Source); |
442 | const LineDir old_target_label_dir = labelDir(type: EndPoint::Target); |
443 | |
444 | QPoint s = endPointPos(type: EndPoint::Source); |
445 | QPoint t = endPointPos(type: EndPoint::Target); |
446 | const QRect sr = m_source_rect; |
447 | const QRect tr = m_target_rect; |
448 | |
449 | m_knee_list.clear(); |
450 | m_arrow_head.clear(); |
451 | |
452 | if (m_source == nullptr || s == QPoint(-1, -1) || t == QPoint(-1, -1)) |
453 | return; |
454 | |
455 | const QRect r = sr | tr; |
456 | |
457 | m_knee_list.append(t: s); |
458 | if (m_target == nullptr) { |
459 | m_knee_list.append(t: QPoint(t.x(), s.y())); |
460 | } else if (m_target == m_edit->m_bg_widget) { |
461 | m_knee_list.append(t: QPoint(s.x(), t.y())); |
462 | } else if (tr.contains(r: sr) || sr.contains(r: tr)) { |
463 | /* |
464 | +------------------+ |
465 | | +----------+ | |
466 | | | | | |
467 | | | o | | |
468 | | +---|------+ | |
469 | | | x | |
470 | +-----|-----|------+ |
471 | +-----+ |
472 | |
473 | We find out which edge of the outer rectangle is closest to the target |
474 | point, and make a loop which exits and re-enters through that edge. |
475 | */ |
476 | const LineDir dir = closestEdge(p: t, r: tr); |
477 | switch (dir) { |
478 | case UpDir: |
479 | m_knee_list.append(t: QPoint(s.x(), r.top() - LOOP_MARGIN)); |
480 | m_knee_list.append(t: QPoint(t.x(), r.top() - LOOP_MARGIN)); |
481 | break; |
482 | case DownDir: |
483 | m_knee_list.append(t: QPoint(s.x(), r.bottom() + LOOP_MARGIN)); |
484 | m_knee_list.append(t: QPoint(t.x(), r.bottom() + LOOP_MARGIN)); |
485 | break; |
486 | case LeftDir: |
487 | m_knee_list.append(t: QPoint(r.left() - LOOP_MARGIN, s.y())); |
488 | m_knee_list.append(t: QPoint(r.left() - LOOP_MARGIN, t.y())); |
489 | break; |
490 | case RightDir: |
491 | m_knee_list.append(t: QPoint(r.right() + LOOP_MARGIN, s.y())); |
492 | m_knee_list.append(t: QPoint(r.right() + LOOP_MARGIN, t.y())); |
493 | break; |
494 | } |
495 | } else { |
496 | if (r.height() < sr.height() + tr.height()) { |
497 | if ((s.y() >= tr.top() && s.y() <= tr.bottom()) || (t.y() >= sr.bottom() || t.y() <= sr.top())) { |
498 | /* |
499 | +--------+ |
500 | | | +--------+ |
501 | | o--+---+--x | |
502 | | o | | | |
503 | +-----|--+ | | |
504 | +------+--x | |
505 | +--------+ |
506 | |
507 | When dragging one end point, move the other end point to the same y position, |
508 | if that does not cause it to exit it's rectangle. |
509 | */ |
510 | if (m_edit->state() == ConnectionEdit::Dragging) { |
511 | if (m_edit->m_drag_end_point.type == EndPoint::Source) { |
512 | const QPoint p(t.x(), s.y()); |
513 | m_knee_list.append(t: p); |
514 | if (tr.contains(p)) |
515 | t = m_target_pos = p; |
516 | } else { |
517 | const QPoint p(s.x(), t.y()); |
518 | m_knee_list.append(t: p); |
519 | if (sr.contains(p)) |
520 | s = m_source_pos = p; |
521 | } |
522 | } else { |
523 | m_knee_list.append(t: QPoint(s.x(), t.y())); |
524 | } |
525 | } else { |
526 | /* |
527 | +--------+ |
528 | | o----+-------+ |
529 | | | +---|----+ |
530 | +--------+ | | | |
531 | | x | |
532 | +--------+ |
533 | */ |
534 | m_knee_list.append(t: QPoint(t.x(), s.y())); |
535 | } |
536 | } else if (r.width() < sr.width() + tr.width()) { |
537 | if ((s.x() >= tr.left() && s.x() <= tr.right()) || t.x() >= sr.right() || t.x() <= sr.left()) { |
538 | /* |
539 | +--------+ |
540 | | | |
541 | | o o+--+ |
542 | +----|---+ | |
543 | +-|------|-+ |
544 | | x x | |
545 | | | |
546 | +----------+ |
547 | |
548 | When dragging one end point, move the other end point to the same x position, |
549 | if that does not cause it to exit it's rectangle. |
550 | */ |
551 | if (m_edit->state() == ConnectionEdit::Dragging) { |
552 | if (m_edit->m_drag_end_point.type == EndPoint::Source) { |
553 | const QPoint p(s.x(), t.y()); |
554 | m_knee_list.append(t: p); |
555 | if (tr.contains(p)) |
556 | t = m_target_pos = p; |
557 | } else { |
558 | const QPoint p(t.x(), s.y()); |
559 | m_knee_list.append(t: p); |
560 | if (sr.contains(p)) |
561 | s = m_source_pos = p; |
562 | } |
563 | } else { |
564 | m_knee_list.append(t: QPoint(t.x(), s.y())); |
565 | } |
566 | } else { |
567 | /* |
568 | +--------+ |
569 | | | |
570 | | o | |
571 | +--|-----+ |
572 | | +--------+ |
573 | +---+-x | |
574 | | | |
575 | +--------+ |
576 | |
577 | */ |
578 | m_knee_list.append(t: QPoint(s.x(), t.y())); |
579 | } |
580 | } else { |
581 | /* |
582 | +--------+ |
583 | | | |
584 | | o o-+--------+ |
585 | +--|-----+ | |
586 | | +-----|--+ |
587 | | | x | |
588 | +--------+-x | |
589 | +--------+ |
590 | |
591 | The line enters the target rectangle through the closest edge. |
592 | */ |
593 | if (sr.topLeft() == r.topLeft()) { |
594 | if (pointAboveLine(l1: tr.topLeft(), l2: tr.bottomRight(), p: t)) |
595 | m_knee_list.append(t: QPoint(t.x(), s.y())); |
596 | else |
597 | m_knee_list.append(t: QPoint(s.x(), t.y())); |
598 | } else if (sr.topRight() == r.topRight()) { |
599 | if (pointAboveLine(l1: tr.bottomLeft(), l2: tr.topRight(), p: t)) |
600 | m_knee_list.append(t: QPoint(t.x(), s.y())); |
601 | else |
602 | m_knee_list.append(t: QPoint(s.x(), t.y())); |
603 | } else if (sr.bottomRight() == r.bottomRight()) { |
604 | if (pointAboveLine(l1: tr.topLeft(), l2: tr.bottomRight(), p: t)) |
605 | m_knee_list.append(t: QPoint(s.x(), t.y())); |
606 | else |
607 | m_knee_list.append(t: QPoint(t.x(), s.y())); |
608 | } else { |
609 | if (pointAboveLine(l1: tr.bottomLeft(), l2: tr.topRight(), p: t)) |
610 | m_knee_list.append(t: QPoint(s.x(), t.y())); |
611 | else |
612 | m_knee_list.append(t: QPoint(t.x(), s.y())); |
613 | } |
614 | } |
615 | } |
616 | m_knee_list.append(t); |
617 | |
618 | if (m_knee_list.size() == 2) |
619 | m_knee_list.clear(); |
620 | |
621 | trimLine(); |
622 | |
623 | const LineDir new_source_label_dir = labelDir(type: EndPoint::Source); |
624 | const LineDir new_target_label_dir = labelDir(type: EndPoint::Target); |
625 | if (new_source_label_dir != old_source_label_dir) |
626 | updatePixmap(type: EndPoint::Source); |
627 | if (new_target_label_dir != old_target_label_dir) |
628 | updatePixmap(type: EndPoint::Target); |
629 | } |
630 | |
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(p: m_knee_list.at(i: 1))) |
643 | m_knee_list.removeFirst(); |
644 | |
645 | cnt = m_knee_list.size(); |
646 | if (cnt < 2) |
647 | return; |
648 | |
649 | if (!tr.contains(r: sr) && tr.contains(p: m_knee_list.at(i: cnt - 2))) |
650 | m_knee_list.removeLast(); |
651 | |
652 | cnt = m_knee_list.size(); |
653 | if (cnt < 2) |
654 | return; |
655 | |
656 | if (sr.contains(p: m_knee_list.at(i: 0)) && !sr.contains(p: m_knee_list.at(i: 1))) |
657 | m_knee_list[0] = lineEntryPos(p1: m_knee_list.at(i: 1), p2: m_knee_list.at(i: 0), rect: sr); |
658 | |
659 | if (tr.contains(p: m_knee_list.at(i: cnt - 1)) && !tr.contains(p: m_knee_list.at(i: cnt - 2))) { |
660 | m_knee_list[cnt - 1] |
661 | = lineEntryPos(p1: m_knee_list.at(i: cnt - 2), p2: m_knee_list.at(i: cnt - 1), rect: tr); |
662 | m_arrow_head = arrowHead(p1: m_knee_list.at(i: cnt - 2), p2: m_knee_list.at(i: cnt - 1)); |
663 | } |
664 | } |
665 | |
666 | void Connection::setSource(QObject *source, const QPoint &pos) |
667 | { |
668 | if (source == m_source && m_source_pos == pos) |
669 | return; |
670 | |
671 | update(update_widgets: false); |
672 | |
673 | m_source = source; |
674 | if (QWidget *widget = qobject_cast<QWidget*>(o: source)) { |
675 | m_source_pos = pos; |
676 | m_source_rect = m_edit->widgetRect(w: widget); |
677 | updateKneeList(); |
678 | } |
679 | |
680 | update(update_widgets: false); |
681 | } |
682 | |
683 | void Connection::setTarget(QObject *target, const QPoint &pos) |
684 | { |
685 | if (target == m_target && m_target_pos == pos) |
686 | return; |
687 | |
688 | update(update_widgets: false); |
689 | |
690 | m_target = target; |
691 | if (QWidget *widget = qobject_cast<QWidget*>(o: target)) { |
692 | m_target_pos = pos; |
693 | m_target_rect = m_edit->widgetRect(w: widget); |
694 | updateKneeList(); |
695 | } |
696 | |
697 | update(update_widgets: false); |
698 | } |
699 | |
700 | static QRect lineRect(const QPoint &a, const QPoint &b) |
701 | { |
702 | const QPoint c(qMin(a: a.x(), b: b.x()), qMin(a: a.y(), b: b.y())); |
703 | const QPoint d(qMax(a: a.x(), b: b.x()), qMax(a: a.y(), b: b.y())); |
704 | |
705 | QRect result(c, d); |
706 | return expand(r: result, i: LINE_PROXIMITY_RADIUS); |
707 | } |
708 | |
709 | 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 | |
720 | 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(r: lineRect(a: m_knee_list.at(i), b: m_knee_list.at(i: i + 1))); |
726 | |
727 | if (!m_arrow_head.isEmpty()) { |
728 | QRect r = m_arrow_head.boundingRect().toRect(); |
729 | r = expand(r, i: 1); |
730 | result = result.united(r); |
731 | } else if (ground()) { |
732 | result = result.united(r: groundRect()); |
733 | } |
734 | |
735 | result = result.united(r: labelRect(type: EndPoint::Source)); |
736 | result = result.united(r: labelRect(type: EndPoint::Target)); |
737 | |
738 | return result; |
739 | } |
740 | |
741 | 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 | |
755 | void Connection::paint(QPainter *p) const |
756 | { |
757 | for (int i = 0; i < m_knee_list.size() - 1; ++i) |
758 | p->drawLine(p1: m_knee_list.at(i), p2: m_knee_list.at(i: i + 1)); |
759 | |
760 | if (!m_arrow_head.isEmpty()) { |
761 | p->save(); |
762 | p->setBrush(p->pen().color()); |
763 | p->drawPolygon(polygon: m_arrow_head); |
764 | p->restore(); |
765 | } else if (ground()) { |
766 | paintGround(p, r: groundRect()); |
767 | } |
768 | } |
769 | |
770 | bool Connection::contains(const QPoint &pos) const |
771 | { |
772 | return region().contains(p: pos); |
773 | } |
774 | |
775 | QRect Connection::endPointRect(EndPoint::Type type) const |
776 | { |
777 | if (type == EndPoint::Source) { |
778 | if (m_source_pos != QPoint(-1, -1)) |
779 | return endPointRectHelper(pos: m_source_pos); |
780 | } else { |
781 | if (m_target_pos != QPoint(-1, -1)) |
782 | return endPointRectHelper(pos: m_target_pos); |
783 | } |
784 | return QRect(); |
785 | } |
786 | |
787 | 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(p1: m_knee_list.at(i: 0), p2: m_knee_list.at(i: 1)); |
796 | else |
797 | dir = classifyLine(p1: m_knee_list.at(i: cnt - 2), p2: m_knee_list.at(i: cnt - 1)); |
798 | |
799 | if (dir == LeftDir) |
800 | dir = RightDir; |
801 | if (dir == UpDir) |
802 | dir = DownDir; |
803 | |
804 | return dir; |
805 | } |
806 | |
807 | 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(i: 0); |
820 | p2 = m_knee_list.at(i: 1); |
821 | } else { |
822 | p1 = m_knee_list.at(i: cnt - 1); |
823 | p2 = m_knee_list.at(i: cnt - 2); |
824 | } |
825 | const LineDir dir = classifyLine(p1, p2); |
826 | |
827 | QRect result; |
828 | switch (dir) { |
829 | case UpDir: |
830 | result = QRect(p1 + QPoint(-size.width()/2, 0), size); |
831 | break; |
832 | case DownDir: |
833 | result = QRect(p1 + QPoint(-size.width()/2, -size.height()), size); |
834 | break; |
835 | case LeftDir: |
836 | result = QRect(p1 + QPoint(0, -size.height()/2), size); |
837 | break; |
838 | case RightDir: |
839 | result = QRect(p1 + QPoint(-size.width(), -size.height()/2), size); |
840 | break; |
841 | } |
842 | |
843 | return result; |
844 | } |
845 | |
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 | |
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(flags: Qt::TextSingleLine, str: text) + QSize(HLABEL_MARGIN*2, VLABEL_MARGIN*2); |
871 | *pm = QPixmap(size); |
872 | QColor color = m_edit->palette().color(cg: QPalette::Normal, cr: QPalette::Base); |
873 | color.setAlpha(190); |
874 | pm->fill(fillColor: color); |
875 | |
876 | QPainter p(pm); |
877 | p.setPen(m_edit->palette().color(cg: QPalette::Normal, cr: QPalette::Text)); |
878 | p.drawText(x: -fm.leftBearing(text.at(i: 0)) + HLABEL_MARGIN, y: fm.ascent() + VLABEL_MARGIN, s: text); |
879 | p.end(); |
880 | |
881 | const LineDir dir = labelDir(type); |
882 | |
883 | if (dir == DownDir) |
884 | *pm = pm->transformed(QTransform(0.0, -1.0, 1.0, 0.0, 0.0, 0.0)); |
885 | } |
886 | |
887 | void Connection::checkWidgets() |
888 | { |
889 | bool changed = false; |
890 | |
891 | if (QWidget *sourceWidget = qobject_cast<QWidget*>(o: m_source)) { |
892 | const QRect r = m_edit->widgetRect(w: sourceWidget); |
893 | if (r != m_source_rect) { |
894 | if (m_source_pos != QPoint(-1, -1) && !r.contains(p: m_source_pos)) { |
895 | QPoint offset = m_source_pos - m_source_rect.topLeft(); |
896 | m_source_pos = pointInsideRect(r, p: r.topLeft() + offset); |
897 | } |
898 | m_edit->update(m_source_rect); |
899 | m_source_rect = r; |
900 | changed = true; |
901 | } |
902 | } |
903 | |
904 | if (QWidget *targetWidget = qobject_cast<QWidget*>(o: m_target)) { |
905 | const QRect r = m_edit->widgetRect(w: targetWidget); |
906 | if (r != m_target_rect) { |
907 | if (m_target_pos != QPoint(-1, -1) && !r.contains(p: m_target_pos)) { |
908 | const QPoint offset = m_target_pos - m_target_rect.topLeft(); |
909 | m_target_pos = pointInsideRect(r, p: r.topLeft() + offset); |
910 | } |
911 | m_edit->update(m_target_rect); |
912 | m_target_rect = r; |
913 | changed = true; |
914 | } |
915 | } |
916 | |
917 | if (changed) { |
918 | update(); |
919 | updateKneeList(); |
920 | update(); |
921 | } |
922 | } |
923 | |
924 | /******************************************************************************* |
925 | ** ConnectionEdit |
926 | */ |
927 | |
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, on: true); |
940 | setFocusPolicy(Qt::ClickFocus); |
941 | |
942 | connect(sender: form, signal: &QDesignerFormWindowInterface::widgetRemoved, receiver: this, slot: &ConnectionEdit::widgetRemoved); |
943 | connect(sender: form, signal: &QDesignerFormWindowInterface::objectRemoved, receiver: this, slot: &ConnectionEdit::objectRemoved); |
944 | } |
945 | |
946 | ConnectionEdit::~ConnectionEdit() |
947 | { |
948 | qDeleteAll(c: m_con_list); |
949 | } |
950 | |
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 | |
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 | |
971 | void ConnectionEdit::enableUpdateBackground(bool enable) |
972 | { |
973 | m_enable_update_background = enable; |
974 | if (enable) |
975 | updateBackground(); |
976 | } |
977 | |
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(t&: m_con_list)) |
988 | c->updateVisibility(); |
989 | |
990 | updateLines(); |
991 | update(); |
992 | } |
993 | |
994 | QWidget *ConnectionEdit::widgetAt(const QPoint &pos) const |
995 | { |
996 | if (m_bg_widget == nullptr) |
997 | return nullptr; |
998 | QWidget *widget = m_bg_widget->childAt(p: pos); |
999 | if (widget == nullptr) |
1000 | widget = m_bg_widget; |
1001 | |
1002 | return widget; |
1003 | } |
1004 | |
1005 | |
1006 | 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(p: pos); |
1014 | return r; |
1015 | } |
1016 | |
1017 | 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 | |
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(p: r.topLeft(), pm: con->labelPixmap(type)); |
1036 | p->drawRect(r: fixRect(r)); |
1037 | } |
1038 | |
1039 | void ConnectionEdit::paintConnection(QPainter *p, Connection *con, |
1040 | WidgetSet *heavy_highlight_set, |
1041 | WidgetSet *light_highlight_set) const |
1042 | { |
1043 | QWidget *source = con->widget(type: EndPoint::Source); |
1044 | QWidget *target = con->widget(type: EndPoint::Target); |
1045 | |
1046 | const bool heavy = selected(con) || con == m_tmp_con; |
1047 | WidgetSet *set = heavy ? heavy_highlight_set : light_highlight_set; |
1048 | p->setPen(heavy ? m_active_color : m_inactive_color); |
1049 | con->paint(p); |
1050 | |
1051 | if (source != nullptr && source != m_bg_widget) |
1052 | set->insert(akey: source, avalue: source); |
1053 | |
1054 | if (target != nullptr && target != m_bg_widget) |
1055 | set->insert(akey: target, avalue: target); |
1056 | } |
1057 | |
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(t&: m_con_list)) { |
1066 | if (!con->isVisible()) |
1067 | continue; |
1068 | |
1069 | paintConnection(p: &p, con, heavy_highlight_set: &heavy_highlight_set, light_highlight_set: &light_highlight_set); |
1070 | } |
1071 | |
1072 | if (m_tmp_con != nullptr) |
1073 | paintConnection(p: &p, con: m_tmp_con, heavy_highlight_set: &heavy_highlight_set, light_highlight_set: &light_highlight_set); |
1074 | |
1075 | if (!m_widget_under_mouse.isNull() && m_widget_under_mouse != m_bg_widget) |
1076 | heavy_highlight_set.insert(akey: m_widget_under_mouse, avalue: m_widget_under_mouse); |
1077 | |
1078 | QColor c = m_active_color; |
1079 | p.setPen(c); |
1080 | c.setAlpha(BG_ALPHA); |
1081 | p.setBrush(c); |
1082 | |
1083 | for (QWidget *w : qAsConst(t&: heavy_highlight_set)) { |
1084 | p.drawRect(r: fixRect(r: widgetRect(w))); |
1085 | light_highlight_set.remove(akey: w); |
1086 | } |
1087 | |
1088 | c = m_inactive_color; |
1089 | p.setPen(c); |
1090 | c.setAlpha(BG_ALPHA); |
1091 | p.setBrush(c); |
1092 | |
1093 | for (QWidget *w : qAsConst(t&: light_highlight_set)) |
1094 | p.drawRect(r: fixRect(r: widgetRect(w))); |
1095 | |
1096 | p.setBrush(palette().color(cr: QPalette::Base)); |
1097 | p.setPen(palette().color(cr: QPalette::Text)); |
1098 | for (Connection *con : qAsConst(t&: m_con_list)) { |
1099 | if (con->isVisible()) { |
1100 | paintLabel(p: &p, type: EndPoint::Source, con); |
1101 | paintLabel(p: &p, type: EndPoint::Target, con); |
1102 | } |
1103 | } |
1104 | |
1105 | p.setPen(m_active_color); |
1106 | p.setBrush(m_active_color); |
1107 | |
1108 | for (Connection *con : qAsConst(t&: m_con_list)) { |
1109 | if (!selected(con) || !con->isVisible()) |
1110 | continue; |
1111 | |
1112 | paintEndPoint(p: &p, pos: con->endPointPos(type: EndPoint::Source)); |
1113 | |
1114 | if (con->widget(type: EndPoint::Target) != nullptr) |
1115 | paintEndPoint(p: &p, pos: con->endPointPos(type: EndPoint::Target)); |
1116 | } |
1117 | } |
1118 | |
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 | |
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(event: e); |
1138 | return; |
1139 | } |
1140 | |
1141 | e->accept(); |
1142 | // Prefer a non-background widget over the connection, |
1143 | // otherwise, widgets covered by the connection labels cannot be accessed |
1144 | Connection *con_under_mouse = nullptr; |
1145 | if (!m_widget_under_mouse || m_widget_under_mouse == m_bg_widget) |
1146 | con_under_mouse = connectionAt(pos: e->pos()); |
1147 | |
1148 | m_start_connection_on_drag = false; |
1149 | const bool toggleSelection = e->modifiers().testFlag(flag: Qt::ControlModifier); |
1150 | switch (cstate) { |
1151 | case Connecting: |
1152 | if (button == Qt::RightButton) |
1153 | abortConnection(); |
1154 | break; |
1155 | case Dragging: |
1156 | break; |
1157 | case Editing: |
1158 | if (!m_end_point_under_mouse.isNull()) { |
1159 | if (!toggleSelection) |
1160 | startDrag(end_point: m_end_point_under_mouse, pos: e->pos()); |
1161 | } else if (con_under_mouse != nullptr) { |
1162 | if (toggleSelection) { |
1163 | setSelected(con: con_under_mouse, sel: !selected(con: con_under_mouse)); |
1164 | } else { |
1165 | selectNone(); |
1166 | setSelected(con: con_under_mouse, sel: true); |
1167 | } |
1168 | } else { |
1169 | if (!toggleSelection) { |
1170 | selectNone(); |
1171 | if (!m_widget_under_mouse.isNull()) |
1172 | m_start_connection_on_drag = true; |
1173 | } |
1174 | } |
1175 | break; |
1176 | } |
1177 | } |
1178 | |
1179 | void ConnectionEdit::mouseDoubleClickEvent(QMouseEvent *e) |
1180 | { |
1181 | if (e->button() != Qt::LeftButton) { |
1182 | QWidget::mouseDoubleClickEvent(event: e); |
1183 | return; |
1184 | } |
1185 | |
1186 | e->accept(); |
1187 | switch (state()) { |
1188 | case Connecting: |
1189 | abortConnection(); |
1190 | break; |
1191 | case Dragging: |
1192 | break; |
1193 | case Editing: |
1194 | if (!m_widget_under_mouse.isNull()) |
1195 | emit widgetActivated(wgt: m_widget_under_mouse); |
1196 | else if (m_sel_con_set.size() == 1) |
1197 | modifyConnection(con: m_sel_con_set.constBegin().key()); |
1198 | break; |
1199 | } |
1200 | |
1201 | } |
1202 | |
1203 | void ConnectionEdit::mouseReleaseEvent(QMouseEvent *e) |
1204 | { |
1205 | if (e->button() != Qt::LeftButton) { |
1206 | QWidget::mouseReleaseEvent(event: e); |
1207 | return; |
1208 | } |
1209 | e->accept(); |
1210 | |
1211 | switch (state()) { |
1212 | case Connecting: |
1213 | if (m_widget_under_mouse.isNull()) |
1214 | abortConnection(); |
1215 | else |
1216 | endConnection(target: m_widget_under_mouse, pos: e->pos()); |
1217 | #if QT_CONFIG(cursor) |
1218 | setCursor(QCursor()); |
1219 | #endif |
1220 | break; |
1221 | case Editing: |
1222 | break; |
1223 | case Dragging: |
1224 | endDrag(pos: e->pos()); |
1225 | break; |
1226 | } |
1227 | } |
1228 | |
1229 | |
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(w: m_widget_under_mouse)); |
1245 | m_widget_under_mouse = w; |
1246 | if (!m_widget_under_mouse.isNull()) |
1247 | update(widgetRect(w: m_widget_under_mouse)); |
1248 | } |
1249 | |
1250 | const EndPoint hs = endPointAt(pos); |
1251 | if (hs != m_end_point_under_mouse) { |
1252 | #if QT_CONFIG(cursor) |
1253 | if (m_end_point_under_mouse.isNull()) |
1254 | setCursor(Qt::PointingHandCursor); |
1255 | else |
1256 | setCursor(QCursor()); |
1257 | #endif |
1258 | m_end_point_under_mouse = hs; |
1259 | } |
1260 | } |
1261 | |
1262 | void ConnectionEdit::mouseMoveEvent(QMouseEvent *e) |
1263 | { |
1264 | findObjectsUnderMouse(pos: e->pos()); |
1265 | switch (state()) { |
1266 | case Connecting: |
1267 | continueConnection(target: m_widget_under_mouse, pos: e->pos()); |
1268 | break; |
1269 | case Editing: |
1270 | if ((e->buttons() & Qt::LeftButton) |
1271 | && m_start_connection_on_drag |
1272 | && !m_widget_under_mouse.isNull()) { |
1273 | m_start_connection_on_drag = false; |
1274 | startConnection(source: m_widget_under_mouse, pos: e->pos()); |
1275 | #if QT_CONFIG(cursor) |
1276 | setCursor(Qt::CrossCursor); |
1277 | #endif |
1278 | } |
1279 | break; |
1280 | case Dragging: |
1281 | continueDrag(pos: e->pos()); |
1282 | break; |
1283 | } |
1284 | |
1285 | e->accept(); |
1286 | } |
1287 | |
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 | |
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(type: EndPoint::Source, w: source, pos); |
1310 | } |
1311 | |
1312 | void ConnectionEdit::endConnection(QWidget *target, const QPoint &pos) |
1313 | { |
1314 | Q_ASSERT(m_tmp_con != nullptr); |
1315 | |
1316 | m_tmp_con->setEndPoint(type: EndPoint::Target, w: target, pos); |
1317 | |
1318 | QWidget *source = m_tmp_con->widget(type: EndPoint::Source); |
1319 | Q_ASSERT(source != nullptr); |
1320 | Q_ASSERT(target != nullptr); |
1321 | setEnabled(false); |
1322 | Connection *new_con = createConnection(source, target); |
1323 | setEnabled(true); |
1324 | if (new_con != nullptr) { |
1325 | new_con->setEndPoint(type: EndPoint::Source, w: source, pos: m_tmp_con->endPointPos(type: EndPoint::Source)); |
1326 | new_con->setEndPoint(type: EndPoint::Target, w: target, pos: m_tmp_con->endPointPos(type: EndPoint::Target)); |
1327 | m_undo_stack->push(cmd: new AddConnectionCommand(this, new_con)); |
1328 | emit connectionChanged(con: new_con); |
1329 | } |
1330 | |
1331 | delete m_tmp_con; |
1332 | m_tmp_con = nullptr; |
1333 | |
1334 | findObjectsUnderMouse(pos: mapFromGlobal(QCursor::pos())); |
1335 | } |
1336 | |
1337 | void ConnectionEdit::continueConnection(QWidget *target, const QPoint &pos) |
1338 | { |
1339 | Q_ASSERT(m_tmp_con != nullptr); |
1340 | |
1341 | m_tmp_con->setEndPoint(type: EndPoint::Target, w: target, pos); |
1342 | } |
1343 | |
1344 | void ConnectionEdit::modifyConnection(Connection *) |
1345 | { |
1346 | } |
1347 | |
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> |
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(type: ConnectionEdit::EndPoint::Source) == *oi1 || con->object(type: ConnectionEdit::EndPoint::Target) == *oi1) |
1365 | rc.insert(akey: con, avalue: con); |
1366 | } |
1367 | } |
1368 | return rc; |
1369 | } |
1370 | |
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(t: widget); |
1379 | |
1380 | const ConnectionSet remove_set = findConnectionsOf(cl: m_con_list, oi1: child_list.constBegin(), oi2: child_list.constEnd()); |
1381 | |
1382 | if (!remove_set.isEmpty()) { |
1383 | auto cmd = new DeleteConnectionsCommand(this, ConnectionList(remove_set.cbegin(), remove_set.cend())); |
1384 | m_undo_stack->push(cmd); |
1385 | } |
1386 | |
1387 | updateBackground(); |
1388 | } |
1389 | |
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(t: o); |
1398 | const ConnectionSet remove_set = findConnectionsOf(cl: m_con_list, oi1: child_list.constBegin(), oi2: child_list.constEnd()); |
1399 | if (!remove_set.isEmpty()) { |
1400 | auto cmd = new DeleteConnectionsCommand(this, ConnectionList(remove_set.cbegin(), remove_set.cend())); |
1401 | m_undo_stack->push(cmd); |
1402 | } |
1403 | |
1404 | updateBackground(); |
1405 | } |
1406 | |
1407 | void ConnectionEdit::setSelected(Connection *con, bool sel) |
1408 | { |
1409 | if (!con || sel == m_sel_con_set.contains(akey: con)) |
1410 | return; |
1411 | |
1412 | if (sel) { |
1413 | m_sel_con_set.insert(akey: con, avalue: con); |
1414 | emit connectionSelected(con); |
1415 | } else { |
1416 | m_sel_con_set.remove(akey: con); |
1417 | } |
1418 | |
1419 | con->update(); |
1420 | } |
1421 | |
1422 | bool ConnectionEdit::selected(const Connection *con) const |
1423 | { |
1424 | return m_sel_con_set.contains(akey: const_cast<Connection*>(con)); |
1425 | } |
1426 | |
1427 | void ConnectionEdit::selectNone() |
1428 | { |
1429 | for (Connection *con : qAsConst(t&: m_sel_con_set)) |
1430 | con->update(); |
1431 | |
1432 | m_sel_con_set.clear(); |
1433 | } |
1434 | |
1435 | void ConnectionEdit::selectAll() |
1436 | { |
1437 | if (m_sel_con_set.size() == m_con_list.size()) |
1438 | return; |
1439 | for (Connection *con : qAsConst(t&: m_con_list)) |
1440 | setSelected(con, sel: true); |
1441 | } |
1442 | |
1443 | 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 | |
1452 | 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(type: EndPoint::Source); |
1458 | const QRect tr = con->endPointRect(type: EndPoint::Target); |
1459 | |
1460 | if (sr.contains(p: pos)) |
1461 | return EndPoint(con, EndPoint::Source); |
1462 | if (tr.contains(p: pos)) |
1463 | return EndPoint(con, EndPoint::Target); |
1464 | } |
1465 | return EndPoint(); |
1466 | } |
1467 | |
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(type: EndPoint::Source); |
1473 | m_old_target_pos = m_drag_end_point.con->endPointPos(type: EndPoint::Target); |
1474 | adjustHotSopt(end_point: m_drag_end_point, pos); |
1475 | } |
1476 | |
1477 | void ConnectionEdit::continueDrag(const QPoint &pos) |
1478 | { |
1479 | Q_ASSERT(!m_drag_end_point.isNull()); |
1480 | adjustHotSopt(end_point: m_drag_end_point, pos); |
1481 | } |
1482 | |
1483 | void ConnectionEdit::endDrag(const QPoint &pos) |
1484 | { |
1485 | Q_ASSERT(!m_drag_end_point.isNull()); |
1486 | adjustHotSopt(end_point: m_drag_end_point, pos); |
1487 | |
1488 | Connection *con = m_drag_end_point.con; |
1489 | const QPoint new_source_pos = con->endPointPos(type: EndPoint::Source); |
1490 | const QPoint new_target_pos = con->endPointPos(type: EndPoint::Target); |
1491 | m_undo_stack->push(cmd: new AdjustConnectionCommand(this, con, m_old_source_pos, m_old_target_pos, |
1492 | new_source_pos, new_target_pos)); |
1493 | |
1494 | m_drag_end_point = EndPoint(); |
1495 | } |
1496 | |
1497 | void ConnectionEdit::adjustHotSopt(const EndPoint &end_point, const QPoint &pos) |
1498 | { |
1499 | QWidget *w = end_point.con->widget(type: end_point.type); |
1500 | end_point.con->setEndPoint(type: end_point.type, w, pos: pointInsideRect(r: widgetRect(w), p: pos)); |
1501 | } |
1502 | |
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 | |
1511 | void ConnectionEdit::addConnection(Connection *con) |
1512 | { |
1513 | m_con_list.append(t: con); |
1514 | } |
1515 | |
1516 | void ConnectionEdit::updateLines() |
1517 | { |
1518 | for (Connection *con : qAsConst(t&: m_con_list)) |
1519 | con->checkWidgets(); |
1520 | } |
1521 | |
1522 | void ConnectionEdit::resizeEvent(QResizeEvent *e) |
1523 | { |
1524 | updateBackground(); |
1525 | QWidget::resizeEvent(event: e); |
1526 | } |
1527 | |
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*>(aName: obj_name); |
1533 | if (object == nullptr && m_bg_widget->objectName() == obj_name) |
1534 | object = m_bg_widget; |
1535 | |
1536 | if (object == con->object(type: EndPoint::Source)) |
1537 | return; |
1538 | } |
1539 | m_undo_stack->push(cmd: new SetEndPointCommand(this, con, EndPoint::Source, object)); |
1540 | } |
1541 | |
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*>(aName: obj_name); |
1547 | if (object == nullptr && m_bg_widget->objectName() == obj_name) |
1548 | object = m_bg_widget; |
1549 | |
1550 | if (object == con->object(type: EndPoint::Target)) |
1551 | return; |
1552 | } |
1553 | m_undo_stack->push(cmd: new SetEndPointCommand(this, con, EndPoint::Target, object)); |
1554 | } |
1555 | |
1556 | Connection *ConnectionEdit::takeConnection(Connection *con) |
1557 | { |
1558 | if (!m_con_list.contains(t: con)) |
1559 | return nullptr; |
1560 | m_con_list.removeAll(t: con); |
1561 | return con; |
1562 | } |
1563 | |
1564 | void ConnectionEdit::clearNewlyAddedConnection() |
1565 | { |
1566 | delete m_tmp_con; |
1567 | m_tmp_con = nullptr; |
1568 | } |
1569 | |
1570 | void ConnectionEdit::(QMenu &) |
1571 | { |
1572 | // Select |
1573 | QAction *selectAllAction = menu.addAction(text: tr(s: "Select All" )); |
1574 | selectAllAction->setEnabled(!connectionList().isEmpty()); |
1575 | connect(sender: selectAllAction, signal: &QAction::triggered, receiver: this, slot: &ConnectionEdit::selectAll); |
1576 | QAction *deselectAllAction = menu.addAction(text: tr(s: "Deselect All" )); |
1577 | deselectAllAction->setEnabled(!selection().isEmpty()); |
1578 | connect(sender: deselectAllAction, signal: &QAction::triggered, receiver: this, slot: &ConnectionEdit::selectNone); |
1579 | menu.addSeparator(); |
1580 | // Delete |
1581 | QAction *deleteAction = menu.addAction(text: tr(s: "Delete" )); |
1582 | deleteAction->setShortcut(QKeySequence::Delete); |
1583 | deleteAction->setEnabled(!selection().isEmpty()); |
1584 | connect(sender: deleteAction, signal: &QAction::triggered, receiver: this, slot: &ConnectionEdit::deleteSelected); |
1585 | } |
1586 | |
1587 | void ConnectionEdit::(QContextMenuEvent * event) |
1588 | { |
1589 | QMenu ; |
1590 | createContextMenu(menu); |
1591 | menu.exec(pos: event->globalPos()); |
1592 | } |
1593 | |
1594 | } // namespace qdesigner_internal |
1595 | |
1596 | QT_END_NAMESPACE |
1597 | |