Idea: Simplified GTK

H

Hal Fulton

Here's an idea. I've begun implementing it.

Tell me why it's dumb, or what its shortcomings are.

Put all your logic inside an App.logic (which will get called from
App.run and wrapped with the stuff it needs). Afterward, call App.run
itself.

You automatically get one toplevel window, called @main in App.
Everything in App.logic defaults to this as a container.

When you create a container, you put a block on the constructor. Any
widget (container or other) created in that block will belong to the
current container. So the nesting of the block structure reflects the
nesting of the containers.

When you create a non-container, you put a block on the constructor.
It will be associated with the "default" (most common) message for
that widget.

Here's some code. It works, for what it's worth.

require 'ez-gtk'
include EZ_GTK

def App.logic
@main.title = "My window"
@hb = HBox.new do
@vb1 = VBox.new do
@btn1 = Button.new("Do it") { puts "pressed btn1" }
@btn2 = Button.new("This too") { puts "pressed btn2" }
end
@vb2 = VBox.new do
@btn3 = Button.new("Third button") { puts "pressed btn3" }
end
end
end

App.run

Please comment on this before asking to see ez-gtk itself. ;)


Thanks,
Hal
 
C

Chad Fowler

Here's an idea. I've begun implementing it.

Tell me why it's dumb, or what its shortcomings are.

Put all your logic inside an App.logic (which will get called from
App.run and wrapped with the stuff it needs). Afterward, call App.run
itself.

You automatically get one toplevel window, called @main in App.
Everything in App.logic defaults to this as a container.

When you create a container, you put a block on the constructor. Any
widget (container or other) created in that block will belong to the
current container. So the nesting of the block structure reflects the
nesting of the containers.

When you create a non-container, you put a block on the constructor.
It will be associated with the "default" (most common) message for
that widget.

Here's some code. It works, for what it's worth.

require 'ez-gtk'
include EZ_GTK

def App.logic
@main.title = "My window"
@hb = HBox.new do
@vb1 = VBox.new do
@btn1 = Button.new("Do it") { puts "pressed btn1" }
@btn2 = Button.new("This too") { puts "pressed btn2" }
end
@vb2 = VBox.new do
@btn3 = Button.new("Third button") { puts "pressed btn3" }
end
end
end

App.run


Do you need the instance variables? It seems like, in many cases, you
wouldn't. It's definitely nicer to look at like this:


def App.logic
@main.title = "My window"
HBox.new do
VBox.new do
Button.new("Do it") { puts "pressed btn1" }
Button.new("This too") { puts "pressed btn2" }
end
VBox.new do
Button.new("Third button") { puts "pressed btn3" }
end
end
end

I know you lose some flexibility (or you make it one level harder), but
I can't help wanting it to look like this:

def app_logic
main {
hbox {
vbox {
button("Do it") { puts "pressed btn1" }
button("This too") { puts "pressed btn2" }
}
vbox {
button("Third button") { puts "pressed btn3" }
}
}
}
end

Even if something like this (your idea) has some shortcomings, I still
think it's worth doing. It will be very useful for the situations when
its shortcomings aren't an issue (for example, quick and dirty little
GUI apps).

Now that I've commented, can I see the code? ;)

Chad
 
C

Charles Comstock

Chad Fowler wrote:

[...]
I know you lose some flexibility (or you make it one level harder), but
I can't help wanting it to look like this:

def app_logic
main {
hbox {
vbox {
button("Do it") { puts "pressed btn1" }
button("This too") { puts "pressed btn2" }
}
vbox {
button("Third button") { puts "pressed btn3" }
}
}
}
end

Just cause you yield the instance to the block, doesn't mean you can't
return the instance as well, so you don't HAVE to lose the object.

Charles Comstock
 
G

Gregory Millam

Of the two:
require 'ez-gtk'
include EZ_GTK

def App.logic
@main.title = "My window"
@hb = HBox.new do
@vb1 = VBox.new do
@btn1 = Button.new("Do it") { puts "pressed btn1" }
@btn2 = Button.new("This too") { puts "pressed btn2" }
end
@vb2 = VBox.new do
@btn3 = Button.new("Third button") { puts "pressed btn3" }
end
end
end

App.run

Or -
def app_logic
main {
hbox {
vbox {
button("Do it") { puts "pressed btn1" }
button("This too") { puts "pressed btn2" }
}
vbox {
button("Third button") { puts "pressed btn3" }
}
}
}
end

The second one looks much more to-the-point. And in fact, wouldn't be that hard to do. The first (original poster) attempts to go halfway between Gtk and the rapidity of the second.

