LINUX.ORG.RU

[Qt] Куда класть наносимое инструментами?

 


0

0

У меня унаследованы от Qt'шных свои View и Scene.

На данный момент делаю различные инструменты: прямоугольное выделение, эллипсоидное выделение, полигональное выделение (всё в стиле GIMP). Также существует анкер-привязка (как в Автокаде, если упрощённо: квадратик, который «прилипает» к ближайшей точке сетки).

Сейчас я их кладу в сцену, т.к. таким образом легко получается их перерисовывать. Например, бывает нужно переместить анкер и тогда я посылаю сигнал anchorPositionChanged(QPoint). Или юзер хочет изменить размер прямоугольного выделения, тогда он берёт и тащит за угол, что приводит к посыланию соответствующих сигналов.

Куда идеологически правильно помещать анкер и эти инструменты? Таки в сцену?

Стоит ли наследовать прямоугольное и эллипсоидное выделение от QRubberBand, или от чего тогда?

★★★★★

Если вопрос в том, должны ли храниться указатели на анкер и выделение внутри своего класса сцены, то ответ - да. Сцена - совокупность интерактивных деталей, которые можно увидеть и пощупать, неважно принадлежат ли эти детали к редактируемым данным или нет. View - всего-лишь визуализатор, по хорошему можно обойтись даже без наследования от него.

Dendy ★★★★★
()
Ответ на: комментарий от Dendy

Спасибо за ответ! Но ведь вызывать инструменты надо в евентах View, верно понимаю?

View - всего-лишь визуализатор, по хорошему можно обойтись даже без наследования от него

у меня ещё рисуются точки сетки, также, если я верно понимаю, во View надо хранить активный инструмент

Obey-Kun ★★★★★
() автор топика
Ответ на: комментарий от Dendy

а если у нас к одной сцене подключено два view... и в этом случае логично хранить указатели на выделение и анкер внутри сцены?

Obey-Kun ★★★★★
() автор топика
Ответ на: комментарий от Obey-Kun

а, ну в общем-то, наверное, да

Obey-Kun ★★★★★
() автор топика

Смотрите, хорошо сделано или нет?

Есть View. У него размер сетки зависит от масштаба.

В этом View в ивент перемещения курсора запихано следущее:

emit cursorPositionChanged(mapToScene(event->pos()));
findAnchor(mapToScene(event->pos()));
/* фигня, которая, если активен какой-либо инструмент и если мы находимся у видимой границы, делает автоскрол */
QGraphicsView::mousePressEvent(event);

Сигнал cursorPositionChanged(QPointF) принимается в mainwindow, что приводит к изменению информации о координатах курсора в строке состояния.

Функция findAnchor(QPointF) посылает соответствующий сигнал в зависимости от возможности или невозможности привязки:

void Area::findAnchor(QPointF point) const
{
    QPoint point_to_anchor;
    if(tryAnchor1(point, point_to_anchor)) {
        emit anchorPositionChanged(point_to_anchor);
    } else {
        emit anchorIsNowNotPossible();
    }
}

Сигнал anchorPositionChanged(QPoint) принимается слотом сцены и меняет позицию анкера. Сигнал anchorIsNowNotPossible() принимается там же и прячем анкер.

P.S.: Функция bool View::tryAnchor1(const QPointF& point, QPoint& anchor_point) const — это попытка привязаться. Привязка довольно хитрая и учитывает не только шаг видимой сетки (!), но и кучу других параметров.

Obey-Kun ★★★★★
() автор топика
Ответ на: комментарий от Obey-Kun

Забыл добавить, сигналы anchorPositionChanged(QPointF) и anchorIsNowNotPossible() принимаются в сцене перегруженным слотом anchorPosition:

void qfgui::Scene::anchorPosition(QPoint new_center)
{
    if(!m_main_anchor->isVisible()) {
        m_main_anchor->show();
    }
    m_main_anchor->setPos(new_center);
}

