--------------080608050401040503050008
Content-Type: text/plain; charset=ISO-8859-1; format=flowed
Content-Transfer-Encoding: 7bit

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. :) 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

--------------080608050401040503050008
Content-Type: text/plain;
 namebject-browser.rb"
Content-Transfer-Encoding: 7bit
Content-Disposition: inline;
 filenamebject-browser.rb"

require 'gtk2'

DEFAULT_OBJECTBROWSER_ROOT  elf

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

module ObjectBrowser

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

  class Interface
    def initialize( root  EFAULT_OBJECTBROWSER_ROOT )
      @root  oot
      Gtk.init
    end

    def display
      window  indow.new( @root )
      window.show_all
    end

    def display_and_wait
      display
      wait
    end

    def wait
      Gtk.main
    end
  end

  class Window < Gtk::Window
    OBJECT  
    CLASS   
    INSTANCE_VARS  
    PUBLIC_METHODS  
    PROTECTED_METHODS  
    PRIVATE_METHODS  
    CLASS_VARS  
    CONSTANTS  
    SUPERCLASS  
    STRING  0
    INSTANCE_METHODS  1

    LABEL  
    TYPE   
    REF    

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

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

      vbox  tk::VBox.new
      add(vbox)

      pane  tk::VPaned.new
      vbox.add pane

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

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

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

      renderer  tk::CellRendererText.new

      col  tk::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  tk::ScrolledWindow.new
      sw.set_policy *[Gtk::POLICY_AUTOMATIC]*2
      sw.shadow_type  tk::SHADOW_IN
      pane.add sw

      @text  tk::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  bjectSpace._id2ref( i[REF].to_i )
          r.text  #{i[LABEL]}#{obj.inspect}"
        when CLASS, SUPERCLASS
          obj  bjectSpace._id2ref( i[REF].to_i )
          r.text  #{i[LABEL]} #{obj.name}"
        else
          r.text  [LABEL]
      end
    end

    def on_row_expanded( widget, iter, path )
      unless iter.first_child[LABEL]
        case iter[1]
          when OBJECT, CLASS, SUPERCLASS then
            obj  bjectSpace._id2ref( iter[REF].to_i )
            add_node iter, obj, iter.first_child
          when INSTANCE_VARS then
            obj  bjectSpace._id2ref( iter.parent[REF].to_i )
            initialize_vars_list( obj, iter, obj.instance_variables.sort,
              :instance_variable_get )
          when PUBLIC_METHODS then
            obj  bjectSpace._id2ref( iter.parent[REF].to_i )
            initialize_methods_list( obj, iter, obj.public_methods(false).sort )
          when PROTECTED_METHODS then
            obj  bjectSpace._id2ref( iter.parent[REF].to_i )
            initialize_methods_list( obj, iter,
              obj.protected_methods(false).sort )
          when PRIVATE_METHODS then
            obj  bjectSpace._id2ref( iter.parent[REF].to_i )
            initialize_methods_list( obj, iter,
              obj.private_methods(false).sort )
          when INSTANCE_METHODS then
            obj  bjectSpace._id2ref( iter.parent[REF].to_i )
            initialize_methods_list( obj, iter,
              obj.instance_methods(false).sort, true )
          when CLASS_VARS then
            obj  bjectSpace._id2ref( iter.parent[REF].to_i )
            initialize_vars_list( obj, iter,
              obj.class_variables.sort, :class_eval )
          when CONSTANTS then
            obj  bjectSpace._id2ref( iter.parent[REF].to_i )
            constants  bj.constants
            if obj.respond_to?(:superclass) && obj.superclass
              constants  onstants - 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  ter.path.to_s + ":" + ( iter.n_children - 1 ).to_s
      path  tk::TreePath.new( path_str )

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

    def add_node( parent, object, nodel )
      unless node
        node  dd_row( parent, "", object, OBJECT, false )
        add_row( node, "class", object.class, CLASS )
      else
        add_row( parent, "class", object.class, CLASS, true, node )
        node  arent
      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  bject.constants
        if object.respond_to?(:superclass) && object.superclass
          constants  onstants - 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_emptyue )
      unless list.empty?
        summary  ist.sort.join( "," )
        summary  ummary[0,60] + "..." if summary.length > 63
        add_row( node, "#{name} (#{summary})", nil, type )
      end
    end

    def add_row( parent, label, value, type, add_emptyue, nodel )
      node  model.append( parent ) unless node

      node[ LABEL ]  abel
      node[ TYPE ]  ype
      node[ REF ]  alue.object_id

      @model.append( node ) if add_empty

      node
    end

    def initialize_methods_list( obj, iter, list, instanceł±se )
      node  ter.first_child
      list.each do |item|
        if instance
          method  bj.instance_method( item.to_sym )
        else
          method  bj.method( item.to_sym )
        end
        add_row iter, item + "(#{method.arity})", obj, STRING, false, node
        node  il
      end
    end

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

end

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

--------------080608050401040503050008--