Chapa 1)


Eev and Squeak

I'm trying to learn Squeak (Wikipedia). I am mostly following the book "Squeak by Example" and the Swiki, but I am also trying to adapt some of the main ideas of eev to Squeak. My super-messy notes on Squeak are here.


1. Elisp hyperlinks, but in Squeak

Eev is a tool for taking "executable notes", and usually this consists on two parts: 1) recording all commands that we send to "shell-like programs" (this is explained here), and 2) saving hyperlinks to everything interesting that we find. In Squeak the idea (1) only applies to things like (re)installing Squeak itself, and the idea (2) needs to be adapted: eev implements it with many kinds elisp hyperlinks, that are one-liners that start with "find-", and I'm translating some elisp hyperlink to "Squeak hyperlinks", that are one-liners that start with "See", like these ones:

See helpBrowser.
See messageNames.
See methodFinder.
See class: See classMethod: #methodFinder.                "(uses ECToolSet)"
See class: ECToolSet.
See class: ECToolSet classMethod: #openSelectorBrowser.       "(this fails)"
ECToolSet superclass.
See class: StandardToolSet.
See class: StandardToolSet classMethod: #openSelectorBrowser. "(this works)"
See class: ToolBuilder.

See class: SelectorBrowser.
See class: SelectorBrowser method: #byExample.
See class: SelectorBrowser method: #byExample:.
See class: SelectorBrowser classMethod: #prototypicalToolWindow.