The following code implements the second bit. Just hbox, vbox, and buttons, though ;). I do like this idea, for fairly rapid prototyping.

# Begin code
require 'gtk2'

class EzGtkWindow
def main
@window = Gtk::Window.new
@addfunc = lambda { |item| @window.add(item) }
yield @window
@window.show_all
end
def hbox
hbox = Gtk::HBox.new(false)
@addfunc.call( hbox )
storefunc = @addfunc
@addfunc = lambda { |item| hbox.pack_start( item ) }
yield hbox
@addfunc = storefunc
end
def vbox
vbox = Gtk::VBox.new(false)
@addfunc.call( vbox )
storefunc = @addfunc
@addfunc = lambda { |item| vbox.pack_start( item ) }
yield vbox
@addfunc = storefunc
end
def button(string,&block)
button = Gtk::Button.new(string)
button.signal_connect("clicked",block)
@addfunc.call( button )
end
end

class MyWindow < EzGtkWindow
def initialize
main {
hbox {
vbox {
button("Do it") { puts "pressed btn1" }
button("This too") { puts "pressed btn2" }
}
vbox {
button("Quit") { puts Gtk.main_quit }
}
}
}
end
end

Gtk.init
c = MyWindow.new
Gtk.main

# End code

- Greg Millam
 
P

Phil Tomson

Here's an idea. I've begun implementing it.

Tell me why it's dumb, or what its shortcomings are.

Put all your logic inside an App.logic (which will get called from
App.run and wrapped with the stuff it needs). Afterward, call App.run
itself.

You automatically get one toplevel window, called @main in App.
Everything in App.logic defaults to this as a container.

When you create a container, you put a block on the constructor. Any
widget (container or other) created in that block will belong to the
current container. So the nesting of the block structure reflects the
nesting of the containers.

When you create a non-container, you put a block on the constructor.
It will be associated with the "default" (most common) message for
that widget.

Here's some code. It works, for what it's worth.

require 'ez-gtk'
include EZ_GTK

def App.logic
@main.title = "My window"
@hb = HBox.new do
@vb1 = VBox.new do
@btn1 = Button.new("Do it") { puts "pressed btn1" }
@btn2 = Button.new("This too") { puts "pressed btn2" }
end
@vb2 = VBox.new do
@btn3 = Button.new("Third button") { puts "pressed btn3" }
end
end
end

App.run

Please comment on this before asking to see ez-gtk itself. ;)


This is exactly how the FLTK ruby bindings I'm using work (unfortunately
they're not publicly available yet) except that instead of defining a
class method we actually define a class and this is done in the
constructor for that class. It is a nice way of doing things.

However, your naming convention seems confusing to me. You're calling
this method App.logic, when in reality this is App.GUI since this is where
you're actually defining your GUI elements. App.logic, (again it seems to
me) should be something like a state machine that responds to events that
come from the GUI.

I guess I'm sensitive to this since the app we're currently
developing needs to work both as a
GUI and as a console app (depending on the environment it's running in) so
I'm trying to seperate the GUI elements from
the underlying logic as much as possible. Basically I have a state
machine that encapsulates all of the logic and then I can plug in
different front ends (or at least that's the goal, it's not all there
yet). I use Observable as a means of passing events back and forth
between the state machine and GUI.

Phil
 
H

Hal Fulton

Chad said:
I know you lose some flexibility (or you make it one level harder), but
I can't help wanting it to look like this:

def app_logic
main {
hbox {
vbox {
button("Do it") { puts "pressed btn1" }
button("This too") { puts "pressed btn2" }
}
vbox {
button("Third button") { puts "pressed btn3" }
}
}
}
end

Nice and clean. Sometimes you'd need to save references to the
objects, though.
Even if something like this (your idea) has some shortcomings, I still
think it's worth doing. It will be very useful for the situations when
its shortcomings aren't an issue (for example, quick and dirty little
GUI apps).

Now that I've commented, can I see the code? ;)

Sure, I'll email it. ;)

Hal
 
H

Hal Fulton

Gregory said:
The second one looks much more to-the-point. And in fact, wouldn't be that hard
to do. The first (original poster) attempts to go halfway between Gtk and the
rapidity of the second.

Correct assessment. But there are times when you'd want to capture
references to the widgets.
The following code implements the second bit. Just hbox, vbox, and buttons,
though ;). I do like this idea, for fairly rapid prototyping.

You coded the same thing I did in half the time, and it's prettier. I
find that disgusting. ;)

I was using an explicit stack in a class variable for the containers.
This is much nicer, and I need to eyeball it and make sure I get it.


Thanks,
Hal
 
H

Hal Fulton

