has anyone gotten a chance to take a look at the GUtopIa code yet?

there's something very strange about it. something i can't figure out. i
mean i wrote it and all, but i never explictly told it how to update the
widget when the object changes. yet it does! how? i keep coming back to
this and looking for some place in the code that does it, but i can't
find it. very spooky.

~transami


On Tue, 2002-07-23 at 18:31, Tom Sawyer wrote:
> well, i just read over my last post. sorry about all the typos. i needed
> a break and didn't bother to proofread. but i'm sure you can make out
> what i was trying to say where i blurbed. okay, now to the good stuff! 
> 
> i spent the last few days hacking out the first steps in the creation of
> a super-duper ruby gui api, which i have dubbed GUtopIa (thanks rich).
> just to kick things off here's a small clip of one of my examples (note
> MyModels is drawn from an unshown require):
> 
>   INTERFACE = 'PDA'
>   fruitapp = FruitApp.new
>   mygui = GUtopIa::GUI.new('Fruity Fun Time')
>   names = {
>     'awindow' => fruitapp.appname,
>     'aradio'  => fruitapp.fruitbasket.name
>   }
>   bindings = {
>     fruitapp.fruitbasket.name => { 
>       :value        => Proc.new { fruitapp.fruit.isa },
>       :value=       => Proc.new { |x| fruitapp.fruit.isa = x },
>       :items        => Proc.new { fruitapp.fruitbasket.contains },
>       :event_change => Proc.new { fruitapp.pickafruit }
>     }
>   }
>   model = MyModels.pick_model(mygui, INTERFACE)
>   mygui.transaction(bindings, names, &model)
>   mygui.build
> 
> the idea is that you take an instance of your application (FruitApp in
> this case), completely untouched --you don't have to add or change a
> single line of code (unless you want too ;-) you then create a model. a
> model (not shown in example) is an executable description of the fixed
> aspects of your gui. it has no references to any part of your app
> (although you can do so if you don't care about SOC) it is a completely
> independent entity and as such can be reused in other fitting contexts.
> you can also draw on multiple sub-models. then you create a binding (as
> seen above). this describes how an instance of your application
> "interfaces" with your model. and then presto out comes you gui-ized
> app.
> 
> also note that GUtopIa uses a transaction mechinism. this effectivly
> bundles an executable model into a single execution. not only does this
> increase speed, but also offers a means to rollback the effects of a
> transaction should there be an error, adding much greater stability to
> your gui-ized application.
> 
> some other things to note: bindings are actually integrated directly
> into the class models of the widgets. when a widget is created it is
> subclassed into a specialized class of that widget which includes
> methods to react to the bindings described.
> 
> i've attached my work thus far so you can checkout all of this in
> detail. be sure to run example.rb (the snipet above) and check out
> orig-example.rb, which is a non-SOC version of the original Rouge/Utopia
> example. then check out gutopia.rb which is the heart of the matter.
> 
> so that's the the word on implementation. i've taken the advice of
> others and i have worked out what i think is shaping up to be the best
> gui api design in existence. Curt Hibbs has offered me a wiki for this
> project. (thanks curt) i will take him up on it. now i hope i can get
> YOU enthused enough to help. there's real potential here, to not only
> create an awsome gui api for ruby that is unique and powerful, but also
> create a full-fledge game sdk! this thing looks to be awesome indeed.
> 
> i need people to take on a few tasks. a ruby wrapper needs to be created
> for ClanLib and we need to build a Themes and Behaviors manager. someone
> needs to hack out the communications layer (i've tried it with DRb and
> ROMP but they bomb). and i could use some help finishing off the main
> api. if we can pull together on this, ruby, our favorite little
> scripting language, will have its very own GUI to rival all others!
> 
> as you might be able to tell, i'm very excited! i hope you are too!
> 
> thanks for your time and hope to hear from you,
> 
> ~transami :-)
> 
> 
> ----
> 

