[QUIZ] Object Browser (#8)

The three rules of Ruby Quiz:

1. Please do not post any solutions or spoiler discussion for this quiz until
48 hours have passed from the time on this message.

2. Support Ruby Quiz by submitting ideas as often as you can:

http://www.grayproductions.net/ruby_quiz/

3. Enjoy!

···

-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=

by Jim Menard

Recently on ruby-talk, itsme123 asked if there was a generic object browser that
will "interactively browse a graph of connected objects by showing their
instance variables and letting me click through to browse".

The quiz challenge: write such a browser. It should be able to start at any
object or, if none is given to it, start at the main object ("self" at the top
level of any Ruby script).

The interface to the browser can be text-based or graphical.

I'm thinking of something like the Squeak Explorer (the new inspector). It's a
window that displays the object with an open/close triangle next to it. Click
the triangle, and the ivars are exposed.

  V root: an OrderedCollection(a MyClass, a Number)
     V 1: a MyClass
      > name: 'the name'
      > anotherIvar: 42
     > 2: a Number

That's just one possible UI, of course.

Bonus points for allowing modification of instance variable values and for
allowing inspection of classes (remember, classes are objects, too!).

I'd suggest looking at the design docs for Omnibrowser
http://www.wiresong.ca/OmniBrowser/, a flexible object browser which uses
explicit meta-objects to configure things like what messages to send the
object being browsed. Class-based meta-objects could simply show all
instance variables; but other meta-objects could do fancier and more
selective browsing.

And if anyone wants to try a graphical view of an object graph (invaluable
at times) ruby-graphviz painlessly gives a png/gif drawing of an object
graph, even a clickable image-map for browsing.

Imho, either of these would be great. Some smart combination of the two
would be dynamite!

"Ruby Quiz" <james@grayproductions.net> wrote in message
news:20041119140626.XVOZ14730.lakermmtao03.cox.net@localhost.localdomain...

The three rules of Ruby Quiz:

1. Please do not post any solutions or spoiler discussion for this quiz

until

···

48 hours have passed from the time on this message.

2. Support Ruby Quiz by submitting ideas as often as you can:

Gray Soft / Not Found

3. Enjoy!

-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=

-=-=-=

by Jim Menard

Recently on ruby-talk, itsme123 asked if there was a generic object

browser that

will "interactively browse a graph of connected objects by showing their
instance variables and letting me click through to browse".

The quiz challenge: write such a browser. It should be able to start at

any

object or, if none is given to it, start at the main object ("self" at the

top

level of any Ruby script).

The interface to the browser can be text-based or graphical.

I'm thinking of something like the Squeak Explorer (the new inspector).

It's a

window that displays the object with an open/close triangle next to it.

Click

the triangle, and the ivars are exposed.

V root: an OrderedCollection(a MyClass, a Number)
   V 1: a MyClass
    > name: 'the name'
    > anotherIvar: 42
   > 2: a Number

That's just one possible UI, of course.

Bonus points for allowing modification of instance variable values and for
allowing inspection of classes (remember, classes are objects, too!).

Well, I was kind of waiting to see what other people came up with, but since the list seems quiet on this topic, I guess I'll go ahead and post first.

This is a VERY rough implementation. It uses ruby-gtk2, and is one of my first projects using that interface, so I've doubtless done all kinds of things wrong. :slight_smile: But it works.

By default, it displays the "main" object. You can see the class, superclass, instance/class variables, public/private/protected methods, and constants (where any of them apply and are non-empty).

I wanted to add the ability to modify values, but didn't quite have time to get that far.

This was a great quiz, though. I'd love to see a more sophisticated version of this. I can use mine, for instance, to do a kind of breakpoint in my code:

   ObjectBrowser.browse( @foo )

And the program will stop, display the window, and wait for the window to close before proceeding.

Anyway. Comments?

- Jamis

object-browser.rb (7.2 KB)

···

--
Jamis Buck
jgb3@email.byu.edu
http://www.jamisbuck.org/jamis

Hello Group,

Thanks for the quiz. I always wanted to learn more about the reflection capabilites of ruby, and indeed there is quite a lot to learn. This quiz was not too complicated, but the design of a good gui takes a lot of time. (Especially if you're not accustomed to the toolkit).

I implemented a gnome2/gtk version. I did not use code from the other solution, but the next step will be to see what I can borrow.

I submit now, because I've already invested too much time. The code is not beautifull and could need a heavy facelift.

The difference to the other solution is, that I'm starting with a class-tree from which you can get to all the objects. I think the right pane in my solution is more or less what was specified in the quiz.

You can see the code and screenshots at:

http://ruby.brian-schroeder.de/quiz/object_browser/

Regards,

Brian

···

--
Brian Schröder
http://www.brian-schroeder.de/

I'm new to using gems. Can you tell me the command I need to run to get ruby-gtk2 so I can run your code?

···

----- Original Message ----- From: "Jamis Buck" <jgb3@email.byu.edu>
To: "ruby-talk ML" <ruby-talk@ruby-lang.org>
Sent: Sunday, November 21, 2004 6:48 PM
Subject: [SOLUTION] Object Browser (#8)

Well, I was kind of waiting to see what other people came up with, but
since the list seems quiet on this topic, I guess I'll go ahead and post
first.

This is a VERY rough implementation. It uses ruby-gtk2, and is one of my
first projects using that interface, so I've doubtless done all kinds of
things wrong. :slight_smile: But it works.

By default, it displays the "main" object. You can see the class,
superclass, instance/class variables, public/private/protected methods,
and constants (where any of them apply and are non-empty).

I wanted to add the ability to modify values, but didn't quite have time
to get that far.

This was a great quiz, though. I'd love to see a more sophisticated
version of this. I can use mine, for instance, to do a kind of
breakpoint in my code:

  ObjectBrowser.browse( @foo )

And the program will stop, display the window, and wait for the window
to close before proceeding.

Anyway. Comments?

- Jamis

--
Jamis Buck
jgb3@email.byu.edu
http://www.jamisbuck.org/jamis

--------------------------------------------------------------------------------

require 'gtk2'

DEFAULT_OBJECTBROWSER_ROOT = self

class Object
alias :pre_objbrowser_inspect :inspect
def inspect
   result = pre_objbrowser_inspect
   result = $1 + " ...>" if result =~ /^(#<.*?:0x\w+) /
   result
end
end

module ObjectBrowser

def browse( root = DEFAULT_OBJECTBROWSER_ROOT )
   Interface.new( root ).display_and_wait
end
module_function :browse

class Interface
   def initialize( root = DEFAULT_OBJECTBROWSER_ROOT )
     @root = root
     Gtk.init
   end

   def display
     window = Window.new( @root )
     window.show_all
   end

   def display_and_wait
     display
     wait
   end

   def wait
     Gtk.main
   end
end

class Window < Gtk::Window
   OBJECT = 1
   CLASS = 2
   INSTANCE_VARS = 3
   PUBLIC_METHODS = 4
   PROTECTED_METHODS = 5
   PRIVATE_METHODS = 6
   CLASS_VARS = 7
   CONSTANTS = 8
   SUPERCLASS = 9
   STRING = 10
   INSTANCE_METHODS = 11

   LABEL = 0
   TYPE = 1
   REF = 2

   def initialize( root )
     super( Gtk::window::TOPLEVEL )

     signal_connect "delete_event", &method( :on_delete )
     signal_connect "destroy", &method( :on_destroy )

     vbox = Gtk::VBox.new
     add(vbox)

     pane = Gtk::VPaned.new
     vbox.add pane

     sw = Gtk::ScrolledWindow.new
     sw.set_policy *[Gtk::POLICY_AUTOMATIC]*2
     sw.shadow_type = Gtk::SHADOW_IN
     pane.add sw

     @model = Gtk::TreeStore.new( String, Integer, Integer )
     add_node( nil, root )

     @tree = Gtk::TreeView.new( @model )
     @tree.set_size_request -1, 400

     renderer = Gtk::CellRendererText.new

     col = Gtk::TreeViewColumn.new( "Data", renderer )
     col.set_cell_data_func renderer, &method( :on_cell_render )

     @tree.append_column col
     @tree.expand_row Gtk::TreePath.new( "0" ), false

     @tree.signal_connect "row_expanded", &method( :on_row_expanded )

     sw.add @tree

     sw = Gtk::ScrolledWindow.new
     sw.set_policy *[Gtk::POLICY_AUTOMATIC]*2
     sw.shadow_type = Gtk::SHADOW_IN
     pane.add sw

     @text = Gtk::TextView.new
     sw.add @text

     set_default_size 650, 500
   end

   def on_delete( widget, event )
     false
   end

   def on_destroy( widget )
     Gtk.main_quit
   end

   def on_cell_render( c, r, m, i )
     case i[TYPE]
       when OBJECT
         obj = ObjectSpace._id2ref( i[REF].to_i )
         r.text = "#{i[LABEL]}#{obj.inspect}"
       when CLASS, SUPERCLASS
         obj = ObjectSpace._id2ref( i[REF].to_i )
         r.text = "#{i[LABEL]} #{obj.name}"
       else
         r.text = i[LABEL]
     end
   end

   def on_row_expanded( widget, iter, path )
     unless iter.first_child[LABEL]
       case iter[1]
         when OBJECT, CLASS, SUPERCLASS then
           obj = ObjectSpace._id2ref( iter[REF].to_i )
           add_node iter, obj, iter.first_child
         when INSTANCE_VARS then
           obj = ObjectSpace._id2ref( iter.parent[REF].to_i )
           initialize_vars_list( obj, iter, obj.instance_variables.sort,
             :instance_variable_get )
         when PUBLIC_METHODS then
           obj = ObjectSpace._id2ref( iter.parent[REF].to_i )
           initialize_methods_list( obj, iter, obj.public_methods(false).sort )
         when PROTECTED_METHODS then
           obj = ObjectSpace._id2ref( iter.parent[REF].to_i )
           initialize_methods_list( obj, iter,
             obj.protected_methods(false).sort )
         when PRIVATE_METHODS then
           obj = ObjectSpace._id2ref( iter.parent[REF].to_i )
           initialize_methods_list( obj, iter,
             obj.private_methods(false).sort )
         when INSTANCE_METHODS then
           obj = ObjectSpace._id2ref( iter.parent[REF].to_i )
           initialize_methods_list( obj, iter,
             obj.instance_methods(false).sort, true )
         when CLASS_VARS then
           obj = ObjectSpace._id2ref( iter.parent[REF].to_i )
           initialize_vars_list( obj, iter,
             obj.class_variables.sort, :class_eval )
         when CONSTANTS then
           obj = ObjectSpace._id2ref( iter.parent[REF].to_i )
           constants = obj.constants
           if obj.respond_to?(:superclass) && obj.superclass
             constants = constants - obj.superclass.constants
           end
           initialize_vars_list( obj, iter, constants.sort, :const_get )
         else
           raise "don't know what to do with row of type #{iter[TYPE]}"
       end
     end

     path_str = iter.path.to_s + ":" + ( iter.n_children - 1 ).to_s
     path = Gtk::TreePath.new( path_str )

     @tree.scroll_to_cell( path, nil, true, 1.0, 0 )
   end

   def add_node( parent, object, node=nil )
     unless node
       node = add_row( parent, "", object, OBJECT, false )
       add_row( node, "class", object.class, CLASS )
     else
       add_row( parent, "class", object.class, CLASS, true, node )
       node = parent
     end

     if object.is_a?( Module )
       if object.respond_to?(:superclass) && object.superclass
         add_row( node, "extends", object.superclass, SUPERCLASS )
       end
       add_row_unless_empty(
         object.class_variables, node, "Class Variables", CLASS_VARS )

       constants = object.constants
       if object.respond_to?(:superclass) && object.superclass
         constants = constants - object.superclass.constants
       end

       add_row_unless_empty( constants, node, "Constants", CONSTANTS )
       add_row_unless_empty( object.instance_methods(false), node,
         "Instance Methods", INSTANCE_METHODS )
     end

     add_row_unless_empty( object.instance_variables, node,
       "Instance Variables", INSTANCE_VARS )
     add_row_unless_empty( object.public_methods(false), node,
       "Public Methods", PUBLIC_METHODS )
     add_row_unless_empty( object.protected_methods(false), node,
       "Protected Methods", PROTECTED_METHODS )
     add_row_unless_empty( object.private_methods(false), node,
       "Private Methods", PRIVATE_METHODS )

     node
   end

   def add_row_unless_empty( list, node, name, type, add_empty=true )
     unless list.empty?
       summary = list.sort.join( "," )
       summary = summary[0,60] + "..." if summary.length > 63
       add_row( node, "#{name} (#{summary})", nil, type )
     end
   end

   def add_row( parent, label, value, type, add_empty=true, node=nil )
     node = @model.append( parent ) unless node

     node[ LABEL ] = label
     node[ TYPE ] = type
     node[ REF ] = value.object_id

     @model.append( node ) if add_empty

     node
   end

   def initialize_methods_list( obj, iter, list, instance=false )
     node = iter.first_child
     list.each do |item|
       if instance
         method = obj.instance_method( item.to_sym )
       else
         method = obj.method( item.to_sym )
       end
       add_row iter, item + "(#{method.arity})", obj, STRING, false, node
       node = nil
     end
   end

   def initialize_vars_list( obj, iter, list, message )
     node = iter.first_child
     list.each do |item|
       value = obj.__send__( message, item )
       add_row iter, "#{item}=", value, OBJECT, true, node
       node = nil
     end
   end
end

end

if __FILE__ == $0
@obj = ObjectBrowser::Interface.new
@obj.display_and_wait
end

Well, I was kind of waiting to see what other people came up with, but since the list seems quiet on this topic, I guess I'll go ahead and post first.

[snip description]

Anyway. Comments?

Yes. Would you mind posting a few screenshots, for those of us having trouble getting past the interface requirements?

James Edward Gray II

···

On Nov 21, 2004, at 6:48 PM, Jamis Buck wrote:

That is *really* nice. Very impressive. Kudos.

-Charlie

···

On Nov 22, 2004, at 4:26 PM, Brian Schröder wrote:

Hello Group,

Thanks for the quiz. I always wanted to learn more about the reflection capabilites of ruby, and indeed there is quite a lot to learn. This quiz was not too complicated, but the design of a good gui takes a lot of time. (Especially if you're not accustomed to the toolkit).

I implemented a gnome2/gtk version. I did not use code from the other solution, but the next step will be to see what I can borrow.

I submit now, because I've already invested too much time. The code is not beautifull and could need a heavy facelift.

The difference to the other solution is, that I'm starting with a class-tree from which you can get to all the objects. I think the right pane in my solution is more or less what was specified in the quiz.

You can see the code and screenshots at:

http://ruby.brian-schroeder.de/quiz/object_browser/

I've been unfortunately, very busy this weekend and haven't had time to play with the quiz. (I promise to get back in the game on the next one!)

However, I have to take a moment to say... Wow. Very nice job Brian. I'm impressed.

James Edward Gray II

···

On Nov 22, 2004, at 6:26 PM, Brian Schröder wrote:

Hello Group,

Thanks for the quiz. I always wanted to learn more about the reflection capabilites of ruby, and indeed there is quite a lot to learn. This quiz was not too complicated, but the design of a good gui takes a lot of time. (Especially if you're not accustomed to the toolkit).

Brian, this is very cool....very very cool! Thanks for making this viewable online w/screenshots as well.

Zach

Brian Schröder wrote:

···

Hello Group,

Thanks for the quiz. I always wanted to learn more about the reflection capabilites of ruby, and indeed there is quite a lot to learn. This quiz was not too complicated, but the design of a good gui takes a lot of time. (Especially if you're not accustomed to the toolkit).

I implemented a gnome2/gtk version. I did not use code from the other solution, but the next step will be to see what I can borrow.

I submit now, because I've already invested too much time. The code is not beautifull and could need a heavy facelift.

The difference to the other solution is, that I'm starting with a class-tree from which you can get to all the objects. I think the right pane in my solution is more or less what was specified in the quiz.

You can see the code and screenshots at:

http://ruby.brian-schroeder.de/quiz/object_browser/

Regards,

Brian

* Brian Schröder <ruby@brian-schroeder.de> [2004-11-23 09:26:11 +0900]:

I particularly liked the clean colorization of the ruby code. What did you use
(and what color style) to convert the Ruby code to HTML?

···

--
Jim Freeze
Code Red. Code Ruby

So I took some time and refactored my solution. It now has a modular and extendible structure (at least I hope so). It should be possible to easily write non-gtk ui's and extend the reporting capabilities.

The code is at the same location as before
http://ruby.brian-schroeder.de/quiz/object_browser/

The screenshots are not updated.

It now is able to do more or less exactly the same as before, but the code has changed a lot. I could not let code as bad as the previous solution exist under my name ;).

Missing things:
- look at jamis solution and merge interesting parts.
- Polish the ui
- Inlcude Breakpoint support.

I hope that I can spare some hours this weekend to accomplish this.

I hope i do not "get charged per email" ;), and nobody will hate me because I put code into the public domain without the capabilities and spare time to support it ;).

Regards,

Brian Schröder

···

On Tue, 23 Nov 2004 09:26:11 +0900 Brian Schröder <ruby@brian-schroeder.de> wrote:

Hello Group,

Thanks for the quiz. I always wanted to learn more about the reflection capabilites of ruby, and indeed there is quite a lot to learn. This quiz was not too complicated, but the design of a good gui takes a lot of time. (Especially if you're not accustomed to the toolkit).

I implemented a gnome2/gtk version. I did not use code from the other solution, but the next step will be to see what I can borrow.

I submit now, because I've already invested too much time. The code is not beautifull and could need a heavy facelift.

The difference to the other solution is, that I'm starting with a class-tree from which you can get to all the objects. I think the right pane in my solution is more or less what was specified in the quiz.

You can see the code and screenshots at:

http://ruby.brian-schroeder.de/quiz/object_browser/

Regards,

Brian

--
Brian Schröder
http://www.brian-schroeder.de/

--
Brian Schröder
http://www.brian-schroeder.de/

Hello Jamis,

I'm polishing my solution, and I'm wondering why you are storing object id's instead of object references in the tree? I'd imagine that storing the object reference takes the same amount of space as storing the id. Is it maybe because objects can't be GCed while they are stored in the tree? On the other hand, wouldn't GCing break your program?

Regards,

Brian

PS: I uploaded a new, usable version of the object browser to the quiz homepage. If anybody gives it a try i'd be happy to hear of suggestions.

···

On Mon, 22 Nov 2004 09:48:37 +0900 Jamis Buck <jgb3@email.byu.edu> wrote:

Anyway. Comments?

--
Brian Schröder
http://ruby.brian-schroeder.de/quiz/

R. Mark Volkmann wrote:

I'm new to using gems. Can you tell me the command I need to run to get ruby-gtk2 so I can run your code?

Unfortunately, ruby-gtk2 is not a gem, nor is it in rpa. So you have to install it the "hard" way--from source. If you're on Windows, it's even harder: you have to install GTK2 first.

   GTK2: http://www.gtk.org
   Ruby-GTK2 (part of Ruby-Gnome2): http://ruby-gnome2.sourceforge.jp/

Having never used GTK in Windows, I have no idea how easy/hard it is to get ruby-gtk2 running under Windows. YMMV. YHBW.

- Jamis

P.S.: A plea to the ruby-gnome2 folks: a gemmable version would be a real boon. A similar plea to the RPA folks... :wink:

···

----- Original Message ----- From: "Jamis Buck" <jgb3@email.byu.edu>
To: "ruby-talk ML" <ruby-talk@ruby-lang.org>
Sent: Sunday, November 21, 2004 6:48 PM
Subject: [SOLUTION] Object Browser (#8)

Well, I was kind of waiting to see what other people came up with, but
since the list seems quiet on this topic, I guess I'll go ahead and post
first.

This is a VERY rough implementation. It uses ruby-gtk2, and is one of my
first projects using that interface, so I've doubtless done all kinds of
things wrong. :slight_smile: But it works.

By default, it displays the "main" object. You can see the class,
superclass, instance/class variables, public/private/protected methods,
and constants (where any of them apply and are non-empty).

I wanted to add the ability to modify values, but didn't quite have time
to get that far.

This was a great quiz, though. I'd love to see a more sophisticated
version of this. I can use mine, for instance, to do a kind of
breakpoint in my code:

  ObjectBrowser.browse( @foo )

And the program will stop, display the window, and wait for the window
to close before proceeding.

Anyway. Comments?

- Jamis

--
Jamis Buck
jgb3@email.byu.edu
http://www.jamisbuck.org/jamis

--------------------------------------------------------------------------------

require 'gtk2'

DEFAULT_OBJECTBROWSER_ROOT = self

class Object
alias :pre_objbrowser_inspect :inspect
def inspect
   result = pre_objbrowser_inspect
   result = $1 + " ...>" if result =~ /^(#<.*?:0x\w+) /
   result
end
end

module ObjectBrowser

def browse( root = DEFAULT_OBJECTBROWSER_ROOT )
   Interface.new( root ).display_and_wait
end
module_function :browse

class Interface
   def initialize( root = DEFAULT_OBJECTBROWSER_ROOT )
     @root = root
     Gtk.init
   end

   def display
     window = Window.new( @root )
     window.show_all
   end

   def display_and_wait
     display
     wait
   end

   def wait
     Gtk.main
   end
end

class Window < Gtk::Window
   OBJECT = 1
   CLASS = 2
   INSTANCE_VARS = 3
   PUBLIC_METHODS = 4
   PROTECTED_METHODS = 5
   PRIVATE_METHODS = 6
   CLASS_VARS = 7
   CONSTANTS = 8
   SUPERCLASS = 9
   STRING = 10
   INSTANCE_METHODS = 11

   LABEL = 0
   TYPE = 1
   REF = 2

   def initialize( root )
     super( Gtk::window::TOPLEVEL )

     signal_connect "delete_event", &method( :on_delete )
     signal_connect "destroy", &method( :on_destroy )

     vbox = Gtk::VBox.new
     add(vbox)

     pane = Gtk::VPaned.new
     vbox.add pane

     sw = Gtk::ScrolledWindow.new
     sw.set_policy *[Gtk::POLICY_AUTOMATIC]*2
     sw.shadow_type = Gtk::SHADOW_IN
     pane.add sw

     @model = Gtk::TreeStore.new( String, Integer, Integer )
     add_node( nil, root )

     @tree = Gtk::TreeView.new( @model )
     @tree.set_size_request -1, 400

     renderer = Gtk::CellRendererText.new

     col = Gtk::TreeViewColumn.new( "Data", renderer )
     col.set_cell_data_func renderer, &method( :on_cell_render )

     @tree.append_column col
     @tree.expand_row Gtk::TreePath.new( "0" ), false

     @tree.signal_connect "row_expanded", &method( :on_row_expanded )

     sw.add @tree

     sw = Gtk::ScrolledWindow.new
     sw.set_policy *[Gtk::POLICY_AUTOMATIC]*2
     sw.shadow_type = Gtk::SHADOW_IN
     pane.add sw

     @text = Gtk::TextView.new
     sw.add @text

     set_default_size 650, 500
   end

   def on_delete( widget, event )
     false
   end

   def on_destroy( widget )
     Gtk.main_quit
   end

   def on_cell_render( c, r, m, i )
     case i[TYPE]
       when OBJECT
         obj = ObjectSpace._id2ref( i[REF].to_i )
         r.text = "#{i[LABEL]}#{obj.inspect}"
       when CLASS, SUPERCLASS
         obj = ObjectSpace._id2ref( i[REF].to_i )
         r.text = "#{i[LABEL]} #{obj.name}"
       else
         r.text = i[LABEL]
     end
   end

   def on_row_expanded( widget, iter, path )
     unless iter.first_child[LABEL]
       case iter[1]
         when OBJECT, CLASS, SUPERCLASS then
           obj = ObjectSpace._id2ref( iter[REF].to_i )
           add_node iter, obj, iter.first_child
         when INSTANCE_VARS then
           obj = ObjectSpace._id2ref( iter.parent[REF].to_i )
           initialize_vars_list( obj, iter, obj.instance_variables.sort,
             :instance_variable_get )
         when PUBLIC_METHODS then
           obj = ObjectSpace._id2ref( iter.parent[REF].to_i )
           initialize_methods_list( obj, iter, obj.public_methods(false).sort )
         when PROTECTED_METHODS then
           obj = ObjectSpace._id2ref( iter.parent[REF].to_i )
           initialize_methods_list( obj, iter,
             obj.protected_methods(false).sort )
         when PRIVATE_METHODS then
           obj = ObjectSpace._id2ref( iter.parent[REF].to_i )
           initialize_methods_list( obj, iter,
             obj.private_methods(false).sort )
         when INSTANCE_METHODS then
           obj = ObjectSpace._id2ref( iter.parent[REF].to_i )
           initialize_methods_list( obj, iter,
             obj.instance_methods(false).sort, true )
         when CLASS_VARS then
           obj = ObjectSpace._id2ref( iter.parent[REF].to_i )
           initialize_vars_list( obj, iter,
             obj.class_variables.sort, :class_eval )
         when CONSTANTS then
           obj = ObjectSpace._id2ref( iter.parent[REF].to_i )
           constants = obj.constants
           if obj.respond_to?(:superclass) && obj.superclass
             constants = constants - obj.superclass.constants
           end
           initialize_vars_list( obj, iter, constants.sort, :const_get )
         else
           raise "don't know what to do with row of type #{iter[TYPE]}"
       end
     end

     path_str = iter.path.to_s + ":" + ( iter.n_children - 1 ).to_s
     path = Gtk::TreePath.new( path_str )

     @tree.scroll_to_cell( path, nil, true, 1.0, 0 )
   end

   def add_node( parent, object, node=nil )
     unless node
       node = add_row( parent, "", object, OBJECT, false )
       add_row( node, "class", object.class, CLASS )
     else
       add_row( parent, "class", object.class, CLASS, true, node )
       node = parent
     end

     if object.is_a?( Module )
       if object.respond_to?(:superclass) && object.superclass
         add_row( node, "extends", object.superclass, SUPERCLASS )
       end
       add_row_unless_empty(
         object.class_variables, node, "Class Variables", CLASS_VARS )

       constants = object.constants
       if object.respond_to?(:superclass) && object.superclass
         constants = constants - object.superclass.constants
       end

       add_row_unless_empty( constants, node, "Constants", CONSTANTS )
       add_row_unless_empty( object.instance_methods(false), node,
         "Instance Methods", INSTANCE_METHODS )
     end

     add_row_unless_empty( object.instance_variables, node,
       "Instance Variables", INSTANCE_VARS )
     add_row_unless_empty( object.public_methods(false), node,
       "Public Methods", PUBLIC_METHODS )
     add_row_unless_empty( object.protected_methods(false), node,
       "Protected Methods", PROTECTED_METHODS )
     add_row_unless_empty( object.private_methods(false), node,
       "Private Methods", PRIVATE_METHODS )

     node
   end

   def add_row_unless_empty( list, node, name, type, add_empty=true )
     unless list.empty?
       summary = list.sort.join( "," )
       summary = summary[0,60] + "..." if summary.length > 63
       add_row( node, "#{name} (#{summary})", nil, type )
     end
   end

   def add_row( parent, label, value, type, add_empty=true, node=nil )
     node = @model.append( parent ) unless node

     node[ LABEL ] = label
     node[ TYPE ] = type
     node[ REF ] = value.object_id

     @model.append( node ) if add_empty

     node
   end

   def initialize_methods_list( obj, iter, list, instance=false )
     node = iter.first_child
     list.each do |item|
       if instance
         method = obj.instance_method( item.to_sym )
       else
         method = obj.method( item.to_sym )
       end
       add_row iter, item + "(#{method.arity})", obj, STRING, false, node
       node = nil
     end
   end

   def initialize_vars_list( obj, iter, list, message )
     node = iter.first_child
     list.each do |item|
       value = obj.__send__( message, item )
       add_row iter, "#{item}=", value, OBJECT, true, node
       node = nil
     end
   end
end

end

if __FILE__ == $0
@obj = ObjectBrowser::Interface.new
@obj.display_and_wait
end

.

--
Jamis Buck
jgb3@email.byu.edu
http://www.jamisbuck.org/jamis

James Edward Gray II wrote:

Well, I was kind of waiting to see what other people came up with, but since the list seems quiet on this topic, I guess I'll go ahead and post first.

[snip description]

Anyway. Comments?

Yes. Would you mind posting a few screenshots, for those of us having trouble getting past the interface requirements?

James Edward Gray II

Here are three screenshots. (It's really nothing special to look
at--like I said, it needs a lot more attention than I can afford to give
it right now.)

http://ruby.jamisbuck.org/objbrowser-01.png
   This is how it looks when you first bring it up.

http://ruby.jamisbuck.org/objbrowser-02.png
   This is the same scenario, with the instance variables expanded.

http://ruby.jamisbuck.org/objbrowser-03.png
   This is the same, drilling down to inspect the constants defined in
the class of one of the instance variables.

I think something like this (maybe used in conjunction with the
breakpoint and assert concepts that have been discussed on this list)
would be useful in debugging, especially if the ability to modify values
were added. I'm still hoping someone submits a nicer solution to the
quiz than mine, since mine is pretty rough, and is really only half done. :slight_smile:

- Jamis

···

On Nov 21, 2004, at 6:48 PM, Jamis Buck wrote:

--
Jamis Buck
jgb3@email.byu.edu
http://www.jamisbuck.org/jamis

I use xemacs' htmlize package to convert the ruby-code to html. Then I include a stylesheet. (You can copy that from my page if you want)

The important snippets of the Makefile I use:

htmlize: $(patsubst %.rb,browse/%-rb.html,$(wildcard *.rb))

browse/%-rb.html: %.rb
  mkdir -p browse/unsuccessfull
  xemacs -nw -eval '(htmlize-file "$<" "$@")' -kill && \
  mv $@ $@.temp && \
  cat $@.temp | ruby -e 'puts $$stdin.read.gsub(/<style type="text\/css">.*<\/style>/m, "<link id=\"css\" href=\"../ruby.css\" rel=\"stylesheet\">")' > $@ && \
  rm $@.temp

Regards,

Brian

···

On Wed, 24 Nov 2004 04:26:17 +0900 jim@freeze.org wrote:

* Brian Schröder <ruby@brian-schroeder.de> [2004-11-23 09:26:11 +0900]:

I particularly liked the clean colorization of the ruby code. What did you use
(and what color style) to convert the Ruby code to HTML?

--
Brian Schröder
http://www.brian-schroeder.de/

So I took some time and refactored my solution. It now has a modular and extendible structure (at least I hope so). It should be possible to easily write non-gtk ui's and extend the reporting capabilities.

Brian, I'll kill ya! I just finished the summary about 15 minutes ago. <laughs>

Seriously, thanks for the update. I'm looking through it and it looks great. Just don't be too mad at me if the summary covers the original solution. :smiley:

Since we're talking...

I want to avoid embarrassing myself in the summary again by claiming to know your code better than you, so I'll embarrass myself in this thread with all of Ruby Talk looking on. See the question in the comment below:

def object_browser(classtree = ClassTreeNode.new(Kernel))
   ObjectSpace.each_object do | x |
     classnode = classtree # <- This line truly isn't needed, right???
     x.class.ancestors.reverse[1..-1].inject(classtree){ | classnode, klass | classnode.add_class(klass) }.add_object(x)
   end
   classtree
end

Tell me I'm right this time, even if you need to lie to me. It's good for my ego.

I hope i do not "get charged per email" ;), and nobody will hate me because I put code into the public domain without the capabilities and spare time to support it ;).

No, now you're in trouble for not properly respecting my schedule. You just can't win, really. :smiley:

James Edward Gray II

···

On Nov 24, 2004, at 6:28 PM, Brian Schröder wrote:

I'm concerned about the state of GUI toolkits for Ruby when in comes to
installation requirements, particularly under Mac OS X. I'm think about the
case where we want to create a Ruby-based GUI application for non-developers to
use.

I recently went through the steps to get Tk working with Ruby under Mac OS X.
It was fairly complicated. After all that work I found that GUIs I had created
under Windows didn't look very good on Mac OS X. I then decided to try FOX.
That required downloading the X11 software from Apple. I installed that and
then found that I didn't automatically get the X11 library files that are
needed in order to build FOX. Maybe I'll get this figured out eventually.

The bottom line is that even if you take the time to write a great GUI app.
using Ruby, your audience of potential users will likely be quite small.
Installing Ruby is easy enough, but setting up a GUI toolkit is prohibitively
complex for a non-developer. Compare this to Java. Install the Java Runtime
Environment or use Java Web Start and Swing is available.

One answer is to create web apps instead. That's not appropriate for every
application though.

Do others think this is an issue?

···

--
R. Mark Volkmann
Partner, Object Computing, Inc.

Brian Schröder wrote:

···

On Mon, 22 Nov 2004 09:48:37 +0900 > Jamis Buck <jgb3@email.byu.edu> wrote:

Anyway. Comments?

Hello Jamis,

I'm polishing my solution, and I'm wondering why you are storing object id's instead of object references in the tree? I'd imagine that storing the object reference takes the same amount of space as storing the id. Is it maybe because objects can't be GCed while they are stored in the tree? On the other hand, wouldn't GCing break your program?

Well...quite possibly this was due to my massive inexperience with ruby-gtk2. When I tried to store objects directly in a node of a tree model, they kept being converted to strings, which rendered them useless. Since my time was limited, I didn't bother trying to figure out what the real problem was. :frowning:

- Jamis

--
Jamis Buck
jgb3@email.byu.edu
http://www.jamisbuck.org/jamis

Jamis Buck ha scritto:

Having never used GTK in Windows, I have no idea how easy/hard it is to get ruby-gtk2 running under Windows. YMMV. YHBW.

not hard, and actually your example runs fine :slight_smile:

- Jamis

P.S.: A plea to the ruby-gnome2 folks: a gemmable version would be a real boon. A similar plea to the RPA folks... :wink:

+1

Hi,

R. Mark Volkmann wrote:
> I'm new to using gems. Can you tell me the command I need to run to get
> ruby-gtk2 so I can run your code?

Unfortunately, ruby-gtk2 is not a gem, nor is it in rpa. So you have to
install it the "hard" way--from source. If you're on Windows, it's even
harder: you have to install GTK2 first.

   GTK2: http://www.gtk.org
   Ruby-GTK2 (part of Ruby-Gnome2): http://ruby-gnome2.sourceforge.jp/

Having never used GTK in Windows, I have no idea how easy/hard it is to
get ruby-gtk2 running under Windows. YMMV. YHBW.

See http://ruby-gnome2.sourceforge.jp/hiki.cgi?Install+Guide+for+Windows
You can install them just four steps, though it's not one click ;).

- Jamis

P.S.: A plea to the ruby-gnome2 folks: a gemmable version would be a
real boon. A similar plea to the RPA folks... :wink:

Good idea. Are there anyone to work for them ?

···

On Mon, 22 Nov 2004 11:38:36 +0900 Jamis Buck <jgb3@email.byu.edu> wrote:

--
.:% Masao Mutoh<mutoh@highway.ne.jp>