diff --git a/Canvas.cpp b/Canvas.cpp index ed62825..d3e9b89 100644 --- a/Canvas.cpp +++ b/Canvas.cpp @@ -1,508 +1,590 @@ #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)); +// =================================================================== +// Вспомогательные функции +// =================================================================== + +/// Вычислить расстояние между двумя точками +static double dist_P2P(QPointF p1, QPointF p2) +{ + return sqrt(pow(p2.x() - p1.x(), 2) + pow(p2.y() - p1.y(), 2)); } +/// Создать упорядоченную пару (чтобы pair(obj1, obj2) и pair(obj2, obj1) считались одинаковыми) template T makeOrderedPair(A* obj1, B* obj2) { - return (obj1 < obj2) ? std::make_pair(obj1, obj2) : std::make_pair(obj2, obj1); + return (obj1 < obj2) ? std::make_pair(obj1, obj2) : std::make_pair(obj2, obj1); } -void Canvas::changeMode(Mode _mode) +// =================================================================== +// Конструктор и деструктор +// =================================================================== + +Canvas::Canvas(QWidget* parent) + : QWidget(parent) { - 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::critical(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); + sys = System(); + setMouseTracking(true); + setBackgroundRole(QPalette::Base); + setAutoFillBackground(true); } Canvas::~Canvas() { - for (double* param : params) { - delete param; - } + // Очистка динамически выделенной памяти - for (Point* pt : points) { - delete pt; - } + for (double* param : params) { + delete param; + } - for (Line* line : lines) { - delete line; - } + for (Point* pt : points) { + delete pt; + } - params.clear(); - points.clear(); - lines.clear(); + for (Line* line : lines) { + delete line; + } - parallelPairs.clear(); - P2Ppairs.clear(); - VERT_pairs.clear(); - HORIZ_pairs.clear(); + // Очистка контейнеров + params.clear(); + points.clear(); + lines.clear(); - if (current_line) - delete current_line; - if (firstPoint) - delete firstPoint; + parallelPairs.clear(); + P2Ppairs.clear(); + VERT_pairs.clear(); + HORIZ_pairs.clear(); + + // Очистка временных указателей + if (current_line) + delete current_line; + if (firstPoint) + delete firstPoint; } +// =================================================================== +// Методы изменения режима +// =================================================================== + +void Canvas::changeMode(Mode _mode) +{ + mode = _mode; +} + +// =================================================================== +// Методы поиска и проверки +// =================================================================== + +Line* Canvas::findAt(QPointF& pos) +{ + // TODO: реализовать проверку, находится ли точка на линии + 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)); +} + +bool Canvas::areHorizontalVertical(Point* p1, Point* p2, bool mode) +{ + // mode = false - проверка горизонтальности + // mode = true - проверка вертикальности + 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 + + // ====================== Режим None: перемещение объектов ====================== + 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; + } + } + + // ====================== Режим Horizontal/Vertical ====================== + 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; + } + + // ====================== Режим DrawingLine: рисование линий ====================== + 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; + } + + // ====================== Режим Parallel: задание параллельности ====================== + 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; + } + } + + // ====================== Режим Coincedent: совпадение точек ====================== + 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; + Line* 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::critical(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)) { // vertical + *pair->x = pos.x(); + } + if (areHorizontalVertical(draggedPoint, pair, false)) { // horizontal + *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) +{ + Q_UNUSED(event); + + if (draggedPoint) { + draggedPoint = nullptr; + update(); + } + + if (draggedLine) { + draggedLine = nullptr; + update(); + } +} + +// =================================================================== +// Метод отрисовки +// =================================================================== + +void Canvas::paintEvent(QPaintEvent* event) +{ + Q_UNUSED(event); + + 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); + } + + // ====================== Подсветка выбранной точки (режим Coincedent) ====================== + 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); + } +} \ No newline at end of file diff --git a/Canvas.h b/Canvas.h index ec40cd3..d39d906 100644 --- a/Canvas.h +++ b/Canvas.h @@ -14,18 +14,25 @@ #include "GCS/GCS.h" using namespace GCS; +// =================================================================== +// Типы и перечисления +// =================================================================== + +/// Режимы работы с холстом enum class Mode : int { - None = 0, - DrawingLine = 1, - Parallel = 2, - Coincedent = 3, - Horizontal = 4, - Vertical = 5 + None = 0, ///< Режим отсутствия действия + DrawingLine = 1, ///< Режим рисования линии + Parallel = 2, ///< Режим задания параллельности + Coincedent = 3, ///< Режим задания совпадения точек + Horizontal = 4, ///< Режим задания горизонтальности + Vertical = 5 ///< Режим задания вертикальности }; -// Удобный тип для хранения пары параллельных линий (порядок не важен) +/// Удобный тип для хранения пары параллельных линий (порядок не важен) using LinePair = std::pair; + +/// Удобный тип для хранения пары точек (порядок не важен) using PointPair = std::pair; // =================================================================== @@ -40,54 +47,89 @@ public: explicit Canvas(QWidget* parent = nullptr); ~Canvas() override; + /// Изменить текущий режим работы void changeMode(Mode newMode); protected: + // Обработчики событий Qt void mousePressEvent(QMouseEvent* event) override; void mouseMoveEvent(QMouseEvent* event) override; void mouseReleaseEvent(QMouseEvent* event) override; void paintEvent(QPaintEvent* event) override; private: - // ====================== Поиск и выбор ====================== - Line* findAt(QPointF&); // ищет линию под курсором - Point* findPointAt(QPointF, qreal tolerance = 5.0); - bool areCoincident(Point*, Point*); - bool areHorizontalVertical(Point*, Point*, bool); - bool isLineHorizontal(Line* line); // проверяет, горизонтальна ли линия - bool isLineVertical(Line* line); // проверяет, вертикальна ли линия - // ====================== Параллельность ====================== - bool areAlreadyParallel(Line* l1, Line* l2); // проверка на дубликат + // ====================== Методы поиска и выбора ====================== - // ====================== Перемещение ====================== - Point* draggedPoint{ nullptr }; - Line* draggedLine{ nullptr }; - QPointF dragOffset; + /// Найти линию под указанной позицией + Line* findAt(QPointF& position); - // ====================== Работа с парами ограничений ====================== + /// Найти точку в указанной позиции с заданной точностью + Point* findPointAt(QPointF position, qreal tolerance = 5.0); + + /// Проверить, совпадают ли две точки (ограничение P2P) + bool areCoincident(Point* point1, Point* point2); + + /// Проверить горизонтальность или вертикальность между двумя точками + /// @param mode: false - горизонтальность, true - вертикальность + bool areHorizontalVertical(Point* point1, Point* point2, bool mode); + + /// Проверить, является ли линия горизонтальной + bool isLineHorizontal(Line* line); + + /// Проверить, является ли линия вертикальной + bool isLineVertical(Line* line); + + /// Проверить, являются ли две линии уже параллельными (дубликат ограничения) + bool areAlreadyParallel(Line* line1, Line* line2); + + // ====================== Методы работы с ограничениями ====================== + + /// Удалить последние добавленные ограничения при ошибке солвера void remove_constraints(); - // ====================== Данные сцены ====================== - System sys; // геометрический солвер - QVector lines; // завершённые линии - QVector points; // все точки (для удобного доступа) - std::vector params; // все параметры, передаваемые в солвер - std::set parallelPairs; // уже запараллеленные пары (защита от дублей) - std::set P2Ppairs; - std::set HORIZ_pairs; - std::set VERT_pairs; + // ====================== Данные для перемещения объектов ====================== - Line* current_line{ nullptr }; - Point* firstPoint{ nullptr }; - Mode mode{ Mode::None }; - bool after_constraint{ false }; + Point* draggedPoint{ nullptr }; ///< Точка, которую перемещают + Line* draggedLine{ nullptr }; ///< Линия, которую перемещают + QPointF dragOffset; ///< Смещение при начале перемещения - int obj_count{ 0 }; // тег для новых объектов - int constraints_count{ 0 }; // тег для новых ограничений + // ====================== Данные геометрической системы ====================== + System sys; ///< Геометрический солвер + QVector lines; ///< Завершённые линии + QVector points; ///< Все точки сцены + std::vector params; ///< Все параметры, передаваемые в солвер + + // ====================== Коллекции ограничений ====================== + + std::set parallelPairs; ///< Пары параллельных линий + std::set P2Ppairs; ///< Пары совпадающих точек + std::set HORIZ_pairs; ///< Пары точек горизонтальных линий + std::set VERT_pairs; ///< Пары точек вертикальных линий + + // ====================== Временные данные для режимов ====================== + + Line* current_line{ nullptr }; ///< Текущая линия в режимах рисования/параллельности + Point* firstPoint{ nullptr }; ///< Первая точка в режиме совпадения + Mode mode{ Mode::None }; ///< Текущий режим работы + + // ====================== Флаги состояния ====================== + + bool after_constraint{ false }; ///< Флаг, что только что добавлено ограничение + + // ====================== Счётчики ====================== + + int obj_count{ 0 }; ///< Счётчик объектов (для тегов) + int constraints_count{ 0 }; ///< Счётчик ограничений (для тегов) + + // ====================== Информация о последнем ограничении ====================== + + /// Структура для хранения информации о последнем добавленном ограничении + /// (используется для отката при ошибке солвера) struct LastConstraint { - Mode mode{ Mode::None }; - std::variant data; + Mode mode{ Mode::None }; ///< Тип последнего ограничения + std::variant data; ///< Данные ограничения }; - LastConstraint lastConstraint; + + LastConstraint lastConstraint; ///< Информация о последнем добавленном ограничении }; \ No newline at end of file