МИНИСТЕРСТВО ОБРАЗОВАНИЯ И НАУКИ РОССИЙСКОЙ ФЕДЕРАЦИИ ГОСУДАРСТВЕННОЕ ОБРАЗОВАТЕЛЬНОЕ УЧРЕЖДЕНИЕ ВЫСШЕГО ПРОФЕССИОНАЛЬНОГО ОБРАЗОВАНИЯ

advertisement
МИНИСТЕРСТВО ОБРАЗОВАНИЯ И НАУКИ РОССИЙСКОЙ ФЕДЕРАЦИИ
ГОСУДАРСТВЕННОЕ ОБРАЗОВАТЕЛЬНОЕ УЧРЕЖДЕНИЕ
ВЫСШЕГО ПРОФЕССИОНАЛЬНОГО ОБРАЗОВАНИЯ
«НОВОСИБИРСКИЙ ГОСУДАРСТВЕННЫЙ ТЕХНИЧЕСКИЙ УНИВЕРСИТЕТ»
Кафедра
Прикладой математики
ОТЧЕТ ПО ПРЕДДИПЛОМАНОЙ ПРАКТИКЕ СТУДЕНТА
Голубевой Владиславы Владимировны
(фамилия, имя, отчество автора)
Проектирование кросс-платформенной системы визуализации данных: изучение
методик проектирования
Направление
подготовки
010500 - «Прикладная математика и информатика»
Руководитель
Автор
Чернышев А.В.
Голубева В.В.
(фамилия, И.О.)
(фамилия, И.О.)
к.т.н.
ФПМИ, ПМ-71
(уч. степень, уч. звание)
(факультет, группа)
(подпись, дата)
(подпись, дата)
Новосибирск, 2012 г.
Оглавление
Введение............................................................................................................... 3
1.
Анализ требований к продукту .................................................................. 5
2.
Требования к программе ............................................................................. 6
2.1. Требования к данным ........................................................................... 6
2.2. Требования к интерфейсу..................................................................... 6
2.3. Требования к реализации ..................................................................... 6
3.
Выбор средств реализации ......................................................................... 6
4.
Проектирование системы ........................................................................... 7
4.1. Прецеденты ............................................................................................ 7
4.2. Проектирование базы данных............................................................ 11
4.2.1.
Набор сущностей ......................................................................... 11
4.2.2. Набор атрибутов ........................................................................... 11
4.2.3. Связи между сущностями ............................................................ 13
4.2.4. Множественность и условность связей ...................................... 13
4.2.5. ER-диаграмма ................................................................................ 14
4.2.6. Создание спроектированной базы данных ................................. 14
4.3. Проектирование программного комплекса ...................................... 16
4.3.1. Выделенные подсистемы ............................................................. 16
4.3.1. Описание классов.......................................................................... 17
4.3.2. UML-диаграмма классов .............................................................. 33
5.
Результаты работы программы ................................................................ 33
Заключение ........................................................................................................ 36
Список использованных источников .............................................................. 38
Введение
Распространенной задачей в области компьютерной графики является
задача визуализация данных, полученных в результате некоторых вычислений.
Данная задача является естественной для компьютера, так как он умеет
обрабатывать числа быстро и в больших количествах.
С точки зрения пользователя визуальное восприятие данных гораздо
проще, чем какие бы то ни было другие представления – например, таблицы.
Хотя оно может оказаться менее точным. Одной из областей человеческой
деятельности,
порождающей
огромное
количество
данных,
требующих
визуального представления, является геофизическая разведка. Данная работа
посвящена
разработке
программного
обеспечения
для
визуализации
геофизических данных.
Важной задачей при разработке программного обеспечения является
написание легко расширяемого и поддерживаемого программного кода.
В течение нескольких десятков лет разработчики решали основную
проблему
создания
программного
обеспечения:
снижение
сложности
программного кода [4]. Для этого было выработано множество методик:
последовательные и итеративные способы разработки [4], применение
шаблонов проектирования[2] и т.д.
Цикл разработки программного обеспечения призван стандартизировать
этапы создания программного продукта. Он включает следующие этапы:
 Анализ требований к продукту
 Разработка спецификации
 Проектирование архитектуры
 Реализация и тестирование
 Внедрение, распространение и поддержка
Каждый из этих шагов важен, поскольку позволяет значительно сократить
количество ошибок на последующих этапах. Обратно, исправление ошибки,
допущенной на раннем этапе, в поздней стадии развития продукта может
обернуться очень серьезными накладными расходами. Так, исправление
ошибки на этапе тестирования, допущенной в анализе требований, обходится в
15 раз дороже, чем исправление этой же ошибки на этапе формирования
требований[4].
Один из этапов цикла разработки - этап проектирования, значительно
облегчается при использовании шаблонов проектирования.
Шаблоны проектирования позволяют повторно использовать удачное
архитектурное решение, найденное другими разработчиками. Библиотека
шаблонов используется также как и библиотека алгоритмов, но применяется
гораздо раньше – на этапе проектирования системы.
В данной работе были выполнены все подготовительные этапы и
применены некоторые шаблоны проектирования.
1. Анализ требований к продукту
Спроектировать и реализовать модуль, отвечающий за обработку и
представление геофизических данных.
Данные представляют собой измерения, собранные на какой-либо
местности
с
помощью
«петель»
и
«пикетов».
В
каждом
пикете,
соответствующем одной петле, замеряется ЭДС как табличная функция
времени.
Петля: генераторный контур, имеет форму квадрата или круга.
Квадрат задается центром и стороной.
Круг задается центром и радиусом.
Характеристики петли:
 Форма
 Угол поворота
 Размер (сторона или радиус)
 Ток
 Количество витков
 Фронт спада
 Центр X
 Центр Y
Пикет: точка, в которой делаются измерения
Характеристики пикета
 Координаты X, Y, Z
 Высота
 Оборудование (петля)
ЭДС: табличная функция ЭДС(t)
Характеристики ЭДС
 значение в вольтах
 время в секундах
К одной петле привязано много пикетов, одному пикету соответствует
только одна ЭДС и только одна петля.
2. Требования к программе
2.1
Требования к данным
Данные должны храниться в базе данных на удаленном сервере.
Возможность импортировать данные из нескольких внешних форматов.
2.2
Требования к интерфейсу
Необходимо реализовать удобное представление иерархии входных
данных.
Визуализировать петли, пикеты, ЭДС и их параметры.
Необходимо иметь возможность конфигурировать подключение к базе
данных.
Необходимо иметь возможность выбора набора данных (местности) для
визуализации.
Необходимо иметь возможность редактировать кривые ЭДС и сохранять
этот результат в базе данных.
2.3
Требования к реализации
Разработанный программный комплекс должен обладать следующими
свойствами:
 Кроссплатформенность (Unix/Windows)
 Отказоустойчивость
(отсутствие
соединения,
ошибки
ввода
пользователя, целостность данных)
3. Выбор средств реализации
 В качестве средства разработки был выбран язык С++ [3]
с
библиотекой QT [1]. Причина: QT кроссплатформенная библиотека,
C++ позволяет добиться лучшей производительности программного
кода.
 Также QT значительно облегчает доступ базе данным и создание
оконного интерфейса на кроссплатформенном уровне.
 Для визуализации сложных элементов интерфейса (например, графика
кривой ЭДС) используется библиотека OpenGL [5], так как она
поддерживает аппаратное ускорение отрисовки двумерной графики, а
также является кроссплатформенной.
 В
качестве
СУБД
выбрана
MySQL.
Это
бесплатная
СУБД,
обеспечивающая высокую производительность, а также обладающая
рядом
полезных
возможностей
(триггеры,
внешние
ключи,
представления, партицирование и т.д.).
 Выбранная