> # GUtopIa Example - Application Layer
> # Copyright (c) 2002 Thomas Sawyer, Ruby License
> 
> class FruitApp
>   attr_accessor :appname
>   attr_reader :fruitbasket, :fruit, :basketname
>   def initialize
>     @appname = 'Fruity Fun'
>     @fruitbasket = FruitBasket.new
>     @fruit = Fruit.new
>   end
>   def pickafruit
>     puts "You got a #{@fruit.isa}!"
>   end
> end
> 
> class FruitBasket
>   attr_accessor :name, :contains
>   def initialize
>     @name = 'Fruit Basket'
>     @contains = ["None", "Apple", "Orange", "Banana"]
>   end
> end
> 
> class Fruit
>   attr_accessor :isa
>   def initialize
>     @isa = "None"
>   end
> end
> ----
> 

> # GUtopIa Daemon Example - Client
> # Copyright (c) 2002 Thomas Sawyer, Ruby License
> 
> require 'drb'
> require 'example-app'
> require 'example-pre'
> 
> 
> # Configure interface
> 
> INTERFACE = 'PDA'
> puts "\nINTERFACE: #{INTERFACE}\n"
> 
> 
> # Make GUI App
> 
> fruitapp = FruitApp.new
> 
> DRb.start_service()
> mygui = DRbObject.new(nil, 'druby://localhost:8080')
> 
> names = {
>   'awindow' => fruitapp.appname,
>   'aradio'  => fruitapp.fruitbasket.name
> }
> 
> bindings = {
>   fruitapp.fruitbasket.name => { 
>     :value        => Proc.new { fruitapp.fruit.isa },
>     :value=       => Proc.new { |x| fruitapp.fruit.isa = x },
>     :items        => Proc.new { fruitapp.fruitbasket.contains },
>     :event_change => Proc.new { fruitapp.pickafruit }
>   }
> }
> 
> model = MyModels.pick_model(mygui, INTERFACE)
> 
> mygui.transaction(bindings, names, &model)
> 
> mygui.build
> 
> 
> # Run some tests
> 
> puts
> puts "MYGUI "
> p mygui
> 
> puts
> puts "MYGUI WIDGETS"
> mygui.widgets.each_key do |k|
>   puts k
> end
> 
> puts
> puts "THE WINDOW"
> p mygui.widgets[fruitapp.appname].class.superclass
> puts "name: #{fruitapp.appname}"
> puts "width: #{mygui.widgets[fruitapp.appname].width}"
> puts "height: #{mygui.widgets[fruitapp.appname].height}"
> 
> puts
> puts "THE RADIO"
> p mygui.widgets[fruitapp.fruitbasket.name].class.superclass
> puts "name: #{fruitapp.fruitbasket.name}"
> puts "items: #{mygui.widgets[fruitapp.fruitbasket.name].items.join(', ')}"
> puts "value: #{mygui.widgets[fruitapp.fruitbasket.name].value}"
> 
> puts
> puts
> puts "TRY OUT!"
> puts
> puts "Current state of fruit and its widget:"
> puts "fruit=#{fruitapp.fruit.isa}"
> puts "widget=#{mygui.widgets[fruitapp.fruitbasket.name].value}"
> puts
> puts "Changing widget to: #{mygui.widgets[fruitapp.fruitbasket.name].value = 'Banana'}"
> puts "fruit=#{fruitapp.fruit.isa}"
> puts
> puts "Changing fruit to: #{fruitapp.fruit.isa = 'Orange'}"
> puts "widget=#{mygui.widgets[fruitapp.fruitbasket.name].value}"
> puts
> puts "Trigger event_change event:"
> mygui.widgets[fruitapp.fruitbasket.name].event_change
> puts
> puts "Changing fruit to: #{fruitapp.fruit.isa = 'Apple'}"
> puts "widget=#{mygui.widgets[fruitapp.fruitbasket.name].value}"
> puts
> puts "Changing widget to: #{mygui.widgets[fruitapp.fruitbasket.name].value = 'Banana'}"
> puts "fruit=#{fruitapp.fruit.isa}"
> 
> puts
> ----
> 

