-
Notifications
You must be signed in to change notification settings - Fork 19
Poking in Shoes.app
Open up the Shoes manual and read the Hello!->Rules. Confused? Excited? Curious? I'm going to try to explain in a little more detail some very curious things about Shoes. Everybody stumbles over those rules but I'm guessing programmers from other languages stumble over this more than newbies.
It's common in Ruby DSL's (Domain Specific Languages) to do some trickery with self. I doubt Shoes was the first. Much of Shoes is dependent on these tricks. It's what makes Shoes great and it also what limits how far you can push the limits. You're all are Ruby programmers so you probably don't believe that so I'll rephrase it: It defines how hard it is to push the limits of Shoes and how much work you'll have to do.
First, It's a GUI Window (mode-less in native toolkit terms). Dialogs Windows like alert() and ask__open_file() are not really Shoes.app's - they are more like oddball controls or widgets. You really can't poke around inside Dialogs but you can poke around in other Apps, if you're a hearty soul.
My preferred way to create a new Shoes.app from a Shoes script is to use the window method and I always use do end keywords because { } can behave differently. If layout is important then I don't trust the part of the manual that says new windows get a flow layout. I don't believe that is always true. Little test scripts can use defaults. Real programs explicitly declare what they want.
Shoes.app title: "Base Window" do
@my_windows = []
button "new window" do
win = window title: "New Windows" do
@win_stack = stack do
para "This is the new window"
end
end
@my_windows << win
end
button "show windows" do
@my_windows.each {|win| @stk.append {para win.inspect} }
end
@stk = stack do
para app.inspect
end
end
This example shows many things: In the show_windows button block we're appending to @stk but it hasn't been defined yet when we read the code from top to bottom. Not true. It's all been read by Shoes before anything appears on screen but not all of it is executed until the buttons are pressed.
You'll also notice that Shoes windows are described as class Shoes::Types::App . Accept it. It is what it is. That is the class of a Shoes Window (or App)
The more observant might also wonder about that para app.inspect
in the @stk block. Who or what is app? It's not a variable you created but that's what it looks like. Then you might notice it's the current Shoes::Types::App (aka Shoes.app or Window) Is that self
for this Window (App)? No. It's more like mini-me
- it's only part of the Window(App), a funky subclass sort of. Look in the manual Shoes->Special and you'll see entries for some methods that are indeed very special
app is always there in a Window(App). That means You probably don't want to create a variable named app in your script and you surely don't want to define a method name app. Kind of like a reserved word only you're not going to get a warning message. _Note to meta-folks, you can override some C methods with Ruby. Dragons be there. _ If it was up to me, I'd name it 'funkydragon' instead of 'app' but that ship set sail long ago and we have 'app'.
This is a Ruby constant in module Shoes:: It returns an array containing all the known mini-me's, at the time it's referenced. Remember, mini-me's are only related to Windows(Shoes.Apps) in mysterious ways.
We know that self.app is a 'mini-me' of the caller and Shoes.APPS is an array of mini-me's. Lets explore some more. Add this button to the example
button "close new" do
Shoes.APPS.each {|appw| appw.close if appw != app}
end
Notice that I named my block argument appw
because ... Pop Quiz! Bueller?
We almost have enough info to get into real trouble (or fun) doing some meta-programming.
Let's set a higher hurdle - From the "Base Window" app , we want to create a "close" button for each window we know about and when clicked it will only close that window.
Shoes.app title: "Base Window" do
@my_windows = []
button "new window" do
win = window title: "New Windows" do
para "This is the new window"
end
@my_windows << win
end
button "show windows" do
@my_windows.each {|win| @stk.append {para win.inspect} }
end
button "Add close button" do
Shoes.APPS.each do |appw|
appw.slot.contents[0].parent.instance_exec () { button "Close this window" do close end }
end
end
@stk = stack do
para app.inspect
end
end
The important line is appw.slot.contents[0].parent.instance_exec () { button "Close this window" do close end }
We see that slot.contents returns an array since the next operator is [0]. From there we ask for parent which gets us to the root of the window's stack & flows. Then instance_exec is called on that with no arguments and one block. That block creates a new button in the other window because instance_exec changes self. The close command in the button could also be written as self.close
(try it).
Perhaps some of you want to know what's in appw.slots.contents[1] ? I don't know for sure. I think its always class Shoes::Types::para with a value of nil. The following script explores that at bit and also demonstrates some interesting variable bindings/lookups that meta-programmers need to think about.
Shoes.app title: "Base Window" do
cntr = 1
@stk = stack do
flow do
button "new window" do
window title: "New Window" do
@ivar = cntr
cntr = cntr + 1
flow do
para "This is the new window #{@ivar}"
button "bump @ivar" do
@ivar += 10
end
end
para @ivar.inspect
end
end
button "check @ivar" do
Shoes.APPS.each do |appw|
@stk.append {para appw.instance_eval("@ivar").inspect}
appw.slot.contents.each do |cts|
@stk.append {para cts.inspect}
end
end
end
end
end
end
Create a few "new" windows and then check ivars. Bump a window's setting. check ivars again. Clearly @ivar is a unique variable for each App (Window), as documented. The binding of cntr
will surely bite the unaware (and we are many).
There is another word of warning to add. We know slot.content[0] has the layout for the window. It can return a flow or stack OR it can return an array of widgets which has the 'default' of flow even though there is no flow given. So when the manual says the default is flow it's not quite accurate. There may not be a flow at the top.
Happy Trails to you.