Песочница
4 subscribers

Разработка программы для рисования графов (часть 5)

В этой серии статей описан процесс разработки приложения для рисования графов на языке C++ с помощью Qt. Результат можно посмотреть на видео.

Предыдущие статьи:

1. Создание интерфейса

2. Подключение примера elasticnodes

3. Разбор GraphWidget, и реализация добавлении вершин

4. Выделение и удаление вершин

Реализуем кнопку Соединение вершин

Соединить вершины можно двумя способами. В первом случае на сцене ничего не выделено. Тогда для соединения необходимо нажать на кнопку "стрелка" и отметить вершину-источник, затем вершину-назначение. Во втором на сцене уже отмечена вершина. И после нажатия на кнопку "стрелка" останется выбрать вершину назначения.

Добавление поля классу Node

Для наглядности, в процессе соединения будем отмечать вершину-источник, окружностью розового цвета. Для этого добавим приватное поле _mark, которое будет использоваться как флаг отметки вершины. Добавим в список инициализации _mark(0).

Используя подсказки, добавим методы установки и получения значения.

подсказа для создания методов установки и получения значения
подсказа для создания методов установки и получения значения

В функции отрисовки Paint будем выбирать цвет ручки в зависимости от значения флага:

painter->setPen(QPen(_mark ? Qt::magenta : Qt::black, 2));

Дополнительное поле в классе Widget

В классе Widget добавляем поле отвечающее за процессом соединения (int connProcess). Будет 3 состояния:

1. Когда ничего ни с кем соединять не надо.

2. Когда необходимо отметить вершину-источник.

3. Когда необходимо отметить вершину-назначение.

Создадим перечисление, для наглядности в файле widget.h:

namespace CONN {
enum {
NONE,
NEED_SOURCE,
NEED_DEST
};
}

В списке инициализации connProcess(CONN::NONE).

Реализация функции кнопки "стрелка"

При нажатии на кнопку Соединить, будем проверять количество выделенных элементов. Если нет выделенных элементов, значит connProcess = CONN::NEED_SOURCE. Если один выделенный элемент, и выделенный элемент является Node, отмечаем ( setMark(true)) выделенную вершину и меняем состояние connProcess = CONN::NEED_DEST. Запустим и проверим, как окружность меняет цвет при нажатии на кнопку "стрелка". Код показан ниже.

цвет меняется, только после перерисовки
цвет меняется, только после перерисовки

Для того, чтобы это исправить, необходимо добавить в очередь на перерисовку этот элемент, вызвав update() в методе setMark().

Обработка события изменения выделенных элементов сцены
При выделении элементов (так при потере выделения) вызывается у сцены сигнал selectionChanged. Использовал эту функцию, но сейчас переделал на сигнал MouseClick. Эту функцию можно увидеть в коммитах.

Обработка нажатия на GraphWidget

У GraphWidget нет сигнала нажатия. Поэтому сделаем его сами, перезагрузив событие

void mousePressEvent(QMouseEvent *event).

добавление mousePressEvent и mousePressedSignal
добавление mousePressEvent и mousePressedSignal

И реализация mousePressEvent:

void GraphWidget::mousePressEvent(QMouseEvent *event)
{
QGraphicsView::mousePressEvent(event);
emit mousePressedSignal(event);
}

В классе Widget создадим слот для привязки с mousePressedSignal. Реализуем его.

Функция itemAt(const QPoint & pos) возвращает верхний элемент в точке pos , либо nullptr.
реализация нажатия на виджет сцены
реализация нажатия на виджет сцены

Запустим программу и посмотрим, как она работает:

пример работы программы. При движении вершин, стрелки следуют за вершиной
пример работы программы. При движении вершин, стрелки следуют за вершиной

А теперь попробуем удалить вершину.

после удаления вершины
после удаления вершины

Ребра вершины также исчезли со сцены. Если попробуем переместить один из вершин, который был связан с удаленной вершиной, то наша программа вылетит.

ошибка после удаления вершины и перемещения связной вершины
ошибка после удаления вершины и перемещения связной вершины

Посмотрев стек вызовов, видим, что вершина пытается "отрегулировать"(adjust) уже удаленное ребро(Edge).

попытка обратиться к удаленному ребру
попытка обратиться к удаленному ребру

Пришло время заняться классом Edge. Для исправления достаточно при удалении ребра (в деструкторе), удалять его из списка ребр в связных вершинах. У нас есть метод для добавления ребр (addEdge). Аналогично делаем для удаления.

добавления дружественного деструктора и метода removeEdge
добавления дружественного деструктора и метода removeEdge

Метод removeEdge.

void Node::removeEdge(Edge *edge)
{
edgeList.removeOne(edge);
}

Деструктор тоже простой:

реализация деструктора
реализация деструктора
На 10-ой строчке заменим 0 на nullptr

И вот наша программа уже строит вершины и может соединять их ребрами. Дальше сделаем красивые стрелки и добавим возможность выделения и удаления стрелок.

Продолжение следует.

Проект выложен на github.com/fryn3/widGraph.

Если прочитал до этого места, то, вероятно, материал был интересен. Поддержи лайком.
Также если появились вопросы - пиши мне в телегу или вк.