| package | package := Package name: 'klikacz'. package paxVersion: 1; basicComment: 'Celem pakietu jest pokazanie jak w Dolphinie obslugiwac rysunek skladajacy sie z wielu pól. Przykladowy program ma wyswietlac w okienku rysunek skldajacy sie z n*m pól, z których kazde wyswietla jeden z dwóch rysunków (bitmap): rys0.bmp i rys1.bmp. Poczatkowo wszystkie pola zawieraja rysunek rys0.bmp. Po kliknieciu na dowolne pole ma ono zmienic swa zawartosc na drugi z tych rysunków. Model jest banalny: tablica n*m wartosci logicznych. Widok: zawiera podwidok (KlikaczMapaWidok) zrobiony specjalnie na potrzeby tego programu. Dodatkowe informacje o tworzeniu specjalizowanych widoków mozna znalesc w http://www.idb.me.uk/tutorials/buildview.html. Prezenter: sa dwa, proste, jeden do calego programu, drugi do KlikaczaMapa. W tym drugim jest przechwytywanie zdarzen klikniecia myszka. Wywolywanie: KlikaczPrezenter show lub KlikaczPrezenter showOn: (KlikaczModel new_n: 7 m: 9) '. package classNames add: #KlikaczMapaPrezenter; add: #KlikaczMapaWidok; add: #KlikaczModel; add: #KlikaczPrezenter; yourself. package binaryGlobalNames: (Set new yourself). package globalAliases: (Set new yourself). package setPrerequisites: (IdentitySet new add: 'Object Arts\Dolphin\Base\Dolphin'; add: 'Object Arts\Dolphin\MVP\Base\Dolphin MVP Base'; yourself). package! "Class Definitions"! Model subclass: #KlikaczModel instanceVariableNames: 'n m tab' classVariableNames: '' poolDictionaries: '' classInstanceVariableNames: ''! Presenter subclass: #KlikaczMapaPrezenter instanceVariableNames: '' classVariableNames: '' poolDictionaries: '' classInstanceVariableNames: ''! Shell subclass: #KlikaczPrezenter instanceVariableNames: '' classVariableNames: '' poolDictionaries: '' classInstanceVariableNames: ''! View subclass: #KlikaczMapaWidok instanceVariableNames: 'szerPola wysPola rys0 rys1' classVariableNames: '' poolDictionaries: '' classInstanceVariableNames: ''! "Global Aliases"! "Loose Methods"! "End of package definition"! "Source Globals"! "Classes"! KlikaczModel guid: (GUID fromString: '{F247E6AF-96FE-4EA4-8693-9EDD6C7799E4}')! KlikaczModel comment: 'Model dla programu klikacz, pamieta które pola tablicy n*m sa w stanie true, a które w stanie false. Poczatkowo wszystkie sa true. Przykladowy test: a := KlikaczModel new_n: 3 m: 12. a at: (3 @ 5). "true" a changeAt: (3 @ 5). a at: (3 @ 5). "false" '! !KlikaczModel categoriesForClass!No category! ! !KlikaczModel methodsFor! at: aPoint "Daje wartosc zapisana we wskazanym elemencie tablicy" ^(tab at: aPoint x) at: aPoint y! changeAt: aPoint "Zmienia wartosc zapisana w modelu pod zadanym indeksem" (tab at: aPoint x) at: aPoint y put: ((tab at: aPoint x) at: aPoint y) not. "Poinformowanie ewentualnych obserwatorów, ze zmienila sie wartosc" self trigger: #valueChangedAt with: aPoint. ! initialize_n: np m: mp "Inicjalizuje wewnetrzna tablice na tablice rozmiaru n*m wypelniona wartosciami true" n := np. m := mp. "Stworzenie tablicy" tab := Array new: n. 1 to: n do: [:i | tab at: i put: (Array new: m)]. "Wypelnienie jej wartosciami true" 1 to: n do: [:i | 1 to: m do: [:j | (tab at: i) at: j put: true]]. "Poinformowanie ewentualnych obserwatorów, ze zmienila sie wartosc" self trigger: #valueChanged. "I przekazanie wyniku" ^self! m ^m! n ^n! ! !KlikaczModel categoriesFor: #at:!public! ! !KlikaczModel categoriesFor: #changeAt:!public! ! !KlikaczModel categoriesFor: #initialize_n:m:!private! ! !KlikaczModel categoriesFor: #m!public! ! !KlikaczModel categoriesFor: #n!public! ! !KlikaczModel class methodsFor! new "Te metode blokuje, mozna by tez przyjac jakis standardowy rozmiar startowy" self error: 'KlikaczModel>>new: próba utworzenia modelu bez podania rozmiarów tablicy'! new_n: np m: mp "Tworzy model z tablica rozmiaru n*m" ^super new initialize_n: np m: mp! ! !KlikaczModel class categoriesFor: #new!private! ! !KlikaczModel class categoriesFor: #new_n:m:!public! ! KlikaczMapaPrezenter guid: (GUID fromString: '{7C0F7E7E-30A6-4E8A-B548-029D93DE0CB4}')! KlikaczMapaPrezenter comment: 'Prezenter mapy. Jedyne co robi, to przechwytywanie klikniec lewym przyciskiem myszy. Ponizsze trzeba wykonac, zeby KlikaczMapaPrezenter wraz z widokiem byli dostepni jako zasób w View Composerze: KlikaczMapaPrezenter addView: KlikaczMapaWidok asResource: ''Mapa'' Testy: KlikaczMapaPrezenter show lub KlikaczMapaPrezenter showOn: (KlikaczModel new_n: 5 m: 10) '! !KlikaczMapaPrezenter categoriesForClass!No category! ! !KlikaczMapaPrezenter methodsFor! onLeftButtonPressed: aMouseEvent "Obsluga klikniecia lewym przyciskiem myszy" "Dzwiek" Sound bell. "Zmiana stanu w modelu" model changeAt: (self view zamienNaWspModelu: aMouseEvent position)! ! !KlikaczMapaPrezenter categoriesFor: #onLeftButtonPressed:!public! ! !KlikaczMapaPrezenter class methodsFor! defaultModel ^KlikaczModel new_n: 5 m: 5! defaultView ^'Mapa'! resource_Mapa "Answer the literal data from which the 'Mapa' resource can be reconstituted. DO NOT EDIT OR RECATEGORIZE THIS METHOD. If you wish to modify this resource evaluate: ViewComposer openOn: (ResourceIdentifier class: self selector: #resource_Mapa) " ^#(#'!!STL' 3 788558 10 ##(Smalltalk.STBViewProxy) 8 ##(Smalltalk.KlikaczMapaWidok) 98 16 0 0 98 2 8 1409286144 1 416 787526 1 ##(Smalltalk.KlikaczModel) 0 7 7 98 3 98 3 16 16 16 98 3 16 16 16 98 3 16 16 16 524550 ##(Smalltalk.ColorRef) 8 4278190080 0 5 0 0 0 416 133 133 395334 3 ##(Smalltalk.Bitmap) 0 16 1114638 ##(Smalltalk.STBSingletonProxy) 8 ##(Smalltalk.ImageRelativeFileLocator) 8 #current 8 'rys0.bmp' 0 0 1 0 642 0 16 688 8 'rys1.bmp' 0 0 1 328198 ##(Smalltalk.Point) 33 33 983302 ##(Smalltalk.MessageSequence) 202 208 98 1 721670 ##(Smalltalk.MessageSend) 8 #createAt:extent: 98 2 786 2559 21 786 401 401 416 983302 ##(Smalltalk.WINDOWPLACEMENT) 8 #[44 0 0 0 0 0 0 0 1 0 0 0 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 4 0 0 10 0 0 0 199 5 0 0 210 0 0 0] 98 0 786 193 193 0 27 )! ! !KlikaczMapaPrezenter class categoriesFor: #defaultModel!public! ! !KlikaczMapaPrezenter class categoriesFor: #defaultView!public! ! !KlikaczMapaPrezenter class categoriesFor: #resource_Mapa!public!resources-views! ! KlikaczPrezenter guid: (GUID fromString: '{E390F6EA-BABE-496B-B9A5-DFF62D45B85F}')! KlikaczPrezenter comment: 'Prezenter dla programu Klikacz. Przechwytuje klikniecia mysza i przekazuje informacje do modelu. Testy dzialania: KlikaczPrezenter show. "Standardowy model" KlikaczPrezenter showOn: (KlikaczModel new_n: 10 m: 10). "Zadany model" '! !KlikaczPrezenter categoriesForClass!No category! ! !KlikaczPrezenter methodsFor! createComponents "Stworzenie swoich komponentów" "O tym trzeba pamietac" super createComponents. "Jest tylko jeden komponent, po prostu go podlaczam" self add: KlikaczMapaPrezenter new name: 'Mapa'! koniec "Obsluga nacisniecia przycisku Koniec" self exit! model: klikaczModel "Podlaczenie modelu" "Standardowa obsluga modelu" super model: klikaczModel. "Podlaczam model calosci jako model KlikaczMapaPrezentera" (self presenterNamed: 'Mapa') model: klikaczModel! ! !KlikaczPrezenter categoriesFor: #createComponents!public! ! !KlikaczPrezenter categoriesFor: #koniec!public! ! !KlikaczPrezenter categoriesFor: #model:!public! ! !KlikaczPrezenter class methodsFor! defaultModel "Daje standardowy model" ^KlikaczModel new_n: 3 m: 6! defaultView ^'KlikaczWidok'! resource_KlikaczWidok "Answer the literal data from which the 'KlikaczWidok' resource can be reconstituted. DO NOT EDIT OR RECATEGORIZE THIS METHOD. If you wish to modify this resource evaluate: ViewComposer openOn: (ResourceIdentifier class: self selector: #resource_KlikaczWidok) " ^#(#'!!STL' 3 788558 10 ##(Smalltalk.STBViewProxy) 8 ##(Smalltalk.ShellView) 98 27 0 0 98 2 27131905 131073 416 0 524550 ##(Smalltalk.ColorRef) 8 4278190080 0 551 0 0 0 416 852230 ##(Smalltalk.FramingLayout) 234 240 98 4 410 8 ##(Smalltalk.KlikaczMapaWidok) 98 16 0 416 98 2 8 1140850688 1 592 787526 1 ##(Smalltalk.KlikaczModel) 0 7 7 98 3 98 3 16 16 16 98 3 16 16 16 98 3 16 16 16 482 8 4278190080 0 7 0 0 0 592 397 219 395334 3 ##(Smalltalk.Bitmap) 0 16 1114638 ##(Smalltalk.STBSingletonProxy) 8 ##(Smalltalk.ImageRelativeFileLocator) 8 #current 8 'rys0.bmp' 0 0 1 0 802 0 16 848 8 'rys1.bmp' 0 0 1 328198 ##(Smalltalk.Point) 33 33 983302 ##(Smalltalk.MessageSequence) 202 208 98 1 721670 ##(Smalltalk.MessageSend) 8 #createAt:extent: 98 2 946 1 1 946 1189 659 592 983302 ##(Smalltalk.WINDOWPLACEMENT) 8 #[44 0 0 0 0 0 0 0 1 0 0 0 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 0 0 0 0 0 0 0 0 82 2 0 0 73 1 0 0] 98 0 946 193 193 0 27 1181766 2 ##(Smalltalk.FramingConstraints) 842 8 ##(Smalltalk.FramingCalculation) 8 #fixedParentLeft 1 842 1264 8 #fixedParentRight 1 842 1264 8 #fixedParentTop 1 842 1264 8 #fixedParentBottom -99 410 8 ##(Smalltalk.PushButton) 98 17 0 416 98 2 8 1140924416 1 1392 0 0 0 7 0 0 0 1392 0 8 4294902905 1180998 4 ##(Smalltalk.CommandDescription) 8 #koniec 8 'Koniec' 1 1 0 0 32 978 202 208 98 3 1042 1072 98 2 946 1009 685 946 141 51 1392 1042 8 #isEnabled: 98 1 32 1392 1042 8 #text: 98 1 8 'Koniec' 1392 1138 8 #[44 0 0 0 0 0 0 0 1 0 0 0 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 248 1 0 0 86 1 0 0 62 2 0 0 111 1 0 0] 98 0 1200 0 27 1218 1296 -179 842 1264 8 #fixedViewLeft 141 1360 -73 842 1264 8 #fixedViewTop 51 234 256 98 2 592 8 'Mapa' 0 461638 4 ##(Smalltalk.MenuBar) 0 16 98 1 265030 4 ##(Smalltalk.Menu) 0 16 98 1 984134 2 ##(Smalltalk.CommandMenuItem) 1 1490 1520 8 '&Koniec' 1 1 0 0 0 8 '&Klikacz' 0 134217729 0 0 23615 0 0 8 '' 0 134217729 0 0 0 0 0 0 0 0 1 0 0 0 946 201 401 1 0 0 978 202 208 98 3 1042 1072 98 2 946 2559 21 946 1221 871 416 1042 1728 98 1 8 'Klikacz' 416 1042 8 #updateMenuBar 98 0 416 1138 8 #[44 0 0 0 0 0 0 0 0 0 0 0 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 4 0 0 10 0 0 0 97 7 0 0 189 1 0 0] 98 2 592 1392 1200 0 27 )! ! !KlikaczPrezenter class categoriesFor: #defaultModel!public! ! !KlikaczPrezenter class categoriesFor: #defaultView!public! ! !KlikaczPrezenter class categoriesFor: #resource_KlikaczWidok!public!resources-views! ! KlikaczMapaWidok guid: (GUID fromString: '{40ACC916-5F50-4271-9AFB-8CB87F772FB6}')! KlikaczMapaWidok comment: 'Klasa widoku komponentu wyswietlajacego mape. szerPola - szerokosc pojedynczego pola wysPola - wysokosc pojedynczego pola Problem marginesu z prawej strony i z dolu rysunku. Mozna go rozwiazac nastepujaco: 1) Korygowac rozmiar okna (do wielokrotnosci rozmiarów modelu), ale p. onPaintRequired., 2) Przy przeliczaniu wspólrzednych traktowac klikniecia w margines jako klikniecia w ostatni wiersz/kolumne (p. zamienNaWspModelu:), 3) Ostatni wiersz/kolumne rysowac szerzej/grubiej (moze nieladnie wygladac, do liczenia trzeba zastosowac i tak rozwiazanie z p. 2). Problem wspólrzednych pionowych: pole 1@1 jest na ekranie w lewym-DOLNYM rogu. Test dzialania (wyswietlania): KlikaczMapaWidok show '! !KlikaczMapaWidok categoriesForClass!No category! ! !KlikaczMapaWidok methodsFor! initialize "Inicjalizacja wolana przez View>>new. UWAGA: ta inicjalizacja wykonuje sie przy dodawaniu widoku jako zasobu (addView: asResource:)!!" "Inicjalizacja odziedziczona z nadklasy" super initialize. "Wczytanie bitmap poszczególnych pól" rys0 := Bitmap fromFile: 'rys0.bmp'. rys1 := Bitmap fromFile: 'rys1.bmp'. rys0 isNil | rys1 isNil ifTrue: [self error: 'Nie udalo sie wczytac plików z bitmapami']! minExtent "Ustalenie minimalnego rozmiaru okna" ^100 @ 100! model: klikaczModel "Podlaczenie modelu do widoku" "Standardowa obsluga modelu" super model: klikaczModel. "Zarejestrowanie swojego zainteresowania zmianami w modelu" klikaczModel when: #valueChanged send: #odswiez to: self. klikaczModel when: #valueChangedAt send: #odswiezPole: to: self ! odswiez "Zmienilo sie cos w modelu - trzeba odswiezyc widok. Uwaga: odswieza caly widok" self invalidate! odswiezPole: pole "Zmienilo sie jedno pole w modelu - trzeba odswiezyc czesc widoku. Nie wyliczam rozmiaru pojedynczego pola, bo przy tym zdarzeniu nie mógl sie zmienic. Rysuje jedno pole." "Pierwsza wersja: dziala ale powoduje migotanie okna, bo za kazdym razem odswieza je cale." "self invalidate." "Druga wersja: bardziej oszczedna (rysuje tylko to co niezbedne), ale: - musi sie bezposrednio odwolac do swojego plotna, - musi zalozyc, ze efekt drawOn:at:extent: rzeczywiscie pojawi sie na ekranie Trzecia wersja nie ma tych wad, w niej rysowanie jest tylko w onPaintRequired." "((model at: pole x @ pole y) ifTrue: [rys1] ifFalse: [rys0]) drawOn: self canvas at: ((pole y - 1) * szerPola) @ ((pole x - 1) * wysPola) extent: szerPola @ wysPola. " "Trzecia wersja: tez dziala i tez nie miga, tyle ze powoduje wywolanie onPaintRequired:, czyli dla odswiezenia jednego pola musze wyrysowac wszystkie, a dopiero z tego system operacyjny wytnie sobie to jedno pole." self invalidateRect: (((pole y - 1) * szerPola) @ ((pole x - 1) * wysPola) extent: szerPola @ wysPola) ! onPaintRequired: aPaintEvent "Obsluga zdarzenia Paint, odrysowuje komponent" | n m | n := model n. m := model m. "Wyliczam rozmiar pojedynczego pola-rysunku" szerPola := self clientWidth // m. wysPola := self clientHeight // n. "Rysuje poszczególne pola" 1 to: n do: [:i | 1 to: m do: [:j | ((model at: i @ j) ifTrue: [rys1] ifFalse: [rys0]) drawOn: aPaintEvent canvas at: ((j - 1) * szerPola) @ ((i - 1) * wysPola) extent: szerPola @ wysPola]] ! onPositionChanged: aPositionEvent "Obsluga zdarzenia zmiany rozmiaru lub przesuniecia komponentu" aPositionEvent isResize ifTrue: [self invalidate]. ^super onPositionChanged: aPositionEvent. ! onPositionChanging: aPositionEvent self minExtent ifNotNil: [:min | aPositionEvent extent: (aPositionEvent width max: min x) @ (aPositionEvent height max: min y)]. ^super onPositionChanging: aPositionEvent! zamienNaWspModelu: punkt "Zamienia wspolrzedne okna x@y (parametr punkt) na wspolrzedne w modelu i@j. Uwaga: w oknie pierwsza wspolrzedna jest wspolrzedna pozioma, w modelu pionowa (bo model traktujemy jako prostokatna tablica). Zwn na efektywnosc nie odczytuje faktycznego rozmiaru komponentu, tylko korzystam ze zmiennych obiektowych tej klasy." "Rozmiar pojedynczego pola-rysunku mam zapamietany na zmiennych instancyjnych" | i j | i := punkt y // wysPola + 1. "+1 bo tablica jest indeksowana od 1" j := punkt x // szerPola + 1. "+1 bo tablica jest indeksowana od 1" "Dokonuje ewentualnej korekty: poniewaz rozmiary okna nie sa wielokrotnosciami rozmiarów modelu, w okienku moye byc margines po prawej i na dole, traktuje wspolrzedne z tego marginesu jako wspolrzedne ó ostatniej kolumny/ostatniego wiersza." i := i min: model n. j := j min: model m. "Podaje wynik" ^i @ j ! ! !KlikaczMapaWidok categoriesFor: #initialize!private! ! !KlikaczMapaWidok categoriesFor: #minExtent!public! ! !KlikaczMapaWidok categoriesFor: #model:!public! ! !KlikaczMapaWidok categoriesFor: #odswiez!private! ! !KlikaczMapaWidok categoriesFor: #odswiezPole:!private! ! !KlikaczMapaWidok categoriesFor: #onPaintRequired:!public! ! !KlikaczMapaWidok categoriesFor: #onPositionChanged:!public! ! !KlikaczMapaWidok categoriesFor: #onPositionChanging:!public! ! !KlikaczMapaWidok categoriesFor: #zamienNaWspModelu:!public! ! !KlikaczMapaWidok class methodsFor! defaultModel ^KlikaczModel new_n: 3 m: 3! ! !KlikaczMapaWidok class categoriesFor: #defaultModel!public! ! "Binary Globals"!