mirror of
https://github.com/qelectrotech/qelectrotech-source-mirror.git
synced 2025-09-14 20:33:05 +02:00
2105 lines
62 KiB
C++
2105 lines
62 KiB
C++
/*
|
|
Copyright 2006-2023 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 "../qetgraphicsitem/conductor.h"
|
|
|
|
#include "../QPropertyUndoCommand/qpropertyundocommand.h"
|
|
#include "../autoNum/numerotationcontextcommands.h"
|
|
#include "../conductorautonumerotation.h"
|
|
#include "../conductorsegment.h"
|
|
#include "../conductorsegmentprofile.h"
|
|
#include "../diagram.h"
|
|
#include "../diagramcommands.h"
|
|
#include "../qetdiagrameditor.h"
|
|
#include "../qetgraphicsitem/terminal.h"
|
|
#include "../ui/conductorpropertiesdialog.h"
|
|
#include "conductortextitem.h"
|
|
#include "element.h"
|
|
#include "../QetGraphicsItemModeler/qetgraphicshandleritem.h"
|
|
#include "../utils/qetutils.h"
|
|
|
|
#include <QMultiHash>
|
|
#include <QtDebug>
|
|
|
|
#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 conductors of each of the two terminals
|
|
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 attribute to 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 profiles are nuls -> we must 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 its 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
|
|
QMultiHash<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.value(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();
|
|
}
|
|
|
|
/**
|
|
* @brief Conductor::extendTerminal
|
|
* @param terminal : point to extend (must be the docking point of a terminal)
|
|
* @param terminal_orientation : the orientation of the terminal
|
|
* @param ext_size : how many to extrend (10 by default)
|
|
* @return the point with an extension of @ext_size
|
|
* and rounded to nearest multiple of ten
|
|
* in order to be snapped to the grid of the diagram.
|
|
*/
|
|
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(),
|
|
std::round((terminal.y() - ext_size)/10)*10);
|
|
break;
|
|
case Qet::East:
|
|
extended_terminal = QPointF(std::round((terminal.x() + ext_size)/10)*10,
|
|
terminal.y());
|
|
break;
|
|
case Qet::South:
|
|
extended_terminal = QPointF(terminal.x(),
|
|
std::round((terminal.y() + ext_size)/10)*10);
|
|
break;
|
|
case Qet::West:
|
|
extended_terminal = QPointF(std::round((terminal.x() - ext_size)/10)*10,
|
|
terminal.y());
|
|
break;
|
|
default: extended_terminal = terminal;
|
|
}
|
|
|
|
return(extended_terminal);
|
|
}
|
|
|
|
/**
|
|
Dessine le conducteur sans antialiasing.
|
|
@param painter 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 *painter, const QStyleOptionGraphicsItem *options, QWidget *qw)
|
|
{
|
|
Q_UNUSED(qw);
|
|
painter -> save();
|
|
painter -> 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
|
|
painter -> 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 QT_VERSION < QT_VERSION_CHECK(6, 0, 0) // ### Qt 6: remove
|
|
if (options && options -> levelOfDetail < 1.0)
|
|
#else
|
|
#if TODO_LIST
|
|
#pragma message("@TODO remove code for QT 6 or later")
|
|
#endif
|
|
if (options && options->levelOfDetailFromTransform(painter->worldTransform()) < 1.0)
|
|
#endif
|
|
{
|
|
final_conductor_pen.setCosmetic(true);
|
|
}
|
|
|
|
painter -> setPen(final_conductor_pen);
|
|
|
|
//Draw the conductor
|
|
painter -> 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);
|
|
painter->save();
|
|
painter->setPen(final_conductor_pen);
|
|
painter->drawPath(path());
|
|
painter->restore();
|
|
}
|
|
|
|
if (m_properties.type == ConductorProperties::Single) {
|
|
painter -> setBrush(final_conductor_color);
|
|
m_properties.singleLineProperties.draw(
|
|
painter,
|
|
middleSegment() -> isHorizontal() ? QET::Horizontal : QET::Vertical,
|
|
QRectF(middleSegment() -> middle() - QPointF(12.0, 12.0), QSizeF(24.0, 24.0))
|
|
);
|
|
if (isSelected()) painter -> 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);
|
|
painter -> setPen(final_conductor_pen);
|
|
painter -> setBrush(junction_brush);
|
|
painter -> setRenderHint(QPainter::Antialiasing, true);
|
|
foreach(QPointF point, junctions_list) {
|
|
painter -> drawEllipse(QRectF(point.x() - 1.5, point.y() - 1.5, 3.0, 3.0));
|
|
}
|
|
}
|
|
|
|
painter -> 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
|
|
if (e.hasAttribute("element1")) {
|
|
if (QUuid(e.attribute("element1")).isNull())
|
|
return false;
|
|
if (QUuid(e.attribute("terminal1")).isNull())
|
|
return false;
|
|
} else {
|
|
e.attribute("terminal1").toInt(&conv_ok);
|
|
if (!conv_ok) return(false);
|
|
}
|
|
|
|
// parse l'ordonnee
|
|
if (e.hasAttribute("element2")) {
|
|
if (QUuid(e.attribute("element2")).isNull())
|
|
return false;
|
|
if (QUuid(e.attribute("terminal2")).isNull())
|
|
return false;
|
|
} else {
|
|
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 before 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()), QETUtils::graphicsHandlerSize(this));
|
|
|
|
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 dom_element
|
|
@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_;
|
|
}
|
|
|
|
/**
|
|
@brief Conductor::toXml
|
|
Exporte les caracteristiques du conducteur sous forme d'une element XML.
|
|
@param dom_document :
|
|
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()));
|
|
|
|
// Terminal is uniquely identified by the uuid of the terminal and the element
|
|
if (terminal1->uuid().isNull()) {
|
|
// legacy method to identify the terminal
|
|
dom_element.setAttribute("terminal1", table_adr_id.value(terminal1)); // for backward compatibility
|
|
} else {
|
|
dom_element.setAttribute("element1", terminal1->parentElement()->uuid().toString());
|
|
dom_element.setAttribute("terminal1", terminal1->uuid().toString());
|
|
}
|
|
|
|
if (terminal2->uuid().isNull()) {
|
|
// legacy method to identify the terminal
|
|
dom_element.setAttribute("terminal2", table_adr_id.value(terminal2)); // for backward compatibility
|
|
} else {
|
|
dom_element.setAttribute("element2", terminal2->parentElement()->uuid().toString());
|
|
dom_element.setAttribute("terminal2", terminal2->uuid().toString());
|
|
}
|
|
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 (of the line) from xml file by checking the segments in the 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 segment
|
|
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 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 property
|
|
@param only_text
|
|
Set property 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_properties.m_wire_color = m_properties.m_wire_color;
|
|
other_properties.m_wire_section = m_properties.m_wire_section;
|
|
other_conductor->setProperties(other_properties);
|
|
}
|
|
else
|
|
{
|
|
other_conductor->setProperties(property);
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
@brief Conductor::setProperties
|
|
Set property as current property of conductor
|
|
@param property : 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);
|
|
QFont font = m_text_item->font();
|
|
font.setPointSize(m_properties.text_size);
|
|
m_text_item->setFont(font);
|
|
m_text_item->setColor(m_properties.text_color);
|
|
|
|
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);
|
|
}
|
|
#if QT_VERSION < QT_VERSION_CHECK(5, 14, 0) // ### Qt 6: remove
|
|
other_conductors += other_conductors_list_t.toSet();
|
|
#else
|
|
#if TODO_LIST
|
|
#pragma message("@TODO remove code for QT 5.14 or later")
|
|
#endif
|
|
other_conductors += QSet<Conductor*>(other_conductors_list_t.begin(),other_conductors_list_t.end());
|
|
#endif
|
|
}
|
|
}
|
|
|
|
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)
|
|
{
|
|
Diagram *diagram_ {diagram()};
|
|
if (!diagram_) {
|
|
diagram_ = terminal1->diagram();
|
|
}
|
|
if (diagram_)
|
|
{
|
|
//Because the variable %F is a reference to another text which can contain variables,
|
|
//we must replace %F by the real text, to check if the real text contains 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;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @brief Conductor::movePointIntoPolygon
|
|
* @param point : A point located outside the polygon
|
|
* @param polygon : The polygon in which we want to move the point
|
|
* @return the position of the point, once brought back into the polygon,
|
|
* or more exactly on the edge of the polygon
|
|
*/
|
|
QPointF Conductor::movePointIntoPolygon(const QPointF &point, const QPainterPath &polygon)
|
|
{
|
|
// decomposes the polygon into lines and points
|
|
const QList<QPolygonF> polygons = polygon.simplified().toSubpathPolygons();
|
|
QList<QLineF> lines;
|
|
QList<QPointF> points;
|
|
|
|
for (QPolygonF polygon : polygons)
|
|
{
|
|
if (polygon.count() <= 1)
|
|
continue;
|
|
|
|
// lines and points are counted
|
|
for (int i = 1 ; i < polygon.count() ; ++ i) {
|
|
lines << QLineF(polygon.at(i - 1), polygon.at(i));
|
|
points << polygon.at(i -1);
|
|
}
|
|
}
|
|
|
|
// we make orthogonal projections of the point on the different
|
|
// segments of the polygon, sorting them by increasing length
|
|
QMap<qreal, QPointF> intersections;
|
|
for (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())
|
|
{
|
|
// the shortest length for an orthogonal project is determined
|
|
QPointF the_point = intersections[intersections.keys().first()];
|
|
return(the_point);
|
|
}
|
|
else
|
|
{
|
|
// determines the corner of the polygon closest to the outer point
|
|
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;
|
|
}
|
|
}
|
|
// we now know the closest corner of the text
|
|
|
|
|
|
if (point_index == -1 ||
|
|
point_index+1 > points.size()) {
|
|
return QPointF(0,0);
|
|
}
|
|
|
|
// no orthogonal projection gave anything, we put the text on one of the corners of the polygon
|
|
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 parameter,
|
|
except conductor itself.
|
|
*/
|
|
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);
|
|
}
|
|
}
|