#!/usr/bin/env ruby # There are three modes of this demo: # Override: when a value is selected in a view and it happens to be selected in # the other it is unselected there. Hadled by selection_changed callback. # Exclude: a value selected in one view is 'disabled' in the other. Disabling # a value is achieved by setting select_func that forbids selecting a value # when it is selected in the other view. Visually the value is rendered # insensitive. The cellrenderer uses a boolean column in the model. There is # one for each view and the values are updated in the selection_changed # callback. # Dependent: here one view is 'master' and can select anything overriding # selection in 'dependent' view. Due to the way the glade callback with user # data works the changed callback of the master selection is called with the # 'dependent' view as user data resulting in method called on the 'dependent' # view with the changed 'master' selection as argument. The dependent view can # uselect its selection if it matches the master selection and save the current # master selection so that it can check it in select_func and disallow # selecting it. require 'gtk2' class Array def mapm_rev receiver, method self.map{|item| receiver.send method, item } end def mapm method, *args self.map{|item| item.send method, *args} end end def echo *strings STDERR.puts strings.join(' ') end class Gtk::TreeSelection # This is needed in exclusion mode and in dependent mode in the dependent view # Here selecting the row which is known to be selected in the other view is forbidden def can_select? path !@exclude or (@other_selected != path) end def selection_changed changed # This is kind of evil. In glade the user_data is the object on which # the signal callback is called. It is set to the *other* unchanged selection which then can note # the item that was selected and either deselect it or disable it as appropriate. echo "sel", *([(selected[0] rescue nil), (changed.selected[0] rescue nil)].mapm :inspect) # This handles the override case. Needed in both override mode and in dependent mode on the dependent column unselect_iter selected if selected and changed.selected and (selected.path == changed.selected.path) # This saves the 'pointer' ( ~ iter ~ path) of the value selected in the other selection. # Needed in exclusion mode and also in the dependent view in dependent mode. self.other_selected = changed.selected end def other_selected_iter return nil unless @other_selected tree_view.model.get_iter @other_selected end def other_selected_text iter = other_selected_iter other_selected_iter[0] if iter end # This sets the treeview rendering of the row as enabled/disabled def vis_enable_row value echo 'en', *([other_selected_text, value].mapm :inspect) other_selected_iter[@index] = value if @other_selected end def other_selected= iter echo 'sav', *([other_selected_text, (iter[0] rescue nil)].mapm :inspect) path = iter.path if iter if path != @other_selected then vis_enable_row true if @exclude # Enable previously selected row @other_selected = path # save selection vis_enable_row false if @exclude # and disable currently selected row end end def exclude= value echo 'exc', @index, value vis_enable_row !value #if @exclude != value # Would issue uninitialized variable warnings @exclude = value end def index= value # Kind of initialization of the exclusion mechanism # Sets the model column used for storing the 'disabled' state of rows # and initializes saved selection in the other list to nil @index = value @other_selected = nil end attr_reader :exclude, :index end class Dialog < Gtk::Window def selections_do (1..2).each{|n| yield @builder["treeview-selection#{n}"], n } end def initialize(path_or_data, root = nil, domain = nil, localedir = nil, flag = nil) super(Gtk::Window::TOPLEVEL) @window = nil gtkversion = [2, 12, 0] # 2.12 should have most recent features. Set to later if needed. available = Gtk.check_version? *gtkversion if available label = Gtk::Label.new("If you can see this message there was an error loading interface description #{path_or_data.inspect}.") else label = Gtk::Label.new("You need GTK+ >= #{gtkversion.join '.'} to run this application.") end add(label) return unless available @builder = Gtk::Builder.new @builder << path_or_data @builder.connect_signals {|name| method(name)} store = @builder['liststore'] (1...20).each{|n| i=store.insert(n) ; i[1] = i[2] = true ; i[0] = "Item %i" % (n % 10)} selections_do{|sel, n| sel.index = n sel.exclude = false sel.set_select_function{|selection, model, path, path_currentry_selected| path_currentry_selected or selection.can_select? path # Always allow deselecting, ask about selecting } } @window = @builder['window1'] end def selection_changed changed changed.tree_view.columns[0].title = (changed.selected[0] rescue nil) end def exclude_toggled button return unless button.active? selections_do{|sel, n| sel.exclude = true } end def override_toggled button return unless button.active? selections_do{|sel, n| sel.exclude = false } end def dependent_toggled button return unless button.active? selections_do{|sel, n| sel.exclude = (n == 2) } end def app_quit Gtk.main_quit end def show @window = self unless @window @window.show_all @window.signal_connect("destroy") { Gtk.main_quit } end end data=' False Tree View Sample True False 6 6 6 6 True False 12 True False 6 True True True True liststore False autosize Item 1 0 True True 0 True True True True liststore False autosize Item 2 0 True True 1 True True -1 True False spread Override True True False False True True False False 0 Exclude True True False False True True override False False 1 Dependent True True False False 0 True True override False True 2 False True 0 True False spread Close True True True True True False False False 6 0 False True 2 ' Dialog.new(data, nil, "Twin Tre View Demo").show Gtk.main