#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 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(p1, p2)); } // False - Horizonta, True - Vertical bool Canvas::areHorizontalVertical(Point* p1, Point* p2, bool mode) { if (!mode) return HORIZ_pairs.count(makeOrderedPair(p1, p2)); else return VERT_pairs.count(makeOrderedPair(p1, p2)); } bool Canvas::isLineHorizontal(Line* line) { if (!line || !line->start_ref || !line->end_ref) return false; return HORIZ_pairs.count(makeOrderedPair(line->start_ref, line->end_ref)); } bool Canvas::isLineVertical(Line* line) { if (!line || !line->start_ref || !line->end_ref) return false; return VERT_pairs.count(makeOrderedPair(line->start_ref, line->end_ref)); } bool Canvas::areAlreadyParallel(Line* l1, Line* l2) { return parallelPairs.count(makeOrderedPair(l1, l2)); } void Canvas::remove_constraints() { sys.clearByTag(constraints_count - 1); switch (lastConstraint.mode) { case Mode::Horizontal: { if (auto* pair = std::get_if(&lastConstraint.data)) { HORIZ_pairs.erase(*pair); } break; } case Mode::Vertical: { if (auto* pair = std::get_if(&lastConstraint.data)) { VERT_pairs.erase(*pair); } break; } case Mode::Parallel: { if (auto* pair = std::get_if(&lastConstraint.data)) { parallelPairs.erase(*pair); } break; } case Mode::Coincedent: { if (auto* pair = std::get_if(&lastConstraint.data)) { P2Ppairs.erase(*pair); } break; } default: break; } lastConstraint.mode = Mode::None; constraints_count--; } 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); return; } Line* found = findAt(scene); if (found) { draggedLine = found; QPointF lineCenter( (*found->p1.x + *found->p2.x) / 2.0, (*found->p1.y + *found->p2.y) / 2.0 ); dragOffset = scene - lineCenter; return; } } else if (mode == Mode::Horizontal || mode == Mode::Vertical) { Line* found = findAt(scene); if (found) { if (mode == Mode::Horizontal && isLineVertical(found)) { QMessageBox::warning(this, QString("Невозможно"), QString("Эта линия уже вертикальна и не может быть горизонтальной!"), QMessageBox::Ok ); mode = Mode::None; return; } // Проверка: если линия уже горизонтальна, нельзя сделать её вертикальной if (mode == Mode::Vertical && isLineHorizontal(found)) { QMessageBox::warning(this, QString("Невозможно"), QString("Эта линия уже горизонтальна и не может быть вертикальной!"), QMessageBox::Ok ); mode = Mode::None; return; } if (mode == Mode::Horizontal) { sys.addConstraintHorizontal(*found, constraints_count++); auto pair = makeOrderedPair(found->start_ref, found->end_ref); HORIZ_pairs.insert(pair); lastConstraint.mode = Mode::Horizontal; lastConstraint.data = pair; } else { sys.addConstraintVertical(*found, constraints_count++); auto pair = makeOrderedPair(found->start_ref, found->end_ref); VERT_pairs.insert(pair); lastConstraint.mode = Mode::Vertical; lastConstraint.data = pair; } update(); after_constraint = true; } mode = Mode::None; } 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)) { auto pair = makeOrderedPair(found, current_line); sys.addConstraintParallel(*found, *current_line, constraints_count++); parallelPairs.insert(pair); lastConstraint.mode = Mode::Parallel; lastConstraint.data = pair; current_line = nullptr; mode = Mode::None; after_constraint = true; update(); } 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++); auto pair = makeOrderedPair(clickedPoint, firstPoint); P2Ppairs.insert(pair); lastConstraint.mode = Mode::Coincedent; lastConstraint.data = pair; 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(); } else if (draggedLine) { QPointF newCenter = event->pos() - dragOffset; QPointF oldCenter( (*draggedLine->p1.x + *draggedLine->p2.x) / 2.0, (*draggedLine->p1.y + *draggedLine->p2.y) / 2.0 ); // Смещение для перемещения double dx = newCenter.x() - oldCenter.x(); double dy = newCenter.y() - oldCenter.y(); Point* linePoints[2] = { draggedLine->start_ref, draggedLine->end_ref }; for (int i = 0; i < 2; i++) { Point* currentPoint = linePoints[i]; *currentPoint->x += dx; *currentPoint->y += dy; for (Point* pair : points) { if (pair != currentPoint && areCoincident(currentPoint, pair)) { *pair->x = *currentPoint->x; *pair->y = *currentPoint->y; } } } update(); } } void Canvas::mouseReleaseEvent(QMouseEvent* event) { if (draggedPoint) { draggedPoint = nullptr; update(); } if (draggedLine) { draggedLine = 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!")); remove_constraints(); } 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(); setMouseTracking(true); setBackgroundRole(QPalette::Base); setAutoFillBackground(true); } Canvas::~Canvas() { for (double* param : params) { delete param; } for (Point* pt : points) { delete pt; } for (Line* line : lines) { delete line; } params.clear(); points.clear(); lines.clear(); parallelPairs.clear(); P2Ppairs.clear(); VERT_pairs.clear(); HORIZ_pairs.clear(); if (current_line) delete current_line; if (firstPoint) delete firstPoint; }