void qfgui::Scene::anchorPosition()
{
    m_main_anchor->hide();
}
Obey-Kun ★★★★★
() автор топика
Ответ на: комментарий от Obey-Kun

Item'ы всё равно принадлежат сцене, а задать поведение, характерное для конкретного View (как в Model/View, вроде поменять столбцы местами) лично я не вижу как. Как вариант можно переопределить QGraphicsScene::drawItems() и фильтровать их в зависимости от подставленого виджета.

А вообще рекомендую отойти от паттерна Сцена/Представление и ввести собственные, вроде редактируемых данных, инструментов, камеры. Воспринимайте GraphicsView как низкоуровневый инструмент.

Dendy ★★★★★
()
Ответ на: комментарий от Obey-Kun

По правде мне сложно понять что делает ваш анкер. Если вопрос - где нужно его обрабатывать, то я бы создал некий класс, управляющий сценой. Относитесь к итемам как к несамостоятельным низкоуровневым примитивам, кирпичикам. Кучка кирпичиков может относиться к данным, кучка - к инструментам, ещё кучка - всмопогательные маркеры на сцене и так далее. Каждой кучкой пусть управляет свой менеджер в зависимости от задач. И спрячьте интерфейс и указатели на сами итемы для доступа извне. И ещё, существует мнение (я и Google с ним согласны), что перегруженых методов лучше избегать везде где только можно. Замените anchorPosition() на hideAnchor(), а anchorPosition(QPoint) на moveAnchor(QPoint).

Dendy ★★★★★
()
Ответ на: комментарий от antony986

гуй для теплофизической модели

юзер задаёт модель (рисует прямоугольнички, задаёт их параметры), после чего запускает её и смотрит (во времени), что происходит... может поставить моделирование «на паузу», чтобы поколдовать над ячейками.

математика давно написана, там 500 физических (т.е. не считая комментов и пустых строк) строк кода, в гуе нынче 1200 физических строк, реализовано всё что нужно для визуализации и создания ячеек по одной.

осталось:

- инструменты рисования и выделения

- изменение свойств ячеек

- запуск-пауза рассчётов

самое сложное было в алгоритме привязки, осталась рутина по сути

Obey-Kun ★★★★★
() автор топика
Ответ на: комментарий от Dendy

> По правде мне сложно понять что делает ваш анкер.

У него могут быть разные способы поведения в зависимости от выбранного инструмента. Приведу упрощённый пример одного из способов поведения. Если курсор находится внутри какой-нибудь ячейки (ячейка - Cell - ключевой элемент сцены, отображается как прямоугольник), привязка осуществляется к ближайшему углу этой ячейки. В остальных случаях (то есть когда курсор находится в пустоте) привязка осуществляется к ближайшему видимому узлу сетки (повторю, шаг сетки изменяется в зависимости от масштаба view!).

Вот пример работы привязки: http://rghost.ru/1246639/image.png. Жёлтые прямоугольники — ячейки, точки на чёрной фигне — узлы сетки, анкор — это два фиолетовых квадратика один в другом. Курсора не видно (спрятался при printscreen). Так как курсор сейчас находится в пустоте, привязка осуществилась к ближайшему к нему видимому узлу сетки.

Как уже говорилось, узлы сетки рисуются как background во View. Ось координат рисуется как foreground там же. Это верно?

Если вопрос - где нужно его обрабатывать, то я бы создал некий класс, управляющий сценой.

Не понял. В каком смысле обрабатывать — находить, куда надо привязаться, или отрисовывать?

В данный момент нахождение точки, куда его надо привязать, происходит во View, а отрисовка идёт в сцене... вернее, анкор лежит в сцене и сам себя отрисовывает, так как является обычным итемом, а сцена принимает сигналы о том, что его нужно переместить и перемещает его.

Замените anchorPosition() на hideAnchor(), а anchorPosition(QPoint) на moveAnchor(QPoint).

Логично, так и сделаю.

