GUI's and the Rouge, Part II

Tom Sawyer wrote:

it was stated that a main goal of Rouge was to create a cross-compatible
gui api that “boiled down” into the native gui of each platform.

As you’ve found, it’s a major piece of work. I should know - I designed
and my company built such a product (called OpenUI) in the 1st half of
the 90’s. There are better toolkits out there for this now, of which I like
Qt and fxruby best. You definitely shouldn’t build your own, it’s a problem
that needs multiple hundreds of thousands of lines of code to solve.

interesting OpenUI…well i’m not really building my own. i’m building a
layer on top of pre-existing api (ClanLib). wouldn’t really consider
writing it all from scratch.

i think too that this is a fantastic idea.
so that’s the final piece and we end up with basically this:

You’ve described the architecture of OpenUI, but perhaps haven’t understood
the factor that makes it really work: rich asynchronous message passing.

I’m still of the firm opinion that this is the single integrating feature
which is needed to revolutionise GUI programming. Message delivery needs
to be based on hierarchies of objects, including propagation of undelivered
(or partly-delivered) messages up the hierarchy, publish-subscribe propagation
down the hierarchy, and proxy-address mapping for remote objects. Add that to
the basic queueing facilities needed for asynchronous delivery, with a
priority-nesting feature to provide some minimal sequencing control for
consequential messaging (needed for remote validation for example), and you
have an exceptionally powerful and flexible application framework supporting
separation of presentation and semantic content to the fullest extent possible.

rich asynchronous message passing. does it really need to be that
“fancy”? perhaps. the communications layer is optional, but i tend to
think standard tcp/ip + marshaling might do the job. after all networked
games these days don’t use much more. (i think). if this part proves too
much of a bottle neck we can either do without it or hopefully pull
together something more like what you describe. ClanLib also includes
some networking features for netowked games. perhaps that would be
useful.

The native GUI widgets should be assembled into hierarchies not by API calls,
but by a hierarchical description language - XML probably. In fact I started
making “qtml” - a Qt markup language, which allows simple assembly of arbitrary
widget hierarchies (UIs), and embedded scripting to animate and control the UI.
I’d love to use Ruby with this, it’s the most suitable language for the purpose.

well, i disagree here. i thought about that option: whether the models
should be in markup versus executable code. i went executable b/c we can
always make markups that “compile” into the transaction code. but you
may be interest in a little program that i threw together that builds tk
interfaces from xml. i’ve attached it. you’ll need rexml to run it and
you may need to tweak the require statements. tkxml-l.rb is the simple
example.

Sorry that much of the above discussion is kind-of schematic, incomplete and
probably confusing, I can discuss the ideas further if they ring anyone’s
bells. Suffice it to say that people who’ve used these ideas in the form of
our (now obsolescent) product have found them to be truly revolutionary.

no apology required. thanks for the comments. i’m interested to know
more about OpenUI and if you should decide that you could contribute to
the project please let me know.

~transami

tkxml.rb (4.39 KB)

tkxml-1.rb (304 Bytes)

···

On Tue, 2002-07-23 at 19:07, Clifford Heath wrote:

let me paint a picture:

you have this super application you wrote --the underbelly of an
factastic hoogy-me-whatsy-do-it-all app and now you have to figure out
how to put a front-end on her. so you need a gui. hmmm…you think, i
want a gui thats flexible and fast nad highly functional, runs on just
about any platform, looks good, preferably just like the native
interface, and is easy as pie to code so as not to require much fussing
with the app you’ve alredy written.

well, if you look around you’re going to discover that everything out
there now has shortcomings. beleive me i’ve looked. so what do you do?
you either pick one that minimizes the shortcomings as suited to your
application, or you do what i’m trying to do: put an end to the problem.
how does that bode for what i’m trying to achieve?

as for plain old Model-View-Controller, honestly, i have no idea what
that even means. i just did what seemed natural. perhpas MVC is just the
right way to go?

~transasmi

···

