/**************************************************************************** | |
** | |
** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). | |
** All rights reserved. | |
** Contact: Nokia Corporation (qt-info@nokia.com) | |
** | |
** This file is part of the Qt Designer of the Qt Toolkit. | |
** | |
** $QT_BEGIN_LICENSE:LGPL$ | |
** GNU Lesser General Public License Usage | |
** This file may be used under the terms of the GNU Lesser General Public | |
** License version 2.1 as published by the Free Software Foundation and | |
** appearing in the file LICENSE.LGPL included in the packaging of this | |
** file. Please review the following information to ensure the GNU Lesser | |
** General Public License version 2.1 requirements will be met: | |
** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. | |
** | |
** In addition, as a special exception, Nokia gives you certain additional | |
** rights. These rights are described in the Nokia Qt LGPL Exception | |
** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. | |
** | |
** GNU General Public License Usage | |
** Alternatively, this file may be used under the terms of the GNU General | |
** Public License version 3.0 as published by the Free Software Foundation | |
** and appearing in the file LICENSE.GPL included in the packaging of this | |
** file. Please review the following information to ensure the GNU General | |
** Public License version 3.0 requirements will be met: | |
** http://www.gnu.org/copyleft/gpl.html. | |
** | |
** Other Usage | |
** Alternatively, this file may be used in accordance with the terms and | |
** conditions contained in a signed written agreement between you and Nokia. | |
** | |
** | |
** | |
** | |
** | |
** $QT_END_LICENSE$ | |
** | |
****************************************************************************/ | |
#include "connectionedit_p.h" | |
#include <QtDesigner/abstractformwindow.h> | |
#include <QtGui/QPainter> | |
#include <QtGui/QPaintEvent> | |
#include <QtGui/QFontMetrics> | |
#include <QtGui/QPixmap> | |
#include <QtGui/QMatrix> | |
#include <QtGui/QApplication> | |
#include <QtGui/QContextMenuEvent> | |
#include <QtGui/QMenu> | |
#include <QtGui/QAction> | |
#include <QtCore/QMultiMap> | |
QT_BEGIN_NAMESPACE | |
static const int BG_ALPHA = 32; | |
static const int LINE_PROXIMITY_RADIUS = 3; | |
static const int LOOP_MARGIN = 20; | |
static const int VLABEL_MARGIN = 1; | |
static const int HLABEL_MARGIN = 3; | |
static const int GROUND_W = 20; | |
static const int GROUND_H = 25; | |
/******************************************************************************* | |
** Tools | |
*/ | |
static QRect fixRect(const QRect &r) | |
{ | |
return QRect(r.x(), r.y(), r.width() - 1, r.height() - 1); | |
} | |
static QRect expand(const QRect &r, int i) | |
{ | |
return QRect(r.x() - i, r.y() - i, r.width() + 2*i, r.height() + 2*i); | |
} | |
static QRect endPointRectHelper(const QPoint &pos) | |
{ | |
const QRect r(pos + QPoint(-LINE_PROXIMITY_RADIUS, -LINE_PROXIMITY_RADIUS), | |
QSize(2*LINE_PROXIMITY_RADIUS, 2*LINE_PROXIMITY_RADIUS)); | |
return r; | |
} | |
static void paintGround(QPainter *p, QRect r) | |
{ | |
const QPoint mid = r.center(); | |
p->drawLine(mid.x(), r.top(), mid.x(), mid.y()); | |
p->drawLine(r.left(), mid.y(), r.right(), mid.y()); | |
int y = r.top() + 4*r.height()/6; | |
int x = GROUND_W/6; | |
p->drawLine(r.left() + x, y, r.right() - x, y); | |
y = r.top() + 5*r.height()/6; | |
x = 2*GROUND_W/6; | |
p->drawLine(r.left() + x, y, r.right() - x, y); | |
p->drawLine(mid.x(), r.bottom(), mid.x() + 1, r.bottom()); | |
} | |
static void paintEndPoint(QPainter *p, const QPoint &pos) | |
{ | |
const QRect r(pos + QPoint(-LINE_PROXIMITY_RADIUS, -LINE_PROXIMITY_RADIUS), | |
QSize(2*LINE_PROXIMITY_RADIUS, 2*LINE_PROXIMITY_RADIUS)); | |
p->fillRect(fixRect(r), p->pen().color()); | |
} | |
static qdesigner_internal::CETypes::LineDir classifyLine(const QPoint &p1, const QPoint &p2) | |
{ | |
if (p1.x() == p2.x()) | |
return p1.y() < p2.y() ? qdesigner_internal::CETypes::DownDir : qdesigner_internal::CETypes::UpDir; | |
Q_ASSERT(p1.y() == p2.y()); | |
return p1.x() < p2.x() ? qdesigner_internal::CETypes::RightDir : qdesigner_internal::CETypes::LeftDir; | |
} | |
static QPoint pointInsideRect(const QRect &r, QPoint p) | |
{ | |
if (p.x() < r.left()) | |
p.setX(r.left()); | |
else if (p.x() > r.right()) | |
p.setX(r.right()); | |
if (p.y() < r.top()) | |
p.setY(r.top()); | |
else if (p.y() > r.bottom()) | |
p.setY(r.bottom()); | |
return p; | |
} | |
namespace qdesigner_internal { | |
/******************************************************************************* | |
** Commands | |
*/ | |
AddConnectionCommand::AddConnectionCommand(ConnectionEdit *edit, Connection *con) | |
: CECommand(edit), m_con(con) | |
{ | |
setText(QApplication::translate("Command", "Add connection")); | |
} | |
void AddConnectionCommand::redo() | |
{ | |
edit()->selectNone(); | |
emit edit()->aboutToAddConnection(edit()->m_con_list.size()); | |
edit()->m_con_list.append(m_con); | |
m_con->inserted(); | |
edit()->setSelected(m_con, true); | |
emit edit()->connectionAdded(m_con); | |
} | |
void AddConnectionCommand::undo() | |
{ | |
const int idx = edit()->indexOfConnection(m_con); | |
emit edit()->aboutToRemoveConnection(m_con); | |
edit()->setSelected(m_con, false); | |
m_con->update(); | |
m_con->removed(); | |
edit()->m_con_list.removeAll(m_con); | |
emit edit()->connectionRemoved(idx); | |
} | |
class AdjustConnectionCommand : public CECommand | |
{ | |
public: | |
AdjustConnectionCommand(ConnectionEdit *edit, Connection *con, | |
const QPoint &old_source_pos, | |
const QPoint &old_target_pos, | |
const QPoint &new_source_pos, | |
const QPoint &new_target_pos); | |
virtual void redo(); | |
virtual void undo(); | |
private: | |
Connection *m_con; | |
const QPoint m_old_source_pos; | |
const QPoint m_old_target_pos; | |
const QPoint m_new_source_pos; | |
const QPoint m_new_target_pos; | |
}; | |
AdjustConnectionCommand::AdjustConnectionCommand(ConnectionEdit *edit, Connection *con, | |
const QPoint &old_source_pos, | |
const QPoint &old_target_pos, | |
const QPoint &new_source_pos, | |
const QPoint &new_target_pos) : | |
CECommand(edit), | |
m_con(con), | |
m_old_source_pos(old_source_pos), | |
m_old_target_pos(old_target_pos), | |
m_new_source_pos(new_source_pos), | |
m_new_target_pos(new_target_pos) | |
{ | |
setText(QApplication::translate("Command", "Adjust connection")); | |
} | |
void AdjustConnectionCommand::undo() | |
{ | |
m_con->setEndPoint(EndPoint::Source, m_con->widget(EndPoint::Source), m_old_source_pos); | |
m_con->setEndPoint(EndPoint::Target, m_con->widget(EndPoint::Target), m_old_target_pos); | |
} | |
void AdjustConnectionCommand::redo() | |
{ | |
m_con->setEndPoint(EndPoint::Source, m_con->widget(EndPoint::Source), m_new_source_pos); | |
m_con->setEndPoint(EndPoint::Target, m_con->widget(EndPoint::Target), m_new_target_pos); | |
} | |
DeleteConnectionsCommand::DeleteConnectionsCommand(ConnectionEdit *edit, | |
const ConnectionList &con_list) | |
: CECommand(edit), m_con_list(con_list) | |
{ | |
setText(QApplication::translate("Command", "Delete connections")); | |
} | |
void DeleteConnectionsCommand::redo() | |
{ | |
foreach (Connection *con, m_con_list) { | |
const int idx = edit()->indexOfConnection(con); | |
emit edit()->aboutToRemoveConnection(con); | |
Q_ASSERT(edit()->m_con_list.contains(con)); | |
edit()->setSelected(con, false); | |
con->update(); | |
con->removed(); | |
edit()->m_con_list.removeAll(con); | |
emit edit()->connectionRemoved(idx); | |
} | |
} | |
void DeleteConnectionsCommand::undo() | |
{ | |
foreach (Connection *con, m_con_list) { | |
Q_ASSERT(!edit()->m_con_list.contains(con)); | |
emit edit()->aboutToAddConnection(edit()->m_con_list.size()); | |
edit()->m_con_list.append(con); | |
edit()->setSelected(con, true); | |
con->update(); | |
con->inserted(); | |
emit edit()->connectionAdded(con); | |
} | |
} | |
class SetEndPointCommand : public CECommand | |
{ | |
public: | |
SetEndPointCommand(ConnectionEdit *edit, Connection *con, EndPoint::Type type, QObject *object); | |
virtual void redo(); | |
virtual void undo(); | |
private: | |
Connection *m_con; | |
const EndPoint::Type m_type; | |
QObject *m_old_widget, *m_new_widget; | |
const QPoint m_old_pos; | |
QPoint m_new_pos; | |
}; | |
SetEndPointCommand::SetEndPointCommand(ConnectionEdit *edit, Connection *con, | |
EndPoint::Type type, QObject *object) : | |
CECommand(edit), | |
m_con(con), | |
m_type(type), | |
m_old_widget(con->object(type)), | |
m_new_widget(object), | |
m_old_pos(con->endPointPos(type)) | |
{ | |
if (QWidget *widget = qobject_cast<QWidget*>(object)) { | |
m_new_pos = edit->widgetRect(widget).center(); | |
} | |
if (m_type == EndPoint::Source) | |
setText(QApplication::translate("Command", "Change source")); | |
else | |
setText(QApplication::translate("Command", "Change target")); | |
} | |
void SetEndPointCommand::redo() | |
{ | |
m_con->setEndPoint(m_type, m_new_widget, m_new_pos); | |
emit edit()->connectionChanged(m_con); | |
} | |
void SetEndPointCommand::undo() | |
{ | |
m_con->setEndPoint(m_type, m_old_widget, m_old_pos); | |
emit edit()->connectionChanged(m_con); | |
} | |
/******************************************************************************* | |
** Connection | |
*/ | |
Connection::Connection(ConnectionEdit *edit) : | |
m_source_pos(QPoint(-1, -1)), | |
m_target_pos(QPoint(-1, -1)), | |
m_source(0), | |
m_target(0), | |
m_edit(edit), | |
m_visible(true) | |
{ | |
} | |
Connection::Connection(ConnectionEdit *edit, QObject *source, QObject *target) : | |
m_source_pos(QPoint(-1, -1)), | |
m_target_pos(QPoint(-1, -1)), | |
m_source(source), | |
m_target(target), | |
m_edit(edit), | |
m_visible(true) | |
{ | |
} | |
void Connection::setVisible(bool b) | |
{ | |
m_visible = b; | |
} | |
void Connection::updateVisibility() | |
{ | |
QWidget *source = widget(EndPoint::Source); | |
QWidget *target = widget(EndPoint::Target); | |
if (source == 0 || target == 0) { | |
setVisible(false); | |
return; | |
} | |
QWidget *w = source; | |
while (w && w->parentWidget()) { | |
if (!w->isVisibleTo(w->parentWidget())) { | |
setVisible(false); | |
return; | |
} | |
w = w->parentWidget(); | |
} | |
w = target; | |
while (w && w->parentWidget()) { | |
if (!w->isVisibleTo(w->parentWidget())) { | |
setVisible(false); | |
return; | |
} | |
w = w->parentWidget(); | |
} | |
setVisible(true); | |
} | |
bool Connection::isVisible() const | |
{ | |
return m_visible; | |
} | |
bool Connection::ground() const | |
{ | |
return m_target != 0 && m_target == m_edit->m_bg_widget; | |
} | |
QPoint Connection::endPointPos(EndPoint::Type type) const | |
{ | |
if (type == EndPoint::Source) | |
return m_source_pos; | |
else | |
return m_target_pos; | |
} | |
static QPoint lineEntryPos(const QPoint &p1, const QPoint &p2, const QRect &rect) | |
{ | |
QPoint result; | |
switch (classifyLine(p1, p2)) { | |
case CETypes::UpDir: | |
result = QPoint(p1.x(), rect.bottom()); | |
break; | |
case CETypes::DownDir: | |
result = QPoint(p1.x(), rect.top()); | |
break; | |
case CETypes::LeftDir: | |
result = QPoint(rect.right(), p1.y()); | |
break; | |
case CETypes::RightDir: | |
result = QPoint(rect.left(), p1.y()); | |
break; | |
} | |
return result; | |
} | |
static QPolygonF arrowHead(const QPoint &p1, const QPoint &p2) | |
{ | |
QPolygonF result; | |
switch (classifyLine(p1, p2)) { | |
case CETypes::UpDir: | |
result.append(p2 + QPoint(0, 1)); | |
result.append(p2 + QPoint(LINE_PROXIMITY_RADIUS, LINE_PROXIMITY_RADIUS*2 + 1)); | |
result.append(p2 + QPoint(-LINE_PROXIMITY_RADIUS, LINE_PROXIMITY_RADIUS*2 + 1)); | |
break; | |
case CETypes::DownDir: | |
result.append(p2); | |
result.append(p2 + QPoint(LINE_PROXIMITY_RADIUS, -LINE_PROXIMITY_RADIUS*2)); | |
result.append(p2 + QPoint(-LINE_PROXIMITY_RADIUS, -LINE_PROXIMITY_RADIUS*2)); | |
break; | |
case CETypes::LeftDir: | |
result.append(p2 + QPoint(1, 0)); | |
result.append(p2 + QPoint(2*LINE_PROXIMITY_RADIUS + 1, -LINE_PROXIMITY_RADIUS)); | |
result.append(p2 + QPoint(2*LINE_PROXIMITY_RADIUS + 1, LINE_PROXIMITY_RADIUS)); | |
break; | |
case CETypes::RightDir: | |
result.append(p2); | |
result.append(p2 + QPoint(-2*LINE_PROXIMITY_RADIUS, -LINE_PROXIMITY_RADIUS)); | |
result.append(p2 + QPoint(-2*LINE_PROXIMITY_RADIUS, LINE_PROXIMITY_RADIUS)); | |
break; | |
} | |
return result; | |
} | |
static CETypes::LineDir closestEdge(const QPoint &p, const QRect &r) | |
{ | |
CETypes::LineDir result = CETypes::UpDir; | |
int min = p.y() - r.top(); | |
int d = p.x() - r.left(); | |
if (d < min) { | |
min = d; | |
result = CETypes::LeftDir; | |
} | |
d = r.bottom() - p.y(); | |
if (d < min) { | |
min = d; | |
result = CETypes::DownDir; | |
} | |
d = r.right() - p.x(); | |
if (d < min) { | |
min = d; | |
result = CETypes::RightDir; | |
} | |
return result; | |
} | |
static bool pointAboveLine(const QPoint &l1, const QPoint &l2, const QPoint &p) | |
{ | |
if (l1.x() == l2.x()) | |
return p.x() >= l1.x(); | |
return p.y() <= l1.y() + (p.x() - l1.x())*(l2.y() - l1.y())/(l2.x() - l1.x()); | |
} | |
void Connection::updateKneeList() | |
{ | |
const LineDir old_source_label_dir = labelDir(EndPoint::Source); | |
const LineDir old_target_label_dir = labelDir(EndPoint::Target); | |
QPoint s = endPointPos(EndPoint::Source); | |
QPoint t = endPointPos(EndPoint::Target); | |
const QRect sr = m_source_rect; | |
const QRect tr = m_target_rect; | |
m_knee_list.clear(); | |
m_arrow_head.clear(); | |
if (m_source == 0 || s == QPoint(-1, -1) || t == QPoint(-1, -1)) | |
return; | |
const QRect r = sr | tr; | |
m_knee_list.append(s); | |
if (m_target == 0) { | |
m_knee_list.append(QPoint(t.x(), s.y())); | |
} else if (m_target == m_edit->m_bg_widget) { | |
m_knee_list.append(QPoint(s.x(), t.y())); | |
} else if (tr.contains(sr) || sr.contains(tr)) { | |
/* | |
+------------------+ | |
| +----------+ | | |
| | | | | |
| | o | | | |
| +---|------+ | | |
| | x | | |
+-----|-----|------+ | |
+-----+ | |
We find out which edge of the outer rectangle is closest to the target | |
point, and make a loop which exits and re-enters through that edge. | |
*/ | |
const LineDir dir = closestEdge(t, tr); | |
switch (dir) { | |
case UpDir: | |
m_knee_list.append(QPoint(s.x(), r.top() - LOOP_MARGIN)); | |
m_knee_list.append(QPoint(t.x(), r.top() - LOOP_MARGIN)); | |
break; | |
case DownDir: | |
m_knee_list.append(QPoint(s.x(), r.bottom() + LOOP_MARGIN)); | |
m_knee_list.append(QPoint(t.x(), r.bottom() + LOOP_MARGIN)); | |
break; | |
case LeftDir: | |
m_knee_list.append(QPoint(r.left() - LOOP_MARGIN, s.y())); | |
m_knee_list.append(QPoint(r.left() - LOOP_MARGIN, t.y())); | |
break; | |
case RightDir: | |
m_knee_list.append(QPoint(r.right() + LOOP_MARGIN, s.y())); | |
m_knee_list.append(QPoint(r.right() + LOOP_MARGIN, t.y())); | |
break; | |
} | |
} else { | |
if (r.height() < sr.height() + tr.height()) { | |
if ((s.y() >= tr.top() && s.y() <= tr.bottom()) || (t.y() >= sr.bottom() || t.y() <= sr.top())) { | |
/* | |
+--------+ | |
| | +--------+ | |
| o--+---+--x | | |
| o | | | | |
+-----|--+ | | | |
+------+--x | | |
+--------+ | |
When dragging one end point, move the other end point to the same y position, | |
if that does not cause it to exit it's rectangle. | |
*/ | |
if (m_edit->state() == ConnectionEdit::Dragging) { | |
if (m_edit->m_drag_end_point.type == EndPoint::Source) { | |
const QPoint p(t.x(), s.y()); | |
m_knee_list.append(p); | |
if (tr.contains(p)) | |
t = m_target_pos = p; | |
} else { | |
const QPoint p(s.x(), t.y()); | |
m_knee_list.append(p); | |
if (sr.contains(p)) | |
s = m_source_pos = p; | |
} | |
} else { | |
m_knee_list.append(QPoint(s.x(), t.y())); | |
} | |
} else { | |
/* | |
+--------+ | |
| o----+-------+ | |
| | +---|----+ | |
+--------+ | | | | |
| x | | |
+--------+ | |
*/ | |
m_knee_list.append(QPoint(t.x(), s.y())); | |
} | |
} else if (r.width() < sr.width() + tr.width()) { | |
if ((s.x() >= tr.left() && s.x() <= tr.right()) || t.x() >= sr.right() || t.x() <= sr.left()) { | |
/* | |
+--------+ | |
| | | |
| o o+--+ | |
+----|---+ | | |
+-|------|-+ | |
| x x | | |
| | | |
+----------+ | |
When dragging one end point, move the other end point to the same x position, | |
if that does not cause it to exit it's rectangle. | |
*/ | |
if (m_edit->state() == ConnectionEdit::Dragging) { | |
if (m_edit->m_drag_end_point.type == EndPoint::Source) { | |
const QPoint p(s.x(), t.y()); | |
m_knee_list.append(p); | |
if (tr.contains(p)) | |
t = m_target_pos = p; | |
} else { | |
const QPoint p(t.x(), s.y()); | |
m_knee_list.append(p); | |
if (sr.contains(p)) | |
s = m_source_pos = p; | |
} | |
} else { | |
m_knee_list.append(QPoint(t.x(), s.y())); | |
} | |
} else { | |
/* | |
+--------+ | |
| | | |
| o | | |
+--|-----+ | |
| +--------+ | |
+---+-x | | |
| | | |
+--------+ | |
*/ | |
m_knee_list.append(QPoint(s.x(), t.y())); | |
} | |
} else { | |
/* | |
+--------+ | |
| | | |
| o o-+--------+ | |
+--|-----+ | | |
| +-----|--+ | |
| | x | | |
+--------+-x | | |
+--------+ | |
The line enters the target rectangle through the closest edge. | |
*/ | |
if (sr.topLeft() == r.topLeft()) { | |
if (pointAboveLine(tr.topLeft(), tr.bottomRight(), t)) | |
m_knee_list.append(QPoint(t.x(), s.y())); | |
else | |
m_knee_list.append(QPoint(s.x(), t.y())); | |
} else if (sr.topRight() == r.topRight()) { | |
if (pointAboveLine(tr.bottomLeft(), tr.topRight(), t)) | |
m_knee_list.append(QPoint(t.x(), s.y())); | |
else | |
m_knee_list.append(QPoint(s.x(), t.y())); | |
} else if (sr.bottomRight() == r.bottomRight()) { | |
if (pointAboveLine(tr.topLeft(), tr.bottomRight(), t)) | |
m_knee_list.append(QPoint(s.x(), t.y())); | |
else | |
m_knee_list.append(QPoint(t.x(), s.y())); | |
} else { | |
if (pointAboveLine(tr.bottomLeft(), tr.topRight(), t)) | |
m_knee_list.append(QPoint(s.x(), t.y())); | |
else | |
m_knee_list.append(QPoint(t.x(), s.y())); | |
} | |
} | |
} | |
m_knee_list.append(t); | |
if (m_knee_list.size() == 2) | |
m_knee_list.clear(); | |
trimLine(); | |
const LineDir new_source_label_dir = labelDir(EndPoint::Source); | |
const LineDir new_target_label_dir = labelDir(EndPoint::Target); | |
if (new_source_label_dir != old_source_label_dir) | |
updatePixmap(EndPoint::Source); | |
if (new_target_label_dir != old_target_label_dir) | |
updatePixmap(EndPoint::Target); | |
} | |
void Connection::trimLine() | |
{ | |
if (m_source == 0 || m_source_pos == QPoint(-1, -1) || m_target_pos == QPoint(-1, -1)) | |
return; | |
int cnt = m_knee_list.size(); | |
if (cnt < 2) | |
return; | |
const QRect sr = m_source_rect; | |
const QRect tr = m_target_rect; | |
if (sr.contains(m_knee_list.at(1))) | |
m_knee_list.removeFirst(); | |
cnt = m_knee_list.size(); | |
if (cnt < 2) | |
return; | |
if (!tr.contains(sr) && tr.contains(m_knee_list.at(cnt - 2))) | |
m_knee_list.removeLast(); | |
cnt = m_knee_list.size(); | |
if (cnt < 2) | |
return; | |
if (sr.contains(m_knee_list.at(0)) && !sr.contains(m_knee_list.at(1))) | |
m_knee_list[0] = lineEntryPos(m_knee_list.at(1), m_knee_list.at(0), sr); | |
if (tr.contains(m_knee_list.at(cnt - 1)) && !tr.contains(m_knee_list.at(cnt - 2))) { | |
m_knee_list[cnt - 1] | |
= lineEntryPos(m_knee_list.at(cnt - 2), m_knee_list.at(cnt - 1), tr); | |
m_arrow_head = arrowHead(m_knee_list.at(cnt - 2), m_knee_list.at(cnt - 1)); | |
} | |
} | |
void Connection::setSource(QObject *source, const QPoint &pos) | |
{ | |
if (source == m_source && m_source_pos == pos) | |
return; | |
update(false); | |
m_source = source; | |
if (QWidget *widget = qobject_cast<QWidget*>(source)) { | |
m_source_pos = pos; | |
m_source_rect = m_edit->widgetRect(widget); | |
updateKneeList(); | |
} | |
update(false); | |
} | |
void Connection::setTarget(QObject *target, const QPoint &pos) | |
{ | |
if (target == m_target && m_target_pos == pos) | |
return; | |
update(false); | |
m_target = target; | |
if (QWidget *widget = qobject_cast<QWidget*>(target)) { | |
m_target_pos = pos; | |
m_target_rect = m_edit->widgetRect(widget); | |
updateKneeList(); | |
} | |
update(false); | |
} | |
static QRect lineRect(const QPoint &a, const QPoint &b) | |
{ | |
const QPoint c(qMin(a.x(), b.x()), qMin(a.y(), b.y())); | |
const QPoint d(qMax(a.x(), b.x()), qMax(a.y(), b.y())); | |
QRect result(c, d); | |
return expand(result, LINE_PROXIMITY_RADIUS); | |
} | |
QRect Connection::groundRect() const | |
{ | |
if (!ground()) | |
return QRect(); | |
if (m_knee_list.isEmpty()) | |
return QRect(); | |
const QPoint p = m_knee_list.last(); | |
return QRect(p.x() - GROUND_W/2, p.y(), GROUND_W, GROUND_H); | |
} | |
QRegion Connection::region() const | |
{ | |
QRegion result; | |
for (int i = 0; i < m_knee_list.size() - 1; ++i) | |
result = result.unite(lineRect(m_knee_list.at(i), m_knee_list.at(i + 1))); | |
if (!m_arrow_head.isEmpty()) { | |
QRect r = m_arrow_head.boundingRect().toRect(); | |
r = expand(r, 1); | |
result = result.unite(r); | |
} else if (ground()) { | |
result = result.unite(groundRect()); | |
} | |
result = result.unite(labelRect(EndPoint::Source)); | |
result = result.unite(labelRect(EndPoint::Target)); | |
return result; | |
} | |
void Connection::update(bool update_widgets) const | |
{ | |
m_edit->update(region()); | |
if (update_widgets) { | |
if (m_source != 0) | |
m_edit->update(m_source_rect); | |
if (m_target != 0) | |
m_edit->update(m_target_rect); | |
} | |
m_edit->update(endPointRect(EndPoint::Source)); | |
m_edit->update(endPointRect(EndPoint::Target)); | |
} | |
void Connection::paint(QPainter *p) const | |
{ | |
for (int i = 0; i < m_knee_list.size() - 1; ++i) | |
p->drawLine(m_knee_list.at(i), m_knee_list.at(i + 1)); | |
if (!m_arrow_head.isEmpty()) { | |
p->save(); | |
p->setBrush(p->pen().color()); | |
p->drawPolygon(m_arrow_head); | |
p->restore(); | |
} else if (ground()) { | |
paintGround(p, groundRect()); | |
} | |
} | |
bool Connection::contains(const QPoint &pos) const | |
{ | |
return region().contains(pos); | |
} | |
QRect Connection::endPointRect(EndPoint::Type type) const | |
{ | |
if (type == EndPoint::Source) { | |
if (m_source_pos != QPoint(-1, -1)) | |
return endPointRectHelper(m_source_pos); | |
} else { | |
if (m_target_pos != QPoint(-1, -1)) | |
return endPointRectHelper(m_target_pos); | |
} | |
return QRect(); | |
} | |
CETypes::LineDir Connection::labelDir(EndPoint::Type type) const | |
{ | |
const int cnt = m_knee_list.size(); | |
if (cnt < 2) | |
return RightDir; | |
LineDir dir; | |
if (type == EndPoint::Source) | |
dir = classifyLine(m_knee_list.at(0), m_knee_list.at(1)); | |
else | |
dir = classifyLine(m_knee_list.at(cnt - 2), m_knee_list.at(cnt - 1)); | |
if (dir == LeftDir) | |
dir = RightDir; | |
if (dir == UpDir) | |
dir = DownDir; | |
return dir; | |
} | |
QRect Connection::labelRect(EndPoint::Type type) const | |
{ | |
const int cnt = m_knee_list.size(); | |
if (cnt < 2) | |
return QRect(); | |
const QString text = label(type); | |
if (text.isEmpty()) | |
return QRect(); | |
const QSize size = labelPixmap(type).size(); | |
QPoint p1, p2; | |
if (type == EndPoint::Source) { | |
p1 = m_knee_list.at(0); | |
p2 = m_knee_list.at(1); | |
} else { | |
p1 = m_knee_list.at(cnt - 1); | |
p2 = m_knee_list.at(cnt - 2); | |
} | |
const LineDir dir = classifyLine(p1, p2); | |
QRect result; | |
switch (dir) { | |
case UpDir: | |
result = QRect(p1 + QPoint(-size.width()/2, 0), size); | |
break; | |
case DownDir: | |
result = QRect(p1 + QPoint(-size.width()/2, -size.height()), size); | |
break; | |
case LeftDir: | |
result = QRect(p1 + QPoint(0, -size.height()/2), size); | |
break; | |
case RightDir: | |
result = QRect(p1 + QPoint(-size.width(), -size.height()/2), size); | |
break; | |
} | |
return result; | |
} | |
void Connection::setLabel(EndPoint::Type type, const QString &text) | |
{ | |
if (text == label(type)) | |
return; | |
if (type == EndPoint::Source) | |
m_source_label = text; | |
else | |
m_target_label = text; | |
updatePixmap(type); | |
} | |
void Connection::updatePixmap(EndPoint::Type type) | |
{ | |
QPixmap *pm = type == EndPoint::Source ? &m_source_label_pm : &m_target_label_pm; | |
const QString text = label(type); | |
if (text.isEmpty()) { | |
*pm = QPixmap(); | |
return; | |
} | |
const QFontMetrics fm = m_edit->fontMetrics(); | |
const QSize size = fm.size(Qt::TextSingleLine, text) + QSize(HLABEL_MARGIN*2, VLABEL_MARGIN*2); | |
*pm = QPixmap(size); | |
QColor color = m_edit->palette().color(QPalette::Normal, QPalette::Base); | |
color.setAlpha(190); | |
pm->fill(color); | |
QPainter p(pm); | |
p.setPen(m_edit->palette().color(QPalette::Normal, QPalette::Text)); | |
p.drawText(-fm.leftBearing(text.at(0)) + HLABEL_MARGIN, fm.ascent() + VLABEL_MARGIN, text); | |
p.end(); | |
const LineDir dir = labelDir(type); | |
if (dir == DownDir) | |
*pm = pm->transformed(QMatrix(0.0, -1.0, 1.0, 0.0, 0.0, 0.0)); | |
} | |
void Connection::checkWidgets() | |
{ | |
bool changed = false; | |
if (QWidget *sourceWidget = qobject_cast<QWidget*>(m_source)) { | |
const QRect r = m_edit->widgetRect(sourceWidget); | |
if (r != m_source_rect) { | |
if (m_source_pos != QPoint(-1, -1) && !r.contains(m_source_pos)) { | |
QPoint offset = m_source_pos - m_source_rect.topLeft(); | |
QPoint old_pos = m_source_pos; | |
m_source_pos = pointInsideRect(r, r.topLeft() + offset); | |
} | |
m_edit->update(m_source_rect); | |
m_source_rect = r; | |
changed = true; | |
} | |
} | |
if (QWidget *targetWidget = qobject_cast<QWidget*>(m_target)) { | |
const QRect r = m_edit->widgetRect(targetWidget); | |
if (r != m_target_rect) { | |
if (m_target_pos != QPoint(-1, -1) && !r.contains(m_target_pos)) { | |
const QPoint offset = m_target_pos - m_target_rect.topLeft(); | |
const QPoint old_pos = m_target_pos; | |
m_target_pos = pointInsideRect(r, r.topLeft() + offset); | |
} | |
m_edit->update(m_target_rect); | |
m_target_rect = r; | |
changed = true; | |
} | |
} | |
if (changed) { | |
update(); | |
updateKneeList(); | |
update(); | |
} | |
} | |
/******************************************************************************* | |
** ConnectionEdit | |
*/ | |
ConnectionEdit::ConnectionEdit(QWidget *parent, QDesignerFormWindowInterface *form) : | |
QWidget(parent), | |
m_bg_widget(0), | |
m_undo_stack(form->commandHistory()), | |
m_enable_update_background(false), | |
m_tmp_con(0), | |
m_start_connection_on_drag(true), | |
m_widget_under_mouse(0), | |
m_inactive_color(Qt::blue), | |
m_active_color(Qt::red) | |
{ | |
setAttribute(Qt::WA_MouseTracking, true); | |
setFocusPolicy(Qt::ClickFocus); | |
connect(form, SIGNAL(widgetRemoved(QWidget*)), this, SLOT(widgetRemoved(QWidget*))); | |
connect(form, SIGNAL(objectRemoved(QObject*)), this, SLOT(objectRemoved(QObject*))); | |
} | |
ConnectionEdit::~ConnectionEdit() | |
{ | |
qDeleteAll(m_con_list); | |
} | |
void ConnectionEdit::clear() | |
{ | |
m_con_list.clear(); | |
m_sel_con_set.clear(); | |
m_bg_widget = 0; | |
m_widget_under_mouse = 0; | |
m_tmp_con = 0; | |
} | |
void ConnectionEdit::setBackground(QWidget *background) | |
{ | |
if (background == m_bg_widget) { | |
// nothing to do | |
return; | |
} | |
m_bg_widget = background; | |
updateBackground(); | |
} | |
void ConnectionEdit::enableUpdateBackground(bool enable) | |
{ | |
m_enable_update_background = enable; | |
if (enable) | |
updateBackground(); | |
} | |
void ConnectionEdit::updateBackground() | |
{ | |
// Might happen while reloading a form. | |
if (m_bg_widget == 0) | |
return; | |
if (!m_enable_update_background) | |
return; | |
foreach(Connection *c, m_con_list) | |
c->updateVisibility(); | |
updateLines(); | |
update(); | |
} | |
QWidget *ConnectionEdit::widgetAt(const QPoint &pos) const | |
{ | |
if (m_bg_widget == 0) | |
return 0; | |
QWidget *widget = m_bg_widget->childAt(pos); | |
if (widget == 0) | |
widget = m_bg_widget; | |
return widget; | |
} | |
QRect ConnectionEdit::widgetRect(QWidget *w) const | |
{ | |
if (w == 0) | |
return QRect(); | |
QRect r = w->geometry(); | |
QPoint pos = w->mapToGlobal(QPoint(0, 0)); | |
pos = mapFromGlobal(pos); | |
r.moveTopLeft(pos); | |
return r; | |
} | |
ConnectionEdit::State ConnectionEdit::state() const | |
{ | |
if (m_tmp_con != 0) | |
return Connecting; | |
if (!m_drag_end_point.isNull()) | |
return Dragging; | |
return Editing; | |
} | |
void ConnectionEdit::paintLabel(QPainter *p, EndPoint::Type type, Connection *con) | |
{ | |
if (con->label(type).isEmpty()) | |
return; | |
const bool heavy = selected(con) || con == m_tmp_con; | |
p->setPen(heavy ? m_active_color : m_inactive_color); | |
p->setBrush(Qt::NoBrush); | |
const QRect r = con->labelRect(type); | |
p->drawPixmap(r.topLeft(), con->labelPixmap(type)); | |
p->drawRect(fixRect(r)); | |
} | |
void ConnectionEdit::paintConnection(QPainter *p, Connection *con, | |
WidgetSet *heavy_highlight_set, | |
WidgetSet *light_highlight_set) const | |
{ | |
QWidget *source = con->widget(EndPoint::Source); | |
QWidget *target = con->widget(EndPoint::Target); | |
const bool heavy = selected(con) || con == m_tmp_con; | |
WidgetSet *set = heavy ? heavy_highlight_set : light_highlight_set; | |
p->setPen(heavy ? m_active_color : m_inactive_color); | |
con->paint(p); | |
if (source != 0 && source != m_bg_widget) | |
set->insert(source, source); | |
if (target != 0 && target != m_bg_widget) | |
set->insert(target, target); | |
} | |
void ConnectionEdit::paintEvent(QPaintEvent *e) | |
{ | |
QPainter p(this); | |
p.setClipRegion(e->region()); | |
WidgetSet heavy_highlight_set, light_highlight_set; | |
foreach (Connection *con, m_con_list) { | |
if (!con->isVisible()) | |
continue; | |
paintConnection(&p, con, &heavy_highlight_set, &light_highlight_set); | |
} | |
if (m_tmp_con != 0) | |
paintConnection(&p, m_tmp_con, &heavy_highlight_set, &light_highlight_set); | |
if (!m_widget_under_mouse.isNull() && m_widget_under_mouse != m_bg_widget) | |
heavy_highlight_set.insert(m_widget_under_mouse, m_widget_under_mouse); | |
QColor c = m_active_color; | |
p.setPen(c); | |
c.setAlpha(BG_ALPHA); | |
p.setBrush(c); | |
foreach (QWidget *w, heavy_highlight_set) { | |
p.drawRect(fixRect(widgetRect(w))); | |
light_highlight_set.remove(w); | |
} | |
c = m_inactive_color; | |
p.setPen(c); | |
c.setAlpha(BG_ALPHA); | |
p.setBrush(c); | |
foreach (QWidget *w, light_highlight_set) | |
p.drawRect(fixRect(widgetRect(w))); | |
p.setBrush(palette().color(QPalette::Base)); | |
p.setPen(palette().color(QPalette::Text)); | |
foreach (Connection *con, m_con_list) { | |
if (!con->isVisible()) | |
continue; | |
paintLabel(&p, EndPoint::Source, con); | |
paintLabel(&p, EndPoint::Target, con); | |
} | |
p.setPen(m_active_color); | |
p.setBrush(m_active_color); | |
foreach (Connection *con, m_con_list) { | |
if (!selected(con) || !con->isVisible()) | |
continue; | |
paintEndPoint(&p, con->endPointPos(EndPoint::Source)); | |
if (con->widget(EndPoint::Target) != 0) | |
paintEndPoint(&p, con->endPointPos(EndPoint::Target)); | |
} | |
} | |
void ConnectionEdit::abortConnection() | |
{ | |
m_tmp_con->update(); | |
delete m_tmp_con; | |
m_tmp_con = 0; | |
#ifndef QT_NO_CURSOR | |
setCursor(QCursor()); | |
#endif | |
if (m_widget_under_mouse == m_bg_widget) | |
m_widget_under_mouse = 0; | |
} | |
void ConnectionEdit::mousePressEvent(QMouseEvent *e) | |
{ | |
// Right click only to cancel | |
const Qt::MouseButton button = e->button(); | |
const State cstate = state(); | |
if (button != Qt::LeftButton && !(button == Qt::RightButton && cstate == Connecting)) { | |
QWidget::mousePressEvent(e); | |
return; | |
} | |
e->accept(); | |
// Prefer a non-background widget over the connection, | |
// otherwise, widgets covered by the connection labels cannot be accessed | |
Connection *con_under_mouse = 0; | |
if (!m_widget_under_mouse || m_widget_under_mouse == m_bg_widget) | |
con_under_mouse = connectionAt(e->pos()); | |
m_start_connection_on_drag = false; | |
switch (cstate) { | |
case Connecting: | |
if (button == Qt::RightButton) | |
abortConnection(); | |
break; | |
case Dragging: | |
break; | |
case Editing: | |
if (!m_end_point_under_mouse.isNull()) { | |
if (!(e->modifiers() & Qt::ShiftModifier)) { | |
startDrag(m_end_point_under_mouse, e->pos()); | |
} | |
} else if (con_under_mouse != 0) { | |
if (!(e->modifiers() & Qt::ShiftModifier)) { | |
selectNone(); | |
setSelected(con_under_mouse, true); | |
} else { | |
setSelected(con_under_mouse, !selected(con_under_mouse)); | |
} | |
} else { | |
if (!(e->modifiers() & Qt::ShiftModifier)) { | |
selectNone(); | |
if (!m_widget_under_mouse.isNull()) | |
m_start_connection_on_drag = true; | |
} | |
} | |
break; | |
} | |
} | |
void ConnectionEdit::mouseDoubleClickEvent(QMouseEvent *e) | |
{ | |
if (e->button() != Qt::LeftButton) { | |
QWidget::mouseDoubleClickEvent(e); | |
return; | |
} | |
e->accept(); | |
switch (state()) { | |
case Connecting: | |
abortConnection(); | |
break; | |
case Dragging: | |
break; | |
case Editing: | |
if (!m_widget_under_mouse.isNull()) { | |
emit widgetActivated(m_widget_under_mouse); | |
} else if (m_sel_con_set.size() == 1) { | |
Connection *con = m_sel_con_set.keys().first(); | |
modifyConnection(con); | |
} | |
break; | |
} | |
} | |
void ConnectionEdit::mouseReleaseEvent(QMouseEvent *e) | |
{ | |
if (e->button() != Qt::LeftButton) { | |
QWidget::mouseReleaseEvent(e); | |
return; | |
} | |
e->accept(); | |
switch (state()) { | |
case Connecting: | |
if (m_widget_under_mouse.isNull()) | |
abortConnection(); | |
else | |
endConnection(m_widget_under_mouse, e->pos()); | |
#ifndef QT_NO_CURSOR | |
setCursor(QCursor()); | |
#endif | |
break; | |
case Editing: | |
break; | |
case Dragging: | |
endDrag(e->pos()); | |
break; | |
} | |
} | |
void ConnectionEdit::findObjectsUnderMouse(const QPoint &pos) | |
{ | |
Connection *con_under_mouse = connectionAt(pos); | |
QWidget *w = widgetAt(pos); | |
// Prefer a non-background widget over the connection, | |
// otherwise, widgets covered by the connection labels cannot be accessed | |
if (w == m_bg_widget && con_under_mouse) | |
w = 0; | |
else | |
con_under_mouse = 0; | |
if (w != m_widget_under_mouse) { | |
if (!m_widget_under_mouse.isNull()) | |
update(widgetRect(m_widget_under_mouse)); | |
m_widget_under_mouse = w; | |
if (!m_widget_under_mouse.isNull()) | |
update(widgetRect(m_widget_under_mouse)); | |
} | |
const EndPoint hs = endPointAt(pos); | |
if (hs != m_end_point_under_mouse) { | |
#ifndef QT_NO_CURSOR | |
if (m_end_point_under_mouse.isNull()) | |
setCursor(Qt::PointingHandCursor); | |
else | |
setCursor(QCursor()); | |
#endif | |
m_end_point_under_mouse = hs; | |
} | |
} | |
void ConnectionEdit::mouseMoveEvent(QMouseEvent *e) | |
{ | |
findObjectsUnderMouse(e->pos()); | |
switch (state()) { | |
case Connecting: | |
continueConnection(m_widget_under_mouse, e->pos()); | |
break; | |
case Editing: | |
if ((e->buttons() & Qt::LeftButton) | |
&& m_start_connection_on_drag | |
&& !m_widget_under_mouse.isNull()) { | |
m_start_connection_on_drag = false; | |
startConnection(m_widget_under_mouse, e->pos()); | |
#ifndef QT_NO_CURSOR | |
setCursor(Qt::CrossCursor); | |
#endif | |
} | |
break; | |
case Dragging: | |
continueDrag(e->pos()); | |
break; | |
} | |
e->accept(); | |
} | |
void ConnectionEdit::keyPressEvent(QKeyEvent *e) | |
{ | |
switch (e->key()) { | |
case Qt::Key_Delete: | |
if (state() == Editing) | |
deleteSelected(); | |
break; | |
case Qt::Key_Escape: | |
if (state() == Connecting) | |
abortConnection(); | |
break; | |
} | |
e->accept(); | |
} | |
void ConnectionEdit::startConnection(QWidget *source, const QPoint &pos) | |
{ | |
Q_ASSERT(m_tmp_con == 0); | |
m_tmp_con = new Connection(this); | |
m_tmp_con->setEndPoint(EndPoint::Source, source, pos); | |
} | |
void ConnectionEdit::endConnection(QWidget *target, const QPoint &pos) | |
{ | |
Q_ASSERT(m_tmp_con != 0); | |
m_tmp_con->setEndPoint(EndPoint::Target, target, pos); | |
QWidget *source = m_tmp_con->widget(EndPoint::Source); | |
Q_ASSERT(source != 0); | |
Q_ASSERT(target != 0); | |
setEnabled(false); | |
Connection *new_con = createConnection(source, target); | |
setEnabled(true); | |
if (new_con != 0) { | |
new_con->setEndPoint(EndPoint::Source, source, m_tmp_con->endPointPos(EndPoint::Source)); | |
new_con->setEndPoint(EndPoint::Target, target, m_tmp_con->endPointPos(EndPoint::Target)); | |
m_undo_stack->push(new AddConnectionCommand(this, new_con)); | |
emit connectionChanged(new_con); | |
} | |
delete m_tmp_con; | |
m_tmp_con = 0; | |
findObjectsUnderMouse(mapFromGlobal(QCursor::pos())); | |
} | |
void ConnectionEdit::continueConnection(QWidget *target, const QPoint &pos) | |
{ | |
Q_ASSERT(m_tmp_con != 0); | |
m_tmp_con->setEndPoint(EndPoint::Target, target, pos); | |
} | |
void ConnectionEdit::modifyConnection(Connection *) | |
{ | |
} | |
Connection *ConnectionEdit::createConnection(QWidget *source, QWidget *target) | |
{ | |
Connection *con = new Connection(this, source, target); | |
return con; | |
} | |
// Find all connections which in which a sequence of objects is involved | |
template <class ObjectIterator> | |
static ConnectionEdit::ConnectionSet findConnectionsOf(const ConnectionEdit::ConnectionList &cl, ObjectIterator oi1, const ObjectIterator &oi2) | |
{ | |
ConnectionEdit::ConnectionSet rc; | |
const ConnectionEdit::ConnectionList::const_iterator ccend = cl.constEnd(); | |
for ( ; oi1 != oi2; ++oi1) { | |
for (ConnectionEdit::ConnectionList::const_iterator cit = cl.constBegin(); cit != ccend; ++cit) { | |
Connection *con = *cit; | |
if (con->object(ConnectionEdit::EndPoint::Source) == *oi1 || con->object(ConnectionEdit::EndPoint::Target) == *oi1) | |
rc.insert(con, con); | |
} | |
} | |
return rc; | |
} | |
void ConnectionEdit::widgetRemoved(QWidget *widget) | |
{ | |
// Remove all connections of that widget and its children. | |
if (m_con_list.empty()) | |
return; | |
QWidgetList child_list = qFindChildren<QWidget*>(widget); | |
child_list.prepend(widget); | |
const ConnectionSet remove_set = findConnectionsOf(m_con_list, child_list.constBegin(), child_list.constEnd()); | |
if (!remove_set.isEmpty()) | |
m_undo_stack->push(new DeleteConnectionsCommand(this, remove_set.keys())); | |
updateBackground(); | |
} | |
void ConnectionEdit::objectRemoved(QObject *o) | |
{ | |
// Remove all connections of that object and its children (in case of action groups). | |
if (m_con_list.empty()) | |
return; | |
QObjectList child_list = o->children(); | |
child_list.prepend(o); | |
const ConnectionSet remove_set = findConnectionsOf(m_con_list, child_list.constBegin(), child_list.constEnd()); | |
if (!remove_set.isEmpty()) | |
m_undo_stack->push(new DeleteConnectionsCommand(this, remove_set.keys())); | |
updateBackground(); | |
} | |
void ConnectionEdit::setSelected(Connection *con, bool sel) | |
{ | |
if (!con || sel == m_sel_con_set.contains(con)) | |
return; | |
if (sel) { | |
m_sel_con_set.insert(con, con); | |
emit connectionSelected(con); | |
} else { | |
m_sel_con_set.remove(con); | |
} | |
con->update(); | |
} | |
bool ConnectionEdit::selected(const Connection *con) const | |
{ | |
return m_sel_con_set.contains(const_cast<Connection*>(con)); | |
} | |
void ConnectionEdit::selectNone() | |
{ | |
foreach (Connection *con, m_sel_con_set) | |
con->update(); | |
m_sel_con_set.clear(); | |
} | |
void ConnectionEdit::selectAll() | |
{ | |
if (m_sel_con_set.size() == m_con_list.size()) | |
return; | |
foreach (Connection *con, m_con_list) | |
setSelected(con, true); | |
} | |
Connection *ConnectionEdit::connectionAt(const QPoint &pos) const | |
{ | |
foreach (Connection *con, m_con_list) { | |
if (con->contains(pos)) | |
return con; | |
} | |
return 0; | |
} | |
CETypes::EndPoint ConnectionEdit::endPointAt(const QPoint &pos) const | |
{ | |
foreach (Connection *con, m_con_list) { | |
if (!selected(con)) | |
continue; | |
const QRect sr = con->endPointRect(EndPoint::Source); | |
const QRect tr = con->endPointRect(EndPoint::Target); | |
if (sr.contains(pos)) | |
return EndPoint(con, EndPoint::Source); | |
if (tr.contains(pos)) | |
return EndPoint(con, EndPoint::Target); | |
} | |
return EndPoint(); | |
} | |
void ConnectionEdit::startDrag(const EndPoint &end_point, const QPoint &pos) | |
{ | |
Q_ASSERT(m_drag_end_point.isNull()); | |
m_drag_end_point = end_point; | |
m_old_source_pos = m_drag_end_point.con->endPointPos(EndPoint::Source); | |
m_old_target_pos = m_drag_end_point.con->endPointPos(EndPoint::Target); | |
adjustHotSopt(m_drag_end_point, pos); | |
} | |
void ConnectionEdit::continueDrag(const QPoint &pos) | |
{ | |
Q_ASSERT(!m_drag_end_point.isNull()); | |
adjustHotSopt(m_drag_end_point, pos); | |
} | |
void ConnectionEdit::endDrag(const QPoint &pos) | |
{ | |
Q_ASSERT(!m_drag_end_point.isNull()); | |
adjustHotSopt(m_drag_end_point, pos); | |
Connection *con = m_drag_end_point.con; | |
const QPoint new_source_pos = con->endPointPos(EndPoint::Source); | |
const QPoint new_target_pos = con->endPointPos(EndPoint::Target); | |
m_undo_stack->push(new AdjustConnectionCommand(this, con, m_old_source_pos, m_old_target_pos, | |
new_source_pos, new_target_pos)); | |
m_drag_end_point = EndPoint(); | |
} | |
void ConnectionEdit::adjustHotSopt(const EndPoint &end_point, const QPoint &pos) | |
{ | |
QWidget *w = end_point.con->widget(end_point.type); | |
end_point.con->setEndPoint(end_point.type, w, pointInsideRect(widgetRect(w), pos)); | |
} | |
void ConnectionEdit::deleteSelected() | |
{ | |
if (m_sel_con_set.isEmpty()) | |
return; | |
m_undo_stack->push(new DeleteConnectionsCommand(this, m_sel_con_set.keys())); | |
} | |
void ConnectionEdit::addConnection(Connection *con) | |
{ | |
m_con_list.append(con); | |
} | |
void ConnectionEdit::updateLines() | |
{ | |
foreach (Connection *con, m_con_list) | |
con->checkWidgets(); | |
} | |
void ConnectionEdit::resizeEvent(QResizeEvent *e) | |
{ | |
updateBackground(); | |
QWidget::resizeEvent(e); | |
} | |
void ConnectionEdit::setSource(Connection *con, const QString &obj_name) | |
{ | |
QObject *object = 0; | |
if (!obj_name.isEmpty()) { | |
object = qFindChild<QObject*>(m_bg_widget, obj_name); | |
if (object == 0 && m_bg_widget->objectName() == obj_name) | |
object = m_bg_widget; | |
if (object == con->object(EndPoint::Source)) | |
return; | |
} | |
m_undo_stack->push(new SetEndPointCommand(this, con, EndPoint::Source, object)); | |
} | |
void ConnectionEdit::setTarget(Connection *con, const QString &obj_name) | |
{ | |
QObject *object = 0; | |
if (!obj_name.isEmpty()) { | |
object = qFindChild<QObject*>(m_bg_widget, obj_name); | |
if (object == 0 && m_bg_widget->objectName() == obj_name) | |
object = m_bg_widget; | |
if (object == con->object(EndPoint::Target)) | |
return; | |
} | |
m_undo_stack->push(new SetEndPointCommand(this, con, EndPoint::Target, object)); | |
} | |
Connection *ConnectionEdit::takeConnection(Connection *con) | |
{ | |
if (!m_con_list.contains(con)) | |
return 0; | |
m_con_list.removeAll(con); | |
return con; | |
} | |
void ConnectionEdit::clearNewlyAddedConnection() | |
{ | |
delete m_tmp_con; | |
m_tmp_con = 0; | |
} | |
void ConnectionEdit::createContextMenu(QMenu &menu) | |
{ | |
// Select | |
QAction *selectAllAction = menu.addAction(tr("Select All")); | |
selectAllAction->setEnabled(connectionList().size()); | |
connect(selectAllAction, SIGNAL(triggered()), this, SLOT(selectAll())); | |
QAction *deselectAllAction = menu.addAction(tr("Deselect All")); | |
deselectAllAction->setEnabled(selection().size()); | |
connect(deselectAllAction, SIGNAL(triggered()), this, SLOT(selectNone())); | |
menu.addSeparator(); | |
// Delete | |
QAction *deleteAction = menu.addAction(tr("Delete")); | |
deleteAction->setShortcut(QKeySequence::Delete); | |
deleteAction->setEnabled(!selection().isEmpty()); | |
connect(deleteAction, SIGNAL(triggered()), this, SLOT(deleteSelected())); | |
} | |
void ConnectionEdit::contextMenuEvent(QContextMenuEvent * event) | |
{ | |
QMenu menu; | |
createContextMenu(menu); | |
menu.exec(event->globalPos()); | |
} | |
} // namespace qdesigner_internal | |
QT_END_NAMESPACE |