Obey-Kun ★★★★★
() автор топика
Ответ на: комментарий от Dendy

Правильно ли я задумал реализацию инструментов? Инструменты у меня бывают трёх типов — рисующие в сцене (точнее, он один, заполняет заданную область ячейками выбранного пользователем размера), выделяющие элементы (прямоугольное, овальное и полигональное выделение) и изменяющие камеру (зум и перетаскивание видимой области).

Привязка нужна только для рисующих и выделяющих инструментов, но не для изменяющих камеру.

Так как логика привязки лежит во View, значит от должен знать о текущем инструменте.

Итак. В одном из тулбаров mainwindow юзер выбирает инструмент. Посылаем сигнал toolChoosen(InstrumentType::type). InstrumentType::type — это элемент enum'а из хедера с константами. Этот сигнал поймался во View и он запомнил выбранный инструмент, что позволяет включить или выключить привязку (а также выбрать нужный способ привязки).

Во View в ивенте нажатия левой кнопки мыши написан switch, который посылает сигнал типа startInstrument(InstumentType::rectangleSelection, QPointF current_position), startInstrument(InstumentType::createCellsInRectangle, QPointF current_position), или startInstrument(InstumentType::zoomOut, QPointF current_position). Сигнал ловится в сцене и она создаёт соответствующую фигуру (но делает её невидимой до тех пор, пока юзер не начинает тащить мышку).

Во View в ивенте перемещения для нажатой левой кнопки мыши отправляется сигнал setInstrumentCorner(QPointF new_position). Сигнал ловится в сцене и она изменяет фигуру активного инструмента.

Во View в ивенте отпускания левой кнопки мыши отправляется сигнал instrumentDone(). Сигнал ловится в сцене, она отправляет или InstrumenFailure() (это значит, что то, что попытался сделать юзер, делать нельзя), или InstrumentSuccesfull(QRectF instrument_rect) [инструмент удался, получилась прямоугольная область], InstrumentSuccesfull(QPointF instrument_point) [инструмент удался, получилась точка], после чего удаляет созданный инструмент. View принимает этот сигнал и, если принятый сигнал — InstrumentSuccesfull(...), делает нужное действие основываясь на instrument_rect и выбранном инструменте (опять switch). То есть или изменяет свой масштаб/камеру, или посылает соответствующий сигнал в сцену (например createRectangles(QRectF zone, QSize rectangles_size)), или создаёт соответствующий path и делает select в сцене.

Нормально?

Obey-Kun ★★★★★
() автор топика
Ответ на: комментарий от Dendy

> Если вопрос в том, должны ли храниться указатели на анкер и выделение внутри своего класса сцены, то ответ - да.

Также непонятна одна ведь. Зачем тогда QRubberBand? Он ведь хранится прямо во view...

Obey-Kun ★★★★★
() автор топика

Всё, кажется я понял: логику создания инструментов таки надо хранить во View. Допустим, у нас есть основной View и View-миниатюра. В них рисуется сцена. И инструменты должны быть свои для каждого View.

Если нужно, чтобы инструмент показывался в обоих View, то нужно класть его в сцену. Если нужно, чтобы только в одном — надо наследовать QRubberBand.

Кажется, всё так.

Obey-Kun ★★★★★
() автор топика
Ответ на: комментарий от Obey-Kun

> Если нужно, чтобы инструмент показывался в обоих View, то нужно класть его в сцену. Если нужно, чтобы только в одном — надо наследовать QRubberBand.

ну а логику обработки надо класть туда, где это логично... если мы создаём итемы, то пусть логика лежит в сцене, если меняется камера — в соответствующем View. Вот и всё.

Obey-Kun ★★★★★
() автор топика
Ответ на: комментарий от Obey-Kun

Всё это хорошо понятно из реализации 40000 chips.

Obey-Kun ★★★★★
() автор топика
Вы не можете добавлять комментарии в эту тему. Тема перемещена в архив.