On Tue, 2002-07-23 at 19:13, Albert Wagner wrote:

Still looks like plain old Model-View-Controller to me. You’ve split the
controller into client and server parts. What is the purpose of separation
into different processes? I don’t mean to sound critical. I really don’t
understand what you are trying to achieve.


~transami

What about WX-Windows. There is no ruby bindings for it, but would that fit
the bill?

Steve Tuckner

···

-----Original Message-----
From: Curt Hibbs [mailto:curt@hibbs.com]
Sent: Wednesday, July 24, 2002 9:23 AM
To: ruby-talk ML
Subject: RE: GUI’s and the Rouge, Part III (yes, finally) 1/2

Tom Sawyer wrote:

[snip]

it was stated that a main goal of Rouge was to create a cross-compatible
gui api that “boiled down” into the native gui of each platform. in my
first post on these matters, i pointed out how ungodly difficult that
would be (as i had been trying to do that myself). in the end either you
left to “patch” your app for each platform or you simply have to give up
the features that they hold in common (at best). it really is simply too
much of pain to create such an system, not to mention maintaining it as
those underlying native api change. yet the goal os a native look and
feel is not unachievable by other means. the solution is simply a matter
of using an underlying gui engine that is sufficiantly themeable. since
all the major gui’s are now themable themselves, we simply need to code
a theme manager that can import the themes of those other guis. we need
not use their underlying api, only their look and feel! so that’s what
can be done to solve that dilemma: use a sufficantly themeable gui api
and crreat and Themes and Behaviors Manger to emulate the look and feel
and even draw upon the native theme configuration files. this is a much
managable task then trying to channel down to the native guis
themselves. the only trick of course is finding that themeable gui api.

This issue wasn’t so much native look-and-feel as it was native integration
(its not the same). Native integration means that the widgets have the same
behavior as native widgets (usually, because they are native widgets).
Sean Russell made a pretty clear case for this in this posting:

http://blade.nagaokaut.ac.jp/cgi-bin/scat.rb/ruby/ruby-talk/32029

Sean also mentioned SWT from the Eclipse project, which does this for
Java:

http://www.eclipse.org/articles/Article-SWT-Design-1/SWT-Design-1.html

I think I said this before, but (just in case), the problem that the
FreeRIDE project had (and still has) finding an adequate Ruby GUI toolkit
that simultaneously satisfied the need for both native integration and
internationalization. Of course other features are important, too, but these
were the two big ones that just couldn’t be found in one package.

Curt

I think he’s trying to make the generation of the View and Controller
parts mostly automatic.

Massimiliano

···

On Wed, Jul 24, 2002 at 10:13:37AM +0900, Albert Wagner wrote:

Still looks like plain old Model-View-Controller to me. You’ve split the
controller into client and server parts. What is the purpose of separation
into different processes? I don’t mean to sound critical. I really don’t
understand what you are trying to achieve.

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 :wink: 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 :slight_smile:


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 :slight_smile:

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

Tom Sawyer wrote:

wouldn’t really consider writing it all from scratch.

cool, just so you know.

rich asynchronous message passing. does it really need to be that
“fancy”? perhaps. the communications layer is optional, but i tend to
think standard tcp/ip + marshaling might do the job.

Actually it isn’t much more than tcp/ip and marshalling, until you get into
the distributed object issues. The key thing here is that behaviour inside
the GUI is also controlled by async msgs - every key up/down, every mouse
up/down/motion is a message which is dynamically matched against message
handlers on the objects - so messages act like a delayed dynamic procedure
call with re-routing. If an object has no handler for a message, or the
handler returns false (saying “handling not complete”), the message is
propagated up to the parent and tried there. When/if it hits the session
object, if that session object is one end of a connection, the message is
marshalled and queued for transmission.

