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