-
Notifications
You must be signed in to change notification settings - Fork 0
/
guide_de.txt
2359 lines (1903 loc) · 100 KB
/
guide_de.txt
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
= GEM Programmierung mit AHCC
== Vorwort
Dieses Dokument ist eine Anleitung zum schreiben von GEM Applikationen
in C mit AHCC. Sie enthält die detaillierten einzelnen Schritte zur
Implementation einer Beispiel GEM Anwendung. Das Beispiel-Programm
beginnt zuerst mit einem einzelnen Fenster mit etwas Text. Dann erweitern wir
das Programm über mehrere Versionen hinweg, um mehrere Fenster mit
Schiebern und anderen Fenster-Widgets zu erzeugen. Das Programm wird
dann erweitert um Menüs, Datei-Dialoge und andere AES Features.
Wieso AHCC? Zwar gibt es viele Optionen für die C-Programmierung auf der
Atari Plattform, doch wird AHCC ständig aktualisiert, und es läuft nativ auf
(allen?) Atari computern und Clones. Es ist eine vollständige IDE, inklusive
eines Editors und eines Compilers und Linkers: Ich benutze dieses Programm
extensiv auf meiner FireBee, und ebenso auf meinem Atari STE. AHCC ist
verfügbar unter dieser URL: http://members.chello.nl/h.robbers/. Obwohl
für AHCC geschrieben, trifft das meiste folgende auch für andere C Compiler
für den Atari zu, aber die Beispiele müssen allenfalls in ihren
Header-, Projekt- und Make-Dateien angepasst werden.
Es gibt auch andere ähnliche Anleitungen da draussen; Ich habe auch
von der folgenden gehört, die CManShip benutzt, geschrieben von
Clayton Walnum, und verfügbar auf AtariForge:
* CManShip HYP http://dev-docs.atariforge.org/files/cmanship.hyp
* CManShip disk http://dev-docs.atariforge.org/files/cmanship.zip
Allerdings benötigen die Beispiele in CManShip einige Anpassungen, um
mit AHCC compiliert zu werden (meine angepassten Dateien für AHCC sind
verfügbar unter https://github.com/petercrlane/cmanship). Spezifisch:
* C89 Syntax (Vor allem Funktions-Signaturen)
* Änderungen in Headern und Library-Aufrufen zur Kompatibilität
AES hat sich ebenfalls in einigen Aspekten verändert, vor allem wenn
man MINT mit XaAES oder MyAES benutzt, mit neuen Messages und Möglichkeiten,
die auf einem Atari ST nicht vorhanden sind. Zum Beispiel ist es möglich,
Menüs innerhalb eines Fensters zu benutzen, und auch Toolbars ab oberen
Rand eines Fensters. Ich werde einige dieser Features in einem späteren
Abschnitt besprechen.
Nachdem ich CManShip durchgearbeitet hatte, versuchte ich ein paar
verschiedene Programme zu schreiben - teils mittels Trial-and-Error,
teils mit der Hilfe von Mitgliedern von http://atari-forum.com - und fühle
mich nun in der Lage, ein paar simple GEM-Programme zu erstellen. Dieses
Dokument ist zum Teil mein Versuch, aufzuzeichnen, was ich während dieses
Prozesses gelernt hatte. Ausserdem ist es auch ein Versuch, die von mir
genutzten Techniken zu vereinfachen und vereinheitlichen. Dieses Dokument
ist aber auch, in mehrerer Hinsicht, eine Dokumentation der Grenzen
meiner Kenntnisse, weshalb ich Sie bitte, alles Geschriebene
mit einer gesunden Portion Skepsis zur Kenntnis zu nehmen, und mir
allfällige Fehler und Verbesserungen mitzuteilen.
Ich setze voraus, dass Sie bereits Kenntnisse der Programmiersprace C
besitzen (genauer, C89), und auch wissen, was ein GEM Programm ist. Ich
werde versuchen aufzuzeigen, was es braucht, um in C ein funktionierende
GEM Programm zu schreiben welches mit dem Operating System interagiert.
Ich werde nicht viele Worte verlieren über die Erstellung einer RSC Datei -
Sie können dafür eine andere Dokumentation eigener Wahl beiziehen
(ich benutze ResourceMaster 3.65).
Diese Anleitung ist keine vollständige Referenz zur GEM Programmierung
mittels AES / VDI. Abgesehen von CManShip benutze ich folgende Referenzen
für die Liste von Konstanten und Funktions-Definitionen:
* "The Concise ATARI ST 68000 Programmer's Reference Guide" von Katherine Peel
* http://toshyp.atari.org (auch verfügbar als hyp-Datei Download).
Alternative Quellen:
* "Professional GEM" http://www.atari-wiki.com/index.php/Professional_GEM, von Tim Oren
* The Atari Compendium http://dev-docs.atariforge.org/files/The_Atari_Compendium.pdf
Diese Anleitung wurde geschrieben auf einer http://firebee.org[Firebee] mittels
QED. Zur Vorschau der Anleitung wurden http://peterlane.info/neso.html[Neso]
und NetSurf benutzt, und schliesslich wurde das PDF auf einem Ubuntu Computer
mittels asciidoc erzeugt. Die Beispiele wurden auf einer FireBee mit Version 5.3 des
http://members.chello.nl/h.robbers/[AHCC C Compilers] getestet, sowie wo
sinnvoll auf einem Hatari-emulierten Atari ST.
Quell-Code für die Anleitung und ihre Beispiel ist verfügbar unter:
https://github.com/petercrlane/gemguide
== AHCC Setup und Projekt-Dateien
AHCC kann heruntergeladen werden von http://members.chello.nl/h.robbers/
Es gibt keinen Installations-Schritt: Einfach entpacken und an einen
passenden Ort auf der Harddisk kopieren. Ich habe einen Desktop Shortcut
für das AHCC Programm, damit es jederzeit zugreifbar ist.
=== Probleme mit Version 5.3
Wir werden die include Libraries für AHCC benutzen (nicht die short-int
Variante in sinclude). Unglücklicherweise fehlen in Version 5.3 einige
der von uns benötigten Konstanten in "include/aes.h". Sie müssen diese
Konstanten kopieren von "sinclude/aes.h". Die fehlenden Konstanten sind:
----
#define WM_SHADED 22360 // <1>
#define WM_UNSHADED 22361
#define FL3DNONE 0x000 // <2>
#define FL3DIND 0x0200
#define FL3DBAK 0x0400
#define FL3DACT 0x0600
#define FL3DMASK 0x0600
#define SUBMENU 0x0800
----
<1> Diese beiden Events werden in TOS 4.0 Programmen verwendet.
<2> Diese Flags werden benötigt um Dialoge in einer Resource-Datei zu erzeugen.
Falls Sie "missing constant" Fehlermeldungen von AHCC erhalten, prüfen Sie zuerst
ob diese Konstanten tatsächlich in "include/aes.h" fehlen.
=== Projekt Dateien
Struktur einer Projekt-Datei:
----
; ein Kommentar
PROGRAM.PRG ; <1>
.C [-7 -iinclude] ; <2>
= ; <3>
ahcstart.o ; <4>
program.c ; <5>
ahccstdi.lib ; <6>
ahccgem.lib
gemf.lib
----
<1> Name des resultierenden compilierten Programmes.
<2> Optionale Liste von Parametern für die Compilierung.
<3> Trenner zwischen Output-Definition und Input-Dateien.
<4> Der Standard Startup-Code für den Linker.
<5> Liste der .c-Dateien Ihres Programmes.
<6> Normalerweise drei Libraries: Diese variieren, abhängig von der Ziel-Plattform.
Was üblicherweise in dem Projekt angepasst werden muss:
* Zeile (1) legt den Namen Ihres Programmes fest.
* Zeile (2) die Ziel-Plattform. -7 bedeutet Firebee / Coldfire, -2 für
68020, und gar nichts für den Atari ST / 68000.
* Zeile (5) enthält alle C-Quelldateien Ihres Projektes.
* Zeile (6) diese und die folgenden Zeilen müssen für verschiedene Atari Plattformen
angepasst werden; zum Beispiel müssen für die FireBee mit Fliesskomma-Support
ahccstdf.lib und ahccgemf.lib angegeben werden. Die oben gezeigten Libraries
passen zum Ausgabeziel Atari ST / 68000.
Die Standard-Library kommt in vier Versionen (Dank an Eero Tamminen
für seine Erklärungen hierzu):
. ahccstdf.lib (Firebee / Coldfire)
. ahccstd.lib (680x0 + FPU)
. ahccstdi.lib (68000 mit Fliesskomma-Support, der in printf/scanf fehlt)
. ahccstfi.lib (Firebee / Coldfire ohne FPU, könnte kompatibel sein mit Falcon)
=== Einstellungen
Wenn AHCC gestartet wird, benutzt es gemäss Voreinstellung die Libraries in
"sinclude". Diese verwenden einen "short int" Datentyp.
Bevor Sie die Programme hier ausprobieren, konfigurieren sie AHCC so dass es
die Libraries in "include" verwendet. Öffnen Sie dazu den "Config" Dialog
(Alt-O), suchen Sie "Options for the compiler" und setzen Sie die Checkbox
für die Zeile "-i include".
== Eigenschaften eines GEM Programmes
Wenn Sie vertraut sind mit GUI Programmierung auf modernen Computern, bitte
vergessen Sie alles, was Sie wissen. Programmierung in GEM ist primitiv. Mit
modernen Toolkits bekommt man alle Arten von Widgets, von Buttons über
Spinboxes bis hin zu kompletten Text-Editoren mit Support für Copy-/Paste
und scrollbaren Anzeige-Bereichen. Widgets können mit Layout Managern
in komplexen Arrangements kombiniert werden; Multi-Threading wird benutzt
um die Anzeige zu aktualisieren während das Programm mit der Berechnung
irgendeines Resultates beschäftigt ist.
Nichts davon ist in GEM verfügbar.
In GEM gibt es essentiell drei Elemente: Menüs, Dialog-Boxen (welche mehrere
"Widgets" enthalten können) und Fenster (Windows). Der Teil, der am meisten
Zeit beanspruchen wird, ist die Handhabung eines Fensters zu erlernen:
Ein Fenster ist ein Teil des Screens in welchem unsere Applikation freie
Hand hat. Sie haben totale Kontrolle über den Inhalt des Fensters. Sie
entscheiden was dort gezeichnet wird, wie das, was gezeichnet wird, reagiert
wenn der Benutzer den Schieber bewegt, und Sie müssen auch sicherstellen das
das, was Sie im Fenster zeichnen dort bleibt (und nur dort) während Ihr
Fenster interagiert mit den Fenstern anderer Applikationen (in MINT) oder
mit Desktop Accessories.
Die folgenden Sektionen werden schrittweise ein GEM Programm aufbauen um den
Umgang mit Fenstern zu illustrieren. Das Endresultat ist ein Programm welches
auf alle die erwarteten Fenster-Events reagiert, Scrollbars besitzt und
friedlich mit anderen Fenstern ko-existiert, während es seinen eigenen Inhalt
bewahrt, ohne seine Nachbarn zu stören. Jede Version des Beispiels steht mit
vollständigem Quellcode für diesen Guide zur Verfügung, und kann mit AHCC
compiliert werden.
Nachdem wir Fenster in den Griff bekommen haben gehen wir weiter zu einem
leichteren Aspekt der GEM-Programmierung: Menüs. Menüs sind definiert in der
RSC-Datei eines Programmes, und alles, was wir tun müssen ist, auf Messages
zu reagieren die anzeigen, dass ein Menüpunkt angewählt wurde. In diesem
Abschnitt werden wir auch anschauen wie man auf Tastatur-Befehle reagiert,
so dass Tastatur-Shortcuts benutzt werden können für Menü-Punkte.
Danach gehen wir weiter zu Dialog-Boxen. Das Hauptproblem mit Dialog-Boxen ist
sie anzuzeigen, und dann auf die Daten zuzugreifen die der Benutzer eingegeben
hat. Nur in Dialog-Boxen finden sich Widgets wie Buttons, Text-Felder und Labels
(ausser Sie benutzen ein spezielles, erweitertes AES welches Toolbars erlaubt,
was wir in einem späteren Abschnitt anschauen). Auch Dialog-Boxen werden
in der RSC-Datei des Programmes definiert.
HINWEIS: Obwohl der Umgang mit GEM viel Code erfordert, ist es mit etwas
Disziplin möglich, einen grossen Teil dieses Codes zwischen mehreren Projekten
wiederzuverwenden. Die Beispiele hier folgen meiner eigenen Praxis diesbezüglich.
Ich werde versuchen klarzumachen, welche Teile der Beispiele zwingend von
GEM vorausgesetzt werden, und welches meine eigenen Empfehlungen sind.
Die verschiedenen Komponenten eines GEM Fensters:
image::images/info.jpg[width=300]
Ein GEM Programm macht typischerweise das folgende:
. Die Applikation initialisieren
. Einen VDI-Screen öffnen und einen Handle dazu erlangen, um darauf zuzugreifen
. Optional eine RSC-Datei öffnen und ein Menü erzeugen
. Ein erstes Fenster oder mehrere Fenster öffnen
. Den Event-Loop starten und auf alle Fenster oder anderen Events antworten bis
die Applikation beendet wird
. Alle Resourcen freigeben
. Den VDI-Screen freigeben
. Die Applikation beenden
== Beginn: Ein einfaches Fenster
In dieser ersten Sektion sehen wir, wie man ein GEM Programm startet und
das einfachste aller Fenster anzeigt. Dies entspricht dem "hello world"
der GEM Programmierung.
Ich finde es nützlich, den Code in drei separate Bereiche aufzuteilen:
. main.c enthält die +main+ Funktion und all den vorher erwähnten Setup-Code
bevor das eigentliche Programm ausgeführt wird.
. windows.c enthält all den Standard GEM Code, sowie Funktionen um auf
GEM Events zu reagieren.
. eg_draw.c enthält den spezifischen Code um den Inhalt des Fensters für diese
Applikation zu zeichnen.
Die Header-Datei "windows.h" ist ebenfalls wichtig. Hier definieren wir einige
Variablen die AES benötigt, und auch unsere eigene Fenster Daten-Struktur (siehe unten).
=== Start einer GEM Applikation
Wir müssen unsere GEM Applikation starten und beenden mit einigem "startup"-
und "teardown"-Code. Dieser Code registriert unsere Applikation im Operating System
und stellt eine eindeutige Referenz zur Verfügung, um unsere Applikation zu identifizieren.
Ich platziere diesen Code im +main+, mit einem Aufruf einer Funktion um unsere
eigene Applikation zu starten (für ein vollständiges Listing, inklusive Header-Dateien
und Variablen-Definitionen, schauen Sie den begleitenden Quellcode zu diesem Guide an):
[source,c]
----
void main (int argc, char ** argv) {
appl_init (); // <1>
open_vwork (); // <2>
start_program (); // <3>
rsrc_free (); // <4>
v_clsvwk (app_handle); // <5>
appl_exit (); // <6>
}
----
<1> Dies ist eine AES-eigene Funktion die unsere Applikation initialisiert.
Sie gibt eine eindeutige Referenz auf unsere Applikation zurück, die wir
aber unmittelbar noch nicht brauchen. Diese Referenz wird nachfolgend
"Handle" genannt.
<2> Diese Funktion müssen wir bereitstellen, und sie wird benutzt um einen
"workspace" für unser Programm zu erstellen und zu öffnen, und den
Applikations-Handle zu speichern.
<3> Diese Funktion stellen wir bereit, hier wird unser Programm ausgeführt.
<4> Nachdem unser Programm beendet wurde, werden Resourcen freigegeben.
<5> Mittels des Applikations-Handles von (2) wird der "workspace" geschlossen.
<6> Und schliesslich wird die Applikation beendet.
Funktionen (2) und (3) müssen wir selber bereitstellen. (2) ist ein Standard
Prozess um eine "workstation" zu öffnen, wie unten gezeigt. Ein wichtiger
Punkt hier ist die Erzeugung einer Referenz auf den Screen, durch den Aufruf
von +graf_handle+; diese Referenz wird gespeichert in der Variable +app_handle+.
[source,c]
----
void open_vwork (void) {
int i;
int dum;
app_handle = graf_handle (&dum, &dum, &dum, &dum); // <1>
work_in[0] = 2 + Getrez (); // <2>
for (i = 1; i < 10; work_in[i++] = 1);
work_in[10] = 2;
v_opnvwk (work_in, &app_handle, work_out); // <3>
}
----
<1> Erzeugt den Ausgabe-Screen und speichert die Referenz. Wir benötigen
die Werte der anderen Parameter nicht (welche sich auf die Grösse
von Text im Screen beziehen).
<2> Erzeugen der Werte um eine virtuelle "workstation" zu deklarieren.
<3> Schliesslich erzeugen der virtuellen "workstation" für unsere Applikation.
=== Ein Fenster anzeigen
Jedes Fenster in unserer Applikation wird dem Benutzer irgendwelche Informationen
anzeigen. Ein Fenster kann auf viele Events reagieren: Es kann auf dem Bildschirm
herum bewegt werden, vergrössert oder verkleinert werden oder seine Scrollbalken-Schieber
werden verschoben, und es muss die jederzeit seinen Inhalt aktuell halten. Es gibt
also mehrere Teilinformationen, die ein Fenster berücksichtigen muss. Aus diesem
Grunde benutze ich eine +struct+ um alle relevanten Daten zu einem einzelnen
Fenster zu speichern. Mein Basis +win_data+ sieht so aus:
[source,c]
----
struct win_data {
int handle; /* handle des Fensters <1> */
char * text; /* text der im Fenster angezeigt wird <2> */
};
----
<1> Jedes Fenster hat einen eindeutigen +handle+ um es zu referenzieren.
<2> Applikations-spezifische Daten, zur Anzeige im Fenster.
Während wir durch diesen Guide voranschreiten, wird der Inhalt von +win_data+
erweitert werden. Zusätzlich wird Ihre Applikation diverse eigene Daten enthalten die
relevant sind fürs Fenster; diese sollten ebenfalls in +win_data+ gespeichert
werden: Für den Moment benutzen wir +text+ als unser Beispiel für
Applikations-spezifische Daten.
Ein Fenster zu erzeugen setzt voraus, dass eine Instanz von +win_data+ mit den
relevanten Daten erzeugt wird, und dann ein Fenster, welches dann angezeigt wird.
Der folgende Code erscheint in "windows.c", in der +start_program+ Funktion.
[source,c]
----
struct win_data wd;
int fullx, fully, fullw, fullh;
/* 1. set up and open our window *
wind_get (0, WF_WORKXYWH, &fullx, &fully, &fullw, &fullh); // <1>
wd.handle = wind_create (NAME|CLOSER, fullx, fully, fullw, fullh); // <2>
wind_set (wd.handle, WF_NAME, "Example: Version 1", 0, 0); // <3>
wind_open (wd.handle, fullx, fully, 300, 200); // <4>
wd.text = "Hello"; // <5>
----
<1> Diese Zeile ermittelt die aktuelle Desktop-Grösse. Der erste Parameter, 0,
referenziert den Desktop. Der nächste, +WF_WORKXYWH+, legt fest welche Daten
die Funktion ermitteln soll, und die verbleibenden Adressen sind die Bereiche
in denen die Resultate gespeichert werden.
<2> Diese Zeile erzeugt das Fenster. Der erste Parameter definiert, welche
Elemente das Fenster besitzen soll. Beachten Sie, dass wir den handle in
unserer +wd+ Variable speichern. Des weiteren legen wir die maximale Grösse
des Fensters fest - in diesem Fall die Grösse des Desktops.
<3> Setzt den Wert von +WF_NAME+ in unserem Fenster: Dies ist der Titel des
Fensters (die Überschrift). Der Titel-Text muss in unserem Programm irgendwo
gespeichert werden, da das AES keine Kopie davon macht.
<4> Zum Schluss wird das Fenster auf dem Screen geöffnet. Die letzten vier
Parameter definieren die x- und y-Position, sowie mit w (width / Breite)
und h (height / Höhe) die Grösse des Fensters. Diese Werte müssen nicht
gleich sein wie die Maximal-Grösse.
<5> Dann werden noch die Applikations-spezifischen Daten für unser Fenster
aufbereitet: In diesem Falle der Text, der im Fenster angezeigt werden soll.
TIP: Auf einem Atari ST, beispielsweise, ist es möglich, dass GEM irgendwann die
Fenster ausgehen und es keine neuen mehr öffnen kann. Wenn das passiert, ist der
von +wind_create+ zurückgegebene Handle negativ. Sie sollten deshalb zwischen
(2) und (3) prüfen ob +wd.handle+ negativ ist, und den Benutzer in diesem Falle
warnen. Wir ignorieren dies aber in unseren Beispiel-Programmen.
Die Funktionen +wind_set+ und +wind_get+ werden öfters zu sehen sein wenn Sie
mit Fenstern arbeiten. Beide verwenden einen Fenster-Handle als ihren ersten
Parameter, und dann einen Identifier für eine Komponente oder Daten dieses
Fensters. Handle 0 bedeutet dabei jeweils eine Referenz auf den Desktop.
Die Identifier beziehen sich auf verschiedene Elemente des Fensters. Ein paar
der Werte die wir benutzen werden sind:
* WF_NAME : Der Text im Titel des Fensters
* WF_INFO : Der Text in der Informations-Zeile des Fensters
* WF_WORKXYWH : Der aktuelle Arbeitsbereich des Fensters, in welchem gezeichnet wird
* WF_CURRXYWH : Die aktuelle Fenstergrösse, einschliesslich seiner Widgets
* WF_PREVXYWH : Die vorherige Fenstergrösse, einschliesslich seiner Widgets
* WF_FULLXYWH : Die maximale Fenstergrösse, einschliesslich seiner Widgets
* WF_HSLIDE : Die Position des horizontal Scrollbar-Schiebers
* WF_VSLIDE : Die Position des vertikalen Scrollbar-Schiebers
* WF_HSLSIZE : Die Grösse des horizontalen Schiebers
* WF_VSLSIZE : Die Grösse des vertikalen Schiebers
Beachten Sie, dass wir in +wind_create+ die Flags +NAME|CLOSER+ benutzt haben. Diese
instruieren unser Fenster, Platz für einen Titel und für eine Schliess-Box bereitzustellen.
Wir setzen dann den Titel durch +WF_NAME+, wie oben. Die Schliess-Box wird benutzt um
das Programm zu beenden, und wir werden genauer betrachten wie dies funktioniert wenn
wir den Event-Loop behandeln. Fenster können viele Elemente beinhalten, und um diese zu
nutzen müssen sie in der Liste von Flags aufgeführt werden. Einige übliche Flags sind:
* NAME : Für den Titel des Fensters
* CLOSER : Addiert eine Schliess-Box
* FULLER : Addiert ein Element um das Fenster zu maximieren und wiederherzustellen
* MOVER : Erlaubt es, das Fenster zu verschieben
* INFO : Eine interne Informations-Zeile für das Fenster
* SIZER : Erlaubt es, die Fenstergrösse zu ändern (re-sizing)
* UPARROW : Zeigt den Aufwärts-Pfeil der vertikalen Scrollbar
* DNARROW : Zeigt den Abwärts-Pfeil der vertikalen Scrollbar
* VSLIDE : Zeigt eine vertikale Scrollbar
* LFARROW : Zeigt den Links-Pfeil der horizontalen Scrollbar
* RTARROW : Zeigt den Rechts-Pfeil der horizontalen Scrollbar
* HSLIDE : Zeigt eine horizontale Scrollbar
Die Funktion +start_program+ geht weiter wie folgt:
[source,c]
----
draw_example (app_handle, &wd); // <1>
/* 2. process events for our window */
event_loop (&wd); // <2>
/* 3. close and remove our window */
wind_close (wd.handle); // <3>
wind_delete (wd.handle); // <4>
----
<1> Zeichnet den Inhalt unseres Fensters (nur für Version 1).
<2> Wartet darauf, dass der Benutzer mit unserem Programm interagiert.
Endet, wenn der Benutzer das Fenster schliesst.
<3> Um aufzuräumen schliessen wir zuerst das Fenster und entfernen es dann vom Screen.
<4> Zum Schluss löschen wir das Fenster und geben seinen Handle frei zur Wiederbenutzung.
=== Inhalt anzeigen
Eine Funktion +draw_example+ zeichnet den Fenster-Inhalt auf den Screen. Unser
Beispiel zeigt schlicht den gegebenen Text im Fenster.
[source,c]
----
void draw_example (int app_handle, struct win_data * wd) {
v_gtext (app_handle, 10, 60, wd->text); // <1>
}
----
<1> Zeigt Text an der gegebenen Koordinate auf dem Screen an. Wichtig:
Wir benutzen +app_handle+, welches den Screen referenziert, wenn
wir VDI Funktionen aufrufen.
=== Event Loop
Alle GEM Programme arbeiten "Event"-getrieben. Das bedeutet dass das Programm
darauf wartet, dass der Benutzer oder das Betriebssystem ihm einen Event schicken,
um etwas zu tun. Das Programm führt dann die gewünschte Aktion für den Event
aus, bevor es in den Loop (Schleife) zurückkehrt. Beispiel: Anklicken der
Schliess-Box links oben im Rahmen des Fensters sendet eine Nachricht (Message)
an unser Programm. Das Programm muss dann darauf entsprechend reagieren; in
diesem Falle müssen wir das Fenster schliessen und das Programm beenden.
Für das Beispiel werden wir Events mittels der Funktion +evnt_mesag+ in unserem
Programm verarbeiten. In einem "richtigen" GEM Programm werden Sie den grossen
Bruder +evnt_multi+ benutzen, welches erlaubt, Events von der Maus, der Tastatur
oder anderen Quellen zu verarbeiten, sowie solche für das Fenster. Für das Beispiel
werden wir der Einfachheit halber nur auf Fenster-Events reagieren, deshalb
benutzen wir diesen Aufruf.
[source,c]
----
void event_loop (struct win_data * wd) {
int msg_buf[8]; // <1>
do {
evnt_mesag (msg_buf); // <2>
} while (msg_buf[0] != WM_CLOSED); // <3>
}
----
<1> Erzeugt Speicherplatz fuer die Event-Message.
<2> Verarbeitet den nächsten Event.
<3> Bleibt in dem Loop bis eine +WM_CLOSED+ Nachricht verarbeitet
wird, die anzeigt dass der Benutzer die Schliessbox angeklickt hat.
In späteren Abschnitten werden wir in dem Loop auf mehr Arten von Events
reagieren. Wir werden dann andere mögliche Werte für +msg_buf+ besprechen.
=== Beispiel Programm: Version 1
An diesem Punkt haben wir ein Fenster welches etwas Text anzeigt. Wenn Sie
aber ein anderes Fenster darüber ziehen, wird der Inhalt verschwinden.
Ausserdem ist keines der Fenster-Widgets ausser der Schliessbox funktional;
wir können das Fenster nicht verschieben, es nicht durch anklicken in den
Vordergrund holen usw. Ausserdem ist der Hintergrund durch das Fenster
hindurch sichtbar, was vor allem in einer Multitasking-Umgebung wie
MINT ein Problem ist.
== Ein Fenster aktualisieren: Redraw Events
Wir möchten gern eine schöne Anzeige in unserem Fenster. Zuerst einmal
soll der Hintergrund nicht zu sehen sein, sondern statt dessen eine hübsche,
saubere Fläche. Zweitens soll das Fenster konstant den Inhalt anzeigen den
wir wollen, selbst wenn wir ein anderes Fenster darüber ziehen und wieder
weg bewegen.
Obgleich nachfolgend mehrere Schritte ausgeführt werden, ist der Code
vollständig wiederverwendbar in all Ihren GEM-Programmen. Wenn Sie mal
eine funktionierende Vorlage haben, können Sie diese einfach kopieren,
und alles sollte funktionieren.
=== In einem Bereich zeichnen
Schauen Sie wiederum unsere Funktion +draw_example+ an: Der Aufruf von
+v_gtext+ referenziert nicht unser Fenster, sondern nur den Screen. Unser
Zeichnungs-Code könnte theoretisch überall auf dem Screen direkt etwas
zeichnen. Doch dies würde natürlich die Illusion zerstören, ein Fenster
zu haben, welches einen Blick auf bestimmte Informationen freigibt.
Eine bessere Lösung ist es, einen _Clipping_ Bereich festzulegen:
Dies ist ein Rechteck welches wir definieren, so dass jegliche VDI
Aufrufe ausserhalb dieses Bereiches automatisch abgeschnitten werden,
und nur auf den entsprechenden Bereich beschränkt zu sehen sind.
Demzufolge packen wir unseren Aufruf von +draw_example+ in eine Funktion +draw_interior+,
welche den Clipping-Bereich definiert und auch die Anzeige unseres Fensters für uns
löscht. Die folgende +draw_interior+ Funktion erledigt eine Menge nützlicher
Dinge für uns. Zuerst versteckt sie die Maus, damit wir nicht darüber zeichnen.
Zweitens definiert sie den Clipping-Bereich (+set_clip+ ist definiert in "windows.c"),
so dass alle unsere VDI-Aufrufe nur innerhalb des gegebenen Rechtecks wirksam sind.
Dann ermitteln wir die Grösse des Arbeitsbereiches unseres Fensters. Dies ist der
gleiche +wind_get+ Aufruf den wir zuvor gemacht haben, nur dass wir jetzt für
den Arbeitsbereich unseres Fensters statt des Desktops nachfragen. Der
Arbeitsbereich schliesst die Fenster-Elemente wie Scrollbars etc. nicht mit ein.
Ein Aufruf um das Fenster zu leeren, und wir können dann den Code aufrufen, der
den gewünschten Inhalt zeichnet. Schliesslich wird das Clipping wieder deaktiviert
und die Maus wieder angezeigt.
[source,c]
----
/* Zeichnet Fenster-Inhalt innerhalb des Clipping-Bereichs */
void draw_interior (struct win_data * wd, GRECT clip) {
int pxy[4];
int wrkx, wrky, wrkw, wrkh; /* ein paar Variablen die den aktuellen Arbeitsbereich beschreiben */
/* bereitet das zeichnen vor, indem der Mauszeiger versteckt und Clipping aktiviert wird */
graf_mouse (M_OFF, 0L); // <1>
set_clip (true, clip);
wind_get (wd->handle, WF_WORKXYWH, &wrkx, &wrky, &wrkw, &wrkh);
/* loescht das display */
vsf_color (app_handle, WHITE); // <2>
pxy[0] = wrkx;
pxy[1] = wrky;
pxy[2] = wrkx + wrkw - 1;
pxy[3] = wrky + wrkh - 1;
vr_recfl (app_handle, pxy);
/* unser spezifischer Code um den Inhalt zu zeichnen */
draw_example (app_handle, wd); // <3>
/* aufraeumen */
set_clip (false, clip); // <4>
graf_mouse (M_ON, 0L);
}
----
<1> Versteckt die Maus, aktiviert das Clipping und ermittelt den Arbeitsbereich.
<2> Löscht den Arbeitsbereich.
<3> Aufruf unseres Zeichnungs-Codes.
<4> Deaktiviert das Clipping wieder und macht den Mauszeiger wieder sichtbar.
=== Display aktualisieren
Einer der komplexeren Aspekte des Umgangs mit Fenstern in GEM ist das Konzept
der Rechteck-Listen, und wie man sie managed. Die Grundidee ist sehr einfach.
Nehmen wir an, das Fenster unseres Programmes ist verdeckt von diversen
anderen Fenstern. Eines dieser Fenster wird geschlossen. Unser Fenster
erhält eine Message um den Teilbereich neu zu zeichnen, der sich unter
dem anderen Fenster befand, welches nun geschlossen wurde.
Wir können aber nun nicht einfach diesen Bereich blind mit dem Inhalt
unseres Fensters füllen, da noch andere Fenster ebenfalls diesen Bereich
teilweise verdecken könnten. Deshalb müssen wir sämtliche Fenster im
System anschauen und prüfen, welche unser Fenster verdecken, und
sicherstellen, dass wir nicht in diese Fenster hinein zeichnen.
Schauen Sie als Beispiel die folgenden zwei Bilder an. Im ersten Bild
haben wir drei Fenster, die sich alle gegenseitig überlappen. Nun schliessen
wir das oberste Fenster: Wie soll das hintere Fenster (das welches die Liste
"Classic 1" anzeigt) aktualisiert werden?
image::images/redraw-1.png[width=300]
Das zweite Bild zeigt die Szene mit dem obersten Fenster geschlossen. Das
hervorgehobene Rechteck zeigt den Bereich, der aktualisiert werden muss -
und nur diesen Bereich. Wenn wir irgendeinen anderen Bereich des unteren
Fensters neuzeichnen, werden wir Inhalte des darüberliegenden Fensters
überschreiben.
image::images/redraw-2.png[width=300]
Der Prozess um sicherzustellen, dass nur der benötigte Bereich aktualisiert
wird, nennt sich "Rechteck-Liste durchgehen". Unsere Applikation enthält
den gesamten Bereich der freigelegt wurde durch das Schliessen des oberen
Fensters, und der aktualisiert werden muss. Unsere Applikation vergleicht
diesen Bereich mit den anderen Fenstern, die darüber liegen, bevor es das
hervorgehobene Rechteck ermittelt für die Zeichenoperation.
Die Rechteck-Liste durchgehen geschieht mittels zweier Operationen:
. +wind_get (wd->handle, WF_FIRSTXYWH, ...)+ ermittelt das erste Rechteck
relevant für unser Fenster, wobei dessen x, y, w, h Werte in den
verbleibenden Referenz-Parametern gespeichert werden.
. +wind_get (wd->handle, WF_NEXTXYWH, ...)+ ermittelt das nächste Rechteck,
wobei wiederum dessen x, y, w, h Werte in den verbleibenden
Referenz-Parametern gespeichert werden.
Dies geht so weiter solange die erhaltenen w (width / Breite) und h (height / Höhe)
Werte nicht Null sind: Wenn beide Null (0) sind haben wir das Ende der
Rechtecks-Liste erreicht.
Alles was wir prüfen ist, ob die Rechtecke in der Liste unser Rechteck zur
Aktualisierung überlappen, und falls ja, zeichnen wir den überlappenden Bereich neu.
[source,c]
----
/* Aufgerufen wenn die Applikation aufgefordert wird, Teile ihres
Displays neu zu zeichnen. Arbeitet die Rechteck-Liste durch und
zeichnet jeden relevanten Fensterteil erneut.
*/
void do_redraw (struct win_data * wd, GRECT * rec1) {
GRECT rec2;
wind_update (BEG_UPDATE); // <1>
wind_get (wd->handle, WF_FIRSTXYWH,
&rec2.g_x, &rec2.g_y, &rec2.g_w, &rec2.g_h); // <2>
while (rec2.g_w && rec2.g_h) { // <3>
if (rc_intersect (rec1, &rec2)) { // <4>
draw_interior (wd, rec2); // <5>
}
wind_get (wd->handle, WF_NEXTXYWH,
&rec2.g_x, &rec2.g_y, &rec2.g_w, &rec2.g_h); // <6>
}
wind_update (END_UPDATE); // <7>
}
----
<1> Deaktiviert alle anderen Fenster-Updates während wir den Inhalt unseres Fensters aktualisieren
<2> Ermittelt das erste zu aktualisierende Rechteck
<3> Nun loopen wir in der Schleife solange es ein Rechteck gibt, das noch aktualisiert werden muss.
<4> Prüfe of das zu aktualisierende Rechteck den Teil des Displays überlappt, den wir neu zeichnen müssen.
<5> Zeichne unseren Fensterinhalt nur in dem Bereich, in dem sich die beiden Rechtecke überlappen.
<6> Ermittle das nächste zu aktualisierende Rechteck.
<7> Schalte die Aktualisierung der anderen Fenster wieder ein.
(Beachten Sie dass +rc_intersect+ von der AHCC library zur Verfügung gestellt wird.)
=== Auf REDRAW Events reagieren
Was macht das AES wenn es denkt dass unser Fenster aktualisiert werden muss?
Es sendet uns einen REDRAW Event. Zusammen mit dem Event teilt es uns den Handle
des zu aktualisierenden Fensters mit, sowie ein Rechteck: Dieses Rechteck definiert
den Bereich unseres Fensters der aktualisiert werden soll.
Für einen REDRAW Event erhalten wir die folgenden Informationen in +msg_buf+:
* msg_buf[0] = WM_REDRAW, der Typ des Events.
* msg_buf[3] = Handle des Fensters, welches aktualisiert werden soll.
* msg_buf[4] = x-Koordinate des zu aktualisierenden Bereichs.
* msg_buf[5] = y-Koordinate des zu aktualisierenden Bereichs.
* msg_buf[6] = Breite des zu aktualisierenden Bereichs, in Pixeln.
* msg_buf[7] = Höhe des zu aktualisierenden Bereichs, in Pixeln.
[source,c]
----
do {
evnt_mesag (msg_buf);
switch (msg_buf[0]) { // <1>
case WM_REDRAW: // <2>
do_redraw (wd, (GRECT *)&msg_buf[4]); // <3>
break;
}
} while (msg_buf[0] != WM_CLOSED);
----
<1> Wir benutzen ein "switch"-Statement um die Aktion auszuwählen, auf
die wir reagieren, abhängig vom Event-Typ.
<2> Der Typ für einen REDRAW-Event.
<3> Wir rufen einfach unsere +do_redraw+ Funktion auf. Da wir in diesem
Programm nur ein Fenster haben, können wir die Daten dafür direkt
übergeben. Beachten Sie wie die x, y, w, h Koordinaten in msg_buf[4,5,6,7]
in den GRECT-Typen übertragen werden.
Dieser Teil des Programms erledigt eine Menge harter Arbeit. Zur Wiederholung:
Unser Programm muss Teile seines Displays neu zeichnen; es wird einen REDRAW
Event erhalten, welcher einen Fenster-Handle enthält sowie den zu aktualisierenden
Bereich. Wir reichen diese Daten an die +do_redraw+ Funktion, welche die
Rechtecks-Liste durcharbeitet und sicherstellt, dass wir nur diese Teile des
Fensters neu zeichnen, welche nicht von einem anderen Fenster verdeckt werden.
+do_redraw+ ruft sodann für jedes nicht verdeckte Rechteck +draw_interior+
welche den Clipping-Bereich für das nicht verdeckte Rechteck setzt, und
zeichnet unsere Applikations-Daten in diesen Bereich.
Die gute Nachricht hier ist zweiteilig. Erstens, nachdem wir nun all diesen
Code geschrieben haben, werden wir das mehr oder weniger genauso machen
für jedes andere GEM Programm. Der Hauptteil, den Sie in einem neuen
Programm anpassen müssen ist +draw_example+, bzw. das äquivalent dazu.
Zweitens wird dieser Ablauf jedesmal dann ausgeführt, wenn der Computer
denkt dass der Screen aktualisiert werden muss, weshalb Sie selber sonst
nichts weiter machen müssen um das Fenster aktuell zu behalten. Insbesondere
wird unser Programm beim starten schon einen REDRAW Event erhalten, was zur
Folge hat, dass wir den Aufruf von +draw_example+ in +start_program+
entfernen können.
=== Beispiel-Programm: Version 2
Diese Version beinhaltet die REDRAW Events, das Durcharbeiten der Rechtecks-Liste
und den Code zum löschen des Display-Hintergrundes. Wenn Sie den Code ausführen
müsste das Display nun wesentlich besser aussehen. Versuchen Sie, ein anderes
Fenster über das unseres Programmes zu ziehen: Beachten Sie, wie das Display
sich selber aktualisiert und damit unser Fenster immer so anzeigt wie gewünscht.
Doch nun ist es Zeit, mal einige der anderen möglichen Fenster-Elemente zu
benutzen, und die von ihnen ausgehenden Events zu verarbeiten.
== Einfache Fenster Events: TOP und MOVE
Die ersten beiden Events die wir behandeln sind sehr simpel, da wir uns darauf
verlassen können, dass das AES die notwendigen Schritte ausführt. Diese beiden
Events werden getriggert wenn unser Fenster in den Vordergrund des Screens
gebracht wird, oder wenn unser Fenster an eine andere Position verschoben wird.
=== TOP
Wenn mehrere Fenster offen sind, ist nur eines "on top", also zuoberst, bzw.
im Vordergrund. Sie können ein Fenster in den Vordergrund bringen indem Sie
es anklicken, oder indem Sie ein anderes, darüber liegendes Fenster schliessen.
Wenn unser Fenster in den Vordergrund gebracht wird, erhält unsere Applikation
die Event-Message +WM_TOPPED+. Der Handle unseres Fensters wird in +msg_buf+
mitgeteilt. Alles, was wir tun müssen, ist unser Fenster mittels +wind_set+
in den Vordergrund zu bringen.
In unserem Event-Loop fügen wir den folgenden Code hinzu:
[source,c]
----
case WM_TOPPED:
wind_set (msg_buf[3], WF_TOP, 0, 0);
break;
----
Sie fragen sich vielleicht wie ein Fenster, das teilweise von einem anderen Fenster
verdeckt worden war, neu gezeichnet wird, wenn es in den Vordergrund gebracht wird.
Die Antwort ist dass ich ein bisschen gelogen hatte als ich sagte, dass alles was
wir zu tun hätten das oben beschriebene sei: AES wird einen REDRAW-Event für ein
Fenster auslösen, wenn es in den Vordergrund gebracht wird, weshalb unser Programm
den Fenster-Inhalt neu zeichnet. Und da wir REDRAW-Events bereits verarbeiten
haben wir nichts weiter zu tun - unser vorheriger Code und AES erledigen bereits alles
notwendige für uns.
=== MOVE
Fenster können verschoben werden, üblicherweise indem man den oberen Rahmen anklickt
und dann zieht. Damit dies funktioniert müssen Sie die MOVER Option in die Element-Liste
für Ihr Fenster hinzufügen, wenn Sie es erzeugen (siehe der Abschnitt zur Erzeugung
von Fenstern).
Wenn Ihr Fenster verschoben wird ist alles, was Sie tun müssen, die Koordinaten des
Fensters auf die Werte der neuen Position zu aktualisieren. Die neue Position wird
mitgeteilt in +msg_buf+, Bytes 4-7.
In unserem Event-Loop fügen wir folgenden Code hinzu:
[source,c]
----
case WM_MOVED:
wind_set (msg_buf[3], WF_CURRXYWH, msg_buf[4],
msg_buf[5], msg_buf[6], msg_buf[7]);
break;
----
AES kopiert den Inhalt unseres Fensters direkt an seine neue Position (AES schickt
auch REDRAW-Messages an jedes Fenster, welches von unserem verdeckt worden war -
was uns aber nicht weiter betrifft).
Wir müssen nun aber einen genaueren Blick auf unseren Zeichnungs-Code werfen, nun
da unser Fenster überallhin auf dem Screen verschoben werden kann. Erinnern Sie
sich daran, dass der Code, der auf den Screen zeichnet, dies mittels des VDI
erledigt, und dass wir keine Referenz auf unser Fenster dafür bereitstellen.
Wenn wir unser Fenster bewegen, müssen wir den Inhalt an einer neuen Position
wieder zeichnen. Um das zu tun übergeben wir die Koordinaten der Ziel-Position
an unsere Zeichen-Funktion. Die angegebenen x,y Koordinaten repräsentieren den
Ursprungs-Punkt für unsere Zeichnungs-Operationen. Wir übergeben diese Koordinaten
von +draw_interior+ an unseren Zeichnungs-Code.
[source,c]
----
void draw_example (int app_handle, struct win_data * wd, int x, int y, int w, int h) {
v_gtext (app_handle, x+10, y+60, wd->text); // <1>
}
----
<1> Wir verschieben die Position fürs Zeichnen um die x,y Koordinaten des Fensters.
=== Beispiel Programm: Version 3
Version 3
Version 3 beinhaltet jetzt diese beiden Events. Beachten Sie auch die Nutzung
von +MOVER+ wenn das Fenster erzeugt wird, und die aktualisierten +draw_example+
und +draw_interior+ Funktionen.
Unser Fenster ist jetzt ein respektables GEM Programm. Es aktualisiert sich
selbst wenn nötig, stört keine anderen Fenster im System, kann auf dem Screen
herum bewegt und in den Vordergrund gebracht werden. Wenn Ihnen ein Fenster
mit fixer Grösse reicht, haben Sie jetzt genug zusammen um eine GEM
Applikation zu implementieren.
== Fenster-Dimensionen anpassen: FULL und RESIZE
Die nächsten beiden Fenster-Elemente die wir anschauen werden benutzt, um die
Grösse des Fensters zu ändern. Das erste, FULLER (Maximier-Box), erlaubt es,
das Fenster auf seine maximale Grösse zu erweitern. Klickt man ein zweites
Mal auf die Maximier-Box schrumpft das Fenster wieder auf seine vorherige
Grösse zurück. Das zweite Element ist der SIZER, die Box rechts unten die
es ermöglicht, das Fenster dynamisch auf jede gewünschte Grösse zu ändern.
Beide diese Elemente benötigen die +wind_set+ Funktion um die aktuellen
x, y, w, h Dimensionen des Fensters festzulegen.
=== FULLER
Das FULL Widget wird benutzt um das Fenster auf die maximale Grösse zu öffnen.
Diese maximale Grösse wurde festgelegt beim erzeugen des Fensters; normalerweise
handelt es sich dabei um die Grösse des Desktops: Diese Werte haben wir in den
+fullx+ etc. Feldern unserer Fester-Daten gespeichert. Einmal maximiert kann
das Fenster wieder auf die vorherige Grösse gebracht werden, indem nochmal das
FULL Widget angeklickt wird. Das AES ermöglicht es, die vorherigen Dimensionen
des Fensters in einem Aufruf von +wind_get+ zu ermitteln mittels +WF_PREVXYWH+.
Glücklicherweise stellt das AES auf zwei Arten sicher, dass das Display immer
aktuell gehalten wird. Erstens, wenn das Fenster kleiner gemacht wird, muss
nichts aktualisiert werden, da nur Inhalte abgeschnitten werden. Zweitens,
wenn das Fenster maximiert wird, wird es grösser, weshalb ein REDRAW Event
an unsere Applikation geschickt wird, um die neuen Rechtecke zu zeichnen.
Da wir diese Events bereits verarbeiten, braucht es keine besonderen Massnahmen.
Die Funktion um den FULL-Event zu behandeln sieht wie folgt aus. Sie ist
aufgeteilt in zwei Teile: Wenn das Fenster schon auf maximaler Grösse ist,
müssen wir seine vorherige Grösse ermitteln und die Dimensionen des Fensters
auf diese Werte setzen. Wenn das Fenster noch nicht seine maximale Grösse
hat, müssen wir diese ermitteln und dann das Fenster auf diese Grösse setzen.
Zusätzlich ist es üblich, eine kleine Animation zu zeigen für das Vergrössern
oder Verkleinern des Fensters, deshalb der Aufruf von +graf_shrinkbox+. Auf
sehr schnellen Computern ist das eventuell gar nicht zu sehen; auf der FireBee
etwa kann ich die Animationen nicht sehen.
[source,c]
----
void do_fulled (struct win_data * wd) {
if (is_full_window (wd)) { /* das Fenster ist schon maximiert, also zurueck-schrumpfen */
int oldx, oldy, oldw, oldh;
int fullx, fully, fullw, fullh;
wind_get (wd->handle, WF_PREVXYWH, &oldx, &oldy, &oldw, &oldh); // <1>
wind_get (wd->handle, WF_FULLXYWH, &fullx, &fully, &fullw, &fullh); // <2>
graf_shrinkbox (oldx, oldy, oldw, oldh, fullx, fully, fullw, fullh); // <3>
wind_set (wd->handle, WF_CURRXYWH, oldx, oldy, oldw, oldh); // <4>
} else { /* Fenster maximieren */
int curx, cury, curw, curh;
int fullx, fully, fullw, fullh;
wind_get (wd->handle, WF_CURRXYWH, &curx, &cury, &curw, &curh);
wind_get (wd->handle, WF_FULLXYWH, &fullx, &fully, &fullw, &fullh); // <2>
graf_growbox (curx, cury, curw, curh, fullx, fully, fullw, fullh);
wind_set (wd->handle, WF_CURRXYWH, fullx, fully, fullw, fullh);
}
}
----
<1> Ermittelt die vorherigen Dimensionen des Fensters.
<2> Ermittelt die maximalen Dimensionen des Fensters.
<3> Zeichnet eine kleine Animation beim Schrumpfen des Fensters.
<4> Setzt die Fenster-Grösse auf die vorherigen Dimensionen.
Beachten Sie die Funktion +is_full_window+, welche +true+ zurückgibt, wenn
das Fenster tatsächlich bereits seine maximale Grösse hat. Diese Funktion
vergleicht schlicht die aktuelle mit der maximalen Grösse des Fensters.
Die Funktion sieht so aus:
[source,c]
----
bool is_full_window (struct win_data * wd) {
int curx, cury, curw, curh;
int fullx, fully, fullw, fullh;
wind_get (wd->handle, WF_CURRXYWH, &curx, &cury, &curw, &curh);
wind_get (wd->handle, WF_FULLXYWH, &fullx, &fully, &fullw, &fullh);
if (curx != fullx || cury != fully || curw != fullw || curh != fullh) {
return false;
} else {
return true;
}
}
----
Und zu guter Letzt muss auf +WM_FULLED+ Events reagiert werden im Event-Loop,
und zwar wie folgt:
----
case WM_FULLED:
do_fulled (wd);
break;
----
=== SIZER
Das SIZER-Element erlaubt es dem Benutzer, die Grösse des Fensters interaktiv
auf jede gewünschte Grösse zu ändern (rechte untere Fenster-Ecke). Das AES
teilt Ihrer Applikation mit, wenn der SIZER benutzt wurde, und damit dann
auch die neue Grösse des Fensters (in +msg_buf+). Ihr Programm muss dann
die Grösse des Fensters ändern, und allfällige andere Änderungen vornehmen
die nötig sein können, damit der Inhalt für die neue Fenstergrösse korrekt
angepasst wird (das ist vor allem wichtig wenn das Fenster Scrollbalken-Schieber
benutzt, was wir im nächsten Abschnitt anschauen).
Glücklicherweise stellt das AES sicher dass das Display aktuell gehalten wird,
genauso wie mit dem vorher besprochenen FULLED-Event.
Der benötigte Code um die Grösse des Fensters effektiv zu ändern ist jetzt
relativ einfach. Alles, was es braucht ist die Dimension des Fensters auf
die neue Grösse zu setzen. Zusätzlich implementieren wir einen einfachen
Test um sicherzustellen, dass die neue Grösse nicht _zu_ klein ist, so dass
der Benutzer das Fenster nur bis zu einem gewissen Minimum verkleinern kann.
[source,c]
----
void do_sized (struct win_data * wd, int * msg_buf) {
if (msg_buf[6] < MIN_WIDTH) msg_buf[6] = MIN_WIDTH; // <1>
if (msg_buf[7] < MIN_HEIGHT) msg_buf[7] = MIN_HEIGHT;
wind_set (wd->handle, WF_CURRXYWH,
msg_buf[4], msg_buf[5], msg_buf[6], msg_buf[7]); // <2>
}
----
<1> Um zu verhindern dass der Benutzer unser Fenster bis zur Unsichtbarkeit
verkleinert, prüfen wir, dass die neue Breite und Grösse nicht zu klein sind.
Die minimalen Dimensionen sind definiert in "windows.h".
<2> Alles was wir tun müssen, ist die Dimensionen unseres Fensters auf die
neue Grösse zu ändern.
Um auf SIZE-Events zu reagieren, muss der Event-Loop noch um die Behandlung
dieser Events erweitert werden:
[source,c]
----
case WM_SIZED:
do_sized (wd, msg_buf); // <1>
break;
----
<1> Übergibt das Fenster und den Message-Buffer an +do_sized+.
=== Beispiel Programm: Version 4
Version 4 nun mehr Fenster-Elemente: Sie können das Fenster vergrössern oder
verkleinern mit dem Button rechts unten, und Sie können das Fenster auch
zwischen seiner Maximal- und seiner aktuellen Grösse hin- und herschalten.
Um die Anzeige interessanter zu gestalten enthält das Fenster mehrere Zeilen
eines Gedichtes. Sie müssen das Fenster vergrössern, um mehr vom Gedicht zu
sehen. Wieviel Sie sehen können hängt von der Grösse Ihres Screens ab. Um
den Rest des Gedichtes zu sehen, müssen wir nun irgendwie das Fenster "über"
das Gedicht legen; das führt uns zum nächsten Thema, "Schieber" ("slider").
== Fenster-Inhalte verschieben
Die Scrollbalken-Schieber ("Sliders") erzeugen die Illusion, dass das Fenster
einen Blick auf eine grössere Fläche gewährt. Schieber sind kompliziert zu
handhaben, da Benutzer verschiedene Möglichkeiten haben, um damit zu interagieren.
Ein Schieber kann direkt gezogen werden, Benutzer können die Pfeile anklicken,
oder den Scrollbalken selber. Da es zwei Schieber gibt, bedeutet das für uns
zehn Funktionen, nur um die Schieber zu handhaben. Dazu kommt, dass eine
Änderung der Grösse oder des Inhaltes des Fensters dazu führt, dass sich
auch die Positionen und die Grössen der Schieber verändern, und entsprechend
angepasst werden müssen.
Ich werde den Code für die Schieber und Pfeile in einem Durchgang behandeln,
da dies alles zusammenhängt. Ausserdem sind die horizontalen und vertikalen
Schieber essentiell dasselbe, was den Code angeht, weshalb ich hauptsächlich
den Code für den vertikalen Schieber aufzeige: Code für den horizontalen
Schieber ist ähnlich, und im Beispiel-Code enthalten.