Re publish/subscribe, before a message can be delivered to an object, a check
is made whether any descendents of that object are listening (subscribers) on
that object for that message or one of its superclasses. In this way a GUI
main window can be set up to receive a message containing all the data values
required to fill out the fields, and the individual widgets subscribe to that
message on the main window, so that the message is delivered to each of them
before being delivered to the main window object itself. That solves the
scatter part of the scatter/gather problem to be solved with the main window
even needing to know which widgets display which data fields.

The distributed object system is maintained by the marshall process, which
turns any object reference from a pointer into either a “local reference” or
a “remote reference”. A local reference corresponds to a local object which
is being exported; the reference is maintained in a table of exported local
objects. On the other end, each local reference received from the other end
gets maintained in a table of proxy objects. When marshalling messages in
return, a pointer to a proxy object gets turned back into the original local
reference from the other end.

In this way, any number of objects spread across arbitrary networks of
point-to-point nodes may be addressed without requiring a UUID scheme, and
any message targeted to any object will be routed back via the connection
from which that object became known. BTW I don’t know if anyone else has
invented this scheme or patented it, but I do know that I now can’t :-).
It’s an exceptionally powerful distributed object paradigm.

The native GUI widgets should be assembled into hierarchies not by API calls,
but by a hierarchical description language - XML probably.
well, i disagree here. i thought about that option: whether the models
should be in markup versus executable code.

The executable code in OpenUI is nearly all in the form of “when” clauses,
which are the message handlers. They’re not used for non-UI things though,
in fact despite having a rich O-O language with a VM and fast compiler
interpreter (very similar to Java but on the market six years earlier),
it deliberately doesn’t have any local O/S access and no external extension
mechanism. You have to write message handlers in native code (C, Pascal, Ada,
COBOL), which are registered at the session level on either the front or
back end.

I’m interested to know more about OpenUI and if you should decide that you
could contribute to the project please let me know.

Hopefully I’ve satisfied some of your curiosity.

Probably can’t contribute much more at this stage, but someday I do want to
rebuild OpenUI’s features, using someone else’s XP GUI toolkit and preferably
someone else’s compiler/interpreter. The async messaging and distributed
object system I expect to need to rebuild myself, though many vaguely similar
things exist.

tkxml.rb

Will play when I get time.

···


Clifford Heath

Steve Tuckner wrote:

I think I said this before, but (just in case), the problem that the
FreeRIDE project had (and still has) finding an adequate Ruby GUI toolkit
that simultaneously satisfied the need for both native integration and
internationalization. Of course other features are important,
too, but these
were the two big ones that just couldn’t be found in one package.

Curt

What about WX-Windows. There is no ruby bindings for it, but
would that fit
the bill?

Steve Tuckner

Yes, WX-Windows would have been a good candidate for FreeRIDE if it had Ruby
bindings.

Back in January (I think it was January), Laurent Julliard did a quick
analysis of various GUI toolkits for the FreeRIDE project, which included
WX-Windows. You can see the results here:

rubyide.org

Curt

What about WX-Windows. There is no ruby bindings for it, but would that fit
the bill?

hi steve,

wxWindows is a good gui tookit, no doubt. but this clip from its webpage
makes it unsuitable for out purposes:

“wxWindows is not a translator from one GUI from another; it cannot take
a Motif application and generate a Windows application, for example. You
need to learn a new API. However, the wxWindows API has been praised for
its intuitiveness and simplicity, and can be far easier to learn and use
than a native GUI API such as Motif or Windows. Porting from MFC is
particularly easy due to its similarity: one user has ported his CASE
tool from MFC to wxWindows in a couple of weeks.”

we require something common, i.e one api for all platforms. the Rouge
project sought to do this by translating to native guis where possible,
allowing for extension in the other cases. this translation is a
duanting task, if even reasonably possible. i purpose instead the use of
a themeable gui engine that can thus mimic the look and feel of the
native guis. this is a much more doable endeavor.

thanks for the suggestion though. i wish wxWindows could do the job. but
alas it cannot. did you have a chance to look at ClanLib. it is my hope
that this library will be suitable. i’ve studied in for awhile and it
looks quite promising.

~transami

···

