|
1 "{ Package: 'stx:libtool2' }" |
|
2 |
|
3 "{ NameSpace: Smalltalk }" |
|
4 |
|
5 Object subclass:#ShowMeHowItWorks |
|
6 instanceVariableNames:'opStream lastComponentName lastComponent' |
|
7 classVariableNames:'' |
|
8 poolDictionaries:'' |
|
9 category:'Interface-Help' |
|
10 ! |
|
11 |
|
12 !ShowMeHowItWorks class methodsFor:'documentation'! |
|
13 |
|
14 documentation |
|
15 " |
|
16 documentation to be added. |
|
17 |
|
18 class: |
|
19 <a short class summary here, describing what instances represent> |
|
20 |
|
21 responsibilities: |
|
22 <describing what my main role is> |
|
23 |
|
24 collaborators: |
|
25 <describing with whom and how I talk to> |
|
26 |
|
27 API: |
|
28 <public api and main messages> |
|
29 |
|
30 example: |
|
31 <a one-line examples on how to use - can also be in a separate example method> |
|
32 |
|
33 implementation: |
|
34 <implementation points> |
|
35 |
|
36 [author:] |
|
37 Claus Gittinger |
|
38 |
|
39 [instance variables:] |
|
40 |
|
41 [class variables:] |
|
42 |
|
43 [see also:] |
|
44 |
|
45 " |
|
46 ! |
|
47 |
|
48 example |
|
49 ShowMeHowItWorks do:#( |
|
50 ( showing: 'Choose the number of arguments' do:( |
|
51 moveTo: NumberOfArguments |
|
52 select: '1' |
|
53 )) |
|
54 (showing: 'Click into the "receiver" field' do:( |
|
55 moveTo: ReceiverEditor |
|
56 click: ReceiverEditor |
|
57 )) |
|
58 (showing: 'Enter a value (or expression) into "receiver" field' do:( |
|
59 enter: '100' |
|
60 )) |
|
61 (showing: 'Click into the "first argument" field' do:( |
|
62 moveTo: Arg1Editor |
|
63 click: ReceiverEditor |
|
64 )) |
|
65 (showing: 'Enter a value (or expression) into "receiver" field' do:( |
|
66 enter: '100' |
|
67 )) |
|
68 |
|
69 ) |
|
70 ! ! |
|
71 |
|
72 !ShowMeHowItWorks class methodsFor:'running'! |
|
73 |
|
74 do:specArray |
|
75 self doStream:specArray readStream |
|
76 |
|
77 "Created: / 19-07-2019 / 10:52:59 / Claus Gittinger" |
|
78 "Modified: / 19-07-2019 / 14:30:43 / Claus Gittinger" |
|
79 ! |
|
80 |
|
81 doStream:specStream |
|
82 "must run as a separate process; |
|
83 otherwise - if started by the app itself - |
|
84 no events will be processed while running" |
|
85 |
|
86 [ |
|
87 self new doStream:specStream |
|
88 ] fork. |
|
89 |
|
90 "Created: / 19-07-2019 / 10:53:07 / Claus Gittinger" |
|
91 "Modified (comment): / 19-07-2019 / 14:30:29 / Claus Gittinger" |
|
92 ! ! |
|
93 |
|
94 !ShowMeHowItWorks methodsFor:'commands'! |
|
95 |
|
96 pause |
|
97 <action> |
|
98 |
|
99 Dialog information:'Show Paused.\Click on "OK" to proceed' |
|
100 |
|
101 "Created: / 19-07-2019 / 15:03:17 / Claus Gittinger" |
|
102 ! |
|
103 |
|
104 showing:message do:operations |
|
105 "execute operations while showing (and speaking) some message." |
|
106 |
|
107 <action> |
|
108 |
|
109 |messageView talkDone| |
|
110 |
|
111 self assert:operations isSequenceable. |
|
112 |
|
113 messageView := ActiveHelpView for:message. |
|
114 "/ messageView shapeStyle:#cartoon. |
|
115 [ |
|
116 messageView realize. |
|
117 |
|
118 self talking ifTrue:[ |
|
119 talkDone := Semaphore new. |
|
120 [ |
|
121 self tell:message. |
|
122 talkDone signal |
|
123 ] fork. |
|
124 |
|
125 "/ |
|
126 "/ allow speaker some headoff |
|
127 Delay waitForSeconds:(message size / 20). |
|
128 ]. |
|
129 |
|
130 self doStream:(operations readStream). |
|
131 ] ensure:[ |
|
132 messageView destroy |
|
133 ]. |
|
134 self talking ifTrue:[ |
|
135 talkDone wait. |
|
136 ]. |
|
137 |
|
138 "Created: / 19-07-2019 / 11:19:27 / Claus Gittinger" |
|
139 "Modified (format): / 19-07-2019 / 15:02:12 / Claus Gittinger" |
|
140 ! |
|
141 |
|
142 wait:seconds |
|
143 <action> |
|
144 |
|
145 Delay waitForSeconds:seconds |
|
146 |
|
147 "Created: / 19-07-2019 / 15:09:45 / Claus Gittinger" |
|
148 ! ! |
|
149 |
|
150 !ShowMeHowItWorks methodsFor:'commands - mouse'! |
|
151 |
|
152 click:buttonNr |
|
153 "press-release" |
|
154 |
|
155 <action> |
|
156 |
|
157 self click:buttonNr inComponent:lastComponent |
|
158 |
|
159 "Created: / 19-07-2019 / 13:21:20 / Claus Gittinger" |
|
160 "Modified: / 19-07-2019 / 14:55:18 / Claus Gittinger" |
|
161 ! |
|
162 |
|
163 enter:aString |
|
164 "enter text into the last component" |
|
165 |
|
166 <action> |
|
167 |
|
168 lastComponent simulateTextInput:aString at:(lastComponent extent // 2) sendDisplayEvent:false |
|
169 |
|
170 "Created: / 19-07-2019 / 14:29:27 / Claus Gittinger" |
|
171 ! |
|
172 |
|
173 moveTo:componentName |
|
174 "move the mouse to componentName, |
|
175 then circle around it a few times" |
|
176 |
|
177 <action> |
|
178 |
|
179 |component| |
|
180 |
|
181 lastComponentName := componentName. |
|
182 |
|
183 component := self findComponent:componentName. |
|
184 component isNil ifTrue:[ |
|
185 self error:'no component found for: ',componentName. |
|
186 ]. |
|
187 lastComponent := component. |
|
188 |
|
189 self movePointerToComponent:component. |
|
190 self circlePointerAroundComponent:component. |
|
191 |
|
192 "Created: / 19-07-2019 / 11:20:42 / Claus Gittinger" |
|
193 "Modified (format): / 19-07-2019 / 14:55:27 / Claus Gittinger" |
|
194 ! |
|
195 |
|
196 select:itemsLabel |
|
197 "select an item by label, |
|
198 allowed after moving to: |
|
199 aComboBox |
|
200 aSelectionInListView |
|
201 " |
|
202 |
|
203 <action> |
|
204 |
|
205 |idx| |
|
206 |
|
207 (lastComponent isKindOf:ComboView) ifTrue:[ |
|
208 "/ click on the menubutton |
|
209 self movePointerToComponent:lastComponent menuButton. |
|
210 self click:1 inComponent:lastComponent menuButton. |
|
211 Delay waitForSeconds:0.3. |
|
212 (idx := lastComponent list indexOf:itemsLabel ifAbsent:[nil]) isNil ifTrue:[ |
|
213 self error:'no such item in comboList: ',itemsLabel |
|
214 ]. |
|
215 lastComponent select:idx. |
|
216 Delay waitForSeconds:0.3. |
|
217 lastComponent shownMenu notNil ifTrue:[ |
|
218 lastComponent shownMenu hide. |
|
219 ]. |
|
220 ^ self |
|
221 ]. |
|
222 self error:'cannot select this component' |
|
223 |
|
224 "Created: / 19-07-2019 / 12:34:25 / Claus Gittinger" |
|
225 "Modified (format): / 19-07-2019 / 14:55:34 / Claus Gittinger" |
|
226 ! |
|
227 |
|
228 selectIndex:itemsIndex |
|
229 "select an item by index, |
|
230 allowed after moving to: |
|
231 aComboBox |
|
232 aSelectionInListView |
|
233 " |
|
234 |
|
235 <action> |
|
236 |
|
237 (lastComponent isKindOf:ComboView) ifTrue:[ |
|
238 "/ click on the menubutton |
|
239 self movePointerToComponent:lastComponent menuButton. |
|
240 self click:1 inComponent:lastComponent menuButton. |
|
241 Delay waitForSeconds:0.5. |
|
242 lastComponent select:itemsIndex. |
|
243 Delay waitForSeconds:0.5. |
|
244 self halt. |
|
245 ^ self |
|
246 ]. |
|
247 self error:'cannot select this component' |
|
248 |
|
249 "Created: / 19-07-2019 / 14:20:11 / Claus Gittinger" |
|
250 ! ! |
|
251 |
|
252 !ShowMeHowItWorks methodsFor:'defaults'! |
|
253 |
|
254 circlingCount |
|
255 "circle around move-end position that many times" |
|
256 |
|
257 ^ 3 |
|
258 |
|
259 "Created: / 19-07-2019 / 13:03:45 / Claus Gittinger" |
|
260 ! |
|
261 |
|
262 circlingRadius |
|
263 "radius when circling" |
|
264 |
|
265 ^ 30 "/ pixels |
|
266 |
|
267 "Created: / 19-07-2019 / 13:07:59 / Claus Gittinger" |
|
268 ! |
|
269 |
|
270 circlingSpeed |
|
271 "time per round when circling" |
|
272 |
|
273 ^ 0.3 seconds. "/ time per round |
|
274 |
|
275 "Created: / 19-07-2019 / 13:02:34 / Claus Gittinger" |
|
276 ! |
|
277 |
|
278 clickTime |
|
279 "when clicking" |
|
280 |
|
281 ^ 100 milliseconds |
|
282 |
|
283 "Created: / 19-07-2019 / 13:17:20 / Claus Gittinger" |
|
284 ! |
|
285 |
|
286 pointerAnimationDelay |
|
287 ^ 50 milliseconds. "/ 20 updates per second |
|
288 |
|
289 "Created: / 19-07-2019 / 13:04:45 / Claus Gittinger" |
|
290 ! |
|
291 |
|
292 pointerMoveSpeed |
|
293 ^ 400. "/ pixels per second |
|
294 |
|
295 "Created: / 19-07-2019 / 13:05:40 / Claus Gittinger" |
|
296 ! |
|
297 |
|
298 talking |
|
299 ^ true |
|
300 |
|
301 "Created: / 19-07-2019 / 14:31:14 / Claus Gittinger" |
|
302 ! ! |
|
303 |
|
304 !ShowMeHowItWorks methodsFor:'helper'! |
|
305 |
|
306 findComponent:componentName |
|
307 "find a component by name - in the active and possibly in any app" |
|
308 |
|
309 |app component candidates| |
|
310 |
|
311 app := WindowGroup activeMainApplication. |
|
312 app notNil ifTrue:[ |
|
313 component := self findComponent:componentName in:app. |
|
314 ]. |
|
315 |
|
316 component isNil ifTrue:[ |
|
317 "/ search through all current applications |
|
318 candidates := OrderedCollection new. |
|
319 WindowGroup scheduledWindowGroups do:[:eachWG | |
|
320 |eachApp| |
|
321 |
|
322 (eachApp := eachWG application) notNil ifTrue:[ |
|
323 component := self findComponent:componentName in:eachApp. |
|
324 component notNil ifTrue:[ |
|
325 candidates add:component |
|
326 ]. |
|
327 ]. |
|
328 ]. |
|
329 |
|
330 candidates size == 1 ifTrue:[ |
|
331 component := candidates first |
|
332 ] ifFalse:[ |
|
333 candidates notEmpty ifTrue:[ |
|
334 self error:'multiple components found by name: ',componentName. |
|
335 ] |
|
336 ]. |
|
337 ]. |
|
338 ^ component |
|
339 |
|
340 "Created: / 19-07-2019 / 12:02:30 / Claus Gittinger" |
|
341 ! |
|
342 |
|
343 findComponent:componentName in:anApplication |
|
344 |component componentNameSymbol foundByName foundByTitle foundByLabel| |
|
345 |
|
346 (component := anApplication componentAt:componentName) notNil ifTrue:[^ component]. |
|
347 (componentNameSymbol := componentName asSymbolIfInterned) notNil ifTrue:[ |
|
348 (component := anApplication componentAt:componentNameSymbol) notNil ifTrue:[^ component]. |
|
349 ]. |
|
350 |
|
351 "/ mhmh - search through all widgets of anApplication; |
|
352 "/ maybe it was not created via the builder/spec, |
|
353 "/ or it has changed its name. |
|
354 "/ look for: widget's name, widget's title, widget's label |
|
355 foundByName := OrderedCollection new. |
|
356 foundByTitle := OrderedCollection new. |
|
357 foundByLabel := OrderedCollection new. |
|
358 |
|
359 anApplication window allSubViewsDo:[:each | |
|
360 [ |
|
361 each name = componentName ifTrue:[ foundByName add:each ]. |
|
362 ] on:MessageNotUnderstood do:[:ex | ]. |
|
363 [ |
|
364 each title = componentName ifTrue:[ foundByTitle add:each ]. |
|
365 ] on:MessageNotUnderstood do:[:ex | ]. |
|
366 [ |
|
367 each label = componentName ifTrue:[ foundByLabel add:each ]. |
|
368 ] on:MessageNotUnderstood do:[:ex | ]. |
|
369 ]. |
|
370 foundByName notEmpty ifTrue:[ |
|
371 self assert:(foundByName size == 1) message:'multiple components found by name'. |
|
372 ^ foundByName first. |
|
373 ]. |
|
374 foundByTitle notEmpty ifTrue:[ |
|
375 self assert:(foundByTitle size == 1) message:'multiple components found by title'. |
|
376 ^ foundByTitle first. |
|
377 ]. |
|
378 foundByLabel notEmpty ifTrue:[ |
|
379 self assert:(foundByLabel size == 1) message:'multiple components found by label'. |
|
380 ^ foundByLabel first. |
|
381 ]. |
|
382 ^ component |
|
383 |
|
384 "Created: / 19-07-2019 / 11:36:21 / Claus Gittinger" |
|
385 ! |
|
386 |
|
387 tell:message |
|
388 self talking ifTrue:[ |
|
389 OperatingSystem speak:message. |
|
390 ]. |
|
391 |
|
392 "Created: / 19-07-2019 / 14:57:50 / Claus Gittinger" |
|
393 ! ! |
|
394 |
|
395 !ShowMeHowItWorks methodsFor:'helpers - broken'! |
|
396 |
|
397 click:buttonNr atPosition:position |
|
398 "press-release at position" |
|
399 |
|
400 |screen| |
|
401 |
|
402 screen := Screen current. |
|
403 |
|
404 screen setPointerPosition:position. |
|
405 screen flush. |
|
406 self click:buttonNr |
|
407 |
|
408 "Created: / 19-07-2019 / 13:14:51 / Claus Gittinger" |
|
409 ! ! |
|
410 |
|
411 !ShowMeHowItWorks methodsFor:'helpers - mouse movement'! |
|
412 |
|
413 circlePointerAroundComponent:component |
|
414 "circle around it a few times" |
|
415 |
|
416 self circlePointerAroundPosition:(component screenBounds center rounded) |
|
417 |
|
418 "Created: / 19-07-2019 / 13:12:35 / Claus Gittinger" |
|
419 ! |
|
420 |
|
421 circlePointerAroundPosition:position |
|
422 "circle around it a few times" |
|
423 |
|
424 |screen stepDelayTime numCircles circlingSpeed radius| |
|
425 |
|
426 screen := Screen current. |
|
427 |
|
428 circlingSpeed := self circlingSpeed. "/ time per round |
|
429 numCircles := self circlingCount. |
|
430 stepDelayTime := self pointerAnimationDelay. "/ update interval |
|
431 |
|
432 radius := self circlingRadius. |
|
433 |
|
434 "/ move it around a few times |
|
435 1 to:numCircles do:[:round | |
|
436 |n angle| |
|
437 |
|
438 n := circlingSpeed / stepDelayTime. "/ nr of steps per circle |
|
439 angle := 360 / n. "/ angle-delta per step |
|
440 1 to:n do:[:step | |
|
441 |a x y| |
|
442 |
|
443 a := angle * step. |
|
444 "/ clockwise starting above the center |
|
445 x := position x + (radius * a degreesToRadians sin). |
|
446 y := position y + (radius * a degreesToRadians cos). |
|
447 "/ Transcript showCR:(x@y). |
|
448 screen setPointerPosition:(x@y) rounded. |
|
449 screen flush. |
|
450 Delay waitFor:stepDelayTime. |
|
451 ]. |
|
452 "/ and back |
|
453 screen setPointerPosition:position rounded. |
|
454 screen flush. |
|
455 Delay waitFor:stepDelayTime. |
|
456 ]. |
|
457 |
|
458 "Created: / 19-07-2019 / 13:12:40 / Claus Gittinger" |
|
459 ! |
|
460 |
|
461 click:buttonNr inComponent:component |
|
462 "press-release in a component" |
|
463 |
|
464 component simulateButtonPress:buttonNr at:(component extent // 2) sendDisplayEvent:false. |
|
465 Delay waitForSeconds:(self clickTime). |
|
466 component simulateButtonRelease:buttonNr at:(component extent // 2) sendDisplayEvent:false. |
|
467 |
|
468 "/ self click:buttonNr atPosition:(component extent // 2) |
|
469 |
|
470 "Created: / 19-07-2019 / 13:18:27 / Claus Gittinger" |
|
471 ! |
|
472 |
|
473 movePointerToComponent:aWidget |
|
474 "move the mouse to aWidget's center" |
|
475 |
|
476 self movePointerToPosition:(aWidget screenBounds center rounded). |
|
477 |
|
478 "Created: / 19-07-2019 / 13:11:33 / Claus Gittinger" |
|
479 ! |
|
480 |
|
481 movePointerToPosition:newPosition |
|
482 "move the mouse to newPosition" |
|
483 |
|
484 |screen distance start numSteps moveTime stepDelayTime delta| |
|
485 |
|
486 screen := Screen current. |
|
487 start := screen pointerPosition. |
|
488 |
|
489 distance := start dist:newPosition. |
|
490 moveTime := (distance / self pointerMoveSpeed) seconds. "/ time to move |
|
491 stepDelayTime := self pointerAnimationDelay. "/ update every 50ms |
|
492 |
|
493 numSteps := moveTime / stepDelayTime. |
|
494 numSteps = 0 ifTrue:[ |
|
495 "/ already there |
|
496 ^ self |
|
497 ]. |
|
498 |
|
499 delta := (newPosition - start) / numSteps. |
|
500 1 to:numSteps do:[:step | |
|
501 |p| |
|
502 |
|
503 p := (start + (delta * step)) rounded. |
|
504 "/ Transcript showCR:p. |
|
505 screen setPointerPosition:p. |
|
506 screen flush. |
|
507 Delay waitFor:stepDelayTime. |
|
508 ]. |
|
509 |
|
510 "Created: / 19-07-2019 / 12:57:30 / Claus Gittinger" |
|
511 ! |
|
512 |
|
513 press:buttonNr |
|
514 "press at the current position" |
|
515 |
|
516 |position screen x y| |
|
517 |
|
518 screen := Screen current. |
|
519 position := screen pointerPosition. |
|
520 x := position x. |
|
521 y := position y. |
|
522 |
|
523 self movePointerToPosition:position. |
|
524 |
|
525 false "OperatingSystem isOSXlike" ifTrue:[ |
|
526 |osxPos| |
|
527 |
|
528 osxPos := OperatingSystem getMousePosition. |
|
529 x := osxPos x rounded. |
|
530 y := osxPos y rounded. |
|
531 OperatingSystem generateButtonEvent:buttonNr down:true x:x y:y. |
|
532 ^ self. |
|
533 ]. |
|
534 |
|
535 screen sendKeyOrButtonEvent:#buttonPress x:x y:y keyOrButton:buttonNr state:0 toViewId:(screen rootWindowId). |
|
536 screen flush. |
|
537 |
|
538 "Created: / 19-07-2019 / 13:52:38 / Claus Gittinger" |
|
539 ! |
|
540 |
|
541 release:buttonNr |
|
542 "press-release at the current position" |
|
543 |
|
544 |position screen x y| |
|
545 |
|
546 screen := Screen current. |
|
547 position := screen pointerPosition. |
|
548 x := position x. |
|
549 y := position y. |
|
550 |
|
551 self movePointerToPosition:position. |
|
552 |
|
553 false "OperatingSystem isOSXlike" ifTrue:[ |
|
554 |osxPos| |
|
555 |
|
556 osxPos := OperatingSystem getMousePosition. |
|
557 x := osxPos x rounded. |
|
558 y := osxPos y rounded. |
|
559 OperatingSystem generateButtonEvent:buttonNr down:false x:x y:y. |
|
560 ^ self. |
|
561 ]. |
|
562 |
|
563 screen sendKeyOrButtonEvent:#buttonRelease x:x y:y keyOrButton:buttonNr state:0 toViewId:(screen rootWindowId). |
|
564 screen flush. |
|
565 |
|
566 "Created: / 19-07-2019 / 13:53:05 / Claus Gittinger" |
|
567 ! ! |
|
568 |
|
569 !ShowMeHowItWorks methodsFor:'running'! |
|
570 |
|
571 doStream:specStream |
|
572 |previousStream| |
|
573 |
|
574 previousStream := opStream. |
|
575 [ |
|
576 opStream := specStream. |
|
577 [opStream atEnd] whileFalse:[ |
|
578 self nextCommand. |
|
579 Display shiftDown ifTrue:[ |
|
580 self tell:'Shou stopped by shiftkee'. |
|
581 ^ AbortOperationRequest raise |
|
582 ]. |
|
583 ]. |
|
584 ] ensure:[ |
|
585 opStream := previousStream |
|
586 ]. |
|
587 |
|
588 "<<END |
|
589 ShowMeHowItWorks do:#( |
|
590 showing: 'Choose the number of arguments' |
|
591 do: ( |
|
592 moveTo: NumberOfArguments |
|
593 select: '1' |
|
594 ) |
|
595 showing: 'Click into the "receiver" field' |
|
596 do: ( |
|
597 moveTo: ReceiverEditor |
|
598 click: ReceiverEditor |
|
599 ) |
|
600 showing: 'Enter a value (or expression) into "receiver" field' |
|
601 do: ( |
|
602 enter: '100' |
|
603 ) |
|
604 showing: 'Click into the "first argument" field' |
|
605 do: ( |
|
606 moveTo: Arg1Editor |
|
607 click: ReceiverEditor |
|
608 ) |
|
609 showing: 'Enter a value (or expression) into "receiver" field' |
|
610 do: ( |
|
611 enter: '100' |
|
612 ) |
|
613 ) |
|
614 END" |
|
615 |
|
616 "Created: / 19-07-2019 / 10:52:24 / Claus Gittinger" |
|
617 "Modified: / 19-07-2019 / 15:00:44 / Claus Gittinger" |
|
618 ! |
|
619 |
|
620 nextCommand |
|
621 |op numArgs sel args method| |
|
622 |
|
623 op := opStream next. |
|
624 op isArray ifTrue:[ |
|
625 "/ construct a selector from keyword parts at odd indices |
|
626 sel := ((op with:(1 to:op size) select:[:el :idx | idx odd]) asStringWith:'') asSymbol. |
|
627 "/ construct arg vector from parts at even indices |
|
628 args := op with:(1 to:op size) select:[:el :idx | idx even]. |
|
629 ] ifFalse:[ |
|
630 sel := op. |
|
631 numArgs := sel argumentCount. |
|
632 args := opStream next:numArgs. |
|
633 ]. |
|
634 |
|
635 (self respondsTo:sel) ifFalse:[ |
|
636 self error:'bad operation: ',sel |
|
637 ]. |
|
638 method := self class lookupMethodFor:sel. |
|
639 (method hasAnnotation:#action) ifFalse:[self halt]. |
|
640 |
|
641 self perform:sel withArguments:args. |
|
642 |
|
643 "<<END |
|
644 ShowMeHowItWorks do:#( |
|
645 showing: 'Choose the number of arguments' |
|
646 do: ( |
|
647 moveTo: NumberOfArguments |
|
648 select: '1' |
|
649 ) |
|
650 showing: 'Click into the "receiver" field' |
|
651 do: ( |
|
652 moveTo: ReceiverEditor |
|
653 click: ReceiverEditor |
|
654 ) |
|
655 showing: 'Enter a value (or expression) into "receiver" field' |
|
656 do: ( |
|
657 enter: '100' |
|
658 ) |
|
659 showing: 'Click into the "first argument" field' |
|
660 do: ( |
|
661 moveTo: Arg1Editor |
|
662 click: ReceiverEditor |
|
663 ) |
|
664 showing: 'Enter a value (or expression) into "receiver" field' |
|
665 do: ( |
|
666 enter: '100' |
|
667 ) |
|
668 ) |
|
669 END" |
|
670 |
|
671 "Created: / 19-07-2019 / 10:54:04 / Claus Gittinger" |
|
672 "Modified: / 19-07-2019 / 14:53:15 / Claus Gittinger" |
|
673 ! ! |
|
674 |
|
675 !ShowMeHowItWorks class methodsFor:'documentation'! |
|
676 |
|
677 version_CVS |
|
678 ^ '$Header$' |
|
679 ! ! |
|
680 |