Phil said:
This is exactly how the FLTK ruby bindings I'm using work (unfortunately
they're not publicly available yet) except that instead of defining a
class method we actually define a class and this is done in the
constructor for that class. It is a nice way of doing things.

I'll check that out when it is available.
However, your naming convention seems confusing to me. You're calling
this method App.logic, when in reality this is App.GUI since this is where
you're actually defining your GUI elements. App.logic, (again it seems to
me) should be something like a state machine that responds to events that
come from the GUI.

I've tried before to separate gui logic from the rest of the app's
logic. It has not worked well for me yet, though I approve of it in
principle.

Here I'm simply not separating those.
I guess I'm sensitive to this since the app we're currently
developing needs to work both as a
GUI and as a console app (depending on the environment it's running in) so
I'm trying to seperate the GUI elements from
the underlying logic as much as possible. Basically I have a state
machine that encapsulates all of the logic and then I can plug in
different front ends (or at least that's the goal, it's not all there
yet). I use Observable as a means of passing events back and forth
between the state machine and GUI.

That's pretty interesting. I've never yet conceived of a state-
machine approach in that kind of situation.

If I were you, I'd put together a working example (not too complex,
not too simple) and write an article about it. Bet Dr Dobbs would
go for it.

Cheers,
Hal
 
V

Vadim Nasardinov

That's pretty interesting. I've never yet conceived of a state-
machine approach in that kind of situation.

I liked this book a lot:
"Constructing the User Interface with Statecharts"
by Ian Horrocks
ISBN: 0201342782

(But then again, since I don't write GUIs, I haven't had a chance
to try out Horrocks' ideas in practice.)
 
G

Gregory Millam

Received: Thu, 8 Apr 2004 03:13:24 +0900
Correct assessment. But there are times when you'd want to capture
references to the widgets.

That's the reason I yielded with the widget itself. The code I wrote is just a hack, and would probably be better like this

def vbox
myvbox = VBox.new
... (same)
myvbox /* return the widget */
end

That way, one could even make something that does:

@myvbox = vbox { ... }

It would also be nicer to have a class-independent method for this, so you could:

myvbox = EzGtk.vbox { ... }

So it wouldn't use class variables. Perhaps a thread-global variable?

Thread.current["storefunc"] = ...

There are a large number of cleaner ways to rewrite the code I gave above.

You could also have other EzGtk constructs like ...

main {
title "Hello"
width 20
height 20
}

def width (x)
@currentstackwidget.height = x
end

Other simple things like that.

That button () construct doesn't quite strike me as correct - and could probably be done better

button "buttonname" {
on_click { ... }
}

Make it more generic across container-type objects. OTOH, Buttons are rarely used as such a container. *ponder ponder ponder* ...

Maybe
button_onclick "buttonname" {
...
}

I'd be interested in giving a hand if you like this idea and want to go with it. Just email me.

- Greg Millam
 
P

Phil Tomson

I'll check that out when it is available.

I hope it will eventually be available. These particular RubyFLTK
bindings were developed by someone else in the company where I'm
contracting. Theoretically, they could be released, but there is
probably some paperwork to get through to clear it with the Legal folks.
It probably won't happen very soon, unfortuneately, it's too bad because
they're quite nice to work with and they've even got the FLUID GUI-Builder
emitting Ruby code.

I've tried before to separate gui logic from the rest of the app's
logic. It has not worked well for me yet, though I approve of it in
principle.

Here I'm simply not separating those.


That's pretty interesting. I've never yet conceived of a state-
machine approach in that kind of situation.

I started out in hardware design. In hardware we use state-machines
everywhere so they just seem natural. Of course in software you can be
much more creative in the types of state-machines you develop. I often
wonder why it's not a more commonly used design pattern.
If I were you, I'd put together a working example (not too complex,
not too simple) and write an article about it. Bet Dr Dobbs would
go for it.

Hmmm.... That might be worth a try. Thanks for the idea.

Phil
 
P

Phil Tomson

I liked this book a lot:
"Constructing the User Interface with Statecharts"
by Ian Horrocks
ISBN: 0201342782