On Wed, 2002-07-24 at 08:39, Steve Tuckner wrote:

Steve Tuckner

-----Original Message-----
From: Curt Hibbs [mailto:curt@hibbs.com]
Sent: Wednesday, July 24, 2002 9:23 AM
To: ruby-talk ML
Subject: RE: GUI’s and the Rouge, Part III (yes, finally) 1/2

Tom Sawyer wrote:

[snip]

it was stated that a main goal of Rouge was to create a cross-compatible
gui api that “boiled down” into the native gui of each platform. in my
first post on these matters, i pointed out how ungodly difficult that
would be (as i had been trying to do that myself). in the end either you
left to “patch” your app for each platform or you simply have to give up
the features that they hold in common (at best). it really is simply too
much of pain to create such an system, not to mention maintaining it as
those underlying native api change. yet the goal os a native look and
feel is not unachievable by other means. the solution is simply a matter
of using an underlying gui engine that is sufficiantly themeable. since
all the major gui’s are now themable themselves, we simply need to code
a theme manager that can import the themes of those other guis. we need
not use their underlying api, only their look and feel! so that’s what
can be done to solve that dilemma: use a sufficantly themeable gui api
and crreat and Themes and Behaviors Manger to emulate the look and feel
and even draw upon the native theme configuration files. this is a much
managable task then trying to channel down to the native guis
themselves. the only trick of course is finding that themeable gui api.

This issue wasn’t so much native look-and-feel as it was native integration
(its not the same). Native integration means that the widgets have the same
behavior as native widgets (usually, because they are native widgets).
Sean Russell made a pretty clear case for this in this posting:

http://blade.nagaokaut.ac.jp/cgi-bin/scat.rb/ruby/ruby-talk/32029

Sean also mentioned SWT from the Eclipse project, which does this for
Java:

SWT: The Standard Widget Toolkit

I think I said this before, but (just in case), the problem that the
FreeRIDE project had (and still has) finding an adequate Ruby GUI toolkit
that simultaneously satisfied the need for both native integration and
internationalization. Of course other features are important, too, but these
were the two big ones that just couldn’t be found in one package.

Curt


~transami

let me paint a picture:

you have this super application you wrote --the underbelly of an
factastic hoogy-me-whatsy-do-it-all app and now you have to figure out
how to put a front-end on her.

I have never written such a program, that is, an “application” that I decided,
after the fact, that I need a GUI for. The nearest to that, that I can
remember, is the mainframe batch programs I wrote in the '60s and '70s. User
interface was limited to printing reports and keypunching input data.

I cannot imagine a GUI app where the GUI is an “addon”. All of my GUI apps
were GUI-centric. I design the GUI first. Most often my GUI apps are
nothing but widgets with callbacks: existing data is presented to the user
and then the user drives the process.

What drives your application? data, user events, realtime events, what?

> as for plain old Model-View-Controller, honestly, i have no idea what > that even means.

Ah hah! I think I know what the problem is now.

···

On Tuesday 23 July 2002 10:25 pm, Tom Sawyer wrote:

Tom Sawyer transami@transami.net wrote in message news:1027481479.853.582.camel@silver

let me paint a picture:

you have this super application you wrote --the underbelly of an
factastic hoogy-me-whatsy-do-it-all app and now you have to figure out
how to put a front-end on her. so you need a gui. hmmm…you think, i
want a gui thats flexible and fast nad highly functional, runs on just
about any platform, looks good, preferably just like the native
interface, and is easy as pie to code so as not to require much fussing
with the app you’ve alredy written.

What are the relative priorities of these requirements?

well, if you look around you’re going to discover that everything out
there now has shortcomings. beleive me i’ve looked. so what do you do?
you either pick one that minimizes the shortcomings as suited to your
application, or you do what i’m trying to do: put an end to the problem.
how does that bode for what i’m trying to achieve?