> # GUtopIa Example - Models
> # Copyright (c) 2002 Thomas Sawyer, Ruby License
> 
> require 'gutopia'
> 
> module MyModels
> 
>   def MyModels.pick_model(gui, which)
>     case which
>     when 'PC'
>       return MyModels.pc_model(gui)
>     when 'PDA'
>       return MyModels.pda_model(gui)
>     end
>   end
> 
>   private
> 
>   # PC model using serial notatation (slower)
>   def MyModels.pc_model(gui)
> 
>     Proc.new {
>   
>       # RadioBox
>       r = gui.widgetFactory(:RadioBox, 'aradio')
> 
>       # Window
>       w = gui.widgetFactory(:Window, 'awindow')
>       w.width = 640
>       w.height = 400
>       w.grid = [ [ r ] ]
>     
>     }
> 
>   end
> 
>   # PDA model using parallel notation (faster)
>   def MyModels.pda_model(gui)
> 
>     Proc.new {
>   
>       # RadioBox
>       r = gui.widgetFactory(:RadioBox, 'aradio')
> 
>       # Window
>       w = gui.widgetFactory(:Window, 'awindow',
>         :width  => 320,
>         :height => 240,
>         :grid => [ [ r ] ]
>       )
>       
>     }
>     
>   end
> 
> end  # MyModels
> ----
> 

> # GUtopIa Daemon Example - Client
> # Copyright (c) 2002 Thomas Sawyer, Ruby License
> 
> require 'romp/romp'
> require 'example-app'
> require 'example-pre'
> 
> 
> # Configure interface
> 
> INTERFACE = 'PDA'
> puts "\nINTERFACE: #{INTERFACE}\n"
> 
> 
> # Make GUI App
> 
> fruitapp = FruitApp.new
> 
> client = ROMP::Client.new('tcpromp://localhost:8080')
> mygui = client.resolve('gutopia-romp')
> 
> names = {
>   'awindow' => fruitapp.appname,
>   'aradio'  => fruitapp.fruitbasket.name
> }
> 
> bindings = {
>   fruitapp.fruitbasket.name => { 
>     :value        => Proc.new { fruitapp.fruit.isa },
>     :value=       => Proc.new { |x| fruitapp.fruit.isa = x },
>     :items        => Proc.new { fruitapp.fruitbasket.contains },
>     :event_change => Proc.new { fruitapp.pickafruit }
>   }
> }
> 
> model = MyModels.pick_model(mygui, INTERFACE)
> 
> mygui.transaction(bindings, names, &model)
> 
> mygui.build
> 
> 
> # Run some tests
> 
> puts
> puts "MYGUI "
> p mygui
> 
> puts
> puts "MYGUI WIDGETS"
> mygui.widgets.each_key do |k|
>   puts k
> end
> 
> puts
> puts "THE WINDOW"
> p mygui.widgets[fruitapp.appname].class.superclass
> puts "name: #{fruitapp.appname}"
> puts "width: #{mygui.widgets[fruitapp.appname].width}"
> puts "height: #{mygui.widgets[fruitapp.appname].height}"
> 
> puts
> puts "THE RADIO"
> p mygui.widgets[fruitapp.fruitbasket.name].class.superclass
> puts "name: #{fruitapp.fruitbasket.name}"
> puts "items: #{mygui.widgets[fruitapp.fruitbasket.name].items.join(', ')}"
> puts "value: #{mygui.widgets[fruitapp.fruitbasket.name].value}"
> 
> puts
> puts
> puts "TRY OUT!"
> puts
> puts "Current state of fruit and its widget:"
> puts "fruit=#{fruitapp.fruit.isa}"
> puts "widget=#{mygui.widgets[fruitapp.fruitbasket.name].value}"
> puts
> puts "Changing widget to: #{mygui.widgets[fruitapp.fruitbasket.name].value = 'Banana'}"
> puts "fruit=#{fruitapp.fruit.isa}"
> puts
> puts "Changing fruit to: #{fruitapp.fruit.isa = 'Orange'}"
> puts "widget=#{mygui.widgets[fruitapp.fruitbasket.name].value}"
> puts
> puts "Trigger event_change event:"
> mygui.widgets[fruitapp.fruitbasket.name].event_change
> puts
> puts "Changing fruit to: #{fruitapp.fruit.isa = 'Apple'}"
> puts "widget=#{mygui.widgets[fruitapp.fruitbasket.name].value}"
> puts
> puts "Changing widget to: #{mygui.widgets[fruitapp.fruitbasket.name].value = 'Banana'}"
> puts "fruit=#{fruitapp.fruit.isa}"
> 
> puts
> ----
> 