среда
разработки
–
QtCreator.
Так
как
она
кроссплатформенная, бесплатная, а также является естественной
средой разработки под QT.
4. Проектирование системы
2.3
Прецеденты
1. Запуск программы
a. Пользователь видит диалог конфигурирования соединения с
базой данных, если отсутствуют сохраненные настройки
соединения
b. В
случае
отсутствия
настроек
соединения
блокируется
подпункт меню «Переподключиться» пункта меню «База
данных»
c. Если
существуют
сохраненные
настройки
соединения,
программа пытается подключиться к базе данных
d. Пользователь видит на экране сообщение «Подключение
выполнено» в случае успешного подключения и «Подключение
не выполнено» в случае ошибки подключения
2. Конфигурирование подключения к базе данных
a. Пользователь выбирает пункт меню «База данных»
b. Пользователь
выбирает
подпункт
«Конфигурация
подключения» пункта меню «База данных»
c. Пользователь видит диалог настройки подключения
d. Пользователь заполняет поля «Имя базы данных», «Логин»,
«Пароль», «Имя хоста»
e. Пользователь нажимает кнопку «Подключиться»
f. Пользователь видит на экране сообщение «Подключение
выполнено» в случае успешного подключения и «Подключение
не выполнено» в случае ошибки подключения
g. В случае успешного подключения программа сохраняет
настройки в постоянном хранилище (например, в файле)
h. Если в диалоге конфигурирования пользователь нажимает
«Отмена», диалог закрывается
i. В случае отсутствия соединения с базой весь интерфейс
блокируется,
в
а
заголовке
окна
появляется
надпись
«Соединение не активно»
3. Переподключение к базе данных
a. Если пользователь видит сообщение «соединение не активно»
он может попытаться выполнить повторное соединение с базой
данных с текущими настройками
b. Пользователь выбирает пункт меню «База данных»
c. Пользователь выбирает подпункт «Переподключиться» пункта
меню «База данных»
d. Пользователь видит на экране сообщение «Подключение
выполнено» в случае успешного подключения и «Подключение
не выполнено» в случае ошибки подключения
4. Выбор набора данных для работы
a. В случае успешного подключения становится активным пункт
меню «Набор данных»
b. Пользователь выбирает пункт меню «Набор данных»
c. Пользователь выбирает подпункт «Выбрать полигон» пункта
меню «Набор данных»
d. Пользователь видит диалог поиска полигона
e. Пользователь видит список всех существующих в базе данных
полигонов
f. Пользователь фильтрует список по названию полигона
g. Пользователь выбирает полигон из списка
h. Пользователь нажимает кнопку «Выбрать»
i. Диалог закрывается
5. Просмотр иерархии набора данных в выбранном полигоне
a. Пользователь выбирает полигон
b. Пользователь видит слева в окне два дерева – петли и пикеты.
c. При щелчке по корню дерева раскрывается список, в котором
можно видеть набор петель или пикетов данной местности
6. Просмотр информации о петле
a. При щелчке ПК мыши по выбранной петле появляется
контекстное меню с пунктом «Свойства»
b. При выборе пункта меню «Свойства» появляется диалог с
информацией о петле
7. Просмотр информации о пикете
a. При щелчке ПК мыши по выбранному пикету появляется
контекстное меню с пунктом «Свойства»
b. При выборе пункта меню «Свойства» появляется диалог с
информацией о пикете
8. Просмотр диаграммы пикетов и петель
a. Пользователь щелкает ЛК мыши по любой петле или пикету
b. В рабочей области окна появляется диаграмма пикетов и петель
c. Выбранная петля или пикет подсвечивается с помощью
двойной обводки
d. В правом окне приложения отображается информация о
выбранном пикете или петле
e. Если ничего не выбрано, диаграмма отображается без
выбранных элементов
9. Просмотр информации о пикете и петле с помощью диаграммы
a. Пользователь щелкает ЛК мыши по пикету или петле
b. В правом окне отображается информация о выбранном пикете
или петле
c. В левом окне в дереве выбирается узел, соответствующий
выбранному пикету или петле
10. Просмотр ЭДС пикетов
a. Пользователь щелкает ПК мыши по пикету в диаграмме или
дереве
b. Пользователь видит контекстное меню
c. Пользователь выбирает пункт меню «Просмотреть ЭДС»
d. Рабочая область переключается в режим просмотра ЭДС
e. На рабочую область добавляется ЭДС от выбранного пикета
f. Пользователь щелкает ЛК мыши по рабочей области
g. Программа выбирает соответствующую точку на оси времени
h. Программа
в
правом
окне
отображает
значения
всех
загруженных ЭДС в выбранный момент времени
11.Удаление ЭДС пикета из рабочей области
a. Пользователь в правом окне видит список ЭДС
b. Пользователь щелкает ПК мыши по выбранной ЭДС
c. Пользователь видит контекстное меню
d. Пользователь выбирает пункт «Убрать ЭДС из рабочей
области»
12. Изменение центра координат рабочей области
a. Пользователь нажимает и удерживает нажатой ЛК мыши на
свободном месте рабочей области и перемещает мышь –
координаты меняются
13.Масштабирование
a. Пользователь выделяет рабочую область
b. Пользователь крутит колесо мыши – масштаб меняется
14. Отображение всех ЭДС выбранной петли
a. Пользователь щелкает ПК мыши по петле в дереве или
выбирает петлю в рабочей области
b. Пользователь видит контекстное меню в дереве либо кнопку
«Посмотреть ЭДС» в правом окне в зависимости от пункта «а»
c. Пользователь
выбирает
пункт
«Посмотреть
ЭДС»
либо
нажимает кнопку «Посмотреть ЭДС»
d. В рабочей области появляется график всех ЭДС, связанных с
этой петлей
2.4
Проектирование базы данных
4..1. Набор сущностей
Выделен следующий набор сущностей, отражающий предметную область
и обеспечивающий её адекватное представление.
 Полигон (местность)
 Петля
 Форма петли
 Пикет
 ЭДС
Сущность ЭДС в данном случае является набором время/значение.
4..2. Набор атрибутов
Выделен необходимый набор атрибутов для каждой сущности
Полигон
*ID полигона
указывающий
Описание местности
описательный
Петля
*ID петли
указывающий
ID полигона
вспомогательный
ID формы петли
вспомогательный
Угол поворота
описательный
Размер (сторона или радиус)
описательный
Ток
описательный
Количество витков
описательный
Форма петли
*ID формы петли
указывающий
Форма
описательный
Пикет
*ID пикета
указывающий
ID петли
вспомогательный
ID эдс
вспомогательный
X
описательный
Y
описательный
Z
описательный
Высота
описательный
ЭДС
*ID эдс
указывающий
Вольт
описательный
Секунды
описательный
4..3. Связи между сущностями
 Петля – полигон
 Петля – форма петли
 Пикет – петля
 Пикет – ЭДС
Эта связь – результат упрощения связи Пикет – Кривая ЭДС –
Координаты ЭДС.
Избыточных связей нет.
4..4. Множественность и условность связей
Связь
Тип
Описание
Петля – полигон
M:1
Петля может принадлежать только одному
полигону.
На
одном
полигоне
могут
располагаться множество петель
Петля – форма петли
М:1
У
петли
может
быть
только
одна
геометрическая
форма.
Одна
геометрическая
форма
может
характеризовать множество петель.
Пикет – петля
М:1
Пикет может принадлежать только одной
петле. У одной петли может быть много
пикетов.
Пикет – ЭДС
1:М
У пикета может быть одна кривая ЭДС, у
кривой ЭДС – много координат. Для
упрощения схемы будем говорить, что у
одного пикета может быть много ЭДС
(координат). ЭДС может принадлежать
только одному пикету.
4..5. ER-диаграмма
Планируется партицировать таблицу ЭДС по idEmf.
Полученная база данных находится в третьей нормальной форме [6].
4..6. Создание спроектированной базы данных
CREATE TABLE `Polygon` (`idPolygon`, `description` TEXT)
*Стержневая сущность
Первичный ключ (idPolygon)
Ограничения
Значение idPolygon должно быть уникальным
Значение idPolygon не должно быть NULL
CREATE TABLE `LoopForm` (`idLoopForm`,`form` TEXT)
*Обозначающая сущность для Петля
Первичный ключ (idLoopForm)
Ограничения
Значение idLoopForm должно быть уникальным
Значение idLoopForm не должно быть NULL
CREATE
TABLE `GeneratorLoop` ( `idGeneratorLoop` INT, `idPolygon`
INT,`idLoopForm` INT,`turnAngle` DOUBLE, `size` DOUBLE, `current` DOUBLE,
`turnsNumber` INT, `centerX` INT, `centerY` INT)
* Обозначающая сущность для Полигон
Первичный ключ (idGeneratorLoop)
Внешний ключ (idPolygon) из Полигон
Значение idPolygon не должно быть NULL
Внешний ключ (idLoopForm) из Форма Петли
Значение idLoopForm не должно быть NULL
Ограничения
Значение idGeneratorLoop должно быть уникальным
Значение idGeneratorLoop не должно быть NULL
Значение turnAngle не должно быть NULL
Значение size не должно быть NULL
Значение current не должно быть NULL
Значение turnsNumber не должно быть NULL
Значение centerX не должно быть NULL
Значение centerY не должно быть NULL
CREATE TABLE `Emf` (`idEmf` INT, `time` DOUBLE, `value` DOUBLE,
PRIMARY KEY (`idEmf`, `time`) )
Первичный ключ (idEmf, time)
Ограничения
Значение idEmf не должно быть NULL
Значение time не должно быть NULL
Значение value не должно быть NULL
CREATE TABLE `Picket` (`idPicket` INT, `idGeneratorLoop` INT,
`idEmf` INT, `x` DOUBLE, `y` DOUBLE, `z` DOUBLE, `height` DOUBLE)
Первичный ключ (idPicket)
Внешний ключ (idGeneratorLoop) из Петля
Внешний ключ (idEmf) из ЭДС
Ограничения
Значение idPicket должно быть уникальным
Значение idPicket не должно быть NULL
Значение idEmf не должно быть NULL
Значение idGeneratorLoop не должно быть NULL
Значение height не должно быть NULL
Значение x не должно быть NULL
Значение y не должно быть NULL
Значение z не должно быть NULL
2.5
Проектирование программного комплекса
При проектировании программной системы был использован итеративный
подход к созданию набора классов. Изначально в качестве базиса был
разработан набор подсистем и выделена их первичная ответственность.
Далее каждая подсистема наполнялась классами по мере необходимости.
После каждой итерации система проводился пересмотр все системы в целом, и
изменялись или удалялись узкие места.
Проектирование системы базировалось на принципах проектирования ПО
[4]:
 Принцип единой ответственности
 Принцип открытости/закрытости
