From: Clifford Heath Date: 2006-10-20T09:25:05+09:00 Subject: Re: Gtk: Writing a CellRenderer in Ruby 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[s] = 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 !@states.include?(s) @state = s self.pixbuf=(icon) end def icon(s = nil) s ||= @state puts "loading icon for #{s}" if (!@states[s]) @states[s] ||= 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.