(But then again, since I don't write GUIs, I haven't had a chance
to try out Horrocks' ideas in practice.)

Looks like an interesting book.

As I mentioned earlier, as a former hardware engineer (you use state
machines everywhere in hardware design) it just seems natural to use a
state-machine as the underlying logic for a GUI. You've got events coming
in (button presses, generally) and you transistion to different states in
the machine depending upon the interaction and data provided by the user.
It's easy to keep the underlying logic seperate from the actual GUI
code when you use a state-machine.

So in the UI I'm currently developing (I say UI because it can be
console-based or GUI based depending on the environment it's running in) I
have a login-state for logging into a site (asks for user-name and serial
number). If the user is using the GUI they type in the info into a
couple of text boxes and then hit the 'next' button and the http
connection is attempted, if successful the machine moves into the next
state and the GUI is notified to display the next tab. In this case the
next botton generates the event to the
state-machine. If the user is using the console version of the same app,
in the login-state they're asked to enter their email address and then to
enter their serial number. The second 'return' after entering the
serial number triggers the event to the state-machine and the connection
is attempted, and again, if the connection was successful the machine
moves into the next state where another set of questions is asked on the
console. The same state-machine can be used in both cases since the
state-machine itself has no GUI/UI code in it.

I kind of thought everyone did things this way, but it sounds like it
isn't common practice.

Do others use state-machines for this sort of thing?


Phil
 
H

Hal Fulton

Gregory said:
That's the reason I yielded with the widget itself. The code I wrote is just a
hack, and would probably be better like this

def vbox
myvbox = VBox.new
... (same)
myvbox /* return the widget */
end

That way, one could even make something that does:

@myvbox = vbox { ... }

That seems reasonable, as long as we avoid doing an instance_eval. (see
below)
It would also be nicer to have a class-independent method for this, so you could:

myvbox = EzGtk.vbox { ... }

So it wouldn't use class variables. Perhaps a thread-global variable?

Thread.current["storefunc"] = ...

That is going right over my head at the moment.
You could also have other EzGtk constructs like ...

main {
title "Hello"
width 20
height 20
}

def width (x)
@currentstackwidget.height = x
end

Well, I was thinking of a different method. I made a #setup method (not
used in the code I showed) that would yield the widget so that you could
make changes to that widget.

My basic vision for blocks was:
1. For containers, establish that container as a context
2. For non-containers, associate the block with the "most common"
message for that widget

Re: 2, Note that it would still be possible to associate other blocks
with other messages.

ALSO: Note that I never used instance_eval (except in #setup). This is
important, because any @var referenced is an @var of App, regardless
of block nesting.
Other simple things like that.

That button () construct doesn't quite strike me as correct - and could probably be done better

button "buttonname" {
on_click { ... }
}

Make it more generic across container-type objects. OTOH, Buttons are rarely used as such a container. *ponder ponder ponder* ...

Maybe
button_onclick "buttonname" {
...
}

For my part, I still like the idea of a "default message" for a given
non-container widget. I can be convinced I'm wrong, though.
I'd be interested in giving a hand if you like this idea and want to go with it. Just email me.

Cc'ing now.

Hal
 
A

Asim Jalis

Gregory said:
That button () construct doesn't quite strike me as correct -
and could probably be done better

button "buttonname" {
on_click { ... }
}

Make it more generic across container-type objects. OTOH,
Buttons are rarely used as such a container. *ponder ponder
ponder* ...

Maybe
button_onclick "buttonname" {
...
}

Since the only thing that can happen to a button is that someone
clicks it it might be more concise to leave the onclick out.
Something like:

button "name" {
...
}

BTW, this is a great line of thinking. I have often craved a more
ruby-esque GUI API. This could finally make GUI programming fun.


Asim
 
G

Gregory Millam

Received: Thu, 8 Apr 2004 04:15:06 +0900
Since the only thing that can happen to a button is that someone
clicks it it might be more concise to leave the onclick out.
Something like:

button "name" {
...
}

BTW, this is a great line of thinking. I have often craved a more
ruby-esque GUI API. This could finally make GUI programming fun.

Yeah - button "Quit" { Gtk.main_quit } was the original example I used. As Phil suggests, we would split the block actions dependent on container / not container, and most common.

button "name" {
# this is executed on "clicked"
Gtk.main_quit
}

container {
# this is executed immediately, for adding items to the container"
}

My own idea was having it closer to this:

button {
label "name"
on_click { ... }
(right click, mouseover, etc can be set here too)
}

With a shortcut:

button_onclick "name" {
...
}

The big question we should be asking ourselves here - which one follows the POLS? ;).

- Greg Millam
 
H

Hal Fulton

Gregory said:
My own idea was having it closer to this:

button {
label "name"
on_click { ... }
(right click, mouseover, etc can be set here too)
}

With a shortcut:

button_onclick "name" {
...
}

The big question we should be asking ourselves here - which one follows the POLS? ;).

Heh, whose? :)

My way was this...

Short constructor:

b1 = button "Label" { do_this_when_clicked }

Adding more stuff:

b1.setup do
(right click, mouseover, etc can be set here too)
end

The principle to me is: Make the most commonly occurring case the
easiest and shortest, but keep the other stuff possible.

