"Правильный" GUI на OPL
Автор: Осирис
В эпоху общей деградации сайта mypsion.ru и сращения его Конференции с „Помойкой” стала возможной статистическая оценка содержания конференции.
Поддерживая саму идею „эмуляторов” и положительно оценивая чуство юмора создателей сего software, я не могу не отметить некоторые отклонения пользовательского интерфейса от принятых в GUI EPOC32 стандартов.
Грубо, в плане пользовательского интерфейса, все приложения для EPOC можно разбить на 4 категории:
- приложения без пользовательского интерфейса (их очень мало, например, старые версии русификаторов — в новых есть пользовательский интерфейс через контрольную панель) — в основном это системные утилиты, которые не предусматривают вмешательство пользователя;
- приложения с примитивным консольным пользовательским интерфейсом (всякие hello_world и пр.) — это в основном маленькие программки начинающих программистов, которые ещё не постигли искусства программирования графического интерфейса, либо программы–„однодневки”, написанные для личного пользования, либо для конкретной „узкой” задачи;
- „оконные” приложения (например, SysBack, FreeMem etc.) — небольшие программки, которые не требуют многозадачности и легко помещаются вместе со всеми управляющими кнопками и прочим в окошко на экране;
- полноценные “event-driven” приложения (EPOC Word, к примеру), имеющие toolbar, меню, поддерживающие перьевой ввод и пр.
Однако, вернёмся к эмуляторам mypsion.ru. Они, к сожалению, находятся где-то между 2-м и 3-м вариантами — попытка работать с мышью, но не систематизированная, toolbar и меню не поддерживаются, выход по Ctrl+E отсутствует и пр. Все эти недостатки вынудили :) меня ниписать краткое введение в то, как надо писать „правильный” эмулятор... :)
В начале „правильного” эмулятора должны включаться файлы с заголовками функций всех OPX'ов и подгружаемых OPO–файлов, используемых в программе, полезно также включить Const.oph — список полезных констант:
include "Const.oph"
include "toolbar.opj"
include "bmp.oxh"
Удобно также декларировать ещё некоторые константы. Я их включаю во все свои OPL'ные программки:
rem Definition for sidebar keys
const KKeySidebarCut% = 10001
const KKeySidebarInfared% = 10002
const KKeySidebarZoomIn% = 10003
const KKeySidebarZoomOut% = 10004
const Key_Pressed& = &400
const KProgram_To_Foreground& = &401
const KProgram_To_Background& = &402
const KOpen_or_Close_Document& = $404
const KKey_Down& = &406
const KKey_Up& = &407
const KPointer_Used& = &408
const KPointer_Enter = &409
const KPointer_Exit = &40A
const KCmdLetterExit$ = "X"
const Forever = 0
const KErrNotReady% = -62
... и некоторые специфичные для этого примера :)
const KKisaButton% = 1
const KBoardButton% = 2
Если мы хотим сделать полноценную программу, появляющуюся в списке Extras, то надо разкомментировать следующие 4 строчки:
rem app BAA, &101F43B8
rem caption "BAA", KLangEnglish%
rem icon "BAA_48.mbm"
rem enda
„Правильный” эмулятор обязан иметь toolbar!
proc Start:
loadm "z:\system\opl\Toolbar"
TBarLink:("Main")
endp
Теперь, собственно, основная программа, в которой определяются переменные, общие для всех подпрограмм:
PROC Main:
global keycode&
global ScrWid% rem ширина экрана в пикселах
global ScrHght% rem высота экрана в пикселах
global MenuPos% rem позиция в меню
global event&(16)
global evType&
global version$(4) rem версия программы
global Kisa% rem ну, типа, Кису эмулируем или конфу
global IsRevo%, IsGeofox%, IsSeries7% rem это для поддержки разного hardware
global IsOsaris%, IsR380%, IsSeries5%
global Linespacing% rem расстояние между строками
InitApp%: rem прорисовка интерфейса и пр. однократные действия при запуске
rem event-driven программа должна быть зациклена!
do
run:
until forever
endp
Прорисовка интерфейса и прочие однократные действия при старте программы:
proc InitApp%:
version$="0.01"
ScrWid% = gWidth
ScrHght% = gHeight
Kisa% = KTrue%
rem setHardware: rem ну, это для debugging'а
getHardware: rem определение размера экрана и пр.
initTBar: rem создание toolbar'а и управление им
GIPRINT "BAA v" + version$
alert("Эта программа поясняет, как надо", "писать эмуляторы Кисы с Осей :)")
if Kisa%
Kisa% = KFalse%
cmdK%:
else
Kisa% = KTrue%
cmdB%:
endif
endp
Собственно, основной цикл, который „слушает” (GetEvent32) нажатия на кнопки и на экран и передаёт результат подпрограмме event_handler&:, которая, проанализировав ввод, решает, какая подпрограмма будет его обрабатывать
proc run:
do
busy off
GetEvent32 event&()
evType& = event&(KEvaType%)
event_handler&:
rem keycode& = event_handler&: rem для обработки „обычных” символов,
rem без Ctrl, нужно отдельно обрабатывать значение, выдаваемое event_handler&:
rem if keycode& = 0 rem впрочем, можно и иначе реализовать
rem continue rem для простоты, опустим это
rem endif
until forever
endp
Ну, и собственно, сам обработчик клавиатурного и экранного ввода (упрощённая версия, без drag & drop и прочего):
proc event_handler&:
local menukey&
if evType& = KEvCommand&
system_event: rem обработчик системного обращения к программе,
rem например „убивание” из списка задач, или требование открытия файла
return 0
elseif evType& = KProgram_To_Background&
rem background: rem если нужно сделать что-то перед уходом на задний план
return 0
elseif evType& = KProgram_To_Foreground&
rem foreground: rem то же при выходе на передний план
return 0
elseif evType& = KPointer_Used& rem обработка нажатий на экран
if TBarOffer%:(event&(KEvAPtrOplWindowId%), event&(KEvAPtrType%), event&(KEvAPtrPositionX%), event&(KEvAPtrPositionY%))
rem обращения к toolbar'у
return KTrue%
elseif event&(KEvAPtrType%) = 1
rem pointer_event:(event&(KEvAPtrOplWindowId%), event&(KEvAPtrPositionX%), event&(KEvAPtrPositionY%))
rem прочие нажатия стилусом
return 0
else
return 0
endif
elseif (evType& = KKey_Up&)
return 0
elseif (evType& = KKey_Down&)
return 0
elseif (evType& = KKeyMenu32%) or (evType& = KKeySidebarMenu32%)
rem нажатия на клавишу „Menu” или на соответствующую картинку на границе touch screen'а
menukey& = display_menu&: rem вызов и предварительная обработка меню
if menukey& < %A
return 0
elseif menukey& <= %Z
process_ctrl_command%:(menukey& + 32, &6)
elseif menukey&
process_ctrl_command%:(menukey&, &4)
endif
return 0
elseif evType& = KKeySidebarZoomIn%
rem cmdM%:
return 0
elseif evType& = KKeySidebarZoomOut%
rem cmdsM%:
return 0
elseif evType& = KKeyUpArrow32% rem стрелка вверх
return 0
elseif evType& = KKeyDownArrow32% rem стрелка вниз
return 0
elseif evType& = KKeyRightArrow32% rem стрелка вправо
return 0
elseif evType& = KKeyLeftArrow32% rem стрелка влево
return 0
elseif evType& = KKeyPageUp32% rem PgUp
return 0
elseif evType& = KKeyPageDown32% rem PgDn
return 0
elseif ((evType& and Key_Pressed&) = 0)
if event&(KEvAKMod%) and KKmodControl%
process_ctrl_command%:(evType& - 1 + %a, event&(KEvAKMod%))
return 0
else
return evType&
endif
endif
return 0
endp
Если программу „убьют” извне, она должна благополучно закрыться:
proc system_event:
local command$(KMaxStringLen%), cmdLetter$(1)
command$ = getcmd$
cmdLetter$ = left$(command$, 1)
command$ = right$(command$, len(command$) - 1)
if cmdLetter$ = "X"
cmdE%:
endif
endp
Один из главных элементов обработки клавиатурного ввода — обработка нажатий Ctrl+<буква> и Ctrl+Shift+<буква>. Для каждой буквы, которая будет использоваться, необходимо написать процедуру с имеенем cmd<буквы>%: или cmdS<буквы>%:, например, для Ctrl+B — cmdB%:, а для Ctrl+Shift+B — cmdSB%:. Теже самые фукции вызывает и toolbar и меню (!), таким образом можно легко соотносить между собой нажатия на toolbar, выбор в меню и нажатие на „горячую клавишу”:
proc process_ctrl_command%:(key&, modif&)
local cmdRoot$(4)
if modif& and KKmodControl%
cmdRoot$ = "cmd"
if modif& and KKmodShift%
cmdRoot$ = "cmds"
endif
onerr eNotCmd::
@%(cmdRoot$ + chr$(key&)):
return KTrue%
endif
eNotCmd:: rem обработчик оибок нужен для корректной обработки неиспользуемых клавиш,
rem для которых нет функции cmd*%:
onErr off
if err <> KErrNoProc%
rem giprint "Bug: proc " + cmdRoot$ + chr$(key&) + "%:, " + err$(err)
return KTrue%
endif
return KFalse%
endp
proc PrintText%:(y%, text$)
local lenght%
lenght% = gTWIDTH(text$)
gat (ScrWid% - lenght% + TbVis% * TbWidth%)/2, y%
gprintb text$, lenght%, 3
return Ktrue%
endp
Обработка Ctrl+E
proc cmdE%:
stop
endp
Обработка Ctrl+K — выбор эмулятора Кисы и Оси :)
proc cmdK%:
local string$(250), lenght%
if not Kisa%
gcls
TBarLatch:(KKisaButton%) rem эффект „залипания” кнопки toolbar'а —
rem если мы смотрим эмулятор Кисы — залипает „Кисина” кнопка, а если Конфы,
rem то, соответственно, другая
Kisa% = KTrue%
alert("Здесь рисуем Кису с Осей", "и их эмулятор :)")
gfont KFontArialNormal22&
gStyle 1
PrintText%:(ScrHght%/2 - 2 * Linespacing%, "Киса и Ося были тут, " + Mid$(DATIM$, 17, 5) + "!!!")
gStyle 0
gfont KFontArialNormal15&
PrintText%:(ScrHght%/2 - Linespacing%, "Для перехода в эмулятор конфы")
rem PrintText%:(ScrHght%/2, "нажмите на 2-ю иконку toolBAAr'а,")
PrintText%:(ScrHght%/2 + Linespacing%, "выберите соответствующий пункт в меню,")
PrintText%:(ScrHght%/2 + 2 * Linespacing%, "или нажмите Ctrl+B")
lenght% = gTWIDTH("нажмите на 2-ю иконку toolr'a,")
gStyle 1
gat (ScrWid% - lenght% - gTWIDTH("BAA") + TbVis% * TbWidth%)/2, ScrHght%/2
gStyle 0
gprint "нажмите на 2-ю иконку tool" rem, gTWIDTH("нажмите на 2 иконку tool", 2
gStyle 1
gprint "BAA"
gstyle 0
gprint "r'a,"
endif
rem return Kisa%:
endp
Обработка Ctrl+B — вызов на передний план эмулятора Конференции
proc cmdB%:
local string$(250), lenght%
if Kisa%
gcls
TBarLatch:(KBoardButton%)
Kisa% = KFalse%
alert("Здесь рисуем эмулятор конференции")
gfont KFontArialNormal22&
gStyle 1
PrintText%:(ScrHght%/2 - 2 * Linespacing%, "Киса — ламер!!!")
gStyle 0
gfont KFontArialNormal15&
PrintText%:(ScrHght%/2 - Linespacing%, "Для перехода в эмулятор Кисы и Оси")
PrintText%:(ScrHght%/2, "нажмите на 1-ю иконку toolBAAr'а,")
PrintText%:(ScrHght%/2 + Linespacing%, "выберите соответствующий пункт в меню,")
PrintText%:(ScrHght%/2 + 2 * Linespacing%, "или нажмите Ctrl+K")
endif
rem return Kisa%:
endp
Обработка Ctrl+A — сведения о программе:
proc cmdA%:
busy off
dinit "BAA v." + Version$
dtext "", "Baa рулит!!!", $202
dtext "", "© 2001, Osiris", $202
dbuttons "Continue", KKeyEnter% + $100
dialog
return KTrue%
endp
Обработка Ctrl+T — включение/выключение toolbar'а:
rem Toolbar on/off
proc cmdT%:
if TbVis%
TBarHide:
else
TBarShow:
endif
rem Kisa% = -(Kisa% + 1)
if Kisa%
cmdK%:
else
cmdB%:
endif
endp
Собственно, инициализация toolbar'а. На Revo должно появиться 3 кнопки, на остальных машинках — 4. Первые 2 организованы в группу и поочерёдно „залипают”, в зависимости от того, эмулятор чего используется в данный момент. 3-я и 4-я не требуют разъяснений.
proc initTBar:
local x%(6), KisaID&, BoardID&, AboutID&, CloseID&
local KisaIDm&, BoardIDm&, AboutIDm&, CloseIDm&
local MBM$(250)
MBM$ = PARSE$("BAA.mbm", left$(cmd$(1),len(cmd$(1))-4), x%())
KisaID& = bitmapload&:(MBM$, 0)
KisaIDm& = bitmapload&:(MBM$, 1)
BoardID& = bitmapload&:(MBM$, 2)
BoardIDm& = bitmapload&:(MBM$, 3)
AboutID& = bitmapload&:(MBM$, 4)
AboutIDm& = bitmapload&:(MBM$, 5)
CloseID& = bitmapload&:(MBM$, 6)
CloseIDm& = bitmapload&:(MBM$, 7)
TBarInit:("BAA", ScrWid%, ScrHght%)
TBarButt:("k", KKisaButton%, "Киса" + chr$(10) + "и Ося", 0, KisaID&, KisaIDm&, KTbFlgLatchStart% or - (KTbFlgLatched% * Kisa%))
TBarButt:("b", KBoardButton%,"Конфа", 0, BoardID&, BoardIDm&, KTbFlgLatchEnd% or KTbFlgLatched% * (Kisa% + 1))
if IsRevo%
TBarButt:("e", 3, "Close", 0, CloseID&, CloseIDm&, 0)
else
TBarButt:("a", 3, "About", 0, AboutID&, AboutIDm&, 0)
TBarButt:("e", 4, "Close", 0, CloseID&, CloseIDm&, 0)
endif
if ScrWid% < 640
TBarHide:
else
TBarShow:
endif
endp
Определение меню. Опять же, Киса&Ося с Конфой объединены в группу, лишь один элемент которой может быть активным. А „Show toolbar” включает и выключает галку...
proc display_menu&:
local m&
mInit
mCard "File", "Close", %e
rem mCard "View", "Zoom in", %m, "Zoom out", -%M, "Show toolbar", %t or TbMenuSym%
mCard "View", "Киса & Ося", %k + KMenuOptionStart% - (KMenuSymbolOn% * Kisa%), "Конфа", -(%b + KMenuOptionEnd% + (KMenuSymbolOn% * (Kisa% + 1))), "Show toolbar", %t or TbMenuSym%
mCard "Tools", "About", %a
m& = menu(MenuPos%)
return m&
endp
Следующая подпрограмма полезна лишь для грубой прикидки того, как программа будет выглядеть на устройствах другого размера
proc setHardware:
local HT%
local Draftoffset%
HT% = 3
dinit "Hardware Type"
dchoice HT%, "Screen size:", "Osaris,Revo,Series5,GeoFox,Series7"
dbuttons "Continue", KKeyEnter% + $100
if dialog
if HT% = 1
ScrWid% = 320 rem 480
ScrHght% = 200
Draftoffset% = 160
elseif HT% = 2
ScrWid% = 480 rem 560
ScrHght% = 160
Draftoffset% = 80
elseif HT% = 3
ScrWid% = 640
ScrHght% = 240
Draftoffset% = 0
elseif HT% = 4
ScrWid% = 640
ScrHght% = 320
Draftoffset% = 0
else
ScrWid% = 640
ScrHght% = 480
Draftoffset% = 0
endif
else
ScrWid% = gWidth
ScrHght% = gHeight
Draftoffset% = 0
endif
endp
И последняя подпрограмма в данном примере должна дать нам ответ о размерах экрана использзуемого устройства. Если нужно что-то большее, например, заблокировать эмулятор Кисы и Оси на Mako :), то можно проанализировать версию ROM и т.п.
proc getHardware:
IsRevo% = KFalse%
IsGeofox% = KFalse%
IsSeries7% = KFalse%
IsOsaris% = KFalse%
IsR380% = KFalse%
IsSeries5% = KFalse%
if ScrWid% = 480
IsRevo% = KTrue% rem 480 x 160 = 76800
elseif ScrHght% = 320
IsGeofox% = KTrue% rem 640 x 320 = 204800
elseif ScrHght% = 480
IsSeries7% = KTrue% rem 640 x 480 = 307200
elseif ScrHght% = 200
IsOsaris% = KTrue% rem 320 x 200 = 64000
elseif ScrHght% = 120
IsR380% = KTrue% rem 360 x 120 = 43200
else
IsSeries5% = KTrue% rem 640 x 240 = 153600
endif
Linespacing% = 22
endp
Надеюсь, авторы эмуляторов (KM и BAA) найдут таки время для приведения своих славных продуктов к знакомому нам всем „стандартному” EPOC'овскому look & feel. :)
Source code, MBM и OPO файлы прилагаются (для корректной работы MBM и OPO файлы должны быть в одной папке). App я компилировать не стал, но сделать это тривиально.
Дата статьи: | 1 ноября 2001 г |
« Назад в каталог | Обсудить в конференции »