I don’t think it will ever be possible to ‘put an end to the problem’.
The problem is that interface code, whether it’s html, gui, whatever,
has it’s own set of objects with their own hierarchies, etc. Matching
the interface relationships to your existing object relationships will
always be a mess.

This is where I like your x.widgitize() idea – it’s a great pattern
for integrating the presentation code into your objects. This pattern
requires you to discard notions about the “separation of
presentation”, and instead follow rules such as “put functionality
near the required data”. I’m just not sure if it makes a good
library, b/c as I said before, x.widgetize() benefits from providing
generalized application specific widgets.

~ Patrick

figures. as soon as post this i figure it out. never mind.

···

On Wed, 2002-07-24 at 19:37, Tom Sawyer wrote:

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 :wink: 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 :slight_smile:


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 :slight_smile:

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


~transami

But Massimiliano, isn’t the view part mostly automated anyway? I don’t know
about other platforms but I thought they all had things like PowerPlant and
Interface Builder.

···

On 7/24/02 8:54 AM, “Massimiliano Mirra” list@NOSPAMchromatic-harp.com wrote:

On Wed, Jul 24, 2002 at 10:13:37AM +0900, Albert Wagner wrote:

Still looks like plain old Model-View-Controller to me. You’ve split the
controller into client and server parts. What is the purpose of separation
into different processes? I don’t mean to sound critical. I really don’t
understand what you are trying to achieve.

I think he’s trying to make the generation of the View and Controller
parts mostly automatic.


As an adolescent I aspired to lasting fame, I craved factual certainty, and
I thirsted for a meaningful vision of human life - so I became a scientist.
This is like becoming an archbishop so you can meet girls. -Matt Cartmill,
anthropology professor and author (1943- )

Tom Sawyer wrote:

What about WX-Windows. There is no ruby bindings for it, but
would that fit
the bill?

hi steve,

wxWindows is a good gui tookit, no doubt. but this clip from its webpage
makes it unsuitable for out purposes:

“wxWindows is not a translator from one GUI from another; it cannot take
a Motif application and generate a Windows application, for example. You
need to learn a new API. However, the wxWindows API has been praised for
its intuitiveness and simplicity, and can be far easier to learn and use
than a native GUI API such as Motif or Windows. Porting from MFC is
particularly easy due to its similarity: one user has ported his CASE
tool from MFC to wxWindows in a couple of weeks.”

we require something common, i.e one api for all platforms. the Rouge
project sought to do this by translating to native guis where possible,
allowing for extension in the other cases. this translation is a
duanting task, if even reasonably possible. i purpose instead the use of
a themeable gui engine that can thus mimic the look and feel of the
native guis. this is a much more doable endeavor.

I cannot claim that I have done a detailed analysis, but I wouldn’t assume
that the work needed to mimic a look-and-feel is less work than using native
widgets and filling in missing functionality. The graphics side (themes) is
straighforward, but only one part. There is still the behavior side to
consider (which is also a significant amount of work).

Anyway, I’m not saying I know the answer, only that it should be considered
before jumping to any conclusions.

thanks for the suggestion though. i wish wxWindows could do the job. but
alas it cannot. did you have a chance to look at ClanLib. it is my hope
that this library will be suitable. i’ve studied in for awhile and it
looks quite promising.

I looked briefly at ClanLib (not enough to give you any real feedback), but
I do have some concerns about font handling and internationalization.
ClanLib seems to implement its own font handling that is completely
disconnected from the underlying system, and their does not appear to be any
consideration for internationalization.

Curt

···

On Wed, 2002-07-24 at 08:39, Steve Tuckner wrote:

Hi Tom,

Wednesday, July 24, 2002, 8:49:41 AM, you wrote:

wxWindows is a good gui tookit, no doubt. but this clip from its webpage
makes it unsuitable for out purposes:

“wxWindows is not a translator from one GUI from another; it cannot take
a Motif application and generate a Windows application, for example. You
need to learn a new API. However, the wxWindows API has been praised for
its intuitiveness and simplicity, and can be far easier to learn and use
than a native GUI API such as Motif or Windows. Porting from MFC is
particularly easy due to its similarity: one user has ported his CASE
tool from MFC to wxWindows in a couple of weeks.”