See cm: (SelectorBrowser >> #byExample).
See cm: (SelectorBrowser >> #byExample:).
See cm: (SelectorBrowser class >> #prototypicalToolWindow).

2. Three kinds of elisp hyperlinks

In eev I use three kinds of elisp hyperlinks. In

(find-sh  "date")
(find-sh0 "date")
(find-xpdf-page "~/Coetzee99.pdf")

the one with `find-sh' opens its target in another buffer in the same Emacs window, the one with `find-sh0' displays its result in the echo area, and the one with `find-xpdf-page' calls xpdf and displays a PDF in another window, outside Emacs.

In 2023feb09 I sent this question to the mailing list. It contained this block of "executable notes", intended to be used from inside a workspace:

"The first lines were adapted from:
  https://wiki.squeak.org/squeak/2188 PolygonMorph"

a := Array with: 50@100 with: 200@300 with: 100@400.
b := (PolygonMorph
        vertices: a color: Color red
        borderWidth: 1 borderColor: Color black).
World addMorph: b.
b beSmoothCurve.
b beStraightSegments.

b vertices.
b vertices at: 1.
b vertices at: 1 put: 40@100.
b vertices at: 1 put: 50@100.
b vertices at: 1 put: 40@100. b computeBounds.
b vertices at: 1 put: 50@100. b computeBounds.

a at: 1 put: 40@100. b computeBounds.
a at: 1 put: 50@100. b computeBounds.

b color.
b color: Color banana.
b color: Color fern.
b color: Color red.

e := EventHandler new.
b eventHandler.
b eventHandler: nil.
b eventHandler: e.

Some of its lines will only do something interesting it we run a "print it" on them, some lines are to be run with "do it"s, and some of the lines create external windows or morphs when executed... so: three kinds. Note that the block above doesn't use the class See!

3. The class "See"

You can install my class "See", together with two helper classes called "HelpBrowserB" and "EdrxGuideHelp", by downloading the .st file below from the link below - the .st.html is an htmlized version produced with engrave-faces,


and then using the Tools → File List and the "fileIn" button. Then try to run "See see". I need to rewrite the rest of this section!

You can download my class "See" as a .st file here, and you can see a version of it htmlized with engrave-faces here. I am a very beginner-ish beginner, so my code is 1) very primitive at several points, and 2) very ugly everywhere.

Some of the methods of my class "See" are "detached menu entries". For example, here is the method that "See methodFinder" runs:

!See class methodsFor: 'menu items - detached' stamp: 'Edrx 1/29/2023 00:10'!
  "Same as World -> open -> method finder.
     See methodFinder.
  ^ ECToolSet openSelectorBrowser.! !

The "ECToolSet openSelectorBrowser" in the last line does the same as this sequence of mouse clicks:

World menu → open → method finder

I discovered that the "action" of "World menu → open → method finder" is "ECToolSet openSelectorBrowser" by doing this:

  • Go to "World menu → open → method finder",
  • click on the halo button twice to select the morph that corresponds to the entry "method finder",
  • do "halo → duplicate" to create a menu with just one entry,
  • on that mini-menu do "halo → debug",
  • on the lower left pane of the debug window, run, print, or explore each one of the one-liners below:
self submorphs.
self submorphs at: 1.
(self submorphs at: 1) explore.
(self submorphs at: 1) contents.
(self submorphs at: 1) target.
(self submorphs at: 1) selector.
(self submorphs at: 1) arguments.
See item1ctsa: self.

The expression "See item1tsa: self" returns the fields "contents", "target", "selector", and "arguments" of the result of "self submorphs at: 1". Sometimes it returns a 4-uple that is easy to convert to an executable expression, like in this screenshot, that corresponds to the example above:

4. Squeaky targets

...but sometimes "See item1ctsa: self" returns something that I don't know - yet - how to "translate to code". Here's an example. One of the entries of the menu "Help" has the label "Squeak Help". The result of "See item1ctsa: self" on that entry is this:

{'Squeak Help' . a TheWorldMainDockingBar . #squeakHelp . nil}

Here the "target" field is an object of the class "TheWorldMainDockingBar". The "The" in that name suggests that usually there is at most one "main docking bar" in the "world", but I don't know how to write an expression that can be run from anywhere and that will return "the" main docking bar when one exists...

Let me call these "targets" that are hard to rebuild running global code "Squeaky targets". I come from Emacs-land, in which practically all actions are easy to "translate to code", so handling "Squeaky targets" is still a bit hard for me.

In 2023feb01 I sent this question to the mailing list, and Marcel Taeumel answered this. I was able to apply his idea to some menu items, but not to all (yet); the code in the next section was inspired by some of his suggestions.

5. Storing squeaky targets

A reasonable workaround is to store "squeaky targets" in a globally accessible place. Remember that the use of "real" global variables in SmallTalk is frowned upon, and remember if you have two workspaces and you run "foo := 42" in the first of them, then you won't be able to access that variable in the second workspace... also, inspectors and debuggers have mini-workspaces.

Here's what I did. I defined a class variable called "Store" in my class "See", and I defined these four accessor methods for it:

See class >> store
  ^ Store.

See class >> store: s
  Store := s.

See class >> at: key
  ^ Store at: key.

See class >> at: key put: value
  ^ Store at: key put: value.

Then I could initialize the global storage with this,

See store: Dictionary new.

and I could use these methods to transport squeaky targets from a debugger to my main workspace. For example, I could open a debugger on the search bar and run "See at: #sb put: self" there, and then on my main workspace I would do "sb := See at: #sb" to copy that search bar object to a variable with a very short name - "sb" - that is local to that workspace.

6. Methods for morphic

I also added to my class "See" five class methods to help me capture keyboard events and send these events to morphs. Here are their definitions, without comments:

See class >> keyboardFocus
  ^ self currentHand keyboardFocus.

See class >> keyboardFocus: aMorphOrNil
  self currentHand newKeyboardFocus: aMorphOrNil.

See class >> run: aBlock after: ms
  | aButton |
  aButton := SimpleButtonMorph new.
  aButton openInWorld;
          target: aBlock;
          actionSelector: #value;
          addAlarm: #doButtonAction after: ms.

See class >> sendEvent: evt to: morph
  evt resetHandlerFields.
  morph handleEvent: evt.

See class >> sendEventDebug: evt to: morph
  evt resetHandlerFields.
  self halt.
  morph handleEvent: evt.

Due to my beginerishness I had to spend many days, and several e-mails to the mailing list, to write the methods above. The next two sections have examples of how I use them.

7. Using "addAlarm:after:"

Try this in a workspace:

"Create a SimpleSwitchMorph with label 'Toggle'
    and a SimpleButtonMorph with label 'Flash'.
    The button will be placed below the switch."

sm := SimpleSwitchMorph new.
sm openInWorld.
bm := SimpleButtonMorph new.
bm openInWorld.
bm position: bm position + (0@32).

"Three ways of toggling the color of the switch:"

sm toggleState.

bl := [ sm toggleState ].
bl value.

bm target: bl.
bm actionSelector: #value.
bm doButtonAction.

"Two ways of toggling the switch after 1000ms:"

sm addAlarm: #toggleState    after: 1000.
bm addAlarm: #doButtonAction after: 1000.

For more context, see this thread in the mailing list: 1, 2, 3, 4, 5, 6. The method "run:after:" of my class "See" uses the ideas of the posts 4 and 5. Note that some people have suggested other ways to run code after a "sleep 5", but I'm using the first solution that worked.

8. Capturing and re-sending keyboard events

This is my current favorite way (in 2023feb24) of capturing and re-sending keyboard events:

save := [ :evt | ].
bl   := [ :evt | Transcript show: evt; cr. save value: evt. ].
eh   := EventHandler new.
eh on: #keyStroke send: #value: to: bl.

rm   := RectangleMorph new.
rm openInWorld.
rm color: Color red.
rm eventHandler: eh.

save := [ :evt | cr := evt ]. See keyboardFocus: rm.
save := [ :evt | up := evt ]. See keyboardFocus: rm.
save := [ :evt | dn := evt ]. See keyboardFocus: rm.

{ cr . up . dn }

See run: [ sb := See keyboardFocus ] after: 5000.
See sendEvent:      cr to: sb.
See sendEventDebug: cr to: sb.

It is super-tricky. The variable "save" works as an Lvalue, and after, say, "save := [ :evt | foo := evt ]", running "save value: 42" stores 42 in the variable "foo", that is local to my main workspace. Each line of this block needs to be run with a separate "do it",

save := [ :evt | cr := evt ]. See keyboardFocus: rm.
save := [ :evt | up := evt ]. See keyboardFocus: rm.
save := [ :evt | dn := evt ]. See keyboardFocus: rm.

and after running each one the keyboard focus will be transfered to the red rectangle at the top left of the screen. After the "do it" on the first one you have to type a CR on the rectangle, then go back to the workspace; then run a "do it" on the second one, and type an <up>, and go back to the workspace; then run the third with a "do it", type a <down>, and go back to the workspace. Then run a "print it" on this,

{ cr . up . dn }

to check that the variables "cr", "up", and "down" now contain the correct keyboard events. After checking that, run this,

See run: [ sb := See keyboardFocus ] after: 5000.

and click on the search bar - before 5000ms - to transfer the keyboard focus to it; the block "[ sb := See keyboardFocus ]" will be run after these 5000ms, and it will save a submorph of the search bar into the variable "sb". After that run a "print it" on the line with "sb" to check that the variable sb contains an object of the class TextMorphForEditView, and then you can try "do it"s on these lines

See sendEvent:      cr to: sb.
See sendEventDebug: cr to: sb.

to send a CR to the search bar; the first one simply sends the CR, and the second one runs a "self halt" before sending the CR, and this lets us see in the debugger what is the code that a CR in the search bar runs.

9. See edrxGuide

In the two one-liners below,

See terseGuide.
See edrxGuide.

the first one opens the "Terse Guide to Squeak", and the second one opens the "Edrx Guide to Squeak". The edrxGuide is implemented in two classes, and you can examine them with:

See class: EdrxGuideHelp.
See class: HelpBrowserB.
See cm: (See class >> #terseGuide).
See cm: (See class >> #terseGuide:).

You can download my three classes - See, EdrxGuideHelp, and HelpBrowserB - here, Category-Edrx.st, and you see an htmlized version of that .st file here.

11. Etc

In 2023feb06 Stéphane Rollandin announced a new version of his Roguerrants in the squeak-dev mailing list. His code contains tons of things that I want to learn, so I've added it to my to-do list.

Anyway, here are some links to my super-messy notes on Squeak:

(find-es "squeak")
(find-es "squeak" "squeak-by-example")
(find-es "squeak" "See")
(find-es "squeak" "install-2023")
(find-books "__comp/__comp.el" "squeak-by-example")