BTW, I normally prefer do/end for multiple lines and {} for single
lines. But that's just me. Same difference.


Hal
 
J

Jean-Hugues ROBERT

Hi,

I just encountered a rare case where I need a Hash where keys
are object id. Class Hash compares keys by value. As a result
two different String with the same value are considered the
same key. This is fine in most cases. But right now I need to
be more restrictive and I need two String objects to be treated
as two keys even if they have the same value.

I think IdentityHash is a probable name for such a beast, yet
I could not find one in Ruby and I don't see any method in
class Hash that I could redefine in a derived class to change
the way keys are compared.

BTW: IdentityHash should be slightly faster than Hash.

Any clue ?

Thanks,

Jean-Hugues Robert
 
J

Josh Huber

Its Me said:
(Avi Bryan't startlingly concise implementation uses continuations.
Continuations make my head hurt but they sure seem to be powerful
things).

I have to say, his implementation using callcc is pretty bad-ass.
Thanks for the link... Reading and understanding that code helped me
understand continuations quite a bit more than I used to.
 
G

Gregory Millam

Received: Thu, 8 Apr 2004 04:46:10 +0900
Short constructor:

b1 = button "Label" { do_this_when_clicked }

Adding more stuff:

b1.setup do
(right click, mouseover, etc can be set here too)
end

The principle to me is: Make the most commonly occurring case the
easiest and shortest, but keep the other stuff possible.

My own reasoning was - keep all the setup stuff inside the button { ... } constructor. Both do have strong arguments.
BTW, I normally prefer do/end for multiple lines and {} for single
lines. But that's just me. Same difference.

When it comes to most programming, I use do ... end for in order logic, and {} for out of order logic.

x.each do ... end
1.upto(5) do ... end
x = lambda { ... }
signal_connect { .. }
Thread.new { ... }

But coding using the EzGtk example doesn't feel so much like a program, as describing a layout for something. Feels like writing for a different purpose, even though it /is/ just typical ruby code.

They read to me like:
main do # main does:
vbox do # vbox does:
end
end

compared to:

main { # Inside of main ...
vbox { # inside vbox inside of main
}
}

But like you said, same difference.

Hopping back to an earlier topic - You were confused about Thread.current["storefunc"] = ...

What I meant by this:

1) I'm not too big a fan about needing a different class or function for each EzGtk window, or even needing an EzGtk window. This means the functions would need to be independent of instances or classes, but as they still need a variable, the best solution I can see is a thread-level stack variable. It's doable by passing the "storefunc" to the block, but then it's not so "Ez".

ie:
require 'EzGtk' && include EzGtk
mywindow = window { .. }
myotherwindow = window {..}

2) So it doesn't require defining a new child of EzGtk Either done with a class variable on class Object, as it is above, or use a global. But using either isn't thread-safe, so Thread.current["storefunc"] would be thread safe. - This is also, as the website 'Its me' pointed out, a dynamically scoped variable.

So to accomplish (1), I'd just need to change my code to do this:

holdfunc = Thread.current["storefunc"]
Thread.current["storefunc"] = lambda { ... }
yield thewidget
Thread.current["storefunc"] = holdfunc

Tada! Thread-safe, and people can now just call mywin = EzGtk.window { EzGtk.vbox {..} }, or include EzGtk and just call window { vbox { } } - independently for each window they want to make.

- Greg Millam
 
G

Gregory Millam

Received: Thu, 8 Apr 2004 05:29:17 +0900
Perhaps we could uniformly do everything with:

widget_name { block }

We could wrap dynamically scoped variables around these calls. Within any of
the methods (widget_name above, or below: button, label, command, color,
vbox) we can look up dynamically scoped variables with find_in_context
:)var), or set up further (nested) scopes of variables with
with_context:)var=>...).

with_context :)parent=>a_container) {
button {
label "Label"
command { block }
color "green"
}
vbox {
with_context:)color=>"purple") {
button {...}
hbox {...}
}
}

Interesting idea. Adding with_context would make it a bit not-so-Ez-Gtk ... unless we named it ... style?

style:)color=>"yellow",:bgcolor=>"purple") {
window {
label "Barney loves you ... Run!"
}
}

- and style returns whatever returns in its yield.

As for :parent in with_context - I believe it'd flow better if it was automatically placed in the current context parent, like my @storefunc earlier.

And if one doesn't exist - well, it returns itself, so you can then add it as per normal

myvbox = style("...") { vbox { ... } }
mywindow.add myvbox

- Greg Millam
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

Forum statistics

Threads
473,769
Messages
2,569,579
Members
45,053
Latest member
BrodieSola

Latest Threads

Top