I seem to read that quote from wxWindows differently than you do. I
think what they’re saying is that “it cannot take
a Motif application [written without using wxWindows] and
[automatically] generate a Windows application”. But if the
application is written using the wxWindows API, then it can be
moved from one platform to another.

we require something common, i.e one api for all platforms.

This is exactly what wxWindows provides. I used wxWindows a bit about
a year ago. I was only programming on Windows, but my understanding
is that I could have taken my code and recompiled it on Linux or one
of the other supported platforms, and it would have run there with
(essentially) no changes.

I think wxWindows deserves a closer look. It seems mature and stable,
has a fairly large and energetic group of people enhancing it, and
many programmer-years of effort have been expended on it. I
especially like that it has been around about 10 years, and has wide
support, so it’s unlikely to go away anytime soon.

Wayne Vucenic
No Bugs Software
C++/Ruby Contract Programming in Silicon Valley

What are the relative priorities of these requirements?

off hand i would say:

  1. multi-platform
  2. native integration (either direct or mimiced)
  3. easy to code
  4. speed and flexibiity

#3 of course will be taken care of by the GUtoIa API.

I don’t think it will ever be possible to ‘put an end to the problem’.

watch me :wink:

The problem is that interface code, whether it’s html, gui, whatever,
has it’s own set of objects with their own hierarchies, etc. Matching
the interface relationships to your existing object relationships will
always be a mess.

i believe my implementaion of using intermediary bindings will fix this
mess.

This is where I like your x.widgitize() idea – it’s a great pattern
for integrating the presentation code into your objects. This pattern
requires you to discard notions about the “separation of
presentation”, and instead follow rules such as “put functionality
near the required data”. I’m just not sure if it makes a good
library, b/c as I said before, x.widgetize() benefits from providing
generalized application specific widgets.

i too find the x.widgetize() idea compelling, but also beleive you are
right. it does not ultimately make for a good way of dealing with the
problem. conversely, i think a seperation of concerns is far from a
notion to be discarded. in my work thus far on the project, i have found
proper use of SOC to be much more powerful then my original x.wigitize()
notions.

~transami

···

On Wed, 2002-07-24 at 11:28, Patrick May wrote:

Albert Wagner wrote:

I have never written such a program

I have. Using OpenUI, you can first define the content of a set of messages
that will be sent between the application and the GUI, then you can code
either or both independently. An anecdote, but first, I must point out that
I’m not spruiking for a product - you can’t buy this product any more.

A customer had a team of eight Oracle programmers in Sydney, and a team of
UI programmers in Melbourne whose only experience with OpenUI was the
three-day training course. We got one developer from each team to meet with
one of our blokes for two days and they agreed a set of messages between
the presentation (UI in OpenUI) and semantic (C + Oracle) layers. The tech
leads returned to their teams, and spent three months developing the
application, testing the two parts separately against stubs. When the time
came to glue the two together, the GUI was emailed up to Sydney on the Friday,
and we had our bloke booked on a plane to fly up on the Monday afternoon
and spend three weeks making it work. The Sydney crowd phoned at 11AM
Monday to say “Don’t come, it’s already working”. Even we were blown away.
40,000 lines of C and Oracle application worked first time against a GUI
of forty windows.

You can do presentation-semantic independent application design if you
have the right tools. However… call-back-driven widget sets prevent most
people from ever discovering this. You need procedural logic on both sides
of the split, but you need a layer that makes the location of the split
highly evident - the messaging layer did this.

···


Clifford Heath

Uhm, I don’t know those, what are they?

Also, I don’t know if we’re referring to the same thing. With `view
part mostly automated’ do you mean GUI handling or GUI generation?

BTW, the only time I provided a ruby app with a GUI was through
`window = Gtk::Window.new’ etc., and it wasn’t the most exciting thing
in the world to do. I knew about libglade, but laying out with a
mouse isn’t that much better than laying out with a keyboard,
especially for a console junkie like me ;^). And oddly enough, I
don’t miss the 2+ years of GUI design in Borland Delphi (where I was
even writing my own widgets).