> # GUtopIa Example - Presentation
> # Copyright (c) 2002 Thomas Sawyer, Ruby License
> 
> require 'gutopia'
> require 'example-app'
> require 'example-pre'
> 
> 
> # Configure interface
> 
> INTERFACE = 'PDA'
> puts "\nINTERFACE: #{INTERFACE}\n"
> 
> 
> # Make GUI App
> 
> fruitapp = FruitApp.new
> 
> mygui = GUtopIa::GUI.new('Fruity Fun Time')
> 
> names = {
>   'awindow' => fruitapp.appname,
>   'aradio'  => fruitapp.fruitbasket.name
> }
> 
> bindings = {
>   fruitapp.fruitbasket.name => { 
>     :value        => Proc.new { fruitapp.fruit.isa },
>     :value=       => Proc.new { |x| fruitapp.fruit.isa = x },
>     :items        => Proc.new { fruitapp.fruitbasket.contains },
>     :event_change => Proc.new { fruitapp.pickafruit }
>   }
> }
> 
> model = MyModels.pick_model(mygui, INTERFACE)
> 
> mygui.transaction(bindings, names, &model)
> 
> mygui.build
> 
> 
> # Run some tests
> 
> puts
> puts "MYGUI "
> p mygui
> 
> puts
> puts "MYGUI WIDGETS"
> mygui.widgets.each_key do |k|
>   puts k
> end
> 
> puts
> puts "THE WINDOW"
> p mygui.widgets[fruitapp.appname].class.superclass
> puts "name: #{fruitapp.appname}"
> puts "width: #{mygui.widgets[fruitapp.appname].width}"
> puts "height: #{mygui.widgets[fruitapp.appname].height}"
> 
> puts
> puts "THE RADIO"
> p mygui.widgets[fruitapp.fruitbasket.name].class.superclass
> puts "name: #{fruitapp.fruitbasket.name}"
> puts "items: #{mygui.widgets[fruitapp.fruitbasket.name].items.join(', ')}"
> puts "value: #{mygui.widgets[fruitapp.fruitbasket.name].value}"
> 
> puts
> puts
> puts "TRY OUT!"
> puts
> puts "Current state of fruit and its widget:"
> puts "fruit=#{fruitapp.fruit.isa}"
> puts "widget=#{mygui.widgets[fruitapp.fruitbasket.name].value}"
> puts
> puts "Changing widget to: #{mygui.widgets[fruitapp.fruitbasket.name].value = 'Banana'}"
> puts "fruit=#{fruitapp.fruit.isa}"
> puts
> puts "Changing fruit to: #{fruitapp.fruit.isa = 'Orange'}"
> puts "widget=#{mygui.widgets[fruitapp.fruitbasket.name].value}"
> puts
> puts "Trigger event_change event:"
> mygui.widgets[fruitapp.fruitbasket.name].event_change
> puts
> puts "Changing fruit to: #{fruitapp.fruit.isa = 'Apple'}"
> puts "widget=#{mygui.widgets[fruitapp.fruitbasket.name].value}"
> puts
> puts "Changing widget to: #{mygui.widgets[fruitapp.fruitbasket.name].value = 'Banana'}"
> puts "fruit=#{fruitapp.fruit.isa}"
> 
> puts
> ----
> 

