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

887 lines
27 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"
#define WIDGET_POSITION event->pos()
#define UCS_POSITION screenToLogical(WIDGET_POSITION)
std::set<Point*> groups;
// ===================================================================
// Вспомогательные функции
// ===================================================================
/**
* @brief Вычислить расстояние между двумя точками
* @param p1 Первая точка
* @param p2 Вторая точка
* @return Расстояние между точками
*/
static double dist_P2P(QPointF p1, QPointF p2)
{
return sqrt(pow(p2.x() - p1.x(), 2) + pow(p2.y() - p1.y(), 2));
}
/**
* @brief Создать упорядоченную пару
*
* Создает пару, в которой элементы упорядочены по указателям,
* чтобы pair(obj1, obj2) и pair(obj2, obj1) считались одинаковыми
*
* @tparam T Тип возвращаемой пары
* @tparam A Тип первого элемента
* @tparam B Тип второго элемента
* @param obj1 Первый объект
* @param obj2 Второй объект
* @return Упорядоченная пара
*/
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);
setFocusPolicy(Qt::StrongFocus);
setAutoFillBackground(true);
}
Canvas::~Canvas()
{
clearCanvas();
}
// ===================================================================
// Методы изменения режима и масштаба
// ===================================================================
void Canvas::changeMode(Mode _mode)
{
mode = _mode;
}
void Canvas::zoomIn()
{
scaleFactor += ZOOM_STEP;
update();
}
void Canvas::zoomOut()
{
scaleFactor -= ZOOM_STEP;
if (scaleFactor < 0.1) // Минимальный масштаб 10%
scaleFactor = 0.1;
update();
}
void Canvas::zoomReset()
{
scaleFactor = 1.0;
update();
}
// ===================================================================
// Методы поиска и проверки
// ===================================================================
Line* Canvas::findAt(QPointF& pos, qreal tolerance)
{
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_constraint(int tag)
{
auto it = C_Info.find(tag);
if (it == C_Info.end()) return;
ConstraintInfo info = it->second;
// Удаляем ограничение из солвера
sys.clearByTag(tag);
// Удаляем из соответствующих контейнеров в зависимости от типа ограничения
switch (info.mode) {
case Mode::Horizontal: {
if (auto* pair = std::get_if<PointPair>(&info.data)) {
HORIZ_pairs.erase(*pair);
}
break;
}
case Mode::Vertical: {
if (auto* pair = std::get_if<PointPair>(&info.data)) {
VERT_pairs.erase(*pair);
}
break;
}
case Mode::Parallel: {
if (auto* pair = std::get_if<LinePair>(&info.data)) {
parallelPairs.erase(*pair);
}
break;
}
case Mode::Coincedent: {
if (auto* pair = std::get_if<PointPair>(&info.data)) {
P2Ppairs.erase(*pair);
}
break;
}
case Mode::Perpendicular: {
if (auto* pair = std::get_if<LinePair>(&info.data)) {
perpendicularPairs.erase(*pair);
}
break;
}
default:
break;
}
C_Info.erase(it);
}
// ===================================================================
// Обработчики событий мыши
// ===================================================================
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;
getCoincidentGroup(draggedPoint);
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);
C_Info[constraints_count - 1] = { Mode::Horizontal, pair };
}
else {
sys.addConstraintVertical(*found, constraints_count++);
auto pair = makeOrderedPair<PointPair>(found->start_ref, found->end_ref);
VERT_pairs.insert(pair);
C_Info[constraints_count - 1] = { Mode::Vertical, pair };
}
solve_for_canvas();
after_constraint = true;
}
mode = Mode::None;
}
// ====================== Режим DrawingLine: рисование линий ======================
else if (mode == Mode::DrawingLine) {
if (!current_line) {
// Первый клик: создаем новую линию
current_line = new Line();
// Создаем координаты для точек линии
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);
solve_for_canvas();
return;
}
// Добавляем координаты второй точки в параметры
params.push_back(current_line->p2.x);
params.push_back(current_line->p2.y);
current_line->set_tag(obj_count++);
// Завершаем создание линии
lines.append(current_line);
current_line = nullptr;
mode = Mode::None;
after_constraint = true;
}
solve_for_canvas();
return;
}
// ====================== Режим Parallel: задание параллельности ======================
else if (mode == Mode::Parallel) {
Line* found = findAt(scene);
if (!found) {
current_line = nullptr;
solve_for_canvas();
return;
}
// Первый клик: выбираем первую линию
if (!current_line) {
current_line = found;
solve_for_canvas();
return;
}
// Повторный клик на ту же линию: сброс выбора
if (found == current_line) {
current_line = nullptr;
solve_for_canvas();
return;
}
// Второй клик на другую линию: добавляем ограничение параллельности
if (!areAlreadyParallel(found, current_line)) {
auto pair = makeOrderedPair<LinePair>(found, current_line);
sys.addConstraintParallel(*found, *current_line, constraints_count++);
parallelPairs.insert(pair);
C_Info[constraints_count - 1] = { Mode::Parallel, pair };
current_line = nullptr;
mode = Mode::None;
after_constraint = true;
solve_for_canvas();
}
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;
solve_for_canvas();
return;
}
}
// ====================== Режим Coincedent: совпадение точек ======================
else if (mode == Mode::Coincedent) {
Point* clickedPoint = findPointAt(scene);
if (!clickedPoint) {
firstPoint = nullptr;
solve_for_canvas();
return;
}
// Первый клик: выбираем первую точку
if (!firstPoint) {
firstPoint = clickedPoint;
solve_for_canvas();
return;
}
// Повторный клик на ту же точку: сброс выбора
if (clickedPoint == firstPoint) {
firstPoint = nullptr;
solve_for_canvas();
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;
solve_for_canvas();
return;
}
// Добавляем ограничение совпадения точек
sys.addConstraintP2PCoincident(*clickedPoint, *firstPoint, constraints_count++);
auto pair = makeOrderedPair<PointPair>(clickedPoint, firstPoint);
P2Ppairs.insert(pair);
C_Info[constraints_count - 1] = { Mode::Coincedent, pair };
firstPoint = nullptr;
mode = Mode::None;
after_constraint = true;
solve_for_canvas();
return;
}
else if (mode == Mode::Perpendicular) {
Line* found = findAt(scene);
if (!found) {
current_line = nullptr;
solve_for_canvas();
return;
}
// Первый клик: выбираем первую линию
if (!current_line) {
current_line = found;
solve_for_canvas();
return;
}
// Повторный клик на ту же линию: сброс выбора
if (found == current_line) {
current_line = nullptr;
solve_for_canvas();
return;
}
if (!areAlreadyPerpendicular(found, current_line)) {
auto pair = makeOrderedPair<LinePair>(found, current_line);
sys.addConstraintPerpendicular(*found, *current_line, constraints_count++);
perpendicularPairs.insert(pair);
C_Info[constraints_count - 1] = { Mode::Perpendicular, pair };
current_line = nullptr;
mode = Mode::None;
after_constraint = true;
solve_for_canvas();
}
else {
QMessageBox::warning(this,
QString("Wrong"),
QString("Perpendicular lines can not be more perpendicular!"),
QMessageBox::Ok
);
current_line = nullptr;
solve_for_canvas();
return;
}
}
}
void Canvas::mouseMoveEvent(QMouseEvent* event)
{
// ====================== Перемещение точки ======================
if (draggedPoint) {
QPointF pos = UCS_POSITION - dragOffset;
for (Point* pt : groups) {
*pt->x = pos.x();
*pt->y = pos.y();
}
for (Point* basePt : groups) {
for (Point* other : points) {
if (areHorizontalVertical(basePt, other, true)) {
*other->x = pos.x();
}
if (areHorizontalVertical(basePt, other, false)) {
*other->y = pos.y();
}
}
}
solve_for_canvas();
}
// ====================== Перемещение линии ======================
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;
}
}
}
solve_for_canvas();
}
#ifdef _DEBUG
else
showObjectTag(WIDGET_POSITION);
#endif
}
void Canvas::mouseReleaseEvent(QMouseEvent* event)
{
Q_UNUSED(event);
if (draggedPoint) {
draggedPoint = nullptr;
solve_for_canvas();
}
if (draggedLine) {
draggedLine = nullptr;
solve_for_canvas();
}
groups.clear();
}
// ===================================================================
// Метод отрисовки
// ===================================================================
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(scaleFactor, -scaleFactor);
p.setPen(Qt::red);
p.drawLine(-5, 0, 5, 0);
p.drawLine(0, -5, 0, 5);
// ====================== Отрисовка линий ======================
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::keyPressEvent(QKeyEvent* event)
{
switch (event->key()) {
case Qt::Key_Delete:
case Qt::Key_Clear: {
QMessageBox::StandardButton reply;
reply = QMessageBox::question(this,
"Очистка canvas",
"Удалить все объекты и ограничения?",
QMessageBox::Yes | QMessageBox::No);
if (reply == QMessageBox::Yes) {
clearCanvas();
}
break;
}
case Qt::Key_Escape: {
// Отмена текущего действия
if (mode == Mode::DrawingLine && current_line) {
// Отмена рисования линии
delete current_line;
current_line = nullptr;
// Удаляем созданные точки и параметры
if (points.size() >= 2) {
delete points.back();
points.pop_back();
delete points.back();
points.pop_back();
}
if (params.size() >= 4) {
delete params.back();
params.pop_back();
delete params.back();
params.pop_back();
delete params.back();
params.pop_back();
delete params.back();
params.pop_back();
}
mode = Mode::None;
update();
}
else if (mode != Mode::None) {
// Отмена любого другого режима
mode = Mode::None;
current_line = nullptr;
firstPoint = nullptr;
groups.clear();
update();
}
break;
}
case Qt::Key_Z: {
if (constraints_count > 0) {
remove_constraint(constraints_count - 1);
constraints_count--;
solve_for_canvas();
}
break;
}
default:
QWidget::keyPressEvent(event);
}
}
void Canvas::wheelEvent(QWheelEvent* event)
{
if (event->angleDelta().y() > 0) {
zoomIn();
}
else {
zoomOut();
}
event->accept();
}
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.rx() /= scaleFactor;
logical.ry() /= -scaleFactor;
return logical;
}
void Canvas::getCoincidentGroup(Point* p)
{
for (Point* other : points) {
if (other != p && areCoincident(p, other)) {
groups.insert(other);
break;
}
}
for (size_t i = 0; i < points.size(); ++i) {
for (size_t j = i + 1; j < points.size(); j++) {
if (*points[i] == *points[j] && groups.contains(points[j])) {
groups.insert(points[i]);
}
}
}
groups.insert(p);
}
void Canvas::solve_for_canvas()
{
bool flag = false;
int res = sys.solve(params);
if (res != SolveStatus::Success && res != SolveStatus::Converged) {
flag = true;
}
else {
sys.applySolution();
for (Line* line : lines) {
if (abs(*line->p1.x - *line->p2.x) < EPS && abs(*line->p1.y - *line->p2.y) < EPS && after_constraint) {
sys.undoSolution();
flag = true;
break;
}
}
}
if (flag) {
QMessageBox::warning(this, QString("Error!"), QString("Last constraint is unavailable!"));
remove_constraint(constraints_count - 1);
C_Info.erase(constraints_count - 1);
constraints_count--;
}
after_constraint = false;
update();
}
// ===================================================================
// Очистка всего canvas
// ===================================================================
void Canvas::clearCanvas()
{
// Очистка солвера
sys.clear();
// Очистка динамически выделенной памяти
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();
perpendicularPairs.clear();
// Очистка информации об ограничениях
C_Info.clear();
// Сброс счетчиков
obj_count = 0;
constraints_count = 0;
// Очистка временных указателей
if (current_line) {
delete current_line;
current_line = nullptr;
}
if (firstPoint) {
delete firstPoint;
firstPoint = nullptr;
}
draggedPoint = nullptr;
draggedLine = nullptr;
// Сброс режима
mode = Mode::None;
// Обновление отображения
update();
groups.clear();
}