1# 2# tkcombobox.rb : auto scrollbox & combobox 3# 4# by Hidetoshi NAGAI (nagai@ai.kyutech.ac.jp) 5# 6require 'tk' 7 8module Tk 9 module RbWidget 10 class AutoScrollListbox < TkListbox 11 end 12 class Combobox < TkEntry 13 end 14 end 15end 16 17class Tk::RbWidget::AutoScrollListbox 18 include TkComposite 19 20 @@up_bmp = TkBitmapImage.new(:data=><<EOD) 21#define up_arrow_width 9 22#define up_arrow_height 9 23static unsigned char up_arrow_bits[] = { 24 0x00, 0x00, 0x10, 0x00, 0x10, 0x00, 0x38, 0x00, 0x38, 0x00, 0x7c, 0x00, 25 0x7c, 0x00, 0xfe, 0x00, 0x00, 0x00}; 26EOD 27 28 @@down_bmp = TkBitmapImage.new(:data=><<EOD) 29#define up_arrow_width 9 30#define up_arrow_height 9 31static unsigned char down_arrow_bits[] = { 32 0x00, 0x00, 0xfe, 0x00, 0x7c, 0x00, 0x7c, 0x00, 0x38, 0x00, 0x38, 0x00, 33 0x10, 0x00, 0x10, 0x00, 0x00, 0x00}; 34EOD 35 36 ############################ 37 private 38 ############################ 39 def initialize_composite(keys={}) 40 keys = _symbolkey2str(keys) 41 42 @initwait = keys.delete('startwait'){300} 43 @interval = keys.delete('interval'){150} 44 @initwait -= @interval 45 @initwait = 0 if @initwait < 0 46 47 @lbox = TkListbox.new(@frame, :borderwidth=>0) 48 @path = @lbox.path 49 TkPack.propagate(@lbox, false) 50 51 @scr = TkScrollbar.new(@frame, :width=>10) 52 53 @lbox.yscrollcommand(proc{|*args| @scr.set(*args); _config_proc}) 54 @scr.command(proc{|*args| @lbox.yview(*args); _config_proc}) 55 56 @up_arrow = TkLabel.new(@lbox, :image=>@@up_bmp, 57 :relief=>:raised, :borderwidth=>1) 58 @down_arrow = TkLabel.new(@lbox, :image=>@@down_bmp, 59 :relief=>:raised, :borderwidth=>1) 60 61 _init_binding 62 63 @lbox.pack(:side=>:left, :fill=>:both, :expand=>:true) 64 65 delegate('DEFAULT', @lbox) 66 delegate('background', @frame, @scr) 67 delegate('activebackground', @scr) 68 delegate('troughcolor', @scr) 69 delegate('repeatdelay', @scr) 70 delegate('repeatinterval', @scr) 71 delegate('relief', @frame) 72 delegate('borderwidth', @frame) 73 74 delegate_alias('arrowrelief', 'relief', @up_arrow, @down_arrow) 75 delegate_alias('arrowborderwidth', 'borderwidth', @up_arrow, @down_arrow) 76 77 scrollbar(keys.delete('scrollbar')){false} 78 79 configure keys unless keys.empty? 80 end 81 82 def _show_up_arrow 83 unless @up_arrow.winfo_mapped? 84 @up_arrow.pack(:side=>:top, :fill=>:x) 85 end 86 end 87 88 def _show_down_arrow 89 unless @down_arrow.winfo_mapped? 90 @down_arrow.pack(:side=>:bottom, :fill=>:x) 91 end 92 end 93 94 def _set_sel(idx) 95 @lbox.activate(idx) 96 @lbox.selection_clear(0, 'end') 97 @lbox.selection_set(idx) 98 end 99 100 def _check_sel(cidx, tidx = nil, bidx = nil) 101 _set_sel(cidx) 102 unless tidx 103 tidx = @lbox.nearest(0) 104 tidx += 1 if tidx > 0 105 end 106 unless bidx 107 bidx = @lbox.nearest(10000) 108 bidx -= 1 if bidx < @lbox.index('end') - 1 109 end 110 if cidx > bidx 111 _set_sel(bidx) 112 end 113 if cidx < tidx 114 _set_sel(tidx) 115 end 116 end 117 118 def _up_proc 119 cidx = @lbox.curselection[0] 120 idx = @lbox.nearest(0) 121 if idx >= 0 122 @lbox.see(idx - 1) 123 _set_sel(idx) 124 @up_arrow.pack_forget if idx == 1 125 @up_timer.stop if idx == 0 126 _show_down_arrow if @lbox.bbox('end') == [] 127 end 128 if cidx && cidx > 0 && (idx == 0 || cidx == @lbox.nearest(10000)) 129 _set_sel(cidx - 1) 130 end 131 end 132 133 def _down_proc 134 cidx = @lbox.curselection[0] 135 eidx = @lbox.index('end') - 1 136 idx = @lbox.nearest(10000) 137 if idx <= eidx 138 @lbox.see(idx + 1) 139 _set_sel(cidx + 1) if cidx < eidx 140 @down_arrow.pack_forget if idx + 1 == eidx 141 @down_timer.stop if idx == eidx 142 _show_up_arrow if @lbox.bbox(0) == [] 143 end 144 if cidx && cidx < eidx && (eidx == idx || cidx == @lbox.nearest(0)) 145 _set_sel(cidx + 1) 146 end 147 end 148 149 def _key_UP_proc 150 cidx = @lbox.curselection[0] 151 _set_sel(cidx = @lbox.index('activate')) unless cidx 152 cidx -= 1 153 if cidx == 0 154 @up_arrow.pack_forget 155 elsif cidx == @lbox.nearest(0) 156 @lbox.see(cidx - 1) 157 end 158 end 159 160 def _key_DOWN_proc 161 cidx = @lbox.curselection[0] 162 _set_sel(cidx = @lbox.index('activate')) unless cidx 163 cidx += 1 164 if cidx == @lbox.index('end') - 1 165 @down_arrow.pack_forget 166 elsif cidx == @lbox.nearest(10000) 167 @lbox.see(cidx + 1) 168 end 169 end 170 171 def _config_proc 172 if @lbox.size == 0 173 @up_arrow.pack_forget 174 @down_arrow.pack_forget 175 return 176 end 177 tidx = @lbox.nearest(0) 178 bidx = @lbox.nearest(10000) 179 if tidx > 0 180 _show_up_arrow 181 tidx += 1 182 else 183 @up_arrow.pack_forget unless @up_timer.running? 184 end 185 if bidx < @lbox.index('end') - 1 186 _show_down_arrow 187 bidx -= 1 188 else 189 @down_arrow.pack_forget unless @down_timer.running? 190 end 191 cidx = @lbox.curselection[0] 192 _check_sel(cidx, tidx, bidx) if cidx 193 end 194 195 def _init_binding 196 @up_timer = TkAfter.new(@interval, -1, proc{_up_proc}) 197 @down_timer = TkAfter.new(@interval, -1, proc{_down_proc}) 198 199 @up_timer.set_start_proc(@initwait, proc{}) 200 @down_timer.set_start_proc(@initwait, proc{}) 201 202 @up_arrow.bind('Enter', proc{@up_timer.start}) 203 @up_arrow.bind('Leave', proc{@up_timer.stop if @up_arrow.winfo_mapped?}) 204 @down_arrow.bind('Enter', proc{@down_timer.start}) 205 @down_arrow.bind('Leave', proc{@down_timer.stop if @down_arrow.winfo_mapped?}) 206 207 @lbox.bind('Configure', proc{_config_proc}) 208 @lbox.bind('Enter', proc{|y| _set_sel(@lbox.nearest(y))}, '%y') 209 @lbox.bind('Motion', proc{|y| 210 @up_timer.stop if @up_timer.running? 211 @down_timer.stop if @down_timer.running? 212 _check_sel(@lbox.nearest(y)) 213 }, '%y') 214 215 @lbox.bind('Up', proc{_key_UP_proc}) 216 @lbox.bind('Down', proc{_key_DOWN_proc}) 217 end 218 219 ############################ 220 public 221 ############################ 222 def scrollbar(mode) 223 if mode 224 @scr.pack(:side=>:right, :fill=>:y) 225 else 226 @scr.pack_forget 227 end 228 end 229end 230 231################################################ 232 233class Tk::RbWidget::Combobox < TkEntry 234 include TkComposite 235 236 @@down_btn_bmp = TkBitmapImage.new(:data=><<EOD) 237#define down_arrow_width 11 238#define down_arrow_height 11 239static unsigned char down_arrow_bits[] = { 240 0x00, 0x00, 0xfe, 0x03, 0xfc, 0x01, 0xfc, 0x01, 0xf8, 0x00, 0xf8, 0x00, 241 0x70, 0x00, 0x70, 0x00, 0x20, 0x00, 0x20, 0x00, 0x00, 0x00}; 242EOD 243 244 @@up_btn_bmp = TkBitmapImage.new(:data=><<EOD) 245#define up_arrow_width 11 246#define up_arrow_height 11 247static unsigned char up_arrow_bits[] = { 248 0x00, 0x00, 0x20, 0x00, 0x20, 0x00, 0x70, 0x00, 0x70, 0x00, 0xf8, 0x00, 249 0xf8, 0x00, 0xfc, 0x01, 0xfc, 0x01, 0xfe, 0x03, 0x00, 0x00}; 250EOD 251 252 def _button_proc(dir = true) 253 return if @ent.state == 'disabled' 254 @btn.relief(:sunken) 255 x = @frame.winfo_rootx 256 y = @frame.winfo_rooty 257 if dir 258 @top.geometry("+#{x}+#{y + @frame.winfo_height}") 259 else 260 @btn.image(@@up_btn_bmp) 261 @top.geometry("+#{x}+#{y - @top.winfo_reqheight}") 262 end 263 @top.deiconify 264 @lst.focus 265 266 if (idx = values.index(@ent.value)) 267 @lst.see(idx - 1) 268 @lst.activate(idx) 269 @lst.selection_set(idx) 270 elsif @lst.size > 0 271 @lst.see(0) 272 @lst.activate(0) 273 @lst.selection_set(0) 274 end 275 @top.grab 276 277 begin 278 @wait_var.tkwait 279 if (idx = @wait_var.to_i) >= 0 280 # @ent.value = @lst.get(idx) 281 _set_entry_value(@lst.get(idx)) 282 end 283 @top.withdraw 284 @btn.relief(:raised) 285 @btn.image(@@down_btn_bmp) 286 rescue 287 ensure 288 begin 289 @top.grab(:release) 290 @ent.focus 291 rescue 292 end 293 end 294 end 295 private :_button_proc 296 297 def _init_bindings 298 @btn.bind('1', proc{_button_proc(true)}) 299 @btn.bind('3', proc{_button_proc(false)}) 300 301 @lst.bind('1', proc{|y| @wait_var.value = @lst.nearest(y)}, '%y') 302 @lst.bind('Return', proc{@wait_var.value = @lst.curselection[0]}) 303 304 cancel = TkVirtualEvent.new('2', '3', 'Escape') 305 @lst.bind(cancel, proc{@wait_var.value = -1}) 306 end 307 private :_init_bindings 308 309 def _set_entry_value(val) 310 @ent.textvariable.value = val 311 end 312 private :_set_entry_value 313 314 #---------------------------------------------------- 315 316 def _state_control(value = None) 317 if value == None 318 # get 319 @ent.state 320 else 321 # set 322 @ent.state(value.to_s) 323 case value = @ent.state # regulate 'state' string 324 when 'normal', 'readonly' 325 @btn.state 'normal' 326 when 'disabled' 327 @btn.state 'disabled' 328 else 329 # unknown : do nothing 330 end 331 end 332 end 333 private :_state_control 334 335 def __methodcall_optkeys # { key=>method, ... } 336 {'state' => :_state_control} 337 end 338 private :__methodcall_optkeys 339 340 #---------------------------------------------------- 341 342 def _textvariable_control(var = None) 343 if var == None 344 # get 345 ((var = @ent.textvariable) === @default_var)? nil: var 346 else 347 # set 348 @var = var 349 tk_send('configure', '-textvariable', (@var)? var: @default_var) 350 end 351 end 352 private :_textvariable_control 353 354 #---------------------------------------------------- 355 356 def initialize_composite(keys={}) 357 keys = _symbolkey2str(keys) 358 359 @btn = TkLabel.new(@frame, :relief=>:raised, :borderwidth=>2, 360 :image=>@@down_btn_bmp).pack(:side=>:right, 361 :ipadx=>2, :fill=>:y) 362 @ent = TkEntry.new(@frame).pack(:side=>:left) 363 @path = @ent.path 364 365 @top = TkToplevel.new(@btn, :borderwidth=>1, :relief=>:raised) { 366 withdraw 367 transient 368 overrideredirect(true) 369 } 370 371 startwait = keys.delete('startwait'){300} 372 interval = keys.delete('interval'){150} 373 @lst = Tk::RbWidget::AutoScrollListbox.new(@top, :scrollbar=>true, 374 :startwait=>startwait, 375 :interval=>interval) 376 @lst.pack(:fill=>:both, :expand=>true) 377 @ent_list = [] 378 379 @wait_var = TkVariable.new 380 @var = @default_var = TkVariable.new 381 382 @ent.textvariable @default_var 383 384 _init_bindings 385 386 option_methods('textvariable' => :_textvariable_control) 387 388 delegate('DEFAULT', @ent) 389 delegate('height', @lst) 390 delegate('relief', @frame) 391 delegate('borderwidth', @frame) 392 393 delegate('arrowrelief', @lst) 394 delegate('arrowborderwidth', @lst) 395 396 delegate('state', false) 397 398 if mode = keys.delete('scrollbar') 399 scrollbar(mode) 400 end 401 402 configure keys unless keys.empty? 403 end 404 private :initialize_composite 405 406 def scrollbar(mode) 407 @lst.scrollbar(mode) 408 end 409 410 def _reset_width 411 len = @ent.width 412 @lst.get(0, 'end').each{|l| len = l.length if l.length > len} 413 @lst.width(len + 1) 414 end 415 private :_reset_width 416 417 def add(ent) 418 ent = ent.to_s 419 unless @ent_list.index(ent) 420 @ent_list << ent 421 @lst.insert('end', ent) 422 end 423 _reset_width 424 self 425 end 426 427 def remove(ent) 428 ent = ent.to_s 429 @ent_list.delete(ent) 430 if idx = @lst.get(0, 'end').index(ent) 431 @lst.delete(idx) 432 end 433 _reset_width 434 self 435 end 436 437 def values(ary = nil) 438 if ary 439 @lst.delete(0, 'end') 440 @ent_list.clear 441 ary.each{|ent| add(ent)} 442 _reset_width 443 self 444 else 445 @lst.get(0, 'end') 446 end 447 end 448 449 def see(idx) 450 @lst.see(@lst.index(idx) - 1) 451 end 452 453 def list_index(idx) 454 @lst.index(idx) 455 end 456end 457 458 459################################################ 460# test 461################################################ 462if __FILE__ == $0 463# e0 = Tk::RbWidget::Combobox.new.pack 464# e0.values(%w(aa bb cc dd ee ff gg hh ii jj kk ll mm nn oo pp qq rr ss tt uu)) 465 466 v = TkVariable.new 467 e = Tk::RbWidget::Combobox.new(:height=>7, :scrollbar=>true, 468 :textvariable=>v, 469 :arrowrelief=>:flat, :arrowborderwidth=>0, 470 :startwait=>400, :interval=>200).pack 471 e.values(%w(aa bb cc dd ee ff gg hh ii jj kk ll mm nn oo pp qq rr ss tt uu)) 472 #e.see(e.list_index('end') - 2) 473 e.value = 'cc' 474 TkFrame.new{|f| 475 fnt = TkFont.new('Helvetica 10') 476 TkLabel.new(f, :font=>fnt, :text=>'TkCombobox value :').pack(:side=>:left) 477 TkLabel.new(f, :font=>fnt, :textvariable=>v).pack(:side=>:left) 478 }.pack 479 480 TkFrame.new(:relief=>:raised, :borderwidth=>2, 481 :height=>3).pack(:fill=>:x, :expand=>true, :padx=>5, :pady=>3) 482 483 l = Tk::RbWidget::AutoScrollListbox.new(nil, :relief=>:groove, 484 :borderwidth=>4,:height=>7, 485 :width=>20).pack(:fill=>:both, 486 :expand=>true) 487 (0..20).each{|i| l.insert('end', "line #{i}")} 488 489 TkFrame.new(:relief=>:ridge, :borderwidth=>3){ 490 TkButton.new(self, :text=>'ON', 491 :command=>proc{l.scrollbar(true)}).pack(:side=>:left) 492 TkButton.new(self, :text=>'OFF', 493 :command=>proc{l.scrollbar(false)}).pack(:side=>:right) 494 pack(:fill=>:x) 495 } 496 Tk.mainloop 497end 498