> # GUtopIa - ROMP Daemon
> # Copyright (c) 2002 Thomas Sawyer, Ruby License
> 
> require 'drb'
> require 'gutopia'
> 
> gutopia_drb = GUtopIa::GUI.new('DRb')
> 
> DRb.start_service('druby://localhost:8080', gutopia_drb)
> DRb.thread.join
> ----
> 

> # GUtopIa - ROMP Daemon
> # Copyright (c) 2002 Thomas Sawyer, Ruby License
> 
> require 'romp/romp'
> require 'gutopia'
> 
> gutopiad = GUtopIa::GUI.new('Romp')
> 
> server = ROMP::Server.new('tcpromp://localhost:8080', nil, true)
> server.bind(gutopiad, 'gutopia-romp')
> server.thread.join
> ----
> 

> # GUtopIa API
> # Copyright (c)2002 Thomas Sawyer, Ruby License
> 
> module GUtopIa
> 
>   # Returns a GUIFactory object
>   class GUI
> 
>     attr_reader :name, :widgets
> 
>     def initialize(name)
>       @name = name
>       #
>       @widgets = {}
>       @names = {}
>       @bindings = {}
>       @transactions = []
>     end
> 
>     # Method used to prefab GUI
>     def transaction(bindings={}, names={}, &model)
>       @names = names
>       @bindings = bindings
>       @transactions << Proc.new {
>         #commit
>         #begin
>           model.call #(self)
>           #commit
>         #rescue ScriptError
>           #rollback
>         #rescue StandardError
>           #rollback
>         #end
>       }
>     end
>     
>     # Method used to build GUI
>     #   This runs the all pending transactions
>     def build
>       @transactions.each do |transaction|
>         transaction.call(@bindings)
>       end
>       @transactions.clear  # all done with those
>     end
>   
>     # Widget Factory
>     #   Returns a new widget object of specialized widget class
>     def widgetFactory(widget, name, attributes={})
>       
>       # a widget string name will work too
>       widget = widget.intern if widget.is_a?(String)
>        
>       # makes an anoynomous class as subclass of desired widget
>       widgetClass = Class.new(GUtopIa.const_get(widget))
>     
>       # specialize class via bindings
>       if @bindings[@names[name] || name]
>         @bindings[@names[name] || name].each do |k, v|
>           widgetClass.class_eval {
>             define_method(k, v)
>           }
>         end
>       end
>         
>       w = widgetClass.new(attributes)
>       w.name = @names[name] || name
>       @widgets[w.name] = w
>       
>       return w
>       
>     end
> 
>     #
>     def alert(msg)
>       puts msg
>     end
> 
>     #
>     def stop
>       puts "stopping...#{@name}"
>       exit
>     end
> 
>     # Useful Class Methods?
>     
>     def GUI.screen_realestate
>       # { :height => , :width => , :depth => }
>     end
>     
>   end  # GUI
>   
>   
>   # Widgets
>   
>   # ToDo: add a whole bunch more super widgets :-)
>   #       attributes require validation
>   
>   class Widget
>     attr_accessor :name
>   end
>   
>   
>   class Window < Widget
>   
>     attr_accessor :height, :width, :colordepth, :flags
>     attr_accessor :caption, :icon, :background, :grid
>     
>     def initialize(attributes={})
>       @height = attributes[:height] ? attributes[:height] : 200
>       @width = attributes[:width] ? attributes[:width] : 200
>       @colordepth = attributes[:colordepth] ? attributes[:colordepth] : 16
>       @flags = attributes[:flags] ? attributes[:flags] : 0
>       @caption = attributes[:caption] ? attributes[:caption] : nil
>       @icon = attributes[:icon] ? attributes[:icon] : nil
>       @background = attributes[:background] ? attributes[:background] : nil
>       @grid = attributes[:grid] ? attributes[:grid] : []
>       #
>       super()
>     end
>   
>     def show
>       puts "showing window....#{@caption}"
>     end
>   
>     def hide
>       puts "hiding window...#{@caption}"
>     end
>     
>   end  # Window
> 
> 
>   class Panel < Widget
>   
>     attr_accessor :height, :width
>     attr_accessor :background, :grid
>     
>     def initialize(attributes={})
>       @height = attributes[:height] ? attributes[:height] : 200
>       @width = attributes[:width] ? attributes[:width] : 200
>       @background = attributes[:background] ? attributes[:background] : nil
>       @grid = attributes[:grid] ? attributes[:grid] : []
>       #
>       super()
>     end
>   
>   end  # Panel
> 
> 
>   class Label < Widget
>   
>     attr_accessor :text
>   
>     def initialize(attributes={})
>       @text = attributes[:text] ? attributes[:text] : nil
>       #
>       super()
>     end
>   
>   end  # Label
>   
> 
>   class TextField < Widget
>   
>     attr_accessor :text, :value
>   
>     def initialize(attributes={})
>       @text = attributes[:text] ? attributes[:text] : nil
>       @value = attributes[:value] ? attributes[:value] : nil
>       #
>       super()
>     end
>   
>   end  # TextField
> 
> 
>   class TextArea < Widget
>   
>     attr_accessor :text, :value, :cols, :lines
>   
>     def initialize(attributes={})
>       @text = attributes[:text] ? attributes[:text] : nil
>       @value = attributes[:value] ? attributes[:value] : nil
>       @cols = attributes[:cols] ? attributes[:cols] : nil
>       @lines = attributes[:lines] ? attributes[:lines] : nil
>       #
>       super()
>     end
>   
>   end  # TextArea
> 
> 
>   class Button < Widget
>   
>     attr_accessor :text
>     attr_accessor :event_pressed
>   
>     def initialize(attributes={})
>       @text = attributes[:text] ? attributes[:text] : nil
>       # events
>       @event_pressed = attributes[:event_pressed] ? attributes[:event_pressed] : nil
>       #
>       super()
>     end
>   
>   end  # Button
> 
> 
>   class RadioBox < Widget
>   
>     attr_accessor :value, :items
>     attr_accessor :event_change
>   
>     def initialize(attributes={})
>       @value = attributes[:value] ? attributes[:value] : nil
>       @items = attributes[:content] ? attributes[:content] : nil
>       # events
>       @event_change = attributes[:event_change] ? attributes[:event_change] : nil
>       #
>       super()
>     end
>   
>   end  # RadioBox
> 
>   
>   class MenuBar < Widget
>   
>     attr_accessor :items
>     
>     def initialize(attributes={})
>       @items = attributes[:items] ? attributes[:items] : nil
>       #
>       super()
>     end
>   
>   end  # MenuBar
>   
>   
>   class Menu < Widget
>   
>     attr_accessor :text, :items
>     
>     def initialize(attributes={})
>       @text = attributes[:text] ? attributes[:text] : nil
>       @items = attributes[:items] ? attributes[:items] : nil
>       #
>       super()
>     end
>   
>   end  # Menu
> 
> 
>   class MenuItem < Widget
>   
>     attr_accessor :text
>     attr_accessor :event_selected
>   
>     def initialize(attributes={})
>       @text = attributes[:text] ? attributes[:text] : nil
>       # events
>       @event_selected = attributes[:event_selected] ? attributes[:event_selected] : nil
>       #
>       super()
>     end
>   
>   end  # MenuItem
> 
> end  # GUtopIa
> ----
> 

