[SOLUTION] Object Browser (#8)

J

Jamis Buck

--------------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
(e-mail address removed)
http://www.jamisbuck.org/jamis

--------------080608050401040503050008
Content-Type: text/plain;
name="object-browser.rb"
Content-Transfer-Encoding: 7bit
Content-Disposition: inline;
filename="object-browser.rb"

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( :eek:n_delete )
signal_connect "destroy", &method( :eek:n_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( :eek:n_cell_render )

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

@tree.signal_connect "row_expanded", &method( :eek:n_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

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

Ask a Question

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

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

Ask a Question

Members online

Forum statistics

Threads
473,968
Messages
2,570,153
Members
46,701
Latest member
XavierQ83

Latest Threads

Top