require 'gtk2'

class KeyBindingConfigurator < Gtk::Dialog
    
    # accessor for signal to key
    attr_reader :signal_to_key
    
    def initialize gtk_widget, item_list
        super()
        
        vbox.pack_start Gtk::Label.new("Key Binding Configuration"), false
        add_button(Gtk::Stock::OK,2)
        add_button(Gtk::Stock::CANCEL,3)

        @bindings = Array.new
        
        signal_connect("response") { |widget, button|
            case button
            when 2
                self.save_bindings
                self.bind_all
                self.hide
            when 3
                self.load_bindings
                self.hide
            end
        }
        signal_connect("destroy") { 
            self.hide
        }

        @gtk_widget = gtk_widget
        @latest_group = nil
        
        @scrolled_window = Gtk::ScrolledWindow.new
        @scrolled_window.set_policy(Gtk::POLICY_AUTOMATIC, Gtk::POLICY_AUTOMATIC)
        @table = Gtk::VBox.new        
        combobox = Gtk::Combo.new 
        combobox.set_popdown_strings( item_list.keys.sort )
        combobox.entry.editable = false
        
        new_entry = Gtk::Entry.new 
        # make this non editable
        new_entry.editable = false
        
        hbox = Gtk::HBox.new
        edit_button = Gtk::Button.new Gtk::Stock::FIND
        edit_button.signal_connect("clicked") { |widget|
            kc = KeyCatcher.new
            kc.show_all.run
            new_entry.text = kc.value
            
            self << 
                KeyBinding.new(combobox.entry.text, 
                    item_list[combobox.entry.text],
                    kc.value
                    )
            self.show_all
            kc.destroy
        }
        hbox.pack_start edit_button, false
        hbox.pack_start combobox,true
        hbox.pack_start new_entry,false
       
        @table.pack_start hbox, false
        
        @signal_map = Hash.new
        @bindings = Array.new
        @signal_to_key = Hash.new
        
        @scrolled_window.add_with_viewport(@table)
        @table.pack_end Gtk::Label.new(),true
        vbox.add(@scrolled_window)
        

    end
    def << keybinding
        @bindings << keybinding
        
        la = Gtk::Label.new keybinding.name
        en = Gtk::Label.new keybinding.value
        edit_button = Gtk::Button.new Gtk::Stock::FIND
        delbutton = Gtk::Button.new Gtk::Stock::DELETE
        
        hbox = Gtk::HBox.new
        hbox.pack_start edit_button, false 
        hbox.pack_start delbutton, false
        hbox.add la
        hbox.add en
        
        edit_button.signal_connect("clicked") { |widget|
            kc = KeyCatcher.new
            kc.show_all.run
            en.text = kc.value
            kc.destroy
        }
        delbutton.signal_connect("clicked") { |widget|
            @bindings.delete(keybinding) 
            hbox.destroy    
        }
        
        @table.pack_start hbox,false
        
        # update the correspondance tables.
        @signal_to_key[keybinding.signal] = keybinding.value
        @signal_map[keybinding.signal] = en
        
    end
    def show
        super()
    end
    def bind_all
        if @group != nil
            @gtk_widget.remove_accel_group( @group )
            @group = nil
        end

        @group = Gtk::AccelGroup.new
        callback_method = @gtk_widget.method(@callback)
        @bindings.each{ |b|
           @group.connect( 
                b.key,
                b.meta, 
                Gtk::ACCEL_VISIBLE) { 
                    callback_method.call(b.signal)
                }
        }
        
        @gtk_widget.add_accel_group( @group )
    end
    require 'yaml'
    # loads the binding from an external file
    attr_accessor :config_file
    def load_bindings default_shortcut=nil, file=@config_file
        raise "No Config file set" if @config_file==nil
        @default_shortcut ||= default_shortcut
        if FileTest.exists?(@config_file)
            content = YAML::load(File.open(file))
            
            content.each do |l|
                name,value,signal = l
                self << KeyBinding.new(name,signal,value)
            end
        end
        if @default_shortcut != nil && @signal_to_key[@default_shortcut.value]==nil
            self << @default_shortcut
        end
        bind_all
    end
    def save_bindings
        raise "No Config file set" if @config_file==nil
        begin
            config = Array.new()
            
            @bindings.each{ |binding|
                config << [binding.name,binding.value,binding.signal]
            }
            fw = File.open(@config_file,"w")
            fw.write config.to_yaml
            fw.close
        rescue Exception=>e
            puts "Exception occured while saving #{@config_file}: "+e.to_s
        end
    end
    def callback=(method)
        @callback = method
    end
end
class KeyBinding
    attr_reader :name, :value, :signal
    attr_reader :key, :meta
    def initialize name, signal, value
        @name = name
        @value = value
        @signal = signal
        array = Gtk::Accelerator.parse(value)
        @key = array[0]
        @meta = array[1]
        raise "InvalidKeyBinding: #{value}" if @key == nil || @meta == nil
    end
end


class KeyCatcher < Gtk::Dialog
    attr_accessor :value
    def initialize
        super
        vb = self.vbox #Gtk::VBox.new
        vb.add Gtk::Label.new("Press a shorcut key")
        hb = Gtk::HBox.new
        ctrl = Gtk::CheckButton.new("Control")
        alt = Gtk::CheckButton.new("Alt")
        shift = Gtk::CheckButton.new("Shift")
        hb.add ctrl
        hb.add alt
        hb.add shift
        vb.add hb
        current_shortcut = Gtk::Label.new("none")
        vb.add current_shortcut
        self.add_button(Gtk::Stock::CANCEL, 42)
        
        self.signal_connect("key-press-event") {|w,e|
            name = Gdk::Keyval.to_name(e.keyval)
            case name
            when /Shift/
                shift.active = true
            when /Alt/
                alt.active = true
            when /Control/
                ctrl.active = true
            else
                current_shortcut.text = name.downcase
                if shift.active? | alt.active? |ctrl.active? 
                    # we are ready to send the shortcut back
                    @value = ""
                    @value = "<Shift>" if shift.active?
                    @value = @value + "<Alt>" if alt.active?
                    @value = @value +  "<Ctrl>" if ctrl.active?
                    @value = @value + current_shortcut.text
                    self.response 1
                end
            end
        }
        self.signal_connect("key-release-event") {|w,e|
            name = Gdk::Keyval.to_name(e.keyval)
            case name
            when /Shift/
                shift.active = false
            when /Alt/
                alt.active = false
            when /Control/
                ctrl.active = false
            else
                current_shortcut.text = ""
                
            end
        }
    end
end

if __FILE__ == $0
    
    Gtk.init
    a = Gtk::Window.new
    kbc = KeyBindingConfigurator.new a, {
        'Goto next image'=> 'goto-next-image',
        'Goto previous image'=> 'goto-prev-image',
        'Quick quit'=> 'quick-quit',
        'Save current image'=> 'save-current-image',
    }

    # dummy callback   
    kbc.callback = "foo"
    kbc << KeyBinding.new("Next picture", "next-pic", "none")
    kbc << KeyBinding.new("Prev picture", "prev-pic", "none")
    
    kbc.show_all
    Gtk.main
end