> # GUtopIa Example - Presentation
> # Copyright (c) 2002 Thomas Sawyer, Ruby License
> 
> require 'gutopia'
> 
> app = GUtopIa::GUI.new('My Application')
> 
> # here you must refer to widgets with app.widgets['widget's name']
> bindings = {
>   'quit' => {
>     :event_selected => Proc.new {
>       app.stop
>     }
>   },
>   'about' => {
>     :event_selected => Proc.new { 
>       app.widgets['aboutWindow'].show
>     }
>   },
>   'submit' => { 
>     :event_pressed => Proc.new { 
>       app.alert("And the fruit is...#{app.widgets['favoriteFruit'].text}")
>       app.widgets['mainWindow'].hide
>       app.stop
>     }
>   },
>   'clear' => { 
>     :event_pressed => Proc.new {
>       app.widgets['favoriteFruit'].text = nil
>     }
>   },
>   'close' => {
>     :event_pressed => Proc.new {
>       app.widgets['aboutWindow'].hide
>     }
>   }
> }
> 
> # here you can refer to widgets with local name
> #   or the app.widgets['widget's name']
> app.transaction(bindings) {
>     
>   quit = app.widgetFactory(:MenuItem, 'quit',
>     :text => 'Quit'
>   )
>   about = app.widgetFactory(:MenuItem, 'about',
>     :text => 'About'
>   )
>   file = app.widgetFactory(:Menu, 'file',
>     :text => 'File',
>     :items => [ app.widgets['quit'] ]
>   )
>   help = app.widgetFactory(:Menu, 'help',
>     :text => 'Help',
>     :items => [ about ]
>   )
>   menu = app.widgetFactory(:MenuBar, 'menu',
>     :items => [ file, help ]
>   )
>   firstName = app.widgetFactory(:TextField, 'firstName',
>     :label => 'First Name',
>     :label_position => :left
>   )
>   lastName = app.widgetFactory(:TextField, 'lastName',
>     :label => 'Last Name',
>     :label_position => :left
>   )
>   favoriteFruit = app.widgetFactory(:TextArea, 'favoriteFruit',
>     :text => 'Banana',
>     :cols => 5,
>     :lines => 4,
>     :readonly => false,
>     :label => 'Favorite Fruit',
>     :label_position => :top
>   )
>   submit = app.widgetFactory(:Button, 'submit',
>     :text => 'Update',
>     :image => 'check.png'
>   )
>   clear = app.widgetFactory(:Button, 'clear',
>     :text => 'Clear'
>   )
>   fruit_panel = app.widgetFactory(:Panel, 'fruit_panel',
>     :grid => [ [ firstName     ],
>                [ lastName      ],
>                [ favoriteFruit ],
>                [ submit        ],
>                [ clear         ] ]
>   )
>   mainWindow = app.widgetFactory(:Window, 'mainWindow',
>     :caption => 'My Cool Window',
>     :icon => 'myIcon.ico',
>     :grid => [ [ menu        ],
>                [ fruit_panel ] ]
>   )
>   aboutLbl = app.widgetFactory(:Label, 'aboutLbl', 
>     :text => <<-DATA
>     This is a very cool application
>     that I created using the GUtopIa
>     GUI API.
>     DATA
>   )
>   close = app.widgetFactory(:Button, 'close',
>     :text => 'Close'
>   )
>   aboutWindow = app.widgetFactory(:Window, 'aboutWindow',
>     :caption => 'About My Cool App',
>     :grid => [ [ aboutLbl ],
>                [ close    ] ]
>   )
>   
> }
>     
> app.build
> 
> puts
> puts "select about from menu"
> app.widgets['about'].event_selected
> puts "click on close"
> app.widgets['close'].event_pressed
> puts "click on submit"
> app.widgets['submit'].event_pressed
> puts
-- 
~transami