Gtk: Writing a CellRenderer in Ruby

C

Clifford Heath

(Repost from the ruby-gnome2 developers list, where there seems
to be no-one home)

Folk,

I've subclassed the CellRendererPixbuf to make a renderer that
has a "state" property that displays an appropriate icon for each
state, and I want to add mouse button event handlers to it.

Problem is, there doesn't seem to be any way to get the TreeView
object which is the necessary Widget. In the C API, the _GtkTreeView
object (in gtktreeview.h), there's a "parent" member, but no supported
way of accessing it. What am I missing? Is there some standard Gtk
method or property for getting the parent of a Gtk::Object? If not,
why not?

The code, such as it is, is below. The images for <state> are
simply loaded from <state.png>.

Clifford Heath.

module Gtk
class CellRendererIconSet < CellRendererPixbuf
type_register
install_property(GLib::param::String.new(
"state",
"state",
"The state of an IconSet",
"",
GLib::param::READABLE|GLib::param::WRITABLE))

def initialize(*args)
super()
@state = nil
@states = {}
if (args.size > 0)
states = args[0]
if (states.is_a? Hash)
@states = states
elsif (states.is_a? Array)
states.each{|s| @states = nil }
end
# More handling needed here...
end
# and here...
end

attr_reader :state

def state=(s)
throw "CellRendererIconSet: no such state #{s.inspect} in
#{@states}" if [email protected]?(s)
@state = s
self.pixbuf=(icon)
end

def icon(s = nil)
s ||= @state
puts "loading icon for #{s}" if (!@states)
# Needs a directory path from which to load here:
@states ||= Gdk::pixbuf.new(s.to_s.downcase+".png")
end
end
end

I want to move something like the following code into the class:


treeview.add_events(Gdk::Event::BUTTON_PRESS_MASK)
treeview.signal_connect("button_press_event") { |w, e|
path, column, cell_x, cell_y = treeview.get_path_at_pos(e.x,
e.y)

puts "button #{e.button} at (#{e.x}, #{e.y}) row #{path},
column #{column}, at (#{cell_x}, #{cell_y})"

next if column != self
on_click(path, e.button, e.state)
}

.... so I can then write (or similar with signals):

def on_click(path, button, state)
next if e.button != 3

# Get current state and calculate next state:
storeRow = store[path]
state = storeRow[Column::IconState]
newstate = States[(States.index(state)+1) % States.size]
storeRow[Column::IconState] = newstate
true
end
 
C

Clifford Heath

Can it really be true that no-one else has written a CellRenderer for Ruby/GTK?

Here's an updated description of the problem, with code that works.
Comments identify the two problem areas I'm trying to get answers to.

My earlier code contained this line:
tree.signal_connect("button_press_event") { |w, e|

The problem is that inside CellRendererIconSet, it's not possible
to get the "tree" object in order to call signal_connect in the
first place. I have to pass the tree widget to the new renderer
in a separate call, as in the following revised version. It looks
pretty clean, but the extra "set_tree" call is bad - it should
be possible to do this in the constructor without having to pass
an additional argument (which isn't what the attached code does).

It seems to me that when the CellRenderer is associated with a
Column, it should receive a callback telling it the column object
and the tree object, so it can attach to the tree's widget then.

The further problem is that when an event does occur, I can find
out what column it's on, but can't disregard it if it occurred on
another column, because I can't tell what column the renderer is
on. That logic must go in the "click" handler, which is bad.

It's annoying that this is *almost nice*, but I can't see how to
properly finish it.

module Gtk
class CellRendererIconSet < CellRendererPixbuf
type_register
install_property(GLib::param::String.new(
"state",
"state",
"The state of an IconSet",
"",
GLib::param::READABLE|GLib::param::WRITABLE))

def initialize(*args)
super()
@state = nil
@states = {}
if (args.size > 0)
states = args[0]
if (states.is_a? Hash)
@states = states
elsif (states.is_a? Array)
states.each{|s| @states = nil }
end
end
end

# Register events for this Renderer:
signal_new("button_press_event", GLib::Signal::RUN_FIRST, nil, nil,
Gdk::EventButton, Gtk::TreePath, Gtk::TreeViewColumn,
Integer, Integer)

signal_new("button_release_event", GLib::Signal::RUN_FIRST, nil, nil,
Gdk::EventButton, Gtk::TreePath, Gtk::TreeViewColumn,
Integer, Integer)

signal_new("click", GLib::Signal::RUN_FIRST, nil, nil,
Gdk::EventButton, Gtk::TreePath, Gtk::TreeViewColumn,
Integer, Integer)

def signal_do_button_press_event(event, path, column, cell_x, cell_y)
# puts "press event #{event.inspect}, path #{path.inspect}"
end

def signal_do_button_release_event(event, path, column, cell_x, cell_y)
# puts "release event #{event.inspect}, path #{path.inspect}"
end

def signal_do_click(event, path, column, cell_x, cell_y)
# puts "click event #{event.inspect}, path #{path.inspect}"
end

def tree=(tree)
tree.add_events(Gdk::Event::BUTTON_PRESS_MASK|Gdk::Event::BUTTON_RELEASE_MASK)
armed_column = nil
tree.signal_connect("button_press_event") { |w, e|
path, column, cell_x, cell_y = tree.get_path_at_pos(e.x, e.y)
#puts "press #{e.button} at (#{e.x}, #{e.y}) row #{path}, column #{column}, at (#{cell_x}, #{cell_y})"

armed_column = column
signal_emit("button_press_event", e, path, column, cell_x, cell_y)
}
tree.signal_connect("button_release_event") { |w, e|
path, column, cell_x, cell_y = tree.get_path_at_pos(e.x, e.y)
#puts "release #{e.button} at (#{e.x}, #{e.y}) row #{path}, column #{column}, at (#{cell_x}, #{cell_y})"
cell_x ||= -1 # Can't be null
cell_y ||= -1
signal_emit("button_release_event", e, path, column, cell_x, cell_y)

if (column == armed_column)
signal_emit("click", e, path, column, cell_x, cell_y)
end
armed_column = nil
}
end

def state
@state
end

def state=(s)
throw "CellRendererIconSet: no such state #{s.inspect} in #{@states}" if [email protected]?(s)
@state = s
self.pixbuf=(icon)
end

def icon(s = nil)
s ||= @state
puts "loading icon for #{s}" if (!@states)
@states ||= Gdk::pixbuf.new(s.to_s.downcase+".png")
end
end
end


Then in the tree construction code, I put:

tree.append_column(
@iconColumn =
Gtk::TreeViewColumn.new(
"", # Heading
r = Gtk::CellRendererIconSet.new(States),
:state => Column::projectState # data
)
)
r.tree = tree # This call should be unnecessary!

And finally, in order to handle the click event, I add:

r.signal_connect("click") { |r, e, path, column, cell_x, cell_y|
next if column != @iconColumn # This should be unnecessary!
next if e.button != 3 # ignore if not right-click

# Handle the click by choosing the next icon in the States array:
storeRow = store[path]
state = storeRow[Column::projectState]
newstate = States[(States.index(state)+1) % States.size]
puts state+" -> "+newstate
storeRow[Column::projectState] = newstate
}

Does that make it clearer?

Clifford Heath.
 

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,995
Messages
2,570,226
Members
46,816
Latest member
nipsseyhussle

Latest Threads

Top