require 'object_browser_ui'
require 'gtk2'
Thread.abort_on_exception = true
module ObjectBrowser
module UI
module Gtk
include ::Gtk
module Value
TYPE = 0
TEXT = 1
ADDITIONAL_TEXT = 2
OBJECT = 3
end
class FormatableTreeViewColumn < TreeViewColumn
def initialize(*args)
super(*args)
@formats = {}
set_format(:h1, Gtk::Stock::REMOVE, '#000000')
set_format(:h2, Gtk::Stock::REMOVE, '#111111')
set_format(:h3, Gtk::Stock::REMOVE, '#222222')
set_format(:variable, Gtk::Stock::SELECT_FONT, '#cd0000')
set_format(:constant, Gtk::Stock::BOLD, '#8b0000')
set_format(:method, Gtk::Stock::APPLY, '#8b2323')
set_format(:module, Gtk::Stock::YES, '#5500B4')
set_format(:class, Gtk::Stock::YES, '#0000c4')
set_format(:object, Gtk::Stock::NO, 'black')
set_default_format( Gtk::Stock::GO_FORWARD, 'black')
end
def set_format(type, *args)
@formats[type.to_sym] = args
end
def set_default_format(*args)
@formats.default = *args
end
def format(type)
@formats[type.to_sym]
end
end
class PixTextColumn < FormatableTreeViewColumn
def initialize(title = '', pix_index = 0, text_index = 1)
super(title)
pix = Gtk::CellRendererPixbuf.new
self.pack_start(pix, false)
set_cell_data_func(pix) do |tvc, cell, model, iter|
pix.stock_id = format(iter[Value::TYPE])[0]
end
text = Gtk::CellRendererText.new
self.pack_start(text, true)
set_cell_data_func(text) do |tvc, cell, model, iter|
text.text = iter[Value::TEXT]
text.foreground = format(iter[Value::TYPE])[1]
end
add_attribute(text, :text, text_index)
end
end
class TextColumn < FormatableTreeViewColumn
attr_reader :text_renderer
def initialize(title = '', text_index = 0)
super(title)
@text_renderer = Gtk::CellRendererText.new
self.pack_start(@text_renderer, true)
add_attribute(@text_renderer, :text, text_index)
end
end
class BrowserTree < TreeView
def initialize(model = Gtk::TreeStore.new(Symbol, String, String, Object))
super(model)
set_rules_hint(true)
column1 = PixTextColumn.new('How to call this?', Value::TYPE, Value::TEXT)
column2 = TextColumn.new('Additional', Value::ADDITIONAL_TEXT)
append_column(column1)
append_column(column2)
column1.resizable = true
column1.alignment = 0.5
column2.alignment = 0.5
end
def clear
self.model.clear
end
def insert_node(parent, type, text, additional = '', object = nil)
text = text.to_s
additional = additional.to_s if additional
iter = self.model.append(parent)
iter.set_value(Value::TYPE, type)
iter.set_value(Value::TEXT, text)
iter.set_value(Value::ADDITIONAL_TEXT, additional) if additional
iter.set_value(Value::OBJECT, object) if object
iter
end
def change_tree(*args)
begin
self.model.freeze_notify
yield(self, *args)
ensure
self.model.thaw_notify
end
self
end
end
class ClassBrowser < BrowserTree
def initialize(classtree, object, *args)
@object_selected = lambda{}
super(*args)
signal_connect('row-activated') do | treeview, path, column |
iter = self.model.get_iter(path)
@object_selected.call(iter[3], iter)
end
signal_connect('row-expanded') do | treeview, iter, path | beautify(iter) end
update_tree(classtree, object)
end
def on_object_selected(&callback)
@object_selected = callback
end
def update_tree(classtree, object = nil)
self.change_tree do
self.clear
self.fill_model(classtree, nil)
self.find_object(object)
end
end
def find_object(object)
iter = model.get_iter('0')
object.class.ancestors.reverse.each do | klass |
() while iter[Value::OBJECT] != klass and iter.next!
expand_row(iter.path, false)
iter = iter.first_child
end
() while iter[Value::OBJECT] != object and iter.next!
expand_row(iter.path, false)
set_cursor(iter.path, nil, nil)
iter.path
end
protected
def fill_model(classnode = ::ObjectBrowser::create_class_tree(), parent = nil)
if classnode.klass.is_a?Class
node = insert_node(parent, :class, classnode.klass.to_s, "#<#{classnode.klass.id.to_s(16)}>", classnode.klass)
elsif classnode.klass.is_a?Module
node = insert_node(parent, :module, classnode.klass.to_s, "#<#{classnode.klass.id.to_s(16)}>", classnode.klass)
else
raise 'This should not happen. Expected the class of classnode to be either a class or a module'
end
classnode.objects.each do | object |
insert_node(node, :object, "#<#{object.id.to_s(16)}>", "#<#{object.id.to_s(16)}>", object)
end
classnode.subclasses.to_a.sort_by{|k,s| k.to_s}.each do | klass, subclass | fill_model(subclass, node) end
end
protected
def beautify(iterator)
Thread.new(iterator) do | iter |
iter = iter.first_child
begin
object = iter[Value::OBJECT]
iter[Value::TEXT] = DEFAULT_DESCRIBER.display_string(object, 64).inspect[1..-2]
end while iter.next!
end
end
end
class ObjectTreeView < BrowserTree
attr_reader :show_empty, :show_methods, :show_state
def show_empty=(value) @show_empty = value; rebuild_tree; end
def show_methods=(value) @show_methods = value; rebuild_tree; end
def show_state=(value) @show_state = value; rebuild_tree; end
def initialize(object, *args)
super(*args)
@show_empty = false
@show_methods = true
@show_state = true
signal_connect('row-activated') do | tree, path, column |
if row_expanded?(path)
collapse_row(path, false)
else
expand_row(path, false)
end
end
signal_connect('row-expanded') do | tree_view, iter, path |
object = iter[Value::OBJECT]
child = iter.first_child
if !child[0] and object update_tree(object, iter)
self.model.remove(child) end
end
update_tree(object)
end
def update_tree(object, parent = nil)
self.change_tree do
self.clear unless parent
self.fill_model(object, parent)
expand_row(Gtk::TreePath.new("0"), false)
if iter = model.get_iter('0:0')
begin
expand_row(iter.path, false)
end while iter.next!
end
end
end
protected
def fill_model(object, parent = nil)
@object = object unless parent if parent
description_factory = GTKDescriptionFactory.new(self, parent)
else
description_factory = GTKDescriptionFactory.new(self, nil)
description_factory = case object
when Class: description_factory.add_section(:class, DEFAULT_DESCRIBER.short_description(object))
when Module: description_factory.add_section(:module, DEFAULT_DESCRIBER.short_description(object))
else description_factory.add_section(:object, DEFAULT_DESCRIBER.short_description(object))
end
end
DEFAULT_DESCRIBER.show_empty_sections = @show_empty
DEFAULT_DESCRIBER.show_methods = @show_methods
DEFAULT_DESCRIBER.show_state = @show_state
DEFAULT_DESCRIBER.describe(object, description_factory)
end
protected
def rebuild_tree
update_tree(@object)
end
end
class ObjectDescription < Gtk::TextView
def initialize(object = nil, *args)
super(*args)
update(object) if object
end
def update(object)
@object = object
self.buffer.text = DEFAULT_DESCRIBER.short_description(@object)
end
end
class ObjectBrowserWindow < Dialog
TOGGLE_EMPTY_SECTIONS = 1
TOGGLE_STATE = 2
TOGGLE_METHODS = 3
def initialize(object)
super("Object Browser for Ruby", nil,
MODAL|NO_SEPARATOR,
['Show/Hide Empty Entrys', TOGGLE_EMPTY_SECTIONS],
['Show/Hide State', TOGGLE_STATE],
['Show/Hide Methods', TOGGLE_METHODS],
[Gtk::Stock::QUIT, RESPONSE_CLOSE])
GC.start
classbrowser = ClassBrowser.new(::ObjectBrowser::create_class_tree(), object)
object_description = ObjectDescription.new(object)
object_browser = ObjectTreeView.new(object)
hpane = Gtk::HPaned.new()
vbox.add(hpane)
hpane.pack1(Gtk::ScrolledWindow.new.add(classbrowser), true, true)
vpane = Gtk::VPaned.new()
hpane.pack2(vpane, true, true)
vpane.pack1(object_description, false, true)
vpane.pack2(Gtk::ScrolledWindow.new.add(object_browser), true, true)
hpane.position = 260
classbrowser.on_object_selected do | object, iter | object_browser.update_tree(object); object_description.update(object) end
signal_connect(:response) do |widget, response|
case response
when TOGGLE_EMPTY_SECTIONS: object_browser.show_empty = !object_browser.show_empty
when TOGGLE_STATE: object_browser.show_state = !object_browser.show_state
when TOGGLE_METHODS: object_browser.show_methods = !object_browser.show_methods
when RESPONSE_CLOSE: destroy; Gtk.main_quit
end
end
self.show_all
end
end
def browse(object)
::Gtk.init
win = ObjectBrowserWindow.new(object).set_default_size(400, 400).show_all
::Gtk.main
end
class GTKDescriptionFactory < ObjectBrowser::UI::DescriptionFactory
def initialize(object_browser, parent)
@object_browser = object_browser
@parent = parent
end
def add(type, text, object = nil, additional = '')
result = self.class.new(@object_browser, node = @object_browser.insert_node(@parent, type, text, additional, object))
@object_browser.model.append(node) if object
result
end
def add_section(type, text)
add(type, text)
end
end
extend self
end
end
DEFAULT_DESCRIBER = Describer.new()
end