ruby--ruby/ext/tk/sample/tkcombobox.rb

498 lines
13 KiB
Ruby

#
# tkcombobox.rb : auto scrollbox & combobox
#
# by Hidetoshi NAGAI (nagai@ai.kyutech.ac.jp)
#
require 'tk'
module Tk
module RbWidget
class AutoScrollListbox < TkListbox
end
class Combobox < TkEntry
end
end
end
class Tk::RbWidget::AutoScrollListbox
include TkComposite
@@up_bmp = TkBitmapImage.new(:data=><<EOD)
#define up_arrow_width 9
#define up_arrow_height 9
static unsigned char up_arrow_bits[] = {
0x00, 0x00, 0x10, 0x00, 0x10, 0x00, 0x38, 0x00, 0x38, 0x00, 0x7c, 0x00,
0x7c, 0x00, 0xfe, 0x00, 0x00, 0x00};
EOD
@@down_bmp = TkBitmapImage.new(:data=><<EOD)
#define up_arrow_width 9
#define up_arrow_height 9
static unsigned char down_arrow_bits[] = {
0x00, 0x00, 0xfe, 0x00, 0x7c, 0x00, 0x7c, 0x00, 0x38, 0x00, 0x38, 0x00,
0x10, 0x00, 0x10, 0x00, 0x00, 0x00};
EOD
############################
private
############################
def initialize_composite(keys={})
keys = _symbolkey2str(keys)
@initwait = keys.delete('startwait'){300}
@interval = keys.delete('interval'){150}
@initwait -= @interval
@initwait = 0 if @initwait < 0
@lbox = TkListbox.new(@frame, :borderwidth=>0)
@path = @lbox.path
TkPack.propagate(@lbox, false)
@scr = TkScrollbar.new(@frame, :width=>10)
@lbox.yscrollcommand(proc{|*args| @scr.set(*args); _config_proc})
@scr.command(proc{|*args| @lbox.yview(*args); _config_proc})
@up_arrow = TkLabel.new(@lbox, :image=>@@up_bmp,
:relief=>:raised, :borderwidth=>1)
@down_arrow = TkLabel.new(@lbox, :image=>@@down_bmp,
:relief=>:raised, :borderwidth=>1)
_init_binding
@lbox.pack(:side=>:left, :fill=>:both, :expand=>:true)
delegate('DEFAULT', @lbox)
delegate('background', @frame, @scr)
delegate('activebackground', @scr)
delegate('troughcolor', @scr)
delegate('repeatdelay', @scr)
delegate('repeatinterval', @scr)
delegate('relief', @frame)
delegate('borderwidth', @frame)
delegate_alias('arrowrelief', 'relief', @up_arrow, @down_arrow)
delegate_alias('arrowborderwidth', 'borderwidth', @up_arrow, @down_arrow)
scrollbar(keys.delete('scrollbar')){false}
configure keys unless keys.empty?
end
def _show_up_arrow
unless @up_arrow.winfo_mapped?
@up_arrow.pack(:side=>:top, :fill=>:x)
end
end
def _show_down_arrow
unless @down_arrow.winfo_mapped?
@down_arrow.pack(:side=>:bottom, :fill=>:x)
end
end
def _set_sel(idx)
@lbox.activate(idx)
@lbox.selection_clear(0, 'end')
@lbox.selection_set(idx)
end
def _check_sel(cidx, tidx = nil, bidx = nil)
_set_sel(cidx)
unless tidx
tidx = @lbox.nearest(0)
tidx += 1 if tidx > 0
end
unless bidx
bidx = @lbox.nearest(10000)
bidx -= 1 if bidx < @lbox.index('end') - 1
end
if cidx > bidx
_set_sel(bidx)
end
if cidx < tidx
_set_sel(tidx)
end
end
def _up_proc
cidx = @lbox.curselection[0]
idx = @lbox.nearest(0)
if idx >= 0
@lbox.see(idx - 1)
_set_sel(idx)
@up_arrow.pack_forget if idx == 1
@up_timer.stop if idx == 0
_show_down_arrow if @lbox.bbox('end') == []
end
if cidx && cidx > 0 && (idx == 0 || cidx == @lbox.nearest(10000))
_set_sel(cidx - 1)
end
end
def _down_proc
cidx = @lbox.curselection[0]
eidx = @lbox.index('end') - 1
idx = @lbox.nearest(10000)
if idx <= eidx
@lbox.see(idx + 1)
_set_sel(cidx + 1) if cidx < eidx
@down_arrow.pack_forget if idx + 1 == eidx
@down_timer.stop if idx == eidx
_show_up_arrow if @lbox.bbox(0) == []
end
if cidx && cidx < eidx && (eidx == idx || cidx == @lbox.nearest(0))
_set_sel(cidx + 1)
end
end
def _key_UP_proc
cidx = @lbox.curselection[0]
_set_sel(cidx = @lbox.index('activate')) unless cidx
cidx -= 1
if cidx == 0
@up_arrow.pack_forget
elsif cidx == @lbox.nearest(0)
@lbox.see(cidx - 1)
end
end
def _key_DOWN_proc
cidx = @lbox.curselection[0]
_set_sel(cidx = @lbox.index('activate')) unless cidx
cidx += 1
if cidx == @lbox.index('end') - 1
@down_arrow.pack_forget
elsif cidx == @lbox.nearest(10000)
@lbox.see(cidx + 1)
end
end
def _config_proc
if @lbox.size == 0
@up_arrow.pack_forget
@down_arrow.pack_forget
return
end
tidx = @lbox.nearest(0)
bidx = @lbox.nearest(10000)
if tidx > 0
_show_up_arrow
tidx += 1
else
@up_arrow.pack_forget unless @up_timer.running?
end
if bidx < @lbox.index('end') - 1
_show_down_arrow
bidx -= 1
else
@down_arrow.pack_forget unless @down_timer.running?
end
cidx = @lbox.curselection[0]
_check_sel(cidx, tidx, bidx) if cidx
end
def _init_binding
@up_timer = TkAfter.new(@interval, -1, proc{_up_proc})
@down_timer = TkAfter.new(@interval, -1, proc{_down_proc})
@up_timer.set_start_proc(@initwait, proc{})
@down_timer.set_start_proc(@initwait, proc{})
@up_arrow.bind('Enter', proc{@up_timer.start})
@up_arrow.bind('Leave', proc{@up_timer.stop if @up_arrow.winfo_mapped?})
@down_arrow.bind('Enter', proc{@down_timer.start})
@down_arrow.bind('Leave', proc{@down_timer.stop if @down_arrow.winfo_mapped?})
@lbox.bind('Configure', proc{_config_proc})
@lbox.bind('Enter', proc{|y| _set_sel(@lbox.nearest(y))}, '%y')
@lbox.bind('Motion', proc{|y|
@up_timer.stop if @up_timer.running?
@down_timer.stop if @down_timer.running?
_check_sel(@lbox.nearest(y))
}, '%y')
@lbox.bind('Up', proc{_key_UP_proc})
@lbox.bind('Down', proc{_key_DOWN_proc})
end
############################
public
############################
def scrollbar(mode)
if mode
@scr.pack(:side=>:right, :fill=>:y)
else
@scr.pack_forget
end
end
end
################################################
class Tk::RbWidget::Combobox < TkEntry
include TkComposite
@@down_btn_bmp = TkBitmapImage.new(:data=><<EOD)
#define down_arrow_width 11
#define down_arrow_height 11
static unsigned char down_arrow_bits[] = {
0x00, 0x00, 0xfe, 0x03, 0xfc, 0x01, 0xfc, 0x01, 0xf8, 0x00, 0xf8, 0x00,
0x70, 0x00, 0x70, 0x00, 0x20, 0x00, 0x20, 0x00, 0x00, 0x00};
EOD
@@up_btn_bmp = TkBitmapImage.new(:data=><<EOD)
#define up_arrow_width 11
#define up_arrow_height 11
static unsigned char up_arrow_bits[] = {
0x00, 0x00, 0x20, 0x00, 0x20, 0x00, 0x70, 0x00, 0x70, 0x00, 0xf8, 0x00,
0xf8, 0x00, 0xfc, 0x01, 0xfc, 0x01, 0xfe, 0x03, 0x00, 0x00};
EOD
def _button_proc(dir = true)
return if @ent.state == 'disabled'
@btn.relief(:sunken)
x = @frame.winfo_rootx
y = @frame.winfo_rooty
if dir
@top.geometry("+#{x}+#{y + @frame.winfo_height}")
else
@btn.image(@@up_btn_bmp)
@top.geometry("+#{x}+#{y - @top.winfo_reqheight}")
end
@top.deiconify
@lst.focus
if (idx = values.index(@ent.value))
@lst.see(idx - 1)
@lst.activate(idx)
@lst.selection_set(idx)
elsif @lst.size > 0
@lst.see(0)
@lst.activate(0)
@lst.selection_set(0)
end
@top.grab
begin
@wait_var.tkwait
if (idx = @wait_var.to_i) >= 0
# @ent.value = @lst.get(idx)
_set_entry_value(@lst.get(idx))
end
@top.withdraw
@btn.relief(:raised)
@btn.image(@@down_btn_bmp)
rescue
ensure
begin
@top.grab(:release)
@ent.focus
rescue
end
end
end
private :_button_proc
def _init_bindings
@btn.bind('1', proc{_button_proc(true)})
@btn.bind('3', proc{_button_proc(false)})
@lst.bind('1', proc{|y| @wait_var.value = @lst.nearest(y)}, '%y')
@lst.bind('Return', proc{@wait_var.value = @lst.curselection[0]})
cancel = TkVirtualEvent.new('2', '3', 'Escape')
@lst.bind(cancel, proc{@wait_var.value = -1})
end
private :_init_bindings
def _set_entry_value(val)
@ent.textvariable.value = val
end
private :_set_entry_value
#----------------------------------------------------
def _state_control(value = None)
if value == None
# get
@ent.state
else
# set
@ent.state(value.to_s)
case value = @ent.state # regulate 'state' string
when 'normal', 'readonly'
@btn.state 'normal'
when 'disabled'
@btn.state 'disabled'
else
# unknown : do nothing
end
end
end
private :_state_control
def __methodcall_optkeys # { key=>method, ... }
{'state' => :_state_control}
end
private :__methodcall_optkeys
#----------------------------------------------------
def _textvariable_control(var = None)
if var == None
# get
((var = @ent.textvariable) === @default_var)? nil: var
else
# set
@var = var
tk_send('configure', '-textvariable', (@var)? var: @default_var)
end
end
private :_textvariable_control
#----------------------------------------------------
def initialize_composite(keys={})
keys = _symbolkey2str(keys)
@btn = TkLabel.new(@frame, :relief=>:raised, :borderwidth=>2,
:image=>@@down_btn_bmp).pack(:side=>:right,
:ipadx=>2, :fill=>:y)
@ent = TkEntry.new(@frame).pack(:side=>:left)
@path = @ent.path
@top = TkToplevel.new(@btn, :borderwidth=>1, :relief=>:raised) {
withdraw
transient
overrideredirect(true)
}
startwait = keys.delete('startwait'){300}
interval = keys.delete('interval'){150}
@lst = Tk::RbWidget::AutoScrollListbox.new(@top, :scrollbar=>true,
:startwait=>startwait,
:interval=>interval)
@lst.pack(:fill=>:both, :expand=>true)
@ent_list = []
@wait_var = TkVariable.new
@var = @default_var = TkVariable.new
@ent.textvariable @default_var
_init_bindings
option_methods('textvariable' => :_textvariable_control)
delegate('DEFAULT', @ent)
delegate('height', @lst)
delegate('relief', @frame)
delegate('borderwidth', @frame)
delegate('arrowrelief', @lst)
delegate('arrowborderwidth', @lst)
delegate('state', false)
if mode = keys.delete('scrollbar')
scrollbar(mode)
end
configure keys unless keys.empty?
end
private :initialize_composite
def scrollbar(mode)
@lst.scrollbar(mode)
end
def _reset_width
len = @ent.width
@lst.get(0, 'end').each{|l| len = l.length if l.length > len}
@lst.width(len + 1)
end
private :_reset_width
def add(ent)
ent = ent.to_s
unless @ent_list.index(ent)
@ent_list << ent
@lst.insert('end', ent)
end
_reset_width
self
end
def remove(ent)
ent = ent.to_s
@ent_list.delete(ent)
if idx = @lst.get(0, 'end').index(ent)
@lst.delete(idx)
end
_reset_width
self
end
def values(ary = nil)
if ary
@lst.delete(0, 'end')
@ent_list.clear
ary.each{|ent| add(ent)}
_reset_width
self
else
@lst.get(0, 'end')
end
end
def see(idx)
@lst.see(@lst.index(idx) - 1)
end
def list_index(idx)
@lst.index(idx)
end
end
################################################
# test
################################################
if __FILE__ == $0
# e0 = Tk::RbWidget::Combobox.new.pack
# e0.values(%w(aa bb cc dd ee ff gg hh ii jj kk ll mm nn oo pp qq rr ss tt uu))
v = TkVariable.new
e = Tk::RbWidget::Combobox.new(:height=>7, :scrollbar=>true,
:textvariable=>v,
:arrowrelief=>:flat, :arrowborderwidth=>0,
:startwait=>400, :interval=>200).pack
e.values(%w(aa bb cc dd ee ff gg hh ii jj kk ll mm nn oo pp qq rr ss tt uu))
#e.see(e.list_index('end') - 2)
e.value = 'cc'
TkFrame.new{|f|
fnt = TkFont.new('Helvetica 10')
TkLabel.new(f, :font=>fnt, :text=>'TkCombobox value :').pack(:side=>:left)
TkLabel.new(f, :font=>fnt, :textvariable=>v).pack(:side=>:left)
}.pack
TkFrame.new(:relief=>:raised, :borderwidth=>2,
:height=>3).pack(:fill=>:x, :expand=>true, :padx=>5, :pady=>3)
l = Tk::RbWidget::AutoScrollListbox.new(nil, :relief=>:groove,
:borderwidth=>4,:height=>7,
:width=>20).pack(:fill=>:both,
:expand=>true)
(0..20).each{|i| l.insert('end', "line #{i}")}
TkFrame.new(:relief=>:ridge, :borderwidth=>3){
TkButton.new(self, :text=>'ON',
:command=>proc{l.scrollbar(true)}).pack(:side=>:left)
TkButton.new(self, :text=>'OFF',
:command=>proc{l.scrollbar(false)}).pack(:side=>:right)
pack(:fill=>:x)
}
Tk.mainloop
end