МИНИСТЕРСТВО ОБРАЗОВАНИЯ И НАУКИ РОССИЙСКОЙ ФЕДЕРАЦИИ ГОСУДАРСТВЕННОЕ ОБРАЗОВАТЕЛЬНОЕ УЧРЕЖДЕНИЕ ВЫСШЕГО ПРОФЕССИОНАЛЬНОГО ОБРАЗОВАНИЯ «НОВОСИБИРСКИЙ ГОСУДАРСТВЕННЫЙ ТЕХНИЧЕСКИЙ УНИВЕРСИТЕТ» Кафедра Прикладой математики ОТЧЕТ ПО ПРЕДДИПЛОМАНОЙ ПРАКТИКЕ СТУДЕНТА Голубевой Владиславы Владимировны (фамилия, имя, отчество автора) Проектирование кросс-платформенной системы визуализации данных: изучение методик проектирования Направление подготовки 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(); }