I guess I just lack the mental cycles needed to follow up each change
in the model with a change in the GUI, thus the idea idea that some
code might does most of it for me is appealing. I also use
DocBook/SGML and LaTeX for writing, where you create the meaning and
`it’ creates the appearance, instead of word processors, so this must
be a personal trend. :slight_smile:

Massimiliano

···

On Thu, Jul 25, 2002 at 11:17:48AM +0900, Chris Gehlker wrote:

I think he’s trying to make the generation of the View and Controller
parts mostly automatic.

But Massimiliano, isn’t the view part mostly automated anyway? I don’t know
about other platforms but I thought they all had things like PowerPlant and
Interface Builder.

I cannot claim that I have done a detailed analysis, but I wouldn’t assume
that the work needed to mimic a look-and-feel is less work than using native
widgets and filling in missing functionality. The graphics side (themes) is
straighforward, but only one part. There is still the behavior side to
consider (which is also a significant amount of work).

yes, i’ve been thinking about this. personally, i don’t think that the
behaviors should be too difficult. a little tricky, yes, and they may
not be perfect, but i think they could be close enough. on the other
hand, the more difficult task will be integrating with native
clipboards, another resposibility of the Themes and Behaviors Manager. I
read sean’s post concerning these issues. it is a debate that will have
to be hashed out. we should consider though that a gui engine like
ClanLib’s gives us much more then a basic set of GUI widgets. like i
said, it is a full blown game sdk. (not that i personally need such a
thing, but all the better) once the wiki’s up, we can begin the debate
on this and figure out which direction is best.

I looked briefly at ClanLib (not enough to give you any real feedback), but
I do have some concerns about font handling and internationalization.
ClanLib seems to implement its own font handling that is completely
disconnected from the underlying system, and their does not appear to be any
consideration for internationalization.

i am attempting to contact the ClanLib mailing list about this, but the
email address dosen’t seem to work for me. the email address is:
clanlib-user.x.dtu.dk; it dosen’t have an @ sign, and evolution is
rejecting it as invalid. so i am unsure about this. i also looked into
the font support a little. ClanLib supports TTF fonts via FreeType, and
FreeType looked promising, mentioning unicode support. but i am unsure.
perhaps you know about this?

~transami

···

On Wed, 2002-07-24 at 11:15, Curt Hibbs wrote:

Curt


~transami

I seem to read that quote from wxWindows differently than you do. I
think what they’re saying is that “it cannot take
a Motif application [written without using wxWindows] and
[automatically] generate a Windows application”. But if the
application is written using the wxWindows API, then it can be
moved from one platform to another.

i may indeed stand corrected.

I think wxWindows deserves a closer look.

i will look into it more.

soon i plan to establish an “official” debate to hash out the best means
of underlying implementation. i encourage you to join in.

thanks,
~transami

we require something common, i.e one api for all platforms.

This is exactly what wxWindows provides. I used wxWindows a bit about
a year ago. I was only programming on Windows, but my understanding
is that I could have taken my code and recompiled it on Linux or one
of the other supported platforms, and it would have run there with
(essentially) no changes.

It seems mature and stable,

···

On Wed, 2002-07-24 at 12:31, Wayne Vucenic wrote:

has a fairly large and energetic group of people enhancing it, and
many programmer-years of effort have been expended on it. I
especially like that it has been around about 10 years, and has wide
support, so it’s unlikely to go away anytime soon.

Wayne Vucenic
No Bugs Software
C++/Ruby Contract Programming in Silicon Valley


~transami

Curt, i’ve registered with savannah. i’ll let you know about the success
of that. by the way i used the LGPL. do you think that is the best
choice?

~transami

p.s. tried to mail you direct but the email came back.