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
T

705 lines
23 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));
}
/// Создать упорядоченную пару (чтобы 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));
}
bool Canvas::areAlreadyPerpendicular(Line* line1, Line* line2)
{
return perpendicularPairs.count(makeOrderedPair<LinePair>(line1, line2));
}
// ===================================================================
// Методы работы с ограничениями
// ===================================================================
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;
}
else if (mode == Mode::Perpendicular) {
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 (!areAlreadyPerpendicular(found, current_line)) {
auto pair = makeOrderedPair<LinePair>(found, current_line);
sys.addConstraintPerpendicular(*found, *current_line, constraints_count++);
perpendicularPairs.insert(pair);
current_line = nullptr;
mode = Mode::None;
after_constraint = true;
update();
}
else {
QMessageBox::warning(this,
QString("Wrong"),
QString("Perpendicular lines can not be more perpendicular!"),
QMessageBox::Ok
);
current_line = nullptr;
update();
return;
}
}
}
void Canvas::mouseMoveEvent(QMouseEvent* event)
{
// ====================== Перемещение точки ======================
if (draggedPoint) {
QPointF pos = UCS_POSITION - dragOffset;
auto coincidentGroup = getCoincidentGroup(draggedPoint);
for (Point* pt : coincidentGroup) {
*pt->x = pos.x();
*pt->y = pos.y();
}
for (Point* basePt : coincidentGroup) {
for (Point* other : points) {
if (areHorizontalVertical(basePt, other, true)) {
*other->x = pos.x();
}
if (areHorizontalVertical(basePt, other, false)) {
*other->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.scale(1.0, -1.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);
}
}
void Canvas::leaveEvent(QEvent* event)
{
Q_UNUSED(event);
QToolTip::hideText();
}
#ifdef _DEBUG
void Canvas::showObjectTag(QPointF pos)
{
QPointF l = screenToLogical(pos);
Line* lineUnderCursor = findAt(l, 1.0);
if (lineUnderCursor && lineUnderCursor != draggedLine) {
QPointF p1(*lineUnderCursor->p1.x, *lineUnderCursor->p1.y);
QPointF p2(*lineUnderCursor->p2.x, *lineUnderCursor->p2.y);
QLineF info_line(p1, p2);
QString Text = QString("Line %1\nLength = %2mm")
.arg(lineUnderCursor->get_tag() + 1)
.arg(info_line.length());
QToolTip::showText(mapToGlobal(pos.toPoint()), Text, this);
}
else
QToolTip::hideText();
}
#endif
QPointF Canvas::screenToLogical(const QPointF& screenPos) const
{
QPointF logical = screenPos;
logical.rx() -= width() / 2.0;
logical.ry() -= height() / 2.0;
logical.ry() = -logical.ry();
return logical;
}
std::vector<Point*> Canvas::getCoincidentGroup(Point* p)
{
std::vector<Point*> group;
group.push_back(p);
for (Point* other : points) {
if (other != p && areCoincident(p, other)) {
group.push_back(other);
}
}
return group;
}