blacksun 33d757e0d8 Diagram editor : add a new widget in the "curent selection dock" for edit the independent text. WIP
git-svn-id: svn+ssh://svn.tuxfamily.org/svnroot/qet/qet/trunk@5737 bfdf4180-ca20-0410-9c96-a3a8aa849046
2019-02-19 16:42:07 +00:00

1994 lines
60 KiB
C++

/*
Copyright 2006-2019 The QElectroTech Team
This file is part of QElectroTech.
QElectroTech is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 2 of the License, or
(at your option) any later version.
QElectroTech is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with QElectroTech. If not, see <http://www.gnu.org/licenses/>.
*/
#include <QtDebug>
#include "conductor.h"
#include "conductorsegment.h"
#include "conductorsegmentprofile.h"
#include "conductortextitem.h"
#include "element.h"
#include "diagram.h"
#include "diagramcommands.h"
#include "qetdiagrameditor.h"
#include "terminal.h"
#include "conductorautonumerotation.h"
#include "conductorpropertiesdialog.h"
#include "QPropertyUndoCommand/qpropertyundocommand.h"
#include "numerotationcontextcommands.h"
#define PR(x) qDebug() << #x " = " << x;
bool Conductor::pen_and_brush_initialized = false;
QPen Conductor::conductor_pen = QPen();
QBrush Conductor::conductor_brush = QBrush();
class ConductorXmlRetroCompatibility
{
friend class Conductor;
static void loadSequential(const QDomElement &dom_element, const QString& seq, QStringList* list)
{
int i = 0;
while (!dom_element.attribute(seq + QString::number(i+1)).isEmpty())
{
list->append(dom_element.attribute(seq + QString::number(i+1)));
i++;
}
}
static void loadSequential(const QDomElement &dom_element, Conductor *conductor)
{
autonum::sequentialNumbers sn;
loadSequential(dom_element,"sequ_",&sn.unit);
loadSequential(dom_element,"sequf_",&sn.unit_folio);
loadSequential(dom_element,"seqt_",&sn.ten);
loadSequential(dom_element,"seqtf_",&sn.ten_folio);
loadSequential(dom_element,"seqh_",&sn.hundred);
loadSequential(dom_element,"seqhf_",&sn.hundred_folio);
conductor->rSequenceNum() = sn;
}
};
/**
* @brief Conductor::Conductor
* Default constructor.
* @param p1 : first terminal of this conductor.
* @param p2 : second terminal of this conductor.
*/
Conductor::Conductor(Terminal *p1, Terminal* p2) :
terminal1(p1),
terminal2(p2),
m_mouse_over(false),
m_text_item(nullptr),
segments(nullptr),
m_moving_segment(false),
modified_path(false),
has_to_save_profile(false),
must_highlight_(Conductor::None)
{
//set Zvalue at 11 to be upper than the DiagramImageItem and element
setZValue(11);
m_previous_z_value = zValue();
//Add this conductor to the list of conductor of each of the two terminal
bool ajout_p1 = terminal1 -> addConductor(this);
bool ajout_p2 = terminal2 -> addConductor(this);
//m_valid become false if the conductor can't be added to terminal (conductor already exist)
m_valid = (!ajout_p1 || !ajout_p2) ? false : true;
//Default attribut for paint a conductor
if (!pen_and_brush_initialized)
{
conductor_pen.setJoinStyle(Qt::MiterJoin);
conductor_pen.setCapStyle(Qt::SquareCap);
conductor_pen.setColor(Qt::black);
conductor_pen.setStyle(Qt::SolidLine);
conductor_pen.setWidthF(1.0);
conductor_brush.setColor(Qt::white);
conductor_brush.setStyle(Qt::NoBrush);
pen_and_brush_initialized = true;
}
//By default, the 4 profils are nuls -> we must to use priv_calculeConductor
conductor_profiles.insert(Qt::TopLeftCorner, ConductorProfile());
conductor_profiles.insert(Qt::TopRightCorner, ConductorProfile());
conductor_profiles.insert(Qt::BottomLeftCorner, ConductorProfile());
conductor_profiles.insert(Qt::BottomRightCorner, ConductorProfile());
//Generate the path of this conductor.
generateConductorPath(terminal1 -> dockConductor(), terminal1 -> orientation(), terminal2 -> dockConductor(), terminal2 -> orientation());
setFlags(QGraphicsItem::ItemIsSelectable | QGraphicsItem::ItemSendsScenePositionChanges);
setAcceptHoverEvents(true);
// Add the text field
m_text_item = new ConductorTextItem(m_properties.text, this);
connect(m_text_item, &ConductorTextItem::textEdited, this, &Conductor::displayedTextChanged);
//Set the default conductor properties.
if (p1->diagram())
setProperties(p1->diagram()->defaultConductorProperties);
else if (p2->diagram())
setProperties(p2->diagram()->defaultConductorProperties);
}
/**
* @brief Conductor::~Conductor
* Destructor. The conductor is removed from is terminal
*/
Conductor::~Conductor()
{
removeHandler();
terminal1->removeConductor(this);
terminal2->removeConductor(this);
deleteSegments();
}
/**
* @brief Conductor::isValid
* @return true if conductor is valid else false;
* A non valid conductor, is a conductor without two terminal
*/
bool Conductor::isValid() const {
return m_valid;
}
/**
Met a jour la representation graphique du conducteur en recalculant son
trace. Cette fonction est typiquement appelee lorsqu'une seule des bornes du
conducteur a change de position.
@param rect Rectangle a mettre a jour
@see QGraphicsPathItem::update()
*/
void Conductor::updatePath(const QRectF &rect) {
QPointF p1, p2;
p1 = terminal1 -> dockConductor();
p2 = terminal2 -> dockConductor();
if (segmentsCount() && !conductor_profiles[currentPathType()].isNull())
updateConductorPath(p1, terminal1 -> orientation(), p2, terminal2 -> orientation());
else
generateConductorPath(p1, terminal1 -> orientation(), p2, terminal2 -> orientation());
calculateTextItemPosition();
QGraphicsObject::update(rect);
}
/**
* @brief Conductor::segmentsToPath
* Generate the QPainterPath from the list of points
*/
void Conductor::segmentsToPath()
{
QPainterPath path;
if (segments == nullptr)
setPath(path);
//Start the path
path.moveTo(segments -> firstPoint());
//Each segments
ConductorSegment *segment = segments;
while(segment -> hasNextSegment()) {
path.lineTo(segment -> secondPoint());
segment = segment -> nextSegment();
}
//Finish the path
path.lineTo(segment -> secondPoint());
setPath(path);
//If conductor is selected and he's not being modified
//we update the position of the handlers
if (isSelected() && !m_moving_segment)
{
if(handlerPoints().size() == m_handler_vector.size())
adjusteHandlerPos();
else
{
removeHandler();
addHandler();
}
}
}
/**
Gere les updates
@param p1 Coordonnees du point d'amarrage de la borne 1
@param o1 Orientation de la borne 1
@param p2 Coordonnees du point d'amarrage de la borne 2
@param o2 Orientation de la borne 2
*/
void Conductor::updateConductorPath(const QPointF &p1, Qet::Orientation o1, const QPointF &p2, Qet::Orientation o2) {
Q_UNUSED(o1);
Q_UNUSED(o2);
ConductorProfile &conductor_profile = conductor_profiles[currentPathType()];
Q_ASSERT_X(conductor_profile.segmentsCount(QET::Both) > 1, "Conductor::priv_modifieConductor", "pas de points a modifier");
Q_ASSERT_X(!conductor_profile.isNull(), "Conductor::priv_modifieConductor", "pas de profil utilisable");
// recupere les coordonnees fournies des bornes
QPointF new_p1 = mapFromScene(p1);
QPointF new_p2 = mapFromScene(p2);
QRectF new_rect = QRectF(new_p1, new_p2);
// recupere la largeur et la hauteur du profil
qreal profile_width = conductor_profile.width();
qreal profile_height = conductor_profile.height();
// calcule les differences verticales et horizontales a appliquer
qreal h_diff = (qAbs(new_rect.width()) - qAbs(profile_width) ) * getSign(profile_width);
qreal v_diff = (qAbs(new_rect.height()) - qAbs(profile_height)) * getSign(profile_height);
// applique les differences aux segments
QHash<ConductorSegmentProfile *, qreal> segments_lengths;
segments_lengths.unite(shareOffsetBetweenSegments(h_diff, conductor_profile.horizontalSegments()));
segments_lengths.unite(shareOffsetBetweenSegments(v_diff, conductor_profile.verticalSegments()));
// en deduit egalement les coefficients d'inversion (-1 pour une inversion, +1 pour conserver le meme sens)
int horiz_coeff = getCoeff(new_rect.width(), profile_width);
int verti_coeff = getCoeff(new_rect.height(), profile_height);
// genere les nouveaux points
QList<QPointF> points;
points << new_p1;
int limit = conductor_profile.segments.count() - 1;
for (int i = 0 ; i < limit ; ++ i) {
// dernier point
QPointF previous_point = points.last();
// profil de segment de conducteur en cours
ConductorSegmentProfile *csp = conductor_profile.segments.at(i);
// coefficient et offset a utiliser pour ce point
qreal coeff = csp -> isHorizontal ? horiz_coeff : verti_coeff;
qreal offset_applied = segments_lengths[csp];
// applique l'offset et le coeff au point
if (csp -> isHorizontal) {
points << QPointF (
previous_point.x() + (coeff * offset_applied),
previous_point.y()
);
} else {
points << QPointF (
previous_point.x(),
previous_point.y() + (coeff * offset_applied)
);
}
}
points << new_p2;
pointsToSegments(points);
segmentsToPath();
}
/**
@param offset Longueur a repartir entre les segments
@param segments_list Segments sur lesquels il faut repartir la longueur
@param precision seuil en-deca duquel on considere qu'il ne reste rien a repartir
*/
QHash<ConductorSegmentProfile *, qreal> Conductor::shareOffsetBetweenSegments(
const qreal &offset,
const QList<ConductorSegmentProfile *> &segments_list,
const qreal &precision
) const {
// construit le QHash qui sera retourne
QHash<ConductorSegmentProfile *, qreal> segments_hash;
foreach(ConductorSegmentProfile *csp, segments_list) {
segments_hash.insert(csp, csp -> length);
}
// memorise le signe de la longueur de chaque segement
QHash<ConductorSegmentProfile *, int> segments_signs;
foreach(ConductorSegmentProfile *csp, segments_hash.keys()) {
segments_signs.insert(csp, getSign(csp -> length));
}
//qDebug() << "repartition d'un offset de" << offset << "px sur" << segments_list.count() << "segments";
// repartit l'offset sur les segments
qreal remaining_offset = offset;
while (remaining_offset > precision || remaining_offset < -precision) {
// recupere le nombre de segments differents ayant une longueur non nulle
uint segments_count = 0;
foreach(ConductorSegmentProfile *csp, segments_hash.keys()) if (segments_hash[csp]) ++ segments_count;
//qDebug() << " remaining_offset =" << remaining_offset;
qreal local_offset = remaining_offset / segments_count;
//qDebug() << " repartition d'un offset local de" << local_offset << "px sur" << segments_count << "segments";
remaining_offset = 0.0;
foreach(ConductorSegmentProfile *csp, segments_hash.keys()) {
// ignore les segments de longueur nulle
if (!segments_hash[csp]) continue;
// applique l'offset au segment
//qreal segment_old_length = segments_hash[csp];
segments_hash[csp] += local_offset;
// (la longueur du segment change de signe) <=> (le segment n'a pu absorbe tout l'offset)
if (segments_signs[csp] != getSign(segments_hash[csp])) {
// on remet le trop-plein dans la reserve d'offset
remaining_offset += qAbs(segments_hash[csp]) * getSign(local_offset);
//qDebug() << " trop-plein de" << qAbs(segments_hash[csp]) * getSign(local_offset) << "remaining_offset =" << remaining_offset;
segments_hash[csp] = 0.0;
} else {
//qDebug() << " offset local de" << local_offset << "accepte";
}
}
}
return(segments_hash);
}
/**
Calcule un trajet "par defaut" pour le conducteur
@param p1 Coordonnees du point d'amarrage de la borne 1
@param o1 Orientation de la borne 1
@param p2 Coordonnees du point d'amarrage de la borne 2
@param o2 Orientation de la borne 2
*/
void Conductor::generateConductorPath(const QPointF &p1, Qet::Orientation o1, const QPointF &p2, Qet::Orientation o2) {
QPointF sp1, sp2, depart, newp1, newp2, arrivee, depart0, arrivee0;
Qet::Orientation ori_depart, ori_arrivee;
// s'assure qu'il n'y a ni points
QList<QPointF> points;
// mappe les points par rapport a la scene
sp1 = mapFromScene(p1);
sp2 = mapFromScene(p2);
// prolonge les bornes
newp1 = extendTerminal(sp1, o1);
newp2 = extendTerminal(sp2, o2);
// distingue le depart de l'arrivee : le trajet se fait toujours de gauche a droite (apres prolongation)
if (newp1.x() <= newp2.x()) {
depart = newp1;
arrivee = newp2;
depart0 = sp1;
arrivee0 = sp2;
ori_depart = o1;
ori_arrivee = o2;
} else {
depart = newp2;
arrivee = newp1;
depart0 = sp2;
arrivee0 = sp1;
ori_depart = o2;
ori_arrivee = o1;
}
// debut du trajet
points << depart0;
// prolongement de la borne de depart
points << depart;
// commence le vrai trajet
if (depart.y() < arrivee.y()) {
// trajet descendant
if ((ori_depart == Qet::North && (ori_arrivee == Qet::South || ori_arrivee == Qet::West)) || (ori_depart == Qet::East && ori_arrivee == Qet::West)) {
// cas "3"
int ligne_inter_x = qRound(depart.x() + arrivee.x()) / 2;
while (ligne_inter_x % Diagram::xGrid) -- ligne_inter_x;
points << QPointF(ligne_inter_x, depart.y());
points << QPointF(ligne_inter_x, arrivee.y());
} else if ((ori_depart == Qet::South && (ori_arrivee == Qet::North || ori_arrivee == Qet::East)) || (ori_depart == Qet::West && ori_arrivee == Qet::East)) {
// cas "4"
int ligne_inter_y = qRound(depart.y() + arrivee.y()) / 2;
while (ligne_inter_y % Diagram::yGrid) -- ligne_inter_y;
points << QPointF(depart.x(), ligne_inter_y);
points << QPointF(arrivee.x(), ligne_inter_y);
} else if ((ori_depart == Qet::North || ori_depart == Qet::East) && (ori_arrivee == Qet::North || ori_arrivee == Qet::East)) {
points << QPointF(arrivee.x(), depart.y()); // cas "2"
} else {
points << QPointF(depart.x(), arrivee.y()); // cas "1"
}
} else {
// trajet montant
if ((ori_depart == Qet::West && (ori_arrivee == Qet::East || ori_arrivee == Qet::South)) || (ori_depart == Qet::North && ori_arrivee == Qet::South)) {
// cas "3"
int ligne_inter_y = qRound(depart.y() + arrivee.y()) / 2;
while (ligne_inter_y % Diagram::yGrid) -- ligne_inter_y;
points << QPointF(depart.x(), ligne_inter_y);
points << QPointF(arrivee.x(), ligne_inter_y);
} else if ((ori_depart == Qet::East && (ori_arrivee == Qet::West || ori_arrivee == Qet::North)) || (ori_depart == Qet::South && ori_arrivee == Qet::North)) {
// cas "4"
int ligne_inter_x = qRound(depart.x() + arrivee.x()) / 2;
while (ligne_inter_x % Diagram::xGrid) -- ligne_inter_x;
points << QPointF(ligne_inter_x, depart.y());
points << QPointF(ligne_inter_x, arrivee.y());
} else if ((ori_depart == Qet::West || ori_depart == Qet::North) && (ori_arrivee == Qet::West || ori_arrivee == Qet::North)) {
points << QPointF(depart.x(), arrivee.y()); // cas "2"
} else {
points << QPointF(arrivee.x(), depart.y()); // cas "1"
}
}
// fin du vrai trajet
points << arrivee;
// prolongement de la borne d'arrivee
points << arrivee0;
// inverse eventuellement l'ordre des points afin que le trajet soit exprime de la borne 1 vers la borne 2
if (newp1.x() > newp2.x()) {
QList<QPointF> points2;
for (int i = points.size() - 1 ; i >= 0 ; -- i) points2 << points.at(i);
points = points2;
}
pointsToSegments(points);
segmentsToPath();
}
/**
Prolonge une borne.
@param terminal Le point correspondant a la borne
@param terminal_orientation L'orientation de la borne
@param ext_size la taille de la prolongation
@return le point correspondant a la borne apres prolongation
*/
QPointF Conductor::extendTerminal(const QPointF &terminal, Qet::Orientation terminal_orientation, qreal ext_size) {
QPointF extended_terminal;
switch(terminal_orientation) {
case Qet::North:
extended_terminal = QPointF(terminal.x(), terminal.y() - ext_size);
break;
case Qet::East:
extended_terminal = QPointF(terminal.x() + ext_size, terminal.y());
break;
case Qet::South:
extended_terminal = QPointF(terminal.x(), terminal.y() + ext_size);
break;
case Qet::West:
extended_terminal = QPointF(terminal.x() - ext_size, terminal.y());
break;
default: extended_terminal = terminal;
}
return(extended_terminal);
}
/**
Dessine le conducteur sans antialiasing.
@param qp Le QPainter a utiliser pour dessiner le conducteur
@param options Les options de style pour le conducteur
@param qw Le QWidget sur lequel on dessine
*/
void Conductor::paint(QPainter *qp, const QStyleOptionGraphicsItem *options, QWidget *qw)
{
Q_UNUSED(qw);
qp -> save();
qp -> setRenderHint(QPainter::Antialiasing, false);
// Set the color of conductor
QColor final_conductor_color(m_properties.color);
if (must_highlight_ == Normal) {
final_conductor_color = QColor::fromRgb(69, 137, 255, 255);
} else if (must_highlight_ == Alert) {
final_conductor_color =QColor::fromRgb(255, 69, 0, 255);
} else if (isSelected()) {
final_conductor_color = Qt::red;
} else {
if (Diagram *parent_diagram = diagram()) {
if (!parent_diagram -> drawColoredConductors()) {
final_conductor_color = Qt::black;
}
}
}
//Draw the conductor bigger when is hovered
conductor_pen.setWidthF(m_mouse_over? (m_properties.cond_size) +4 : (m_properties.cond_size));
//Set the QPen and QBrush to the QPainter
qp -> setBrush(conductor_brush);
QPen final_conductor_pen = conductor_pen;
//Set the conductor style
final_conductor_pen.setColor(final_conductor_color);
final_conductor_pen.setStyle(m_properties.style);
final_conductor_pen.setJoinStyle(Qt::SvgMiterJoin); // better rendering with dot
//Use a cosmetique line, below a certain zoom
if (options && options -> levelOfDetail < 1.0) {
final_conductor_pen.setCosmetic(true);
}
qp -> setPen(final_conductor_pen);
//Draw the conductor
qp -> drawPath(path());
//Draw the second color
if(m_properties.m_bicolor)
{
final_conductor_pen.setColor(m_properties.m_color_2);
final_conductor_pen.setStyle(Qt::CustomDashLine);
QVector<qreal> dash_pattern;
dash_pattern << m_properties.m_dash_size-2 << m_properties.m_dash_size;
final_conductor_pen.setDashPattern(dash_pattern);
qp->save();
qp->setPen(final_conductor_pen);
qp->drawPath(path());
qp->restore();
}
if (m_properties.type == ConductorProperties::Single) {
qp -> setBrush(final_conductor_color);
m_properties.singleLineProperties.draw(
qp,
middleSegment() -> isHorizontal() ? QET::Horizontal : QET::Vertical,
QRectF(middleSegment() -> middle() - QPointF(12.0, 12.0), QSizeF(24.0, 24.0))
);
if (isSelected()) qp -> setBrush(Qt::NoBrush);
}
//Draw the junctions
QList<QPointF> junctions_list = junctions();
if (!junctions_list.isEmpty()) {
final_conductor_pen.setStyle(Qt::SolidLine);
QBrush junction_brush(final_conductor_color, Qt::SolidPattern);
qp -> setPen(final_conductor_pen);
qp -> setBrush(junction_brush);
qp -> setRenderHint(QPainter::Antialiasing, true);
foreach(QPointF point, junctions_list) {
qp -> drawEllipse(QRectF(point.x() - 1.5, point.y() - 1.5, 3.0, 3.0));
}
}
qp -> restore();
}
/// @return le Diagram auquel ce conducteur appartient, ou 0 si ce conducteur est independant
Diagram *Conductor::diagram() const {
return(qobject_cast<Diagram *>(scene()));
}
/**4
@return le champ de texte associe a ce conducteur
*/
ConductorTextItem *Conductor::textItem() const {
return(m_text_item);
}
/**
Methode de validation d'element XML
@param e Un element XML sense represente un Conducteur
@return true si l'element XML represente bien un Conducteur ; false sinon
*/
bool Conductor::valideXml(QDomElement &e){
// verifie le nom du tag
if (e.tagName() != "conductor") return(false);
// verifie la presence des attributs minimaux
if (!e.hasAttribute("terminal1")) return(false);
if (!e.hasAttribute("terminal2")) return(false);
bool conv_ok;
// parse l'abscisse
e.attribute("terminal1").toInt(&conv_ok);
if (!conv_ok) return(false);
// parse l'ordonnee
e.attribute("terminal2").toInt(&conv_ok);
if (!conv_ok) return(false);
return(true);
}
/**
* @brief Conductor::mouseDoubleClickEvent
* Manage the mouse double click
* @param event
*/
void Conductor::mouseDoubleClickEvent(QGraphicsSceneMouseEvent *event) {
event->accept();
editProperty();
}
/**
* @brief Conductor::mousePressEvent
* Manage the mouse press event
* @param event
*/
void Conductor::mousePressEvent(QGraphicsSceneMouseEvent *event)
{
QGraphicsObject::mousePressEvent(event);
if (event->modifiers() & Qt::ControlModifier)
setSelected(!isSelected());
}
/**
* @brief Conductor::mouseReleaseEvent
* @param event
*/
void Conductor::mouseReleaseEvent(QGraphicsSceneMouseEvent *event)
{
if (!(event -> modifiers() & Qt::ControlModifier))
QGraphicsObject::mouseReleaseEvent(event);
}
/**
* @brief Conductor::hoverEnterEvent
* Manage the hover enter event
* @param event
*/
void Conductor::hoverEnterEvent(QGraphicsSceneHoverEvent *event) {
Q_UNUSED(event);
m_mouse_over = true;
update();
}
/**
* @brief Conductor::hoverLeaveEvent
* Manage the mouse leave event
* @param event
*/
void Conductor::hoverLeaveEvent(QGraphicsSceneHoverEvent *event) {
Q_UNUSED(event);
update();
m_mouse_over = false;
}
/**
* @brief Conductor::itemChange
* @param change
* @param value
* @return
*/
QVariant Conductor::itemChange(GraphicsItemChange change, const QVariant &value)
{
if (change == QGraphicsItem::ItemSelectedChange)
{
if (value.toBool())
{
m_previous_z_value = zValue();
setZValue(qAbs(m_previous_z_value) + 10000);
addHandler();
}
else
{
setZValue(m_previous_z_value);
removeHandler();
}
}
else if (change == QGraphicsItem::ItemSceneHasChanged)
{
calculateTextItemPosition();
if(!scene())
removeHandler();
else if (scene() && isSelected())
addHandler();
}
else if (change == QGraphicsItem::ItemVisibleHasChanged) {
calculateTextItemPosition();
}
else if (change == QGraphicsItem::ItemPositionHasChanged && isSelected()) {
adjusteHandlerPos();
}
return(QGraphicsObject::itemChange(change, value));
}
/**
* @brief Conductor::sceneEventFilter
* @param watched
* @param event
* @return
*/
bool Conductor::sceneEventFilter(QGraphicsItem *watched, QEvent *event)
{
//Watched must be an handler
if(watched->type() == QetGraphicsHandlerItem::Type)
{
QetGraphicsHandlerItem *qghi = qgraphicsitem_cast<QetGraphicsHandlerItem *>(watched);
if(m_handler_vector.contains(qghi)) //Handler must be in m_vector_index, then we can start resize
{
m_vector_index = m_handler_vector.indexOf(qghi);
if (m_vector_index != -1)
{
if(event->type() == QEvent::GraphicsSceneMousePress) //Click
{
handlerMousePressEvent(qghi, static_cast<QGraphicsSceneMouseEvent *>(event));
return true;
}
else if(event->type() == QEvent::GraphicsSceneMouseMove) //Move
{
handlerMouseMoveEvent(qghi, static_cast<QGraphicsSceneMouseEvent *>(event));
return true;
}
else if (event->type() == QEvent::GraphicsSceneMouseRelease) //Release
{
handlerMouseReleaseEvent(qghi, static_cast<QGraphicsSceneMouseEvent *>(event));
return true;
}
else if (event->type() == QEvent::GraphicsSceneMouseDoubleClick) //Double click
{
editProperty();
return true;
}
}
}
}
return false;
}
/**
* @brief Conductor::adjusteHandlerPos
* Adjust the position of the handler item
*/
void Conductor::adjusteHandlerPos()
{
if (m_handler_vector.isEmpty())
return;
if (m_handler_vector.size() == handlerPoints().size())
{
QVector <QPointF> points_vector = mapToScene(handlerPoints());
for (int i = 0 ; i < points_vector.size() ; ++i)
m_handler_vector.at(i)->setPos(points_vector.at(i));
}
}
/**
* @brief Conductor::handlerMousePressEvent
* @param qghi
* @param event
*/
void Conductor::handlerMousePressEvent(QetGraphicsHandlerItem *qghi, QGraphicsSceneMouseEvent *event)
{
Q_UNUSED(event);
//we get the segment corresponding to the handler
if (m_vector_index > -1)
{
qghi->setColor(Qt::cyan);
m_moving_segment = true;
m_moved_segment = segmentsList().at(m_vector_index+1);
before_mov_text_pos_ = m_text_item -> pos();
for(QetGraphicsHandlerItem *handler : m_handler_vector)
if(handler != qghi)
handler->hide();
}
}
/**
* @brief Conductor::handlerMouseMoveEvent
* @param qghi
* @param event
*/
void Conductor::handlerMouseMoveEvent(QetGraphicsHandlerItem *qghi, QGraphicsSceneMouseEvent *event)
{
if (m_moving_segment)
{
//Snap the mouse pos to grid
QPointF pos_ = Diagram::snapToGrid(mapFromScene(event->scenePos()));
//Position of the last point
QPointF p = m_moved_segment -> middle();
//Calcul the movement
m_moved_segment -> moveX(pos_.x() - p.x());
m_moved_segment -> moveY(pos_.y() - p.y());
//Apply the movement
modified_path = true;
has_to_save_profile = true;
segmentsToPath();
calculateTextItemPosition();
qghi->setPos(mapToScene(m_moved_segment->middle()));
}
}
/**
* @brief Conductor::handlerMouseReleaseEvent
* @param qghi
* @param event
*/
void Conductor::handlerMouseReleaseEvent(QetGraphicsHandlerItem *qghi, QGraphicsSceneMouseEvent *event)
{
Q_UNUSED(event);
Q_UNUSED(qghi);
m_vector_index = -1;
m_moving_segment = false;
if (has_to_save_profile)
{
saveProfile();
has_to_save_profile = false;
}
//When handler is released, the conductor can have more segment than befor the handler was moved
//then we remove all handles and new ones are added
removeHandler();
addHandler();
}
/**
* @brief Conductor::addHandler
* Add handlers for this item
*/
void Conductor::addHandler()
{
if (m_handler_vector.isEmpty() && scene())
{
m_handler_vector = QetGraphicsHandlerItem::handlerForPoint(mapToScene(handlerPoints()));
for(QetGraphicsHandlerItem *handler : m_handler_vector)
{
handler->setColor(Qt::blue);
scene()->addItem(handler);
handler->installSceneEventFilter(this);
handler->setZValue(this->zValue()+1);
}
}
}
/**
* @brief Conductor::removeHandler
* Remove the handlers of this item
*/
void Conductor::removeHandler()
{
if (!m_handler_vector.isEmpty())
{
qDeleteAll(m_handler_vector);
m_handler_vector.clear();
}
}
/**
* @brief Conductor::boundingRect
* @return
*/
QRectF Conductor::boundingRect() const
{
QRectF br = shape().boundingRect();
return br.adjusted(-10, -10, 10, 10);
}
/**
* @brief Conductor::shape
* @return the shape of conductor.
* The shape thickness is bigger when conductor is hovered
*/
QPainterPath Conductor::shape() const
{
QPainterPathStroker pps;
pps.setWidth(m_mouse_over? 5 : 1);
pps.setJoinStyle(conductor_pen.joinStyle());
QPainterPath shape_(pps.createStroke(path()));
return shape_;
}
/**
* @brief Conductor::nearShape
* @return : An area in which it is considered a point is near this conductor.
*/
QPainterPath Conductor::nearShape() const
{
QPainterPathStroker pps;
pps.setWidth(1300);
pps.setJoinStyle(conductor_pen.joinStyle());
return pps.createStroke(path());
}
/**
@param type Type de Segments
@return Le nombre de segments composant le conducteur.
*/
uint Conductor::segmentsCount(QET::ConductorSegmentType type) const {
QList<ConductorSegment *> segments_list = segmentsList();
if (type == QET::Both) return(segments_list.count());
uint nb_seg = 0;
foreach(ConductorSegment *conductor_segment, segments_list) {
if (conductor_segment -> type() == type) ++ nb_seg;
}
return(nb_seg);
}
/**
Genere une liste de points a partir des segments de ce conducteur
@return La liste de points representant ce conducteur
*/
QList<QPointF> Conductor::segmentsToPoints() const {
// liste qui sera retournee
QList<QPointF> points_list;
// on retourne la liste tout de suite s'il n'y a pas de segments
if (segments == nullptr) return(points_list);
// recupere le premier point
points_list << segments -> firstPoint();
// parcourt les segments pour recuperer les autres points
ConductorSegment *segment = segments;
while(segment -> hasNextSegment()) {
points_list << segment -> secondPoint();
segment = segment -> nextSegment();
}
// recupere le dernier point
points_list << segment -> secondPoint();
//retourne la liste
return(points_list);
}
/**
Regenere les segments de ce conducteur a partir de la liste de points passee en parametre
@param points_list Liste de points a utiliser pour generer les segments
*/
void Conductor::pointsToSegments(const QList<QPointF>& points_list) {
// supprime les segments actuels
deleteSegments();
// cree les segments a partir de la liste de points
ConductorSegment *last_segment = nullptr;
for (int i = 0 ; i < points_list.size() - 1 ; ++ i) {
last_segment = new ConductorSegment(points_list.at(i), points_list.at(i + 1), last_segment);
if (!i) segments = last_segment;
}
}
/**
* @brief Conductor::fromXml
* Load the conductor and her information from xml element
* @param e
* @return true is loading success else return false
*/
bool Conductor::fromXml(QDomElement &dom_element)
{
setPos(dom_element.attribute("x", nullptr).toDouble(),
dom_element.attribute("y", nullptr).toDouble());
bool return_ = pathFromXml(dom_element);
m_text_item -> fromXml(dom_element);
ConductorProperties pr;
pr.fromXml(dom_element);
//Load Sequential Values
if (dom_element.hasAttribute("sequ_1") || dom_element.hasAttribute("sequf_1") || dom_element.hasAttribute("seqt_1") || dom_element.hasAttribute("seqtf_1") || dom_element.hasAttribute("seqh_1") || dom_element.hasAttribute("sequf_1"))
ConductorXmlRetroCompatibility::loadSequential(dom_element, this);
else
m_autoNum_seq.fromXml(dom_element.firstChildElement("sequentialNumbers"));
m_freeze_label = dom_element.attribute("freezeLabel") == "true"? true : false;
setProperties(pr);
return return_;
}
/**
Exporte les caracteristiques du conducteur sous forme d'une element XML.
@param d Le document XML a utiliser pour creer l'element XML
@param table_adr_id Hash stockant les correspondances entre les ids des
bornes dans le document XML et leur adresse en memoire
@return Un element XML representant le conducteur
*/
QDomElement Conductor::toXml(QDomDocument &dom_document, QHash<Terminal *, int> &table_adr_id) const
{
QDomElement dom_element = dom_document.createElement("conductor");
dom_element.setAttribute("x", QString::number(pos().x()));
dom_element.setAttribute("y", QString::number(pos().y()));
dom_element.setAttribute("terminal1", table_adr_id.value(terminal1));
dom_element.setAttribute("terminal2", table_adr_id.value(terminal2));
dom_element.setAttribute("freezeLabel", m_freeze_label? "true" : "false");
// on n'exporte les segments du conducteur que si ceux-ci ont
// ete modifies par l'utilisateur
if (modified_path)
{
// parcours et export des segments
QDomElement current_segment;
foreach(ConductorSegment *segment, segmentsList())
{
current_segment = dom_document.createElement("segment");
current_segment.setAttribute("orientation", segment -> isHorizontal() ? "horizontal" : "vertical");
current_segment.setAttribute("length", QString("%1").arg(segment -> length()));
dom_element.appendChild(current_segment);
}
}
QDomElement dom_seq = m_autoNum_seq.toXml(dom_document);
dom_element.appendChild(dom_seq);
// Export the properties and text
m_properties. toXml(dom_element);
if(m_text_item->wasMovedByUser())
{
dom_element.setAttribute("userx", QString::number(m_text_item->pos().x()));
dom_element.setAttribute("usery", QString::number(m_text_item->pos().y()));
}
if(m_text_item->wasRotateByUser())
dom_element.setAttribute("rotation", QString::number(m_text_item->rotation()));
return(dom_element);
}
/**
* @brief Conductor::pathFromXml
* Generate the path from xml file
* @param e
* @return true if generate path success else return false
*/
bool Conductor::pathFromXml(const QDomElement &e) {
// parcourt les elements XML "segment" et en extrait deux listes de longueurs
// les segments non valides sont ignores
QList<qreal> segments_x, segments_y;
for (QDomNode node = e.firstChild() ; !node.isNull() ; node = node.nextSibling()) {
// on s'interesse aux elements XML "segment"
QDomElement current_segment = node.toElement();
if (current_segment.isNull() || current_segment.tagName() != "segment") continue;
// le segment doit avoir une longueur
if (!current_segment.hasAttribute("length")) continue;
// cette longueur doit etre un reel
bool ok;
qreal segment_length = current_segment.attribute("length").toDouble(&ok);
if (!ok) continue;
if (current_segment.attribute("orientation") == "horizontal") {
segments_x << segment_length;
segments_y << 0.0;
} else {
segments_x << 0.0;
segments_y << segment_length;
}
}
//If there isn't segment we generate automatic path and return true
if (!segments_x.size()) {
generateConductorPath(terminal1 -> dockConductor(), terminal1 -> orientation(), terminal2 -> dockConductor(), terminal2 -> orientation());
return(true);
}
// les longueurs recueillies doivent etre coherentes avec les positions des bornes
qreal width = 0.0, height = 0.0;
foreach (qreal t, segments_x) width += t;
foreach (qreal t, segments_y) height += t;
QPointF t1 = terminal1 -> dockConductor();
QPointF t2 = terminal2 -> dockConductor();
qreal expected_width = t2.x() - t1.x();
qreal expected_height = t2.y() - t1.y();
// on considere que le trajet est incoherent a partir d'une unite de difference avec l'espacement entre les bornes
if (
qAbs(expected_width - width) > 1.0 ||
qAbs(expected_height - height) > 1.0
) {
qDebug() << "Conductor::fromXml : les segments du conducteur ne semblent pas coherents - utilisation d'un trajet automatique";
return(false);
}
/* on recree les segments a partir des donnes XML */
// cree la liste de points
QList<QPointF> points_list;
points_list << mapFromScene(t1);
for (int i = 0 ; i < segments_x.size() ; ++ i) {
points_list << QPointF(
points_list.last().x() + segments_x.at(i),
points_list.last().y() + segments_y.at(i)
);
}
pointsToSegments(points_list);
// initialise divers parametres lies a la modification des conducteurs
modified_path = true;
saveProfile(false);
segmentsToPath();
return(true);
}
/**
* @brief Conductor::handlerPoints
* @return The points used to draw the handler square, used to modify
* the path of the conductor.
* The points stored in the QVector are the middle point of each segments that compose the conductor,
* at exception of the first and last segment because there just here to extend the terminal.
*/
QVector<QPointF> Conductor::handlerPoints() const
{
QList <ConductorSegment *> sl = segmentsList();
if (sl.size() >= 3)
{
sl.removeFirst();
sl.removeLast();
}
QVector <QPointF> middle_points;
foreach(ConductorSegment *segment, sl)
middle_points.append(segment->middle());
return middle_points;
}
/// @return les segments de ce conducteur
const QList<ConductorSegment *> Conductor::segmentsList() const {
if (segments == nullptr) return(QList<ConductorSegment *>());
QList<ConductorSegment *> segments_vector;
ConductorSegment *segment = segments;
while (segment -> hasNextSegment()) {
segments_vector << segment;
segment = segment -> nextSegment();
}
segments_vector << segment;
return(segments_vector);
}
/**
* @brief Conductor::length
* @return the length of this conductor
*/
qreal Conductor::length() const{
return path().length();
}
/**
@return Le segment qui contient le point au milieu du conducteur
*/
ConductorSegment *Conductor::middleSegment() {
if (segments == nullptr) return(nullptr);
qreal half_length = length() / 2.0;
ConductorSegment *s = segments;
qreal l = 0;
while (s -> hasNextSegment()) {
l += qAbs(s -> length());
if (l >= half_length) break;
s = s -> nextSegment();
}
// s est le segment qui contient le point au milieu du conducteur
return(s);
}
/**
* @brief Conductor::posForText
* Calculate and return the better pos for text.
* @param flag : flag is used to know if text pos is near of
* a vertical or horizontal conductor segment.
*/
QPointF Conductor::posForText(Qt::Orientations &flag)
{
ConductorSegment *segment = segments;
bool all_segment_is_vertical = true;
bool all_segment_is_horizontal = true;
//Go to first segement
while (!segment->isFirstSegment()) {
segment = segment->previousSegment();
}
QPointF p1 = segment -> firstPoint(); //<First point of conductor
ConductorSegment *biggest_segment = segment; //<biggest segment: contain the longest segment of conductor.
if (segment -> firstPoint().x() != segment -> secondPoint().x())
all_segment_is_vertical = false;
if (segment -> firstPoint().y() != segment -> secondPoint().y())
all_segment_is_horizontal = false;
while (segment -> hasNextSegment())
{
segment = segment -> nextSegment();
if (segment -> firstPoint().x() != segment -> secondPoint().x())
all_segment_is_vertical = false;
if (segment -> firstPoint().y() != segment -> secondPoint().y())
all_segment_is_horizontal = false;
//We must to compare length segment, but they can be negative
//so we multiply by -1 to make it positive.
int saved = biggest_segment -> length();
if (saved < 0) saved *= -1;
int curent = segment->length();
if (curent < 0) curent *= -1;
if (curent > saved) biggest_segment = segment;
}
QPointF p2 = segment -> secondPoint();//<Last point of conductor
//If the conductor is horizontal or vertical
//Return the point at the middle of conductor
if (all_segment_is_vertical) { //<Vertical
flag = Qt::Vertical;
if (p1.y() > p2.y()) {
p1.setY(p1.y() - (length()/2));
} else {
p1.setY(p1.y() + (length()/2));
}
} else if (all_segment_is_horizontal) { //<Horizontal
flag = Qt::Horizontal;
if (p1.x() > p2.x()) {
p1.setX(p1.x() - (length()/2));
} else {
p1.setX(p1.x() + (length()/2));
}
} else { //Return the point at the middle of biggest segment.
p1 = biggest_segment->middle();
flag = (biggest_segment->isHorizontal())? Qt::Horizontal : Qt::Vertical;
}
return p1;
}
/**
* @brief Conductor::calculateTextItemPosition
* Move the text at middle of conductor (if is vertical or horizontal)
* otherwise, move conductor at the middle of the longest segment of conductor.
* If text was moved by user, this function do nothing, except check if text is near conductor.
*/
void Conductor::calculateTextItemPosition()
{
if (!m_text_item || !diagram() || m_properties.type != ConductorProperties::Multi)
return;
if (diagram() -> defaultConductorProperties.m_one_text_per_folio == true &&
relatedPotentialConductors(false).size() > 0)
{
Conductor *longuest_conductor = longuestConductorInPotential(this);
//The longuest conductor isn't this conductor
//we call calculateTextItemPosition of the longuest conductor
if(longuest_conductor != this)
{
longuest_conductor -> calculateTextItemPosition();
return;
}
//At this point this conductor is the longuest conductor we hide all text of conductor_list
foreach (Conductor *c, relatedPotentialConductors(false)) {
c -> textItem() -> setVisible(false);
}
//Make sure text item is visible
m_text_item -> setVisible(true);
}
//position
if (m_text_item -> wasMovedByUser())
{
//Text field was moved by user :
//we check if text field is yet near the conductor
QPointF text_item_pos = m_text_item -> pos();
QPainterPath near_shape = nearShape();
if (!near_shape.contains(text_item_pos)) {
m_text_item -> setPos(movePointIntoPolygon(text_item_pos, near_shape));
}
}
else
{
//Position and rotation of text is calculated.
Qt::Orientations rotation;
QPointF text_pos = posForText(rotation);
if (!m_text_item -> wasRotateByUser())
{
rotation == Qt::Vertical ? m_text_item -> setRotation(m_properties.verti_rotate_text):
m_text_item -> setRotation(m_properties.horiz_rotate_text);
}
//Adjust the position of text if his rotation
//is 0° or 270°, to be exactly centered to the conductor
if (m_text_item -> rotation() == 0)
{
text_pos.rx() -= m_text_item -> boundingRect().width()/2;
if(m_properties.m_horizontal_alignment == Qt::AlignTop)
text_pos.ry() -= m_text_item->boundingRect().height();
}
else if (m_text_item -> rotation() == 270)
{
text_pos.ry() += m_text_item -> boundingRect().width()/2;
if(m_properties.m_vertical_alignment == Qt::AlignLeft)
text_pos.rx() -= m_text_item->boundingRect().height();
}
m_text_item -> setPos(text_pos);
//Ensure text item don't collide with this conductor
while (m_text_item->collidesWithItem(this))
{
if(rotation == Qt::Vertical)
{
if(m_properties.m_vertical_alignment == Qt::AlignRight)
m_text_item->setX(m_text_item->x()+1);
else if (m_properties.m_vertical_alignment == Qt::AlignLeft)
m_text_item->setX(m_text_item->x()-1);
else
return; //avoid infinite loop
}
else if (rotation == Qt::Horizontal)
{
if(m_properties.m_horizontal_alignment == Qt::AlignTop)
m_text_item->setY(m_text_item->y()-1);
else if (m_properties.m_horizontal_alignment == Qt::AlignBottom)
m_text_item->setY(m_text_item->y()+1);
else
return; //avoid infinite loop
}
}
}
}
/**
Sauvegarde le profil courant du conducteur pour l'utiliser ulterieurement
dans priv_modifieConductor.
*/
void Conductor::saveProfile(bool undo) {
Qt::Corner current_path_type = currentPathType();
ConductorProfile old_profile(conductor_profiles[current_path_type]);
conductor_profiles[current_path_type].fromConductor(this);
Diagram *dia = diagram();
if (undo && dia) {
ChangeConductorCommand *undo_object = new ChangeConductorCommand(
this,
old_profile,
conductor_profiles[current_path_type],
current_path_type
);
undo_object -> setConductorTextItemMove(before_mov_text_pos_, m_text_item -> pos());
dia -> undoStack().push(undo_object);
}
}
/**
@param value1 Premiere valeur
@param value2 Deuxieme valeur
@return 1 si les deux valeurs sont de meme signe, -1 sinon
*/
int Conductor::getCoeff(const qreal &value1, const qreal &value2) {
return(getSign(value1) * getSign(value2));
}
/**
@param value valeur
@return 1 si valeur est negatif, 1 s'il est positif ou nul
*/
int Conductor::getSign(const qreal &value) {
return(value < 0 ? -1 : 1);
}
/**
Applique un nouveau profil a ce conducteur
@param cp Profil a appliquer a ce conducteur
@param path_type Type de trajet pour lequel ce profil convient
*/
void Conductor::setProfile(const ConductorProfile &cp, Qt::Corner path_type) {
conductor_profiles[path_type] = cp;
// si le type de trajet correspond a l'actuel
if (currentPathType() == path_type) {
if (conductor_profiles[path_type].isNull()) {
generateConductorPath(terminal1 -> dockConductor(), terminal1 -> orientation(), terminal2 -> dockConductor(), terminal2 -> orientation());
modified_path = false;
} else {
updateConductorPath(terminal1 -> dockConductor(), terminal1 -> orientation(), terminal2 -> dockConductor(), terminal2 -> orientation());
modified_path = true;
}
if (type() == ConductorProperties::Multi) {
calculateTextItemPosition();
}
}
}
/// @return le profil de ce conducteur
ConductorProfile Conductor::profile(Qt::Corner path_type) const {
return(conductor_profiles[path_type]);
}
/**
* @brief Conductor::refreshText
* Refresh the text of this conductor.
* recalcule and set the text according to the formula.
*/
void Conductor::refreshText()
{
if (m_freeze_label)
{
m_text_item->setPlainText(m_properties.text);
}
else
{
if (!m_properties.m_formula.isEmpty())
{
if (diagram())
{
QString text = autonum::AssignVariables::formulaToLabel(m_properties.m_formula, m_autoNum_seq, diagram());
m_properties.text = text;
m_text_item->setPlainText(text);
}
else
{
m_properties.text = m_properties.m_formula;
m_text_item->setPlainText(m_properties.text);
}
}
else
{
m_text_item->setPlainText(m_properties.text);
}
}
}
void Conductor::setPath(const QPainterPath &path)
{
if(path == m_path)
return;
prepareGeometryChange();
m_path = path;
update();
}
QPainterPath Conductor::path() const
{
return m_path;
}
/**
* @brief Conductor::setPropertiesToPotential
* @param properties
* @param only_text
* Set @propertie to conductor and every conductors in the same potential of @conductor.
* If @only_text is true only formula, text, function and tension/protocol is set
* to other conductor in the same potential, the other values of property stay unmodified
*/
void Conductor::setPropertyToPotential(const ConductorProperties &property, bool only_text)
{
setProperties(property);
QSet <Conductor *> potential_list = relatedPotentialConductors();
foreach(Conductor *other_conductor, potential_list)
{
if (only_text)
{
ConductorProperties other_properties = other_conductor->properties();
other_properties.m_formula = m_properties.m_formula;
other_properties.text = m_properties.text;
other_properties.m_function = m_properties.m_function;
other_properties.m_tension_protocol = m_properties.m_tension_protocol;
other_conductor->setProperties(other_properties);
}
else
{
other_conductor->setProperties(property);
}
}
}
/**
* @brief Conductor::setProperties
* Set @property as current property of conductor
* @param properties : properties
*/
void Conductor::setProperties(const ConductorProperties &property)
{
if (m_properties == property) return;
QString formula = m_properties.m_formula;
m_properties = property;
if (!m_properties.m_formula.isEmpty())
{
if (diagram())
{
QString text = autonum::AssignVariables::formulaToLabel(m_properties.m_formula, m_autoNum_seq, diagram());
m_properties.text = text;
}
else if (m_properties.text.isEmpty())
{
m_properties.text = m_properties.m_formula;
}
setUpConnectionForFormula(formula, m_properties.m_formula);
}
m_text_item->setPlainText(m_properties.text);
m_text_item->setFontSize(m_properties.text_size);
if (m_properties.type != ConductorProperties::Multi)
m_text_item->setVisible(false);
else
m_text_item->setVisible(m_properties.m_show_text);
calculateTextItemPosition();
update();
emit propertiesChange();
}
/**
* @brief Conductor::properties
* @return the properties of this Conductor
*/
ConductorProperties Conductor::properties() const
{
return(m_properties);
}
/**
@return true si le conducteur est mis en evidence
*/
Conductor::Highlight Conductor::highlight() const {
return(must_highlight_);
}
/**
@param hl true pour mettre le conducteur en evidence, false sinon
*/
void Conductor::setHighlighted(Conductor::Highlight hl) {
must_highlight_ = hl;
update();
}
/**
* @brief Conductor::displayedTextChanged
* Update the properties (text) of this conductor and other conductors
* at the same potential of this conductor.
*/
void Conductor::displayedTextChanged()
{
QVariant old_value, new_value;
old_value.setValue(m_properties);
ConductorProperties new_properties(m_properties);
new_properties.m_formula = m_text_item->toPlainText();
new_properties.text = m_text_item->toPlainText();
new_value.setValue(new_properties);
QUndoCommand *undo = new QUndoCommand(tr("Modifier les propriétés d'un conducteur", "undo caption"));
new QPropertyUndoCommand(this, "properties", old_value, new_value, undo);
if (!relatedPotentialConductors().isEmpty())
{
undo->setText(tr("Modifier les propriétés de plusieurs conducteurs", "undo caption"));
foreach (Conductor *potential_conductor, relatedPotentialConductors())
{
old_value.setValue(potential_conductor->properties());
ConductorProperties new_properties = potential_conductor->properties();
new_properties.m_formula = m_text_item->toPlainText();
new_properties.text = m_text_item->toPlainText();
new_value.setValue(new_properties);
new QPropertyUndoCommand (potential_conductor, "properties", old_value, new_value, undo);
}
}
diagram()->undoStack().push(undo);
}
/**
* @brief Conductor::relatedPotentialConductors
* Return all conductors at the same potential of this conductor, this conductor isn't
* part of the returned QSet.
* @param all_diagram : if true search in all diagram of the project,
* false search only in the parent diagram of this conductor
* @param t_list, a list of terminal already found for this potential.
* @return a QSet of conductor at the same potential.
*/
QSet<Conductor *> Conductor::relatedPotentialConductors(const bool all_diagram, QList <Terminal *> *t_list)
{
bool declar_t_list = false;
if (t_list == nullptr)
{
declar_t_list = true;
t_list = new QList <Terminal *>;
}
QSet <Conductor *> other_conductors;
QList <Terminal *> this_terminal;
this_terminal << terminal1 << terminal2;
// Return all conductors of terminal 1 and 2
for (Terminal *terminal : this_terminal)
{
if (!t_list->contains(terminal))
{
t_list->append(terminal);
QList <Conductor *> other_conductors_list_t = terminal->conductors();
//Get the other terminals of the parent element of @terminal, who share the same potential
//This is use for element type "folio report" and "terminal element"
for (Terminal *t : relatedPotentialTerminal(terminal, all_diagram))
{
if (!t_list->contains(t))
{
t_list -> append(t);
other_conductors_list_t += t->conductors();
}
}
other_conductors_list_t.removeAll(this);
//Get the conductors at the same potential for each conductors of other_conductors_list_t
for (Conductor *c : other_conductors_list_t) {
other_conductors += c->relatedPotentialConductors(all_diagram, t_list);
}
other_conductors += other_conductors_list_t.toSet();
}
}
other_conductors.remove(this);
if (declar_t_list) delete t_list;
return(other_conductors);
}
/**
* @brief Conductor::diagramEditor
* @return The parent diagram editor or nullptr;
*/
QETDiagramEditor* Conductor::diagramEditor() const {
if (!diagram()) return nullptr;
if (diagram() -> views().isEmpty()) return nullptr;
QWidget *w = const_cast<QGraphicsView *>(diagram() -> views().at(0));
while (w -> parentWidget() && !w -> isWindow()) {
w = w -> parentWidget();
}
return(qobject_cast<QETDiagramEditor *>(w));
}
/**
* @brief Conductor::editProperty
*/
void Conductor::editProperty() {
ConductorPropertiesDialog::PropertiesDialog(this, diagramEditor());
}
void Conductor::setSequenceNum(const autonum::sequentialNumbers& sn)
{
m_autoNum_seq = sn;
refreshText();
}
/**
* @brief Conductor::setUpConnectionForFormula
* setup connection according to the variable of formula
* @param old_formula
* @param new_formula
*/
void Conductor::setUpConnectionForFormula(QString old_formula, QString new_formula)
{
if (diagram())
{
//Because the variable %F is a reference to another text which can contain variables,
//we must to replace %F by the real text, to check if the real text contain the variable %id
if (old_formula.contains("%F"))
old_formula.replace("%F", diagram()->border_and_titleblock.folio());
if (old_formula.contains("%id"))
disconnect(diagram()->project(), &QETProject::projectDiagramsOrderChanged, this, &Conductor::refreshText);
//Label is frozen, so we don't update it.
if (m_freeze_label == true)
return;
if (new_formula.contains("%F"))
new_formula.replace("%F", diagram()->border_and_titleblock.folio());
if (new_formula.contains("%id"))
connect(diagram()->project(), &QETProject::projectDiagramsOrderChanged, this, &Conductor::refreshText);
}
}
/**
@param a point
@param b point
@param c point
@return true si le point a est contenu dans le rectangle delimite par les points b et c
*/
bool isContained(const QPointF &a, const QPointF &b, const QPointF &c) {
return(
isBetween(a.x(), b.x(), c.x()) &&
isBetween(a.y(), b.y(), c.y())
);
}
/**
@return la liste des positions des jonctions avec d'autres conducteurs
*/
QList<QPointF> Conductor::junctions() const {
QList<QPointF> junctions_list;
// pour qu'il y ait des jonctions, il doit y avoir d'autres conducteurs et des bifurcations
QList<Conductor *> other_conductors = relatedConductors(this);
QList<ConductorBend> bends_list = bends();
if (other_conductors.isEmpty() || bends_list.isEmpty()) {
return(junctions_list);
}
QList<QPointF> points = segmentsToPoints();
for (int i = 1 ; i < (points.size() -1) ; ++ i) {
QPointF point = points.at(i);
// determine si le point est une bifurcation ou non
bool is_bend = false;
Qt::Corner current_bend_type = Qt::TopLeftCorner;
foreach(ConductorBend cb, bends_list)
{
if (cb.first == point)
{
is_bend = true;
current_bend_type = cb.second;
break;
}
}
// si le point n'est pas une bifurcation, il ne peut etre une jonction (enfin pas au niveau de ce conducteur)
if (!is_bend) continue;
bool is_junction = false;
QPointF scene_point = mapToScene(point);
foreach(Conductor *c, other_conductors)
{
// exprime le point dans les coordonnees de l'autre conducteur
QPointF conductor_point = c -> mapFromScene(scene_point);
// recupere les segments de l'autre conducteur
QList<ConductorSegment *> c_segments = c -> segmentsList();
if (c_segments.isEmpty())
continue;
// parcoure les segments a la recherche d'un point commun
for (int j = 0 ; j < c_segments.count() ; ++ j)
{
ConductorSegment *segment = c_segments[j];
// un point commun a ete trouve sur ce segment
if (isContained(conductor_point, segment -> firstPoint(), segment -> secondPoint()))
{
is_junction = true;
// ce point commun ne doit pas etre une bifurcation identique a celle-ci
QList<ConductorBend> other_conductor_bends = c -> bends();
foreach(ConductorBend cb, other_conductor_bends)
{
if (cb.first == conductor_point && cb.second == current_bend_type)
{
is_junction = false;
}
}
}
if (is_junction) junctions_list << point;
}
}
}
return(junctions_list);
}
/**
@return la liste des bifurcations de ce conducteur ; ConductorBend est un
typedef pour une QPair\<QPointF, Qt::Corner\>. Le point indique la position
(en coordonnees locales) de la bifurcation tandis que le Corner indique le
type de bifurcation.
*/
QList<ConductorBend> Conductor::bends() const {
QList<ConductorBend> points;
if (!segments) return(points);
// recupere la liste des segments de taille non nulle
QList<ConductorSegment *> visible_segments;
ConductorSegment *segment = segments;
while (segment -> hasNextSegment()) {
if (!segment -> isPoint()) visible_segments << segment;
segment = segment -> nextSegment();
}
if (!segment -> isPoint()) visible_segments << segment;
ConductorSegment *next_segment;
for (int i = 0 ; i < visible_segments.count() -1 ; ++ i) {
segment = visible_segments[i];
next_segment = visible_segments[i + 1];
if (!segment -> isPoint() && !next_segment -> isPoint()) {
// si les deux segments ne sont pas dans le meme sens, on a une bifurcation
if (next_segment -> type() != segment -> type()) {
Qt::Corner bend_type;
qreal sl = segment -> length();
qreal nsl = next_segment -> length();
if (segment -> isHorizontal()) {
if (sl < 0 && nsl < 0) {
bend_type = Qt::BottomLeftCorner;
} else if (sl < 0 && nsl > 0) {
bend_type = Qt::TopLeftCorner;
} else if (sl > 0 && nsl < 0) {
bend_type = Qt::BottomRightCorner;
} else {
bend_type = Qt::TopRightCorner;
}
} else {
if (sl < 0 && nsl < 0) {
bend_type = Qt::TopRightCorner;
} else if (sl < 0 && nsl > 0) {
bend_type = Qt::TopLeftCorner;
} else if (sl > 0 && nsl < 0) {
bend_type = Qt::BottomRightCorner;
} else {
bend_type = Qt::BottomLeftCorner;
}
}
points << qMakePair(segment -> secondPoint(), bend_type);
}
}
}
return(points);
}
/**
@param start Point de depart
@param end Point d'arrivee
@return le coin vers lequel se dirige le trajet de start vers end
*/
Qt::Corner Conductor::movementType(const QPointF &start, const QPointF &end) {
Qt::Corner result = Qt::BottomRightCorner;
if (start.x() <= end.x()) {
result = start.y() <= end.y() ? Qt::BottomRightCorner : Qt::TopRightCorner;
} else {
result = start.y() <= end.y() ? Qt::BottomLeftCorner : Qt::TopLeftCorner;
}
return(result);
}
/// @return le type de trajet actuel de ce conducteur
Qt::Corner Conductor::currentPathType() const {
return(movementType(terminal1 -> dockConductor(), terminal2 -> dockConductor()));
}
/// @return les profils de ce conducteur
ConductorProfilesGroup Conductor::profiles() const {
return(conductor_profiles);
}
/**
* @brief Conductor::setProfiles
* @param cpg : the new profils of conductor
*/
void Conductor::setProfiles(const ConductorProfilesGroup &cpg) {
conductor_profiles = cpg;
if (conductor_profiles[currentPathType()].isNull()) {
generateConductorPath(terminal1 -> dockConductor(), terminal1 -> orientation(), terminal2 -> dockConductor(), terminal2 -> orientation());
modified_path = false;
} else {
updateConductorPath(terminal1 -> dockConductor(), terminal1 -> orientation(), terminal2 -> dockConductor(), terminal2 -> orientation());
modified_path = true;
}
if (properties().type == ConductorProperties::Multi) {
calculateTextItemPosition();
}
}
/// Supprime les segments
void Conductor::deleteSegments() {
if (segments != nullptr) {
while (segments -> hasNextSegment()) delete segments -> nextSegment();
delete segments;
segments = nullptr;
}
}
/**
@param point Un point situe a l'exterieur du polygone
@param polygon Le polygone dans lequel on veut rapatrier le point
@return la position du point, une fois ramene dans le polygone, ou plus
exactement sur le bord du polygone
*/
QPointF Conductor::movePointIntoPolygon(const QPointF &point, const QPainterPath &polygon) {
// decompose le polygone en lignes et points
QList<QPolygonF> polygons = polygon.simplified().toSubpathPolygons();
QList<QLineF> lines;
QList<QPointF> points;
foreach(QPolygonF polygon, polygons) {
if (polygon.count() <= 1) continue;
// on recense les lignes et les points
for (int i = 1 ; i < polygon.count() ; ++ i) {
lines << QLineF(polygon.at(i - 1), polygon.at(i));
points << polygon.at(i -1);
}
}
// on fait des projetes orthogonaux du point sur les differents segments du
// polygone, en les triant par longueur croissante
QMap<qreal, QPointF> intersections;
foreach (QLineF line, lines) {
QPointF intersection_point;
if (QET::orthogonalProjection(point, line, &intersection_point)) {
intersections.insert(QLineF(intersection_point, point).length(), intersection_point);
}
}
if (intersections.count()) {
// on determine la plus courte longueur pour un projete orthogonal
QPointF the_point = intersections[intersections.keys().first()];
return(the_point);
} else {
// determine le coin du polygone le plus proche du point exterieur
qreal minimum_length = -1;
int point_index = -1;
for (int i = 0 ; i < points.count() ; ++ i) {
qreal length = qAbs(QLineF(points.at(i), point).length());
if (minimum_length < 0 || length < minimum_length) {
minimum_length = length;
point_index = i;
}
}
// on connait desormais le coin le plus proche du texte
// aucun projete orthogonal n'a donne quoi que ce soit, on met le texte sur un des coins du polygone
return(points.at(point_index));
}
}
/**
* @brief longuestConductorInPotential
* @param conductor : a conductor in the potential to search
* @param all_diagram : true -> search in the whole project, false -> search only in the diagram of conductor
* @return the longuest conductor in the same potential of conductor
*/
Conductor * longuestConductorInPotential(Conductor *conductor, bool all_diagram) {
Conductor *longuest_conductor = conductor;
//Search the longuest conductor
foreach (Conductor *c, conductor -> relatedPotentialConductors(all_diagram))
if (c -> length() > longuest_conductor -> length())
longuest_conductor = c;
return longuest_conductor;
}
/**
* @brief relatedConductors
* @param conductor
* @return return all conductors who share the same terminals of @conductor given as parametre,
* except @conductor himself.
*/
QList <Conductor *> relatedConductors(const Conductor *conductor) {
QList<Conductor *> other_conductors_list = conductor -> terminal1 -> conductors();
other_conductors_list << conductor -> terminal2->conductors();
other_conductors_list.removeAll(const_cast<Conductor *> (conductor));
return(other_conductors_list);
}
/**
* @brief Conductor::setFreezeLabel
* Freeze this conductor label if true
* Unfreeze this conductor label if false
* @param freeze
*/
void Conductor::setFreezeLabel(bool freeze)
{
m_freeze_label = freeze;
if (m_freeze_label != freeze)
{
m_freeze_label = freeze;
QString f = m_properties.m_formula;
setUpConnectionForFormula(f,f);
}
}