Подготовим к использованию новую форму в проекте ).
Для этого следует выполнить следующие действия:
paintForm и удалить из нее все кнопки;Paint ("Рисование") пункта меню Тест;Обработчик выбора команды меню определяется так же, как и ранее:
predicates
onTestPaint : window::menuItemListener.
clauses
onTestPaint(_Source, _MenuTag):-
_ = paintForm::display(This).
Создадим панель инструментов окна paintForm.
Выделим папку paintForm дерева проекта и выберем пункт New In New Package (контекстного) меню. Выделение узла дерева проекта (папки) используется для того, чтобы новый узел оказался внутри этой папки.
В окне ). В поле Style выберем элемент Left (слева). Затем нажмем кнопку OK.
(рис 2.1) Окно атрибутов панели инструментов
После этого откроется редактор панели инструментов (рис. 2.2 рис 2.2).
(рис 2.2) Редактор панели инструментов
Закроем редактор.
Создадим изображения ().
(рис 2.3) Окно атрибутов изображения
Оставим параметры, установленные по умолчанию, и закроем это окно. Откроется графический редактор, показанный на рис. 2.4 рис 2.4.
(рис 2.4) Графический редактор изображения
Далее следует нарисовать изображение, затем закрыть окно, сохранив изменения.
Аналогично создаются растровые изображения (.
(рис 2.5) Панель инструментов окнаПоместим кнопки на панель инструментов. Откроем редактор панели инструментов ). В окне ).
(рис 2.6) Окно Controls
В результате откроется диалоговое окно атрибутов кнопки ).
(рис 2.7) Окно атрибутов кнопки
Заполним поля следующим образом:
Constant: id_color;Status Text: цвет;выберите цвет;Bitmap Name: color.В поле Constant указывается идентификатор команды меню, в поле Status Text — тексты подсказок. При наведении курсора на кнопку color появится подсказка (Tooltip) "цвет". Одновременно в строке состояния (Status Line) отобразится текст "выберите цвет".
Аналогично создаются кнопки ).
(рис 2.8) Создание кнопок в редакторе панели инструментов
Далее следует закрыть редактор панели инструментов.
Теперь необходимо разместить панель инструментов в окне. Для этого следует открыть редактор формы .
Идентификаторы всех ресурсов автоматически записываются в интерфейс resourceIdentifiers (файл resourceIdentifiers.i открывается из дерева проекта). В раздел open имплементации класса paintForm следует добавить имя интерфейса resourceIdentifiers:
open core, vpiDomains, resourceIdentifiers
Весь приведенный ниже код следует поместить в имплементацию класса paintForm.
Инструментами создания изображения в нашем приложении являются "карандаш", "маркер" и "ластик". "Карандаш" ставит точки размером в один пиксель, маркер — крупные "точки" (квадраты) размером 10х10 пикселей, ластик имеет размер 20х20 пикселей. Координаты точек сохраняются в базе данных.
Основные параметры, которые используются для создания изображения, приведены ниже.
domains
dotType = dot; marker; eraser.
constants
dotsize = 5. % половина стороны крупной точки
erasersize = 10. % половина стороны ластика
facts - dbpoints % база данных точек
point: (pnt, dotType, color).
facts
switch : dotType := dot. % метка команды меню
convexhull : pnt* := []. % вершины вып. обол.
bgcolor : color := color_White. % цвет фона
currentColor : color := color_Blue. % текущий цвет
isMouseDown : boolean := false. % нажатие кнопки мыши
Добавим с помощью редактора формы paintForm обработчики событий SizeListener, PaintResponder и EraseBackgroundResponder. Предикаты onSize и onEraseBackground определяются так же, как и ранее.
clauses
onSize(_Source):-
invalidate().
clauses
onEraseBackground(_Source, _GDI) = noEraseBackground.
Ниже приведено определение предиката onPaint.
clauses
onPaint(_Source, Rectangle, GDI):-
true = isMouseDown,
!,
drawPoint(GDI, switch, Rectangle).
onPaint(_Source, _Rectangle, GDI):-
GDI:clear(bgcolor),
point(Pnt, Type, Color),
drawPoint(GDI, Type, Pnt, Color),
fail.
onPaint(_Source, _Rectangle, GDI):-
convexhull <> [],
!,
GDI:setPen(pen(1, ps_Solid, color_Black)),
GDI:setBrush(brush(pat_Hollow, color_Black)),
GDI:drawPolygon(convexhull).
onPaint(_Source, _Rectangle, _GDI).
Все рисование выполняется предикатом onPaint. Первое правило используется непосредственно в процессе рисования при нажатой левой кнопке мыши: обновляется только квадрат размером с "точку" или "ластик". Второе правило строит все изображение. Третье правило строит изображение выпуклой оболочки по списку вершин, который хранит факт-переменная convexhull: рисуется многоугольник с тонкой черной границей и прозрачной заливкой.
Ниже приведено определение предикатов drawPoint/3 и drawPoint/4, а также вспомогательных предикатов.
predicates
drawPoint: (windowGDI, dotType, rct).
drawPoint: (windowGDI, dotType, pnt, color).
clauses
drawPoint(GDI, dot, rct(X, Y, _, _)):- !,
GDI:drawPixel(pnt(X, Y), currentColor).
drawPoint(GDI, Type, Rectangle):- !,
drawRect(GDI, Rectangle, color(Type)).
drawPoint(GDI, dot, Pnt, Color):- !,
GDI:drawPixel(Pnt, Color).
drawPoint(GDI, Type, Pnt, Color):-
drawRect(GDI, rectangle(Pnt, Type), Color).
predicates
drawRect: (windowGDI, rct, color).
clauses
drawRect(GDI, Rectangle, Color):-
GDI:setPen(pen(1, ps_Solid, Color)),
GDI:setBrush(brush(pat_Solid, Color)),
GDI:drawRect(Rectangle).
predicates
rectangle: (pnt, dotType) -> rct.
size: (dotType) -> integer.
color: (dotType) -> integer.
clauses
rectangle(pnt(X, Y), dot) = rct(X, Y, X + 1, Y + 1):- !.
rectangle(pnt(X, Y), Type) = rct(X - S, Y - S, X + S, Y + S):-
S = size(Type).
size(eraser) = erasersize:- !.
size(_) = dotsize.
color(eraser) = bgcolor:- !.
color(_) = currentColor.
Предикат drawPixel/2 закрашивает один пиксель.
Как событие выбора пункта меню, так и событие нажатия кнопки на панели инструментов приводит к вызову обработчика событий MenuItemListener. Добавим его в редакторе формы paintForm. Каждое правило в его определении соответствует нажатию одной из кнопок панели инструментов.
predicates
onMenuItem : window::menuItemListener.
clauses
onMenuItem(_Source, id_color):- % выбор цвета
NewColor = vpiCommonDialogs::getColor(currentColor),
!,
currentColor := NewColor.
onMenuItem(_Source, id_clear):- !, % стирается рисунок
retractFactDb(dbpoints),
convexhull := [],
invalidate().
onMenuItem(_Source, id_point):- !, % запоминается
switch := dot. % команда меню
onMenuItem(_Source, id_marker):- !,
switch := marker.
onMenuItem(_Source, id_eraser):- !,
switch := eraser.
onMenuItem(_Source, id_convexhull):- !, % строится
convexhull := createConvexHull(), % выпуклая оболочка
invalidate().
onMenuItem(_Source, _MenuTAG).
После нажатия кнопки выбора цвета открывается диалоговое окно "Цвет" (рис. 2.9 рис 2.9). Факт-переменная currentColor хранит последнее выбранное значение цвета.
(рис 2.9) Окно выбора цвета
После нажатия кнопки "Очистить" панели инструментов поле окна очищается, при этом удаляются все факты из базы данных точек. В результате нажатия кнопки "Выпуклая оболочка" строится выпуклая оболочка множества крупных "точек" (см. ниже). Остальные кнопки используются для рисования или удаления "точек".
В редакторе окна paintForm добавим обработчики событий мыши MouseDownListener, MouseMoveListener и MouseUpListener.
После нажатия левой кнопки мыши факт-переменная isMouseDown принимает значение true. Далее запускается процедура обработки координат точки касания курсора, которая зависит от того, какая кнопка на панели инструментов была нажата последней (см. предикат drawing/2).
predicates
onMouseDown : window::mouseDownListener.
clauses
onMouseDown(_Source, Point, _ShiftControlAlt, _Button):-
isMouseDown := true,
convexhull := [],
drawing(switch, Point).
Во время перемещении указателя мыши при нажатой кнопке мыши выполняется та же процедура обработки координат точки касания указателя.
predicates
onMouseMove : window::mouseMoveListener.
clauses
onMouseMove(_Source, Point, _ShiftControlAlt, _Button):-
true = isMouseDown,
!,
drawing(switch, Point).
onMouseMove(_Source, _Point, _ShiftControlAlt, _Button).
После отпускания кнопки мыши факт-переменная isMouseDown принимает значение false.
predicates
onMouseUp : window::mouseUpListener.
clauses
onMouseUp(_Source, _Point, _ShiftControlAlt, _Button):-
isMouseDown := false,
invalidate().
Определение предиката drawing/2 приведено ниже.
predicates
drawing: (dotType, pnt Point).
clauses
drawing(Type, Point):-
Rectangle = rectangle(Point, Type),
erasePoint(Type, Rectangle),
addPoint(Type, Point),
invalidate(Rectangle).
predicates
erasePoint: (dotType, rct).
addPoint: (dotType, pnt).
clauses
erasePoint(eraser, Rectangle):-
point(Dot, Type, _),
pntInsideEraser(Rectangle, Dot, Type),
retractAll(point(Dot, _, _)),
fail.
erasePoint(_, _).
addPoint(eraser, _):- !.
addPoint(Type, Point):-
retractAll(point(Point, Type, _)),
assert(point(Point, Type, currentColor)).
predicates
pntInsideEraser: (rct Rectangle, pnt Dot, dotType) determ.
clauses
pntInsideEraser(Rect, Dot, dot):- !,
vpi::rectPntInside(Rect, Dot).
pntInsideEraser(Rect, Dot, Type):-
rct(A, B, C, D) = vpi::rectIntersect(rectangle(Dot, Type), Rect),
(C - A) * (D - B) > 2 * dotsize^2.
Предикат invalidate/1 помечает как недействительную прямоугольную область, в данном случае, размером с "точку" или "ластик", что приводит к ее перерисовке. Предикат rectPntInside/2 истинен, если точка находится внутри прямоугольника. Предикат rectIntersect/2 находит пересечение двух прямоугольников.
Маленькая "точка" удаляется из базы данных, если она попадает под ластик. Удаление крупной "точки" производится в том случае, если ластик покрывает ее более чем наполовину.
Упражнение. Создайте кнопку для выбора цвета фона рисунка и добавьте соответствующее правило для предиката onMenuItem.
Остается определить процедуру построения выпуклой оболочки.
Подмножество M точек плоскости называется выпуклым, если для произвольных двух точек из M отрезок, который соединяет эти точки, целиком содержится в M. Выпуклой оболочкой множества X называется наименьшее выпуклое множество, содержащее множество X.
В нашем случае множество крупных точек X — это конечное подмножество точек плоскости, поэтому его выпуклой оболочкой является многоугольник.
Для построения границы выпуклой оболочки используем алгоритм P множества X. Затем на множестве остальных точек вводится следующее отношение порядка. Если точки Q и R не лежат на одном и том же луче, выходящем из точки P, то полагается $$$Q\leq R$$$, если система векторов $$$\overline{PQ}$$$, $$$\overline{PR}$$$ имеет положительную ориентацию. Это означает, что они ориентированы так же, как и орты координатных осей, так что определитель матрицы, составленной из координат этих векторов, положителен. Для точек P(x, y), Q(a, b) и R(c, d) этот определитель находится следующим образом:
Если точки P, Q и R лежат на одной прямой, то полагается $$$Q\leq R$$$, если $$$|PQ|\leq |PR|$$$.
Множество точек, отличных от ).
(рис 2.10) Порядок на множестве точек:$$$Q\leq R\leq U\leq T$$$
На следующем этапе алгоритма выполняется обход оставшегося множества точек, начиная с точки P. Используются список точек и стек, в который изначально помещаются точка P и затем первая из остальных точек. Напомним, что к этому моменту множество точек является упорядоченным, при этом из точек, лежащих на одном луче, оставлена одна, наиболее удаленная от P точка. Далее, пока не закончатся точки из списка, из него берется очередная точка K, а из стека последовательно берутся (но не удаляются) точка M и точка N. Если система векторов $$$\overline{MN}$$$, $$$\overline{MR}$$$ ориентирована положительно, то точка M удаляется из стека, и алгоритм продолжает работу для старого списка точек. В противном случае точка K перекладывается в стек, и алгоритм продолжает работу для оставшегося списка точек. Когда список окажется пустым, стек будет содержать множество вершин выпуклой оболочки.
Например, рассмотрим множество точек, изображенное на рис. 2.10 рис 2.10.
После сортировки из него удаляется точка ). Ориентация векторов $$$\overline{QP}$$$, $$$\overline{QS}$$$ отрицательна, поэтому точка ). Векторы $$$\overline{SQ}$$$, $$$\overline{SR}$$$ ориентированы положительно, поэтому точка ), поэтому точка R добавляется в стек, и так далее.
(рис 2.11) Обход Грэхема
Выпуклую оболочку множества крупных "точек" строит предикат createConvexHull.
predicates
createConvexHull: () -> pnt*.
clauses
createConvexHull() = graham::createConvexHull(PntList):-
PntList = [Pnt || point(Pnt, marker, _)].
Создадим класс graham (не порождающий объекты). В его декларации (файл graham.cl) объявим предикат createConvexHull.
predicates
createConvexHull: (vpiDomains::pnt*) -> vpiDomains::pnt*.
Ниже приведена имплементация класса graham.
open core, vpiDomains
clauses
createConvexHull(PntList) = ConvexHull:-
[_, _| _] = PntList,
!,
LeftPoint = list::minimum(PntList),
RestPntList = list::remove(PntList, LeftPoint),
% сортировка множества точек
Ord1 = {(Pnt1, Pnt2) = compare(true, LeftPoint, Pnt1, Pnt2)},
SortedList = list::sortBy(Ord1, RestPntList),
% удаление точек, лежащих на одном луче, кроме дальней
Ord2 = {(Pnt1, Pnt2) = compare(false, LeftPoint, Pnt1, Pnt2)},
List = list::removeConsecutiveDuplicatesBy(Ord2, SortedList),
ConvexHull = graham([LeftPoint | List]).
createConvexHull(_PntList) = [].
% определение двух отношений порядка
class predicates
compare: (boolean, pnt, pnt, pnt) -> compareResult.
compare: (integer Det) -> compareResult.
det: (pnt, pnt, pnt) -> integer.
clauses
% точки, не лежащие на одном луче, сорт-ся в соотв. c det
compare(_, LeftPnt, Pnt1, Pnt2) = compare(Det):-
Det = det(LeftPnt, Pnt1, Pnt2),
Det <> 0,
!.
% точки, лежащие на одном луче, различаются
compare(true, pnt(X, Y), pnt(A, B), pnt(C, D)) = compare(
% манхэттенское расстояние
math::abs(A - X) + math::abs(B - Y),
math::abs(C - X) + math::abs(D - Y)):- !.
% точки, лежащие на одном луче, не различаются
compare(_, _, _, _) = equal.
compare(Det) = less:-
Det > 0,
!.
compare(_) = greater.
det(pnt(X, Y), pnt(A, B), pnt(C, D)) =
(A - X) * (D - Y) - (B - Y) * (C - X).
% обход Грэхема
class predicates
graham: (pnt*) -> pnt*.
graham: (pnt* List, pnt* Stack) -> pnt*.
clauses
graham([LeftPnt, Pnt | List]) = graham(List, [Pnt, LeftPnt]):- !.
graham(List) = List.
graham([K |List], [M, N |Stack]) = graham([K |List], [N |Stack]):-
det(M, N, K) > 0,
!.
graham([K | List], Stack) = graham(List, [K | Stack]).
graham([], Stack) = Stack.
Напомним, что выпуклая оболочка строится при нажатии кнопки ).
(рис 2.12) Выпуклая оболочка множества "крупных" точек
Упражнение. Создайте на панели инструментов новую кнопку и добавьте процедуру сохранения рисунка в bmp-файле при нажатии на эту кнопку. Указание. Диалоговое окно "Сохранить как" вызывается следующим образом:
FileName = vpiCommonDialogs::getFileName("*.bmp",
["Файл bmp", "*.bmp"], "Сохранить как", [dlgfn_Save], "", _).
Получить рисунок из заданной прямоугольной области окна Rectangle и сохранить его в bmp-файле можно с помощью предикатов pictGetFromWin/2 и pictSave/2:
Picture = vpi::pictGetFromWin(getVpiWindow(), Rectangle),
vpi::pictSave(Picture, "mypicture.bmp").
В данном случае необходимо учесть размеры панели инструментов. Прямоугольник TBRect, который занимает панель инструментов, возвращает предикат getRect/3:
vpiToolbar::getRect(getVpiWindow(), vpiToolbar::tb_left, TBRect).
Добавим к окну paintForm всплывающее меню и меню окна.
Создадим всплывающее меню. Для этого выделим папку paintForm дерева проекта и выберем команду всплывающего меню New In Existing Package. В окне Create Project Item выделим элемент Menu и назовем его paintPopUpMenu.
Создадим меню, показанное на рис. 2.13 рис 2.13. Для пунктов меню важно выбрать идентификаторы, совпадающие с идентификаторами аналогичных кнопок панели инструментов (id_color, id_point, id_marker, id_eraser, id_clear и id_convexhull), чтобы использовать уже существующее определение предиката onMenuItem для обработки события выбора пункта контекстного меню.
(рис 2.13) Окно редактора paintPopUpMenu
Всплывающее меню будет появляться при нажатии правой кнопки мыши. Поэтому в качестве первого предложения для предиката onMouseDown необходимо добавить следующее правило:
onMouseDown(_Source, Point, _, mouse_button_right):- !,
Menu = vpi::menuGetRes(id_paintpopupmenu),
menuPopUp(Menu, Point, align_Left).
Предикат .
(рис 2.14) Всплывающее меню
Аналогично создадим меню окна и назовем его ).
(рис 2.15) Окно редактора paintMenu
Добавим меню в окно ).
(рис 2.16) Меню окна paintForm
Создадим новую кнопку и поместим ее на панель инструментов главного окна приложения. При нажатии на эту кнопку будет открываться окно paintForm.
Подготовим изображение (bitmap) для кнопки размером 16 -> 16. С помощью элемента дерева проекта TaskWindow\Toolbars\ProjectToolbar.tb откроем редактор панели инструментов главного окна приложения. Добавим на панель кнопку так же, как это было описано ранее (Constant: id_paintForm). С помощью элемента taskWindow.win дерева проекта откроем эксперт окон, раскроем узлы Menu и ProjectToolbar, выделим элемент id_paintForm и добавим обработчик события нажатия на кнопку панели инструментов. Определим его так же, как и предикат onTestPaint (см. п. 2.1).
Добавим строку состояния в окно ).
(рис 2.17) Редактор строки состояния paintStatusLine
Заполним поля в окне атрибутов текста так, как показано на рис. 2.18 рис 2.18:
Initial Text: <пустое поле>;Text Width: 400;Foreground Color: Green.
(рис 2.18) Редактор атрибутов текста
Перед добавлением строки состояния в окно paintForm, создадим для этого окна новую иконку (пиктограмму). Выделим узел paintForm дерева проекта, откроем диалоговое окно Create Project Item, выделим элемент Icon и напишем в поле Name его название: paintIcon. Закроем появившееся окно атрибутов изображения. Создадим изображение в открывшемся графическом редакторе.
Наконец, изменим определение конструктора new/1 в имплементации класса paintForm следующим образом:
new(Parent):-
formWindow::new(Parent),
generatedInitialize(),
%
setIcon(idi_paintIcon),
whenCreated({ :- paintStatusLine::create(getVpiWindow()) }).
Строка состояния окна показана на рис. 2.19 рис 2.19.
(рис 2.19) Строка состояния окна paintForm
Включить пункт меню и добавить обработчик события выбора команды меню можно программным образом. Например, изменим в файле taskWindow.pro определение предиката onShow так, как показано ниже, а также добавим код, определяющий предикат myFileOpen:
clauses
onShow(_, _CreationData) :-
_MessageForm = messageForm::display(This),
menuEnable(resourceIdentifiers::id_file_open, true),
menuSetText(resourceIdentifiers::id_file_open, "Открыть"),
addMenuItemListener(myFileOpen).
predicates
myFileOpen : window::menuItemListener.
clauses
myFileOpen(_, resourceIdentifiers::id_file_open):- !,
vpiCommonDialogs::note("Hello!").
myFileOpen(_, _).
В результате пункт меню File -> Open станет включенным, надпись изменится на "Открыть", и, при выборе данной команды меню, откроется диалоговое окно Note, в поле сообщения которого будет выведен текст "Hello!".
Меню нетрудно создать динамически (см. определение домена dynMenu). Устанавливает меню предикат menuSet/1.
Подробнее о редакторах ресурсов и, в частности, панели инструментов и пиктограмм, см. [14, ].
Упражнения
2.1. Добавьте команду удаления изображения выпуклой оболочки.
2.2. Сделайте автоматической перерисовку выпуклой оболочки при изменении рисунка.
2.3. Добавьте в программу возможность построения других геометрических фигур (отрезков, прямоугольников, эллипсов).
2.4. Создайте инструменты для рисования графа. Вершины и ребра графа должны записываться в базу данных. База данных должна сохраняться в файле на диске и загружаться из него.
Для получения официальных документов о завершении программы дополнительного профессионального образования (удостоверения о повышении квалификации, дипломов о профессиональной переподготовке и MBA) необходимо предоставить:
Внимание! Вы можете не заказывать доставку бумажной версии официального документы, а скачать его в электронном виде и распечатать самостоятельно. Информация о выданном документе в течение 1 месяца загружается в Федеральную информационную систему «Федеральный реестр сведений о документах об образовании и (или) о квалификации, документах об обучении» - ФИС ФРДО.
Доступ на новый сайт осуществляется с использованием адреса электронной почты, который был указан вами при регистрации на "старом". Мы постарались перенести все ваши данные с прежнего ресурса, однако не исключена вероятность потери части информации.
При возникновении проблемы со входом, воспользуйтесь функцией сброса пароля
Если вы обнаружите несоответствия, пожалуйста, сообщите нам.