Ниже описаны основные классы системы.
4..7. Выделенные подсистемы
 Доступ к данным
Подсистема доступа к данным отвечает за работу с базой данных.
Подсистема доступа к данным позволяет соединяться с выбранной
пользователем базой данных. Разработанный класс Settings отвечает за
хранение введенных пользователем данных в постоянном хранилище. Для
реализации самого соединения был создан класс DbConnection.
Также подсистема позволяет иметь доступ к отдельным элементам базы
данных. За создание моделей, посредством которых происходит этот доступ,
отвечает класс-фабрика DbFabric.
 Пользовательский интерфейс
Подсистема отвечает за общение с пользователем, предоставляет
графические элементы управления для редактирования и просмотра данных.
Делится на QT (простые элементы) и OpenGL [5] (сложные).
 Импорт данных
Подсистема
импорта
данных
отвечает
за
конвертацию
данных,
полученных из внешних источников и добавление их в базу. Исходные данные
содержатся в таком формате, из которого импорт в базу данных невозможен.
Задача этой подсистемы взять данные, правильно их интерпретировать и
перевести в формат, удобный для работы с базой данных (*.csv, *.sql, *.xml).
4..1. Описание классов
SETTINGS
Ответственность
Использованные
Хранит настройки приложения
паттерны Паттерн «Одиночка» [2]
проектирования
Мотивация к созданию
Программа
содержит
настроек,
которые
редактировать
Настройки
множество
должны
может
пользователь.
сохраняться
между запусками программы. Удобно
хранить все настройки в одном месте
и иметь доступ к ним из любого
места программы.
Методы
Название
Ответственность
instance
Создает
или
возвращает
единственный экземпляр этого класса
save
Сохраняет настройки приложения в
постоянное хранилище
load
Загружает настройки соединения
Settings
Приватный конструктор
Атрибуты
Название
Ответственность
login
Логин для доступа к базе данных
password
Пароль для доступа к базе данных
dbname
Имя базы данных
hostname
Имя хоста, на котором находится
база данных
idPolygon
Идентификатор полигона, который
однозначно
данных,
определяет
с
которым
набор
работает
программа
_instance
Переменная хранит объект класса
Settings
DBCONNECTION
Ответственность
Создает,
хранит
и
предоставляет
доступ к единственному экземпляру
соединения с базой данных
Использованные
паттерны Паттерн «Одиночка» [2]
проектирования
Мотивация к созданию
Надо
иметь
одно
глобальное
соединение с базой данных и иметь к
нему
доступ
из
любой
точки
приложение. Одно - из соображений
производительности
Методы
Название
Ответственность
DbConnection
Приватный конструктор
instance
Создает
или
возвращает
единственный экземпляр этого класса
setupConnection
Устанавливает соединение
getDb
Возвращает
объект
содержащий
database,
информацию
о
соединении с базой данных
Атрибуты
Название
Ответственность
database
Объект
хранит
информацию
о
соединении с базой данных
_instance
Переменная хранит объект класса
DbConnection
DBFABRIC
Ответственность
Управляет
созданием
связанного
набора объектов, необходимых для
доступа к базе данных
Использованные
паттерны Паттерн «Фабрика» [2]
проектирования
Мотивация к созданию
Необходимо
создавать
иметь
именно
возможность
логически
связанные
объекты
инициализировать
образом.
их
Поэтому
и
правильным
используется
фабрика.
Методы
Название
Ответственность
DbFabric
Конструктор класса
createPolygonModel
Создает модель доступа к таблице
«Полигон»
createLoopModel
Создает модель доступа к таблице
«Петля»
createPicketModel
Создает модель доступа к таблице
«Пикет»
createEmfModel
Создает модель доступа к таблице
«ЭДС»
Атрибуты
Название
Ответственность
LoopModelColumns
Перечисление
«Петля»,
столбцов
являющихся
таблицы
внешними
ключами
PicketModelColumns
Перечисление
«Пикет»,
столбцов
являющихся
таблицы
внешними
ключами
TREEVIEWADAPTER
Ответственность
отображает
интерфейс
модели
QSqlRelationalTableModel,
используемой для доступа к БД на
интерфейс
модели
QStandardItemModel, пригодной для
использования
в
стандартных
представлениях QT
Использованные
паттерны Паттерн «Адаптер» [2]
проектирования
Мотивация к созданию
С базой работает одна модель, а
(стандартное
QTreeView
представление) принимает другую.
Необходимо
где-то
написать
конвертацию одной модели в другую.
Идеально для этого подходит адаптер
Методы
Название
Ответственность
_prepareRow
Переводит строку модели одного
вида в строку модели другого вида
TreeViewAdapter
Конструктор класса. Если передана
модель базы данных, добавляет ее
addModel
Конвертирует пришедшие данные в
нужный формат
INFODOCKELEMENTSBUILDER
Ответственность
Управляет созданием графического
представления переданной модели
Использованные
паттерны Паттерн «Строитель» [2]
проектирования
Мотивация к созданию
Каждый объект базы данных может
иметь
несколько
различных
представлений. Необходимо отделить
процесс конструирования объекта от
его представления.
Методы
Название
Ответственность
build
Конструирование объекта
PICKETINFODOCKBUILDER
Ответственность
Реализация
абстрактного
класса
InfoDockElementsBuilder. Отвечает за
создание структуры объекта «пикет».
Использованные
паттерны Паттерн «Строитель» [2]
проектирования
Мотивация к созданию
Необходимость
отображать
информацию об объекте «Пикет»
Методы
Название
Ответственность
build
Конструирование объекта
LOOPINFODOCKBUILDER
Ответственность
Реализация
абстрактного
класса
InfoDockElementsBuilder. Отвечает за
создание структуры объекта «петля».
Использованные
паттерны Паттерн «Строитель» [2]
проектирования
Мотивация к созданию
Необходимость
отображать
информацию об объекте «Петля»
Методы
Название
Ответственность
build
Конструирование объекта
DRAWABLEBUILDER
Ответственность
Управляет созданием графического
представления переданной модели
Использованные
паттерны Паттерн «Строитель» [2]
проектирования
Мотивация к созданию
Каждый объект базы данных может
иметь
несколько
различных
представлений. Необходимо отделить
процесс конструирования объекта от
его представления.
Методы
Название
Ответственность
build
Конструирование объекта
LOOPDRAWABLEBUILDER
Ответственность
Реализация
абстрактного
DrawableBuilder.
класса
Отвечает
за
создание структуры объекта «петля».
Использованные
паттерны Паттерн «Строитель» [2]
проектирования
Мотивация к созданию
Необходимость
отображать
информацию об объекте «Петля»
Методы
Название
Ответственность
build
Конструирование объекта
PICKETDRAWABLEBUILDER
Ответственность
Реализация
абстрактного
DrawableBuilder.
Отвечает
класса
за
создание структуры объекта «пикет».
Использованные
паттерны Паттерн «Строитель» [2]
проектирования
Мотивация к созданию
Необходимость
отображать
информацию об объекте «Пикет»
Методы
Название
Ответственность
build
Конструирование объекта
EMFDRAWABLEBUILDER
Ответственность
Реализация
абстрактного
DrawableBuilder.
класса
Отвечает
за
создание структуры объекта «ЭДС».
Использованные
паттерны Паттерн «Строитель» [2]
проектирования
Мотивация к созданию
Необходимость
отображать
информацию об объекте «ЭДС»
Методы
Название
Ответственность
build
Конструирование объекта
GLWIDGET
Ответственность
Холст
для
размещения
объектов
OpenGL
Использованные
паттерны Паттерн «Контроллер»
проектирования
Мотивация к созданию
Имеется
элементов,
набор
которые
нетривиальных
необходимо
рисовать с помощью OpenGL. Этот
класс ими управляет
Методы
Название
Ответственность
_calcTotalBounding
Вычисление границ изображения
_applyScaleAndTranslate
Применение
масштаба
и
перемещения
addDrawable
Добавление графического объекта
autoScale
Применение
масштаба
к
изображению
clear
Удаление графических объектов
initializeGL
Реализация
стандартной
унаследованной
от
функции,
QGLWidget,
вызывающаяся при инициализации
графики
paintGL
Реализация
стандартной
унаследованной
от
функции,
QGLWidget.
Рисование графических объектов
resizeGL
Реализация
стандартной
унаследованной
от
функции,
QGLWidget.
Вызывается при изменении размеров
окна.
mousePressEvent
Реализация
стандартной
унаследованной
от
функции,
QGLWidget.
Запоминает координаты мыши.
mouseMoveEvent
Реализация
стандартной
унаследованной
Обработчик
от
функции,
QGLWidget.
перемещения
мыши.
Необходим для смещения центра
координат.
wheelEvent
Реализация
стандартной
унаследованной
от
функции,
QGLWidget.
Обработчик вращения колеса мыши.
Необходим изменения масштаба.
TAGABLE
Ответственность
Интерфейс.
Задает
поведение
объекта, который можно пометить.
Использованные
паттерны -
проектирования
Мотивация к созданию
Создан для того, чтобы можно было
соотнести элементы базы данных с их
графическими представлениями
Методы
Название
Ответственность
setTag
Устанавливает метку
getTag
Возвращает текущую метку
SELECTABLE
Ответственность
Интерфейс.
Задает
поведение
выделенного объекта
Использованные
паттерны -
проектирования
Мотивация к созданию
Имеется
множество
графических
различных
объектов.
Каждый
может быть выделен на холсте.
Объект,
который
выделенным,
хочет
реализует
быть
этот
интерфейс.
Методы
Название
Ответственность
isSelected
Виртуальная функция. Отвечает за
вычисление
попадания
заданной
точки на объект.
DRAWABLE
Ответственность
Интерфейс.
Задает
поведение
объекта, который имеет графическое
представление на экране.
Использованные
паттерны -
проектирования
Мотивация к созданию
Имеется
множество
различных
графических объектов (ЭДС, петли,
пикеты). Всех их нужно рисовать поразному. Объект, желающий иметь
представление на экране, реализует
этот
интерфейс.
Таким
образом,
контроллеру не надо заботиться о
конкретном
типе
отображаемого
элемента.
Методы
Название
Ответственность
draw
Виртуальная функция. Отвечает за
отрисовку объекта.
getBoundingBox
Виртуальная функция. Отвечает за
вычисление сторон охватывающего
объект прямоугольника.
setColor
Виртуальная функция. Отвечает за
установление цвета
SQUARELOOPDRAWABLE
Ответственность
Реализует графическое отображение
объекта
Реализует
«квадратная
интерфейс
петля».
Drawable,
Selectable, Tagable
Использованные
паттерны -
проектирования
Мотивация к созданию
Представление объекта «квадратная
петля»
Методы
Название
Ответственность
draw
Реализация
графического
представления,
унаследована
от
Drawable
getBoundingBox
Реализация
вычисления
охватывающего
сторон
прямоугольника,
унаследована от Drawable
setColor
Реализация задания цвета объекту,
унаследована от Drawable
SquareLoopDrawable
Конструктор класса
isSelected
Реализация вычисления попадания
данной
точки
на
объект,
унаследована от Selectable
CIRCLELOOPDRAWABLE
Ответственность
Реализует графическое отображение
объекта «круглая петля». Реализует
интерфейс
Drawable,
Selectable,
Tagable
Использованные
паттерны -
проектирования
Мотивация к созданию
Представление объекта «квадратная
петля»
Методы
Название
Ответственность
draw
Реализация
графического
представления,
унаследована
от
Drawable
getBoundingBox
Реализация
вычисления
охватывающего
сторон
прямоугольника,
унаследована от Drawable
setColor
Реализация задания цвета объекту,
унаследована от Drawable
CircleLoopDrawable
Конструктор класса
isSelected
Реализация вычисления попадания
данной
точки
на
объект,
унаследована от Selectable
_pointInCircle
Вспомогательная
вычисления
функция
попадания
точки
для
на
объект (isSelected)
PICKETDRAWABLE
Ответственность
Реализует графическое отображение
объекта
интерфейс
«пикет».
Drawable,
Реализует
Selectable,
Tagable
Использованные
паттерны -
проектирования
Мотивация к созданию
Представление объекта «квадратная
петля»
Методы
Название
Ответственность
draw
Реализация
графического
представления,
унаследована
от
Drawable
getBoundingBox
Реализация
вычисления
охватывающего
сторон
прямоугольника,
унаследована от Drawable
setColor
Реализация задания цвета объекту,
унаследована от Drawable
PicketDrawable
Конструктор класса
isSelected
Реализация вычисления попадания
данной
точки
на
объект,
унаследована от Selectable
EMFDRAWABLE
Ответственность
Реализует графическое отображение
объекта «ЭДС». Реализует интерфейс
Drawable, Selectable
Использованные
паттерны -
проектирования
Мотивация к созданию
Представление объекта «квадратная
петля»
Методы
Название
Ответственность
draw
Реализация
графического
представления,
унаследована
от
Drawable
getBoundingBox
Реализация
вычисления
охватывающего
сторон
прямоугольника,
унаследована от Drawable
setColor
Реализация задания цвета объекту,
унаследована от Drawable
EmfDrawable
Конструктор класса
isSelected
Реализация вычисления попадания
данной
точки
на
объект,
унаследована от Selectable
addCoordinates
Добавляет координату к кривой ЭДС
_containsPoint
Вспомогательная
функция
вычисления
охватывающего
для
прямоугольника (isSelected)
COORDINATEGRID
Ответственность
Реализует графическое отображение
сетки и осей координат
Использованные
паттерны -
проектирования
Мотивация к созданию
Необходимость
отображать
координатную сетку на экране
Методы
Название
Ответственность
_drawAxis
Реализация
координат
отрисовки
осей
_drawLines
Реализация
отрисовки
линий
координатной сетки
_getTranslatedBoundingBox
Вычисляет
нужно
область,
нарисовать
на
которой
координатную
сетку так, чтобы она закрыла весь
экран
CoordinateGrid
Конструктор класса
isSelected
Реализация вычисления попадания
данной
точки
на
объект,
унаследована от Selectable
setBoundingBox
Устанавливает границы сетки
setStep
Задает шаг по осям координат
draw
Реализация
графического
отображения координатной сетки
setAxisColor
Устанавливает цвет осей координат
setGridLinesColor
Устанавливает
координатной сетки
цвет
линий
4..2. UML-диаграмма классов
DbConnection
-database
-_instance
+instance()
+setupConnection()
+getDb()
-DbConnection()
TreeViewAdapter
-_prepareRow()
+TreeViewAdapter()
+addModel()
InfoDockWidgetBuilder
Settings
+login
+password
+dbname
+hostname
+idPolygon
-_instance
+instance()
+save()
+load()
-Settings()
-_preparePicketInfoLabel()
-_prepareLoopInfoLabel()
+InfoDockWidgetBuilder()
+createPicketInfoDock()
+createLoopInfoDock()
DrawableBuilder
+DrawableBuilder()
+buildLoopDrawable()
+buildEmfDrawable()
+buildPicketDrawable()
Drawable
+draw()
+getBoundingBox()
+setColor()
SquareLoopDrawable
-_center
-_color
-_side
+draw()
+getBoundingBox()
+setColor()
+SquareLoopDrawable()
+isSelected()
Tagable
-tag
+setTag()
+getTag()
DbFabric
+LooModelColumns
+PicketModelColumns
+DbFabric()
+createPolygonModel()
+createLoopModel()
+createPicketModel()
+createEmfModel()
CircleLoopDrawable
-_center
-_color
-_radius
+draw()
+getBoundingBox()
+setColor()
+CircleLoopDrawable()
+isSelected()
-_pointInCircle()
PicketDrawable
-_center
-_color
+draw()
+getBoundingBox()
+setColor()
+PicketDrawable()
+isSelected()
EmfDrawable
-_color
-_emf
-POINT_DELTA
+draw()
+getBoundingBox()
+setColor()
+EmfDrawable()
+isSelected()
+addCoordinates()
-_containsPoint()
GLWidget
-_drawables
-_grid
-_viewport
-_scale
-_translateX
-_translateY
-_lastPos
-MIN_SCALE
-MAX_SCALE
-SCALE_STEP
-GRID_STEP
-_calcTotalBounding()
-_applyScaleAndTranslate()
+addDrawable()
+autoScale()
+clear()
#initializeGL()
#paintGL()
#resizeGL()
#mousePressEvent()
#mouseMoveEvent()
#wheelEvent()
CoordinateGrid
-_halfWidth
-_halfHeight
-_centerX
-_centerY
-_dx
-_dy
-_axisColor
-_gridLinesColor
-_drawAxis()
-_drawLines()
-_getTranslatedBoundingBox()
+CoordinateGrid()
+setBoundingBox()
+setStep()
+draw()
+setAxisColor()
+setGridLinesColor()
Selectable
+isSelected()
5. Результаты работы программы
Ниже представлены результаты работы программы.
Интерфейс программы визуально разделен на три части: левая панель с
деревом проекта, правая панель с информацией об элементах диаграммы и
рабочая область по центру, в которой отображаются сложные графические
элементы (Рис.1). Элементы состоят из координатной сетки и изображения
петель и пикетов. Дерево проектов заполняется элементами из базы данных,
также как и правая панель, которая содержит информацию о них.
Рис.1.
Интерфейс приложения. ОС Linux Mint.
Интерфейс основан на плавающих окнах. Панели справа и слева свободно
открепляются мышкой и размещаются в любом месте рабочего стола. Также
могут быть прикреплены к любому краю главного окна приложения (Рис.2).
Рис.2.
Интерфейс приложения. ОС Linux Mint. Плавающие доки.
Благодаря библиотеке QT программа является кросс-платформенной на
уровне исходного кода. Исходный текст программы без существенных
изменений был скомпилирован и запущен под ОС Windows. Внешний вид
можно увидеть на Рис.3.
Рис.3.
Интерфейс приложения. ОС Windows 7.
Рабочая область приложения масштабируется и перемещается при помощи
мыши. Это можно увидеть на Рис.4.
Рис.4.
Интерфейс приложения. ОС Windows 7. Масштабирование и сдвиг.
Заключение
В результате выполнения преддипломной практики были изучены
подсистемы библиотеки QT, касающиеся доступа к базе данных, визуальной
верстки
интерфейса,
программной
генерации
элементов
графического
интерфейса, обработка событий, взаимодействие элементов программы
посредством сигналов и слотов.
Разработка производилась в QT Creator под операционными системами
Linux Mint и Microsoft Windows 7.
Были
изучены
и
выполнены
основные
этапы
цикла
разработки
программного обеспечения [4]:
 Анализ требований к продукту
 Разработка спецификации в виде прецедентов
 Проектирование архитектуры базы данных и программы
 Реализация и тестирование на языке С++ в среде QTCreator
