This repository has been archived on 2026-05-28. You can view files and clone it. You cannot open issues or pull requests or push a commit.
Files
DRAwer_2_0/Canvas.cpp

621 lines
20 KiB
C++
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#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));
}
/// Создать упорядоченную пару (чтобы pair(obj1, obj2) и pair(obj2, obj1) считались одинаковыми)
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);
}
// ===================================================================
// Конструктор и деструктор
// ===================================================================
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;
}
// ===================================================================
// Методы изменения режима
// ===================================================================
void Canvas::changeMode(Mode _mode)
{
mode = _mode;
}
// ===================================================================
// Методы поиска и проверки
// ===================================================================
Line* Canvas::findAt(QPointF& pos, qreal tolerance)
{
// TODO: реализовать проверку, находится ли точка на линии
for (Line* line : lines) {
if (line->contains(pos, tolerance)) {
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));
}
bool Canvas::areHorizontalVertical(Point* p1, Point* p2, bool mode)
{
// mode = false - проверка горизонтальности
// mode = true - проверка вертикальности
if (!mode)
return HORIZ_pairs.count(makeOrderedPair<PointPair>(p1, p2));
else
return VERT_pairs.count(makeOrderedPair<PointPair>(p1, p2));
}
bool Canvas::isLineHorizontal(Line* line)
{
if (!line || !line->start_ref || !line->end_ref)
return false;
return HORIZ_pairs.count(makeOrderedPair<PointPair>(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<PointPair>(line->start_ref, line->end_ref));
}
bool Canvas::areAlreadyParallel(Line* l1, Line* l2)
{
return parallelPairs.count(makeOrderedPair<LinePair>(l1, l2));
}
// ===================================================================
// Методы работы с ограничениями
// ===================================================================
void Canvas::remove_constraints()
{
// Удаляем ограничение из солвера
sys.clearByTag(constraints_count - 1);
// Удаляем из соответствующих контейнеров в зависимости от типа ограничения
switch (lastConstraint.mode) {
case Mode::Horizontal: {
if (auto* pair = std::get_if<PointPair>(&lastConstraint.data)) {
HORIZ_pairs.erase(*pair);
}
break;
}
case Mode::Vertical: {
if (auto* pair = std::get_if<PointPair>(&lastConstraint.data)) {
VERT_pairs.erase(*pair);
}
break;
}
case Mode::Parallel: {
if (auto* pair = std::get_if<LinePair>(&lastConstraint.data)) {
parallelPairs.erase(*pair);
}
break;
}
case Mode::Coincedent: {
if (auto* pair = std::get_if<PointPair>(&lastConstraint.data)) {
P2Ppairs.erase(*pair);
}
break;
}
default:
break;
}
// Сбрасываем информацию о последнем ограничении
lastConstraint.mode = Mode::None;
constraints_count--;
}
// ===================================================================
// Обработчики событий мыши
// ===================================================================
void Canvas::mousePressEvent(QMouseEvent* event)
{
QPointF scene = UCS_POSITION;
#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<PointPair>(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<PointPair>(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<LinePair>(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<PointPair>(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 = UCS_POSITION - 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 = UCS_POSITION - 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();
}
#ifdef _DEBUG
else
showObjectTag(WIDGET_POSITION);
#endif
}
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);
p.translate(width() / 2.0, height() / 2.0);
p.setPen(Qt::red);
p.drawLine(-5, 0, 5, 0);
p.drawLine(0, -5, 0, 5);
// ====================== Решение системы уравнений ======================
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);
}
}
#ifdef _DEBUG
void Canvas::showObjectTag(QPointF pos)
{
QPointF l = screenToLogical(pos);
Line* lineUnderCursor = findAt(l, 2.0);
if (lineUnderCursor && lineUnderCursor != draggedLine) {
QString Text = QString("Tag Line: %1").arg(lineUnderCursor->get_tag());
QToolTip::showText(mapToGlobal(pos.toPoint()), Text, this);
}
}
#endif
QPointF Canvas::screenToLogical(const QPointF& screenPos) const
{
QPointF logical = screenPos;
logical.rx() -= width() / 2.0;
logical.ry() -= height() / 2.0;
return logical;
}