372 lines
8.6 KiB
C++
372 lines
8.6 KiB
C++
#include "Canvas.h"
|
||
|
||
static double dist_P2P(QPointF p1, QPointF p2) {
|
||
return sqrt(pow(p2.x() - p1.x(), 2) + pow(p2.y() - p1.y(), 2));
|
||
}
|
||
|
||
template <typename T, typename A, typename B>
|
||
T makeOrderedPair(A* obj1, B* obj2)
|
||
{
|
||
return (obj1 < obj2) ? std::make_pair(obj1, obj2) : std::make_pair(obj2, obj1);
|
||
}
|
||
|
||
void Canvas::changeMode(Mode _mode)
|
||
{
|
||
mode = _mode;
|
||
}
|
||
|
||
// TODO
|
||
Line* Canvas::findAt(QPointF& pos)
|
||
{
|
||
for (Line* line : lines) {
|
||
if (line->contains(pos)) {
|
||
return line;
|
||
}
|
||
}
|
||
return nullptr;
|
||
}
|
||
|
||
Point* Canvas::findPointAt(QPointF pos, qreal tolerance)
|
||
{
|
||
Point* temp = nullptr;
|
||
for (Line* line : lines) {
|
||
QPointF p1(*line->p1.x, *line->p1.y);
|
||
QPointF p2(*line->p2.x, *line->p2.y);
|
||
|
||
if (dist_P2P(p1, pos) <= tolerance)
|
||
temp = line->start_ref;
|
||
if (dist_P2P(p2, pos) <= tolerance)
|
||
temp = line->end_ref;
|
||
}
|
||
return temp;
|
||
}
|
||
|
||
bool Canvas::areCoincident(Point* p1, Point* p2)
|
||
{
|
||
return P2Ppairs.count(makeOrderedPair<PointPair>(p1, p2));
|
||
}
|
||
|
||
// False - Horizonta, True - Vertical
|
||
bool Canvas::areHorizontalVertical(Point* p1, Point* p2, bool mode)
|
||
{
|
||
if (!mode)
|
||
return HORIZ_pairs.count(makeOrderedPair<PointPair>(p1, p2));
|
||
else
|
||
return VERT_pairs.count(makeOrderedPair<PointPair>(p1, p2));
|
||
}
|
||
|
||
bool Canvas::areAlreadyParallel(Line* l1, Line* l2)
|
||
{
|
||
return parallelPairs.count(makeOrderedPair<LinePair>(l1, l2));
|
||
}
|
||
|
||
void Canvas::mousePressEvent(QMouseEvent* event)
|
||
{
|
||
QPointF scene = event->pos();
|
||
|
||
#ifdef _DEBUG
|
||
qDebug() << "Scene point in" << scene.x() << scene.y();
|
||
#endif
|
||
|
||
if (mode == Mode::None){
|
||
Point* p = findPointAt(scene);
|
||
if (p) {
|
||
draggedPoint = p;
|
||
dragOffset = scene - QPointF(*p->x, *p->y);
|
||
}
|
||
}
|
||
|
||
else if (mode == Mode::Horizontal || mode == Mode::Vertical) {
|
||
Line* found = findAt(scene);
|
||
|
||
if (found) {
|
||
if (mode == Mode::Horizontal) {
|
||
sys.addConstraintHorizontal(*found, constraints_count++);
|
||
HORIZ_pairs.insert(makeOrderedPair<PointPair>(found->start_ref, found->end_ref));
|
||
}
|
||
else {
|
||
sys.addConstraintVertical(*found, constraints_count++);
|
||
VERT_pairs.insert(makeOrderedPair<PointPair>(found->start_ref, found->end_ref));
|
||
}
|
||
update();
|
||
}
|
||
mode = Mode::None;
|
||
after_constraint = true;
|
||
}
|
||
|
||
else if (mode == Mode::DrawingLine) {
|
||
if (!current_line) {
|
||
current_line = new Line();
|
||
current_line->set_tag(obj_count);
|
||
double* x1 = new double(scene.x());
|
||
double* y1 = new double(scene.y());
|
||
|
||
double* x2 = new double(scene.x());
|
||
double* y2 = new double(scene.y());
|
||
|
||
params.push_back(x1);
|
||
params.push_back(y1);
|
||
|
||
points.push_back(new Point(x1, y1, obj_count));
|
||
points.push_back(new Point(x2, y2, obj_count));
|
||
|
||
current_line->p1.x = x1;
|
||
current_line->p1.y = y1;
|
||
current_line->p2.x = x2;
|
||
current_line->p2.y = y2;
|
||
current_line->start_ref = points[points.size() - 2];
|
||
current_line->end_ref = points[points.size() - 1];
|
||
}
|
||
else {
|
||
*current_line->p2.x = scene.x();
|
||
*current_line->p2.y = scene.y();
|
||
|
||
// Временный ограничитель длины линии
|
||
double len = sqrt(pow(*current_line->p2.x - *current_line->p1.x, 2) + pow(*current_line->p2.y - *current_line->p1.y, 2));
|
||
if (len < 10) {
|
||
delete current_line;
|
||
current_line = nullptr;
|
||
|
||
delete points.back();
|
||
points.pop_back();
|
||
delete points.back();
|
||
points.pop_back();
|
||
delete params.back();
|
||
params.pop_back();
|
||
delete params.back();
|
||
params.pop_back();
|
||
|
||
mode = Mode::None;
|
||
QMessageBox::critical(this, "WHOOPS", "Sorry, your line is very short", QMessageBox::Ok);
|
||
update();
|
||
return;
|
||
}
|
||
|
||
params.push_back(current_line->p2.x);
|
||
params.push_back(current_line->p2.y);
|
||
|
||
lines.append(current_line);
|
||
current_line = nullptr;
|
||
mode = Mode::None;
|
||
obj_count++;
|
||
after_constraint = true;
|
||
}
|
||
update();
|
||
return;
|
||
}
|
||
|
||
else if (mode == Mode::Parallel) {
|
||
Line* found = findAt(scene);
|
||
|
||
if (!found) {
|
||
current_line = nullptr;
|
||
update();
|
||
return;
|
||
}
|
||
|
||
if (!current_line) {
|
||
current_line = found;
|
||
update();
|
||
return;
|
||
}
|
||
|
||
if (found == current_line){
|
||
current_line = nullptr;
|
||
update();
|
||
return;
|
||
}
|
||
|
||
if (!areAlreadyParallel(found, current_line)) {
|
||
sys.addConstraintParallel(*found, *current_line, constraints_count++);
|
||
parallelPairs.insert(makeOrderedPair<LinePair>(found, current_line));
|
||
current_line = nullptr;
|
||
update();
|
||
mode = Mode::None;
|
||
after_constraint = true;
|
||
}
|
||
else {
|
||
|
||
#ifdef _DEBUG
|
||
qDebug() << "Line" << current_line << "and" << found << "are parallel. Abort!";
|
||
#endif
|
||
QMessageBox::warning(this,
|
||
QString("Wrong"),
|
||
QString("Parallel lines can not be more parallel!"),
|
||
QMessageBox::Ok
|
||
);
|
||
current_line = nullptr;
|
||
update();
|
||
return;
|
||
}
|
||
|
||
update();
|
||
}
|
||
|
||
else if (mode == Mode::Coincedent) {
|
||
Point* clickedPoint = findPointAt(scene);
|
||
if (!clickedPoint) {
|
||
firstPoint = nullptr;
|
||
update();
|
||
return;
|
||
}
|
||
|
||
if (!firstPoint) {
|
||
firstPoint = clickedPoint;
|
||
update();
|
||
return;
|
||
}
|
||
|
||
if (clickedPoint == firstPoint) {
|
||
firstPoint = nullptr;
|
||
update();
|
||
return;
|
||
}
|
||
|
||
Line *l1 = nullptr, *l2 = nullptr;
|
||
for (Line* l : lines) {
|
||
if (l->start_ref == firstPoint || l->end_ref == firstPoint) l1 = l;
|
||
if (l->start_ref == clickedPoint || l->end_ref == clickedPoint) l2 = l;
|
||
}
|
||
|
||
if (l1 == l2 && l1 && l2 ||
|
||
(areCoincident(firstPoint, clickedPoint)))
|
||
{
|
||
QMessageBox::warning(this, QString("NO!"), QString("P2P failed"));
|
||
firstPoint = nullptr;
|
||
mode = Mode::None;
|
||
update();
|
||
return;
|
||
}
|
||
|
||
sys.addConstraintP2PCoincident(*clickedPoint, *firstPoint, constraints_count++);
|
||
P2Ppairs.insert(makeOrderedPair<PointPair>(clickedPoint, firstPoint));
|
||
firstPoint = nullptr;
|
||
mode = Mode::None;
|
||
after_constraint = true;
|
||
update();
|
||
return;
|
||
}
|
||
}
|
||
|
||
void Canvas::mouseMoveEvent(QMouseEvent* event)
|
||
{
|
||
if (draggedPoint) {
|
||
QPointF pos = event->pos() - dragOffset;
|
||
for (Point* pair : points) {
|
||
if (areCoincident(draggedPoint, pair)) {
|
||
*pair->x = pos.x();
|
||
*pair->y = pos.y();
|
||
}
|
||
if (areHorizontalVertical(draggedPoint, pair, true)) {
|
||
*pair->x = pos.x();
|
||
}
|
||
if (areHorizontalVertical(draggedPoint, pair, false)) {
|
||
*pair->y = pos.y();
|
||
}
|
||
}
|
||
*draggedPoint->x = pos.x();
|
||
*draggedPoint->y = pos.y();
|
||
update();
|
||
}
|
||
}
|
||
|
||
void Canvas::mouseReleaseEvent(QMouseEvent* event)
|
||
{
|
||
if (draggedPoint) {
|
||
draggedPoint = nullptr;
|
||
update();
|
||
}
|
||
}
|
||
|
||
void Canvas::paintEvent(QPaintEvent*)
|
||
{
|
||
QPainter p(this);
|
||
p.setRenderHint(QPainter::Antialiasing, true);
|
||
|
||
// === Решаем систему один раз ===
|
||
if (!params.empty()) {
|
||
int res = sys.solve(params);
|
||
if (res == SolveStatus::Success || res == SolveStatus::Converged) {
|
||
sys.applySolution();
|
||
}
|
||
else if (res == SolveStatus::Failed && after_constraint){
|
||
QMessageBox::warning(this, QString("Error!"), QString("Last constraint is unavailable!"));
|
||
sys.removeConstraint(sys.get_last_constraint());
|
||
constraints_count--;
|
||
}
|
||
after_constraint = false;
|
||
}
|
||
|
||
for (Line* line : lines) {
|
||
bool isSelected = (mode == Mode::Parallel && line == current_line);
|
||
|
||
QPen linePen = isSelected ? QPen(Qt::red, 4) : QPen(Qt::black, 2);
|
||
p.setPen(linePen);
|
||
|
||
// Рисуем саму линию
|
||
p.drawLine(QPointF(*line->p1.x, *line->p1.y),
|
||
QPointF(*line->p2.x, *line->p2.y));
|
||
|
||
// Рисуем концы — с учётом выделения
|
||
QBrush pointBrush = isSelected ? QBrush(Qt::red) : QBrush(Qt::darkBlue);
|
||
p.setBrush(pointBrush);
|
||
p.setPen(Qt::NoPen); // убираем обводку у кружков
|
||
|
||
p.drawEllipse(QPointF(*line->p1.x, *line->p1.y), 5, 5);
|
||
p.drawEllipse(QPointF(*line->p2.x, *line->p2.y), 5, 5);
|
||
}
|
||
|
||
// === Подсветка выбранной точки в режиме Coincident ===
|
||
if (mode == Mode::Coincedent && firstPoint) {
|
||
QPointF pt(*firstPoint->x, *firstPoint->y);
|
||
p.setPen(Qt::NoPen);
|
||
p.setBrush(Qt::red);
|
||
p.drawEllipse(pt, 12, 12);
|
||
p.setBrush(Qt::white);
|
||
p.drawEllipse(pt, 8, 8);
|
||
p.setBrush(Qt::red);
|
||
p.drawEllipse(pt, 4, 4); // маленький центр
|
||
}
|
||
|
||
// === Текущая рисуемая линия (DrawingLine) ===
|
||
if (current_line && mode == Mode::DrawingLine) {
|
||
// Концы текущей линии — синие точки
|
||
p.setBrush(Qt::blue);
|
||
p.setPen(Qt::NoPen);
|
||
p.drawEllipse(QPointF(*current_line->p1.x, *current_line->p1.y), 6, 6);
|
||
}
|
||
}
|
||
|
||
Canvas::Canvas(QWidget *parent)
|
||
: QWidget(parent)
|
||
{
|
||
sys = System();
|
||
current_line = nullptr;
|
||
setMouseTracking(true);
|
||
setBackgroundRole(QPalette::Base);
|
||
setAutoFillBackground(true);
|
||
}
|
||
|
||
Canvas::~Canvas()
|
||
{
|
||
for (Line* line : lines) {
|
||
delete line;
|
||
}
|
||
|
||
for (double* param : params) {
|
||
delete param;
|
||
}
|
||
|
||
lines.clear();
|
||
params.clear();
|
||
points.clear();
|
||
parallelPairs.clear();
|
||
P2Ppairs.clear();
|
||
|
||
if (current_line)
|
||
delete current_line;
|
||
if (firstPoint)
|
||
delete firstPoint;
|
||
}
|
||
|