Были
изучены
и
применены
следующие
паттерны
объектно-
ориентированного проектирования [2]:
 Фабрика
 Одиночка
 Адаптер
 Модель/Представление/Контроллер
Для закрепления полученных знаний был разработан рабочий прототип
приложения со следующим функционалом:
 Подключение к базе данных и выборка элементов из таблиц базы
данных
 Отображение петель и пикетов в дереве проекта в левом доке
 Отображение информации о пикетах и петлях в правом доке
 Подключение
библиотеки
OpenGL
и
отображение
диаграммы петель и пикетов на экране
 Масштабирование и перемещение диаграммы мышкой
двумерной
 Отображение координатной сетки
 Программа является кросс-платформенной на уровне исходного кода
Список использованных источников
1. Online Reference Documentation. [Электронный ресурс]. Режим
доступа: http://doc.qt.nokia.com/.
2. Гамма Э., Хелм Р., Джонсон Р., Влиссидес Дж. Приемы объектноориентированного проектирования. Паттерны проектирования. –
СПб: Питер, 2010. – 366с.: ил.
3. Б. Страуструп Язык программирования С++, спец. изд./Пер. с англ. М.; СПб.: "Издательство БИНОМ" - "Невский Диалект", 2001г. 1099 с., ил.
4. Макконнелл С. Совершенный код. Мастер-класс / Пер. с англ. – М. :
Издательство «Русская редакция», 2011. – 896 стр. : ил.
5. М. Э. Рояк, Г. М. Тригубович, А. В. Чернышев. Интерактивная
компьютерная графика : учебно-методическое пособие / Новосиб.
гос. техн. ун-т ; - НГТУ, 2005. - 24 c.
6. Введение в проектирование реляционных баз данных. [Электронный
ресурс]. Режим доступа: http://ami.nstu.ru/~vms/method2m/index.htm.
Приложение
“settings.h”
#ifndef SETTINGS_H
#define SETTINGS_H
#include<QSharedPointer>
#include<QSettings>
class Settings
{
private:
static QSharedPointer<Settings> _instance;
Settings();
public:
/* settings for database */
QString login;
QString password;
QString dbname;
QString hostname;
int idPolygon;
static QSharedPointer<Settings> instance();
QSettings::Status save();
QSettings::Status load();
};
#endif // SETTINGS_H
“settings.cpp”
#include "settings.h"
QSharedPointer<Settings> Settings::_instance;
Settings::Settings()
{
this->login = "root";
this->password = "";
this->dbname = "emdp";
this->hostname = "localhost";
}
QSharedPointer<Settings> Settings::instance()
{
if(_instance.isNull())
{
_instance = QSharedPointer<Settings>(new Settings);
}
return _instance;
}
QSettings::Status Settings::save()
{
QSettings settings;
settings.beginGroup("Database settings");
settings.setValue("login", this->login);
settings.setValue("password", this->password);
settings.setValue("dbname", this->dbname);
settings.setValue("hostname", this->hostname);
settings.endGroup();
return settings.status();
}
QSettings::Status Settings::load()
{
QSettings settings;
settings.beginGroup("MainWindow");
settings.beginGroup("Database settings");
this->login = settings.value("login").toString();
this->password = settings.value("password").toString();
this->dbname = settings.value("dbname").toString();
this->hostname = settings.value("hostname").toString();
settings.endGroup();
return settings.status();
}
“dbconnection.h”
#ifndef DBCONNECTION_H
#define DBCONNECTION_H
#include<settings.h>
#include<QSqlDatabase>
#include<QSharedPointer>
class DbConnection
{
private:
DbConnection();
static QSharedPointer<DbConnection> _instance;
public:
static QSharedPointer<DbConnection> instance();
bool setupConnection(QSharedPointer<Settings> settings);
QSqlDatabase getDb();
~DbConnection();
};
#endif // DBCONNECTION_H
“dbconnection.cpp”
#include "dbconnection.h"
QSharedPointer<DbConnection> DbConnection::_instance;
DbConnection::DbConnection()
{
}
DbConnection::~DbConnection()
{
}
QSharedPointer<DbConnection> DbConnection::instance()
{
if(_instance.isNull())
{
_instance = QSharedPointer<DbConnection>(new DbConnection);
}
return _instance;
}
bool DbConnection::setupConnection(QSharedPointer<Settings> settings)
{
QSqlDatabase database = QSqlDatabase::addDatabase("QMYSQL");
database.setHostName(settings->hostname);
database.setDatabaseName(settings->dbname);
database.setUserName(settings->login);
database.setPassword(settings->password);
return database.open();
}
QSqlDatabase DbConnection::getDb()
{
return QSqlDatabase::database();
}
“coordinategrid.h”
#ifndef COORDINATEGRID_H
#define COORDINATEGRID_H
#include<QPoint>
#include<QtOpenGL>
#include<QColor>
class CoordinateGrid
{
private:
double _halfWidth;
double _halfHeight;
double _centerX;
double _centerY;
double _dx;
double _dy;
QColor _axisColor;
QColor _gridLinesColor;
private:
void _drawAxis();
void _drawLines();
QRectF _getTranslatedBoundingBox();
public:
CoordinateGrid();
void setBoundingBox(double centerX,
double heigth);
void setStep(double dx, double dy);
void draw();
void setAxisColor(QColor color);
void setGridLinesColor(QColor color);
double
centerY,
double
width,
};
#endif // COORDINATEGRID_H
“coordinategrid.cpp”
#include "coordinategrid.h"
#include<math.h>
CoordinateGrid::CoordinateGrid()
{
_axisColor = QColor(0, 0, 0);
_gridLinesColor = QColor(217, 217, 217);
}
void CoordinateGrid::setBoundingBox(double centerX, double centerY, double
width, double heigth)
{
_centerX = centerX;
_centerY = centerY;
_halfWidth = width/2.0;
_halfHeight = heigth/2.0;
}
void CoordinateGrid::setStep(double dx, double dy)
{
if(dx < 1.0) dx = 1.0;
if(dy < 1.0) dy = 1.0;
_dx = dx;
_dy = dy;
}
void CoordinateGrid::draw()
{
glColor3d(_gridLinesColor.red()/255.0,
_gridLinesColor.green()/255.0,
_gridLinesColor.blue()/255.0);
_drawLines();
glColor3d(_axisColor.red()/255.0,
_axisColor.green()/255.0,
_axisColor.blue()/255.0);
_drawAxis();
}
void CoordinateGrid::_drawAxis()
{
QRectF box = _getTranslatedBoundingBox();
glLineWidth(3);
glBegin(GL_LINES);
glVertex2d(0.0, box.top());
glVertex2d(0.0, box.bottom());
glVertex2d(box.left(), 0.0);
glVertex2d(box.right(), 0.0);
glEnd();
glLineWidth(1);
}
void CoordinateGrid::_drawLines()
{
QRectF box = _getTranslatedBoundingBox();
glBegin(GL_LINES);
int i=0;
for(double x = box.left(); x < box.right(); i++)
{
x = box.left() + _dx * i;
glVertex2d(x, box.top());
glVertex2d(x, box.bottom());
}
int j=0;
for(int y = box.top(); y <= box.bottom(); j++)
{
y = box.top() + _dy * j;
glVertex2d(box.left(), y);
glVertex2d(box.right(), y);
}
glEnd();
}
QRectF CoordinateGrid::_getTranslatedBoundingBox()
{
double leftX = _centerX - _halfWidth - fmod(_centerX - _halfWidth, _dx)
- _dx;
double topY = _centerY - _halfHeight - fmod(_centerY - _halfHeight,
_dy) - _dy;
double rightX = leftX + 2*_halfWidth + _dx;
double bottomY = topY + 2*_halfHeight + _dy;
return QRectF(QPointF(leftX, topY), QPointF(rightX, bottomY));
}
void CoordinateGrid::setAxisColor(QColor color)
{
_axisColor = color;
}
void CoordinateGrid::setGridLinesColor(QColor color)
{
_gridLinesColor = color;
}
“dbfabric.h”
#ifndef DBFABRIC_H
#define DBFABRIC_H
#include<QSqlRelationalTableModel>
#include<QSharedPointer>
class DbFabric
{
public:
DbFabric();
enum LoopModelColumns
{
LOOP_MODEL_ID_POLYGON = 1,
LOOP_MODEL_ID_LOOP_FORM = 2
};
enum PicketModelColumns
{
PICKET_MODEL_ID_LOOP = 1,
PICKET_MODEL_ID_EMF = 2
};
QSharedPointer<QSqlRelationalTableModel>
QSharedPointer<QSqlRelationalTableModel>
QSharedPointer<QSqlRelationalTableModel>
QSharedPointer<QSqlRelationalTableModel>
createPolygonModel();
createGeneratorLoopModel();
createPicketModel();
createEmfModel();
};
#endif // DBFABRIC_H
“dbfabric.cpp”
#include "dbfabric.h"
DbFabric::DbFabric()
{
}
QSharedPointer<QSqlRelationalTableModel> DbFabric::createPolygonModel()
{
QSharedPointer<QSqlRelationalTableModel>
model
=
QSharedPointer<QSqlRelationalTableModel>(new QSqlRelationalTableModel);
model->setTable("Polygon");
return model;
}
QSharedPointer<QSqlRelationalTableModel>
DbFabric::createGeneratorLoopModel()
{
QSharedPointer<QSqlRelationalTableModel>
model
=
QSharedPointer<QSqlRelationalTableModel>(new QSqlRelationalTableModel);
model->setTable("GeneratorLoop");
model->setRelation(LOOP_MODEL_ID_POLYGON,
QSqlRelation("Polygon",
"idPolygon", "description"));
model->setRelation(LOOP_MODEL_ID_LOOP_FORM,
QSqlRelation("LoopForm",
"idLoopForm", "form"));
return model;
}
QSharedPointer<QSqlRelationalTableModel> DbFabric::createPicketModel()
{
QSharedPointer<QSqlRelationalTableModel>
model
=
QSharedPointer<QSqlRelationalTableModel>(new QSqlRelationalTableModel);
model->setTable("Picket");
//model->setRelation(PICKET_MODEL_ID_LOOP,
QSqlRelation("GeneratorLoop", "idGeneratorLoop", "idGeneratorLoop"));
//model->setRelation(PICKET_MODEL_ID_EMF, QSqlRelation("Emf", "idEmf",
"idEmf"));
return model;
}
QSharedPointer<QSqlRelationalTableModel> DbFabric::createEmfModel()
{
QSharedPointer<QSqlRelationalTableModel>
model
=
QSharedPointer<QSqlRelationalTableModel>(new QSqlRelationalTableModel);
model->setTable("Emf");
return model;
}
“tagable.h”
#ifndef TAGABLE_H
#define TAGABLE_H
class Tagable
{
protected:
int tag;
public:
virtual void setTag(int _tag) = 0;
virtual int getTag() = 0;
};
#endif // TAGABLE_H
“selectable.h”
#ifndef SELECTABLE_H
#define SELECTABLE_H
#include<QPoint>
class Selectable
{
public:
virtual bool isSelected(QPoint selectedPoint) = 0;
};
#endif // SELECTABLE_H
“drawable.h”
#ifndef DRAWABLE_H
#define DRAWABLE_H
#include<QRect>
#include<QPoint>
#include<QColor>
#include<QtOpenGL>
#include<math.h>
#define PI 3.14159265
class Drawable
{
public:
virtual void draw()=0;
virtual QRect getBoundingBox()=0;
virtual void setColor(QColor color)=0;
};
#endif // DRAWABLE_H
“circleloopdrawable.h”
#ifndef CIRCLELOOPDRAWABLE_H
#define CIRCLELOOPDRAWABLE_H
#include "drawable.h"
#include "selectable.h"
class CircleLoopDrawable:public Drawable, public Selectable
{
private:
QPoint _center;
QColor _color;
int _radius;
bool _pointInCircle(QPoint point);
enum {LINE_WIDTH = 5};
public:
CircleLoopDrawable();
CircleLoopDrawable(QPoint center, int radius);
void draw();
void setColor(QColor color);
QRect getBoundingBox();
bool isSelected(QPoint selectedPoint);
};
#endif // CIRCLELOOPDRAWABLE_H
“circleloopdrawable.cpp”
#include "circleloopdrawable.h"
#include <math.h>
CircleLoopDrawable::CircleLoopDrawable()
{
}
CircleLoopDrawable::CircleLoopDrawable(QPoint center, int radius)
{
_center = center;
_radius = radius;
_color = QColor(255, 0, 0);
}
void CircleLoopDrawable::draw()
{
glLineWidth(LINE_WIDTH);
glBegin(GL_LINE_STRIP);
glColor3d(_color.red()/255.0,
_color.green()/255.0,
_color.blue()/255.0);
for(int i=0; i<=360; i++)
{
double x = _center.x() + _radius*cos(i*PI/180.0);
double y = _center.y() + _radius*sin(i*PI/180.0);
glVertex2d(x, y);
}
glEnd();
glLineWidth(1);
}
void CircleLoopDrawable::setColor(QColor color)
{
_color = color;
}
QRect CircleLoopDrawable::getBoundingBox()
{
QPoint topLeft = QPoint((_center.x() - _radius), (_center.y() +
_radius));
QPoint rightBottom = QPoint((_center.x() + _radius), (_center.y() _radius));
return QRect(topLeft, rightBottom);
}
bool CircleLoopDrawable::_pointInCircle(QPoint point)
{
double x = point.x() - _center.x();
double y = point.y() - _center.y();
double R = _radius;
return ((x*x + y*y) <= R*R );
}
bool CircleLoopDrawable::isSelected(QPoint selectedPoint)
{
QRect boundary = getBoundingBox();
QRect selectedRect = QRect(selectedPoint, selectedPoint);
if(boundary.intersects(selectedRect))
{
if(_pointInCircle(selectedPoint))
{
return true;
}
}
return false;
}
“emfdrawable.h”
#ifndef EMFDRAWABLE_H
#define EMFDRAWABLE_H
#include "drawable.h"
#include "selectable.h"
#include<QPolygon>
class EmfDrawable:public Drawable, public Selectable
{
private:
QColor _color;
QVector<QPoint> _emf;
enum {POINT_DELTA = 1};
bool _containsPoint(QPoint point, QLine line);
public:
EmfDrawable();
void draw();
void setColor(QColor color);
QRect getBoundingBox();
void addCoordinates(QPoint point);
bool isSelected(QPoint selectedPoint);
};
#endif // EMFDRAWABLE_H
“emfdrawable.cpp”
#include "emfdrawable.h"
EmfDrawable::EmfDrawable()
{
_color = QColor(0, 0, 0);
}
void EmfDrawable::addCoordinates(QPoint point)
{
_emf.push_back(point);
}
void EmfDrawable::setColor(QColor color)
{
_color = color;
}
QRect EmfDrawable::getBoundingBox()
{
QPoint maxLeftTop = _emf[0];
QPoint maxRightBottom = _emf[0];
QPoint point;
for(int i=1; i<_emf.size(); i++)
{
point = _emf[i];
if(point.x() < maxLeftTop.x())
{
maxLeftTop.setX(point.x());
}
if(point.y() > maxLeftTop.y())
{
maxLeftTop.setY(point.y());
}
if(point.x() > maxRightBottom.x())
{
maxRightBottom.setX(point.x());
}
if(point.y() < maxRightBottom.y())
{
maxRightBottom.setY(point.y());
}
}
return QRect(maxLeftTop, maxRightBottom);
}
void EmfDrawable::draw()
{
glBegin(GL_LINE_STRIP);
for(int i=0; i<_emf.size(); i++)
{
glColor3d(_color.red(), _color.green(), _color.blue());
glVertex2d(_emf[i].x(), _emf[i].y());
}
glEnd();
}
bool EmfDrawable::isSelected(QPoint selectedPoint)
{
QRect boundary = getBoundingBox();
QRect selectedRect = QRect(selectedPoint, selectedPoint);
if(boundary.intersects(selectedRect))
{
for(int i=1; i<_emf.size(); i++)
{
QLine line = QLine(_emf[i-1], _emf[i]);
if(_containsPoint(selectedPoint, line))
{
return true;
}
}
}
return false;
}
bool EmfDrawable::_containsPoint(QPoint point, QLine line)
{
double up = point.x() - line.p2().x();
double down = line.p1().x() - line.p2().x();
double p = up/down;
double y = p*line.p1().y() + (1 - p)*line.p2().y();
if(p <= 1 && p >= 0 && abs(y - point.y()) <= POINT_DELTA)
{
return true;
}
return false;
}
“drawablebuilder.h”
#ifndef DRAWABLEBUILDER_H
#define DRAWABLEBUILDER_H
#include "drawable.h"
#include "picketdrawable.h"
#include "emfdrawable.h"
#include "squareloopdrawable.h"
#include "circleloopdrawable.h"
#include<QVector>
#include<QSqlRelationalTableModel>
#include<QSqlRecord>
#include<QPoint>
class DrawableBuilder
{
public:
DrawableBuilder();
QVector<Drawable*> buildLoopDrawable(QSqlRelationalTableModel* model);
QVector<Drawable*> buildEmfDrawable(QSqlRelationalTableModel* model);
QVector<Drawable*>
buildPicketDrawable(QSqlRelationalTableModel*
model);
};
#endif // DRAWABLEBUILDER_H
“drawablebuilder.cpp”
#include "drawablebuilder.h"
DrawableBuilder::DrawableBuilder()
{
}
QVector<Drawable
*>
DrawableBuilder::buildPicketDrawable(QSqlRelationalTableModel *model)
{
QVector<Drawable*> vector;
for(int i=0; i<model->rowCount(); i++)
{
QSqlRecord rec = model->record(i);
QVariant x = rec.value("x");
QVariant y = rec.value("y");
PicketDrawable
*picket
=
new
PicketDrawable(QPoint(x.toInt(),
y.toInt()));
vector.push_back(picket);
}
return vector;
}
QVector<Drawable
*>
DrawableBuilder::buildEmfDrawable(QSqlRelationalTableModel *model)
{
QVector<Drawable*> vector;
EmfDrawable *emf = new EmfDrawable();
for(int i=0; i<model->rowCount(); i++)
{
QSqlRecord rec = model->record(i);
QVariant x = rec.value("time");
QVariant y = rec.value("value");
emf->addCoordinates(QPoint(x.toInt(), y.toInt()));
}
vector.push_back(emf);
return vector;
}
QVector<Drawable
*>
DrawableBuilder::buildLoopDrawable(QSqlRelationalTableModel *model)
{
QVector<Drawable*> vector;
for(int i=0; i<model->rowCount(); i++)
{
QSqlRecord rec = model->record(i);
QVariant x = rec.value("centerX");
QVariant y = rec.value("centerY");
QVariant size = rec.value("size");
QVariant form = rec.value("form");
if(form.toString() == "square")
{
SquareLoopDrawable
*loop
=
new
SquareLoopDrawable(QPoint(x.toInt(), y.toInt()), size.toInt());
vector.push_back(loop);
}
else
{
CircleLoopDrawable
*loop
=
new
CircleLoopDrawable(QPoint(x.toInt(), y.toInt()), size.toInt());
vector.push_back(loop);
}
}
return vector;
}
“infodockwidgetbuilder.h”
#ifndef INFODOCKWIDGETBUILDER_H
#define INFODOCKWIDGETBUILDER_H
#include<QWidget>
#include<QSqlRelationalTableModel>
#include<QVBoxLayout>
#include<QLayout>
#include<QLabel>
#include<QPushButton>
#include<QSqlRecord>
class InfoDockWidgetBuilder
{
private:
QVector<QWidget*> _preparePicketInfoLabel(QSqlRecord record);
QVector<QWidget *> _prepareLoopInfoLabel(QSqlRecord record);
public:
QLayout* createPicketInfoDock(QSqlRelationalTableModel* model);
QLayout* createLoopInfoDock(QSqlRelationalTableModel* model);
};
endif // INFODOCKWIDGETBUILDER_H
“infodockwidgetbuilder.cpp”
#include "infodockwidgetbuilder.h"
QLayout
*
InfoDockWidgetBuilder::createPicketInfoDock(QSqlRelationalTableModel *model)
{
QVBoxLayout* box = new QVBoxLayout();
for(int i=0; i<model->rowCount(); i++)
{
QSqlRecord rec = model->record(i);
QVector<QWidget*> picketInfo = _preparePicketInfoLabel(rec);
for(int j=0; j<picketInfo.size(); j++)
box->addWidget(picketInfo[j]);
}
return box;
}
QVector<QWidget*> InfoDockWidgetBuilder::_preparePicketInfoLabel(QSqlRecord
record)
{
QVector<QWidget*> result;
QLabel
*
header
=
new
QLabel("Picket
"
+
record.value("idPicket").toString());
header->setFont(QFont("Arial", 14, 3));
result.push_back(header);
QString item;
item = "x: " + record.value("x").toString() + "\n";
item += "y: " + record.value("y").toString() + "\n";
QLabel * body = new QLabel(item);
result.push_back(body);
return result;
}
QLayout
*
InfoDockWidgetBuilder::createLoopInfoDock(QSqlRelationalTableModel *model)
{
QVBoxLayout* box = new QVBoxLayout();
for(int i=0; i<model->rowCount(); i++)
{
QSqlRecord rec = model->record(i);
QVector<QWidget*> loopInfo = _prepareLoopInfoLabel(rec);
for(int j=0; j<loopInfo.size(); j++)
box->addWidget(loopInfo[j]);
}
return box;
}
QVector<QWidget*>
InfoDockWidgetBuilder::_prepareLoopInfoLabel(QSqlRecord
record)
{
QVector<QWidget*> result;
QLabel
*
header
=
new
QLabel("Loop
"
+
record.value("idGeneratorLoop").toString());
header->setFont(QFont("Arial", 14, 3));
result.push_back(header);
QString item;
item = "centerX: " + record.value("centerX").toString() + "\n";
item += "centerY: " + record.value("centerY").toString() + "\n";
QLabel * body = new QLabel(item);
result.push_back(body);
return result;
}
“glwidget.h”
#ifndef GLWIDGET_H
#define GLWIDGET_H
#include<QGLWidget>
#include<QVector>
#include "drawable.h"
#include "selectable.h"
#include "coordinategrid.h"
class GLWidget : public QGLWidget
{
Q_OBJECT
private:
QVector<Drawable*> _drawables;
CoordinateGrid _grid;
QRect _viewport;
int _scale;
GLfloat _translateX;
GLfloat _translateY;
QPoint _lastPos;
private:
void _calcTotalBounding();
void _applyScaleAndTranslate();
enum { MIN_SCALE=10, MAX_SCALE=400, SCALE_STEP=10 };
enum { GRID_STEP = 5 };
public:
GLWidget(QWidget *parent = 0);
~GLWidget();
void addDrawable(Drawable* object);
void autoScale();
void clear();
protected:
void initializeGL();
void paintGL();
void resizeGL(int width, int height);
void mousePressEvent(QMouseEvent *event);
void mouseMoveEvent(QMouseEvent *event);
void wheelEvent(QWheelEvent *event);
};
#endif
“glwidget.cpp”
#include <QtGui>
#include <QtOpenGL>
#include <math.h>
#include "glwidget.h"
GLWidget::GLWidget(QWidget *parent)
: QGLWidget(parent)
{
_scale = 100;
_translateX = 0;
_translateY = 0;
_grid.setStep(GRID_STEP, GRID_STEP);
_grid.setGridLinesColor(QColor(200, 200, 200));
_grid.setAxisColor(QColor(180, 180, 180));
}
GLWidget::~GLWidget()
{
clear();
}
void GLWidget::addDrawable(Drawable *object)
{
_drawables.push_back(object);
}
void GLWidget::initializeGL()
{
qglClearColor(QColor(Qt::white));
if(_drawables.size() > 0)
{
autoScale();
}
}
void GLWidget::paintGL()
{
makeCurrent();
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
_grid.draw();
for(int i=0; i<_drawables.size(); i++)
{
Selectable* selectItem = dynamic_cast<Selectable*>(_drawables[i]);
_drawables[i]->draw();
}
}
void GLWidget::resizeGL(int width, int height)
{
glViewport(0, 0, width, height);
glMatrixMode(GL_PROJECTION);
glLoadIdentity();
glOrtho(-width/2, width/2, -height/2, height/2, -1.0, 1.0);
glMatrixMode(GL_MODELVIEW);
_applyScaleAndTranslate();
}
void GLWidget::mousePressEvent(QMouseEvent *event)
{
_lastPos = event->pos();
}
void GLWidget::mouseMoveEvent(QMouseEvent *event)
{
double dScale = _scale/100.0;
GLfloat dx = (GLfloat)(event->x() - _lastPos.x())/dScale;
GLfloat dy = (GLfloat)(event->y() - _lastPos.y())/dScale;
if(event->buttons() == Qt::LeftButton)
{
_translateX += dx;
_translateY -= dy;
_applyScaleAndTranslate();
updateGL();
}
_lastPos = event->pos();
}
void GLWidget::wheelEvent(QWheelEvent *event)
{
if(event->delta() > 0)
{
_scale += SCALE_STEP;
} else
{
_scale -= SCALE_STEP;
}
if(_scale <= MIN_SCALE) _scale = MIN_SCALE;
if(_scale >= MAX_SCALE) _scale = MAX_SCALE;
_applyScaleAndTranslate();
updateGL();
}
void GLWidget::_calcTotalBounding()
{
QRect currentRect = _drawables[0]->getBoundingBox();
QPoint maxLeftTop = currentRect.topLeft();
QPoint maxRightBottom = currentRect.bottomRight();
QPoint leftTop;
QPoint rightBottom;
for(int i=1; i<_drawables.size(); i++)
{
currentRect = _drawables[i]->getBoundingBox();
leftTop = currentRect.topLeft();
rightBottom = currentRect.bottomRight();
if(leftTop.x() < maxLeftTop.x())
{
maxLeftTop.setX(leftTop.x());
}
if(leftTop.y() > maxLeftTop.y())
{
maxLeftTop.setY(leftTop.y());
}
if(rightBottom.x() > maxRightBottom.x())
{
maxRightBottom.setX(rightBottom.x());
}
if(rightBottom.y() < maxRightBottom.y())
{
maxRightBottom.setY(rightBottom.y());
}
}
maxLeftTop += QPoint(-10, 10);
maxRightBottom += QPoint(10, -10);
_viewport = QRect(maxLeftTop, maxRightBottom);
}
void GLWidget::_applyScaleAndTranslate()
{
double scale = _scale / 100.0;
glMatrixMode(GL_MODELVIEW);
glLoadIdentity();
glScaled(scale, scale, 1);
glTranslatef(_translateX, _translateY, 0);
_grid.setBoundingBox(-_translateX,
-_translateY,
width()/scale,
height()/scale);
_grid.setStep(GRID_STEP, GRID_STEP);
}
void GLWidget::autoScale()
{
_calcTotalBounding();
_translateX = -(_viewport.left()+_viewport.right())/2.0;
_translateY = -(_viewport.bottom()+_viewport.top())/2.0;
int viewPortHeight = abs(_viewport.height());
int viewPortWidth = abs(_viewport.width());
if(viewPortHeight >= viewPortWidth)
{
_scale = 100*height()/viewPortHeight;
}
else
{
_scale = 100*width()/viewPortWidth;
}
if(_scale <= MIN_SCALE) _scale = MIN_SCALE;
if(_scale >= MAX_SCALE) _scale = MAX_SCALE;
_applyScaleAndTranslate();
}
void GLWidget::clear()
{
for(int i=0; i<_drawables.size(); i++)
{
delete _drawables[i];
}
_drawables.clear();
}
“treeviewadapter.h”
#ifndef TREEVIEWADAPTER_H
#define TREEVIEWADAPTER_H
#include<QStandardItemModel>
#include<QStandardItem>
#include<QSqlRelationalTableModel>
#include<QSqlRecord>
#include<QSqlIndex>
class TreeViewAdapter:public QStandardItemModel
{
private:
QList<QStandardItem *> _prepareRow(QSqlRecord* record, QString pkName);
public:
TreeViewAdapter();
TreeViewAdapter(QString header, QSqlRelationalTableModel* model);
void addModel(QString header, QSqlRelationalTableModel* model);
};
#endif // TREEVIEWADAPTER_H
“treeviewadapter.cpp”
#include "treeviewadapter.h"
QList<QStandardItem
*>
TreeViewAdapter::_prepareRow(QSqlRecord*
record,
QString pkName)
{
QList<QStandardItem*> rowItems;
for(int j=0; j<record->count(); j++)
{
QStandardItem*
item
=
new
QStandardItem(record>value(j).toString());
item->setData(record->value(pkName));
item->setEditable(false);
rowItems << item;
}
return rowItems;
}
TreeViewAdapter::TreeViewAdapter()
{
this->setHorizontalHeaderItem(0, new QStandardItem( "Project" ));
}
TreeViewAdapter::TreeViewAdapter(QString header, QSqlRelationalTableModel
*model)
{
this->setHorizontalHeaderItem(0, new QStandardItem( "Project" ));
addModel(header, model);
}
void
TreeViewAdapter::addModel(QString
header,
QSqlRelationalTableModel
*model)
{
QStandardItem *item = new QStandardItem(header);
this->appendRow(item);
for(int i=0; i<model->rowCount(); i++)
{
QSqlRecord record = model->record(i);
QList<QStandardItem*> preparedRow = _prepareRow(&record, model>primaryKey().fieldName(0));
item->appendRow(preparedRow);
}
}
“mainwindow.h”
#ifndef MAINWINDOW_H
#define MAINWINDOW_H
#include<QMainWindow>
#include "glwidget.h"
namespace Ui {
class MainWindow;
}
class MainWindow : public QMainWindow
{
Q_OBJECT
public:
explicit MainWindow(QWidget *parent = 0);
~MainWindow();
GLWidget *glWidget;
QVBoxLayout *rightPanel;
private:
Ui::MainWindow *ui;
QStandardItem* _getTreeSelectedItem(QModelIndex index);
void _showDiagram(QModelIndex index);
void _showDiagramInfo();
public slots:
void onTreeViewDoubleClick(QModelIndex index);
};
#endif // MAINWINDOW_H
“mainwindow.cpp”
#include
#include
#include
#include
#include
"mainwindow.h"
"ui_mainwindow.h"
"dbconnection.h"
"dbfabric.h"
"glwidget.h"
#include "settings.h"
#include "treeviewadapter.h"
#include "picketdrawable.h"
#include "circleloopdrawable.h"
#include "squareloopdrawable.h"
#include "emfdrawable.h"
#include "drawablebuilder.h"
#include "infodockwidgetbuilder.h"
#include<QtGui>
#include<QMessageBox>
#include<QSqlError>
#include<QWidget>
MainWindow::MainWindow(QWidget *parent) :
QMainWindow(parent),
ui(new Ui::MainWindow)
{
ui->setupUi(this);
glWidget = new GLWidget;
rightPanel = new QVBoxLayout;
ui->openGlLayout->addWidget(glWidget);
QSharedPointer<Settings> settings = Settings::instance();
QSharedPointer<DbConnection> dbconnect = DbConnection::instance();
QMessageBox msgBox;
if(!dbconnect->setupConnection(settings))
{
msgBox.setText(dbconnect->getDb().lastError().text());
msgBox.exec();
return;
}
DbFabric fabric;
QSharedPointer<QSqlRelationalTableModel>
modelPicket
=
fabric.createPicketModel();
modelPicket->select();
QSharedPointer<QSqlRelationalTableModel>
modelLoop
=
fabric.createGeneratorLoopModel();
modelLoop->select();
TreeViewAdapter *adapter = new TreeViewAdapter();
adapter->addModel("Loops", modelLoop.data());
adapter->addModel("Picket", modelPicket.data());
ui->leftPanel->setModel(adapter);
connect(ui->leftPanel,
SIGNAL(doubleClicked(QModelIndex)),
this,
SLOT(onTreeViewDoubleClick(QModelIndex)));
ui->rightScrollAreaWidgetContents->setLayout(rightPanel);
ui->leftPanel->expandAll();
}
MainWindow::~MainWindow()
{
delete ui;
}
void MainWindow::_showDiagram(QModelIndex index)
{
DbFabric fabric;
QSharedPointer<QSqlRelationalTableModel>
modelPicket
=
fabric.createPicketModel();
modelPicket->select();
QSharedPointer<QSqlRelationalTableModel>
modelLoop
=
fabric.createGeneratorLoopModel();
modelLoop->select();
DrawableBuilder builder;
QVector<Drawable*> picketVector;
picketVector = builder.buildPicketDrawable(modelPicket.data());
for(int i=0; i<picketVector.size(); i++)
{
glWidget->addDrawable(picketVector[i]);
}
QVector<Drawable*> loopsVector;
loopsVector = builder.buildLoopDrawable(modelLoop.data());
for(int i=0; i<loopsVector.size(); i++)
{
glWidget->addDrawable(loopsVector[i]);
}
glWidget->autoScale();
glWidget->updateGL();
}
QStandardItem * MainWindow::_getTreeSelectedItem(QModelIndex index)
{
QVariant id = index.data(Qt::UserRole+1);
return 0;
}
void MainWindow::onTreeViewDoubleClick(QModelIndex index)
{
_getTreeSelectedItem(index);
_showDiagram(index);
_showDiagramInfo();
}
void MainWindow::_showDiagramInfo()
{
DbFabric fabric;
QSharedPointer<QSqlRelationalTableModel>
modelPicket
=
fabric.createPicketModel();
modelPicket->select();
QSharedPointer<QSqlRelationalTableModel>
modelLoop
=
fabric.createGeneratorLoopModel();
modelLoop->select();
while(rightPanel->count())
{
rightPanel->removeItem(rightPanel->itemAt(0));
}
InfoDockWidgetBuilder info;
QLayout* picketLayout = info.createPicketInfoDock(modelPicket.data());
QLayout* loopLayout = info.createLoopInfoDock(modelLoop.data());
rightPanel->addLayout(picketLayout);
rightPanel->addLayout(loopLayout);
rightPanel->addStretch();
}
#include <QtGui/QApplication>
#include "mainwindow.h"
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
MainWindow w;
w.show();
return a.exec();
}
“main.cpp”
#include <QtGui/QApplication>
#include "mainwindow.h"
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
MainWindow w;
w.show();
return a.exec();
}
Download