1# 2# tkmultilistframe.rb : multiple listbox widget on scrollable frame 3# by Hidetoshi NAGAI (nagai@ai.kyutech.ac.jp) 4# 5require 'tk' 6 7class TkMultiListFrame < TkListbox 8 include TkComposite 9 10 # lbox_height : height of listboxes (pixel) 11 # title_info : array [ [<title_string>,<init_width>], ... ] 12 # keys : hash {<option>=><value>, ... } 13 def initialize_composite(lbox_height, title_info, keys={}) 14 # argument check 15 if (! title_info.kind_of? Array) or (title_info.size < 2) 16 raise 17 end 18 19 # mode 20 @keep_minsize = true 21 @show_each_hscr = true 22 @show_win_hscr = true 23 24 # init arrays 25 @base_list = [] 26 @rel_list = [] 27 @title_list = [] 28 @title_cmd = [] 29 @lbox_list = [] 30 @hscr_list = [] 31 32 # decide total width 33 @lbox_total = title_info.size 34 @width_total = 0 35 title_info.each{|title, width, cmd| 36 @width_total += width.to_f 37 @title_cmd << cmd 38 } 39 40 # rel-table of label=>index 41 @name_index = {} 42 43 # size definition 44 @window_width = @width_total 45 @sash = 5 46 @scrbar_width = 15 47 @scrbar_border = 3 48 @lbox_border = 1 49 @title_border = 3 50 @h_l_thick = 0 51 52 # init status 53 @mode = :title 54 55 # virtical scrollbar 56=begin 57 @v_scroll = TkScrollbar.new(@frame, 'highlightthickness'=>@h_l_thick, 58 'borderwidth'=>@scrbar_border, 59 'orient'=>'vertical', 'width'=>@scrbar_width) 60=end 61 @v_scroll = TkYScrollbar.new(@frame, 'highlightthickness'=>@h_l_thick, 62 'borderwidth'=>@scrbar_border, 63 'width'=>@scrbar_width) 64 65 # horizontal scrollbar 66=begin 67 @h_scroll = TkScrollbar.new(@frame, 'highlightthickness'=>@h_l_thick, 68 'borderwidth'=>@scrbar_border, 69 'orient'=>'horizontal', 'width'=>@scrbar_width) 70=end 71 @h_scroll = TkXScrollbar.new(@frame, 'highlightthickness'=>@h_l_thick, 72 'borderwidth'=>@scrbar_border, 73 'width'=>@scrbar_width) 74 75 # create base flames 76 @c_title = TkCanvas.new(@frame, 'highlightthickness'=>@h_l_thick, 77 'width'=>@window_width) 78 @f_title = TkFrame.new(@c_title, 'width'=>@width_total) 79 @w_title = TkcWindow.new(@c_title, 0, 0, 80 'window'=>@f_title, 'anchor'=>'nw') 81 82 @c_lbox = TkCanvas.new(@frame, 'highlightthickness'=>@h_l_thick, 83 'width'=>@window_width) 84 @f_lbox = TkFrame.new(@c_lbox, 'width'=>@width_total) 85 @w_lbox = TkcWindow.new(@c_lbox, 0, 0, 'window'=>@f_lbox, 'anchor'=>'nw') 86 87 @c_hscr = TkCanvas.new(@frame, 'highlightthickness'=>@h_l_thick, 88 'width'=>@window_width) 89 @f_hscr = TkFrame.new(@c_hscr, 'width'=>@width_total) 90 @w_hscr = TkcWindow.new(@c_hscr, 0, 0, 'window'=>@f_hscr, 'anchor'=>'nw') 91 92 # create each listbox 93 sum = 0.0 94 @rel_list << sum/@width_total 95 title_info.each_with_index{|(label, width), idx| 96 # set relation between label and index 97 if @name_index.include?(label) 98 @name_index[label] << idx 99 else 100 @name_index[label] = [idx] 101 end 102 103 # calculate relative positioning 104 sum += width 105 @rel_list << sum/@width_total 106 107 # title field 108 f = TkFrame.new(@f_title, 'width'=>width) 109 base = [f] 110 111 title = TkLabel.new(f, 'text'=>label, 'borderwidth'=>@title_border, 112 'relief'=>'raised', 'highlightthickness'=>@h_l_thick) 113 title_binding(title, idx) 114 title.pack('fill'=>'x') 115 116 @title_list << title 117 118 f.place('relx'=>@rel_list[idx], 'y'=>0, 'anchor'=>'nw', 'width'=>1, 119 'relheight'=>1.0, 120 'relwidth'=>@rel_list[idx+1] - @rel_list[idx]) 121 122 # listbox field 123 f = TkFrame.new(@f_lbox, 'width'=>width) 124 base << f 125 @lbox_list << TkListbox.new(f, 'highlightthickness'=>@h_l_thick, 126 'borderwidth'=>@lbox_border 127 ).pack('fill'=>'both', 'expand'=>true) 128 f.place('relx'=>@rel_list[idx], 'y'=>0, 'anchor'=>'nw', 'width'=>1, 129 'relwidth'=>@rel_list[idx+1] - @rel_list[idx], 'relheight'=>1.0) 130 131 # scrollbar field 132 f = TkFrame.new(@f_hscr, 'width'=>width) 133 base << f 134=begin 135 @hscr_list << TkScrollbar.new(f, 'orient'=>'horizontal', 136 'width'=>@scrbar_width, 137 'borderwidth'=>@scrbar_border, 138 'highlightthickness'=>@h_l_thick 139 ).pack('fill'=>'x', 'anchor'=>'w') 140=end 141 @hscr_list << TkXScrollbar.new(f, 'width'=>@scrbar_width, 142 'borderwidth'=>@scrbar_border, 143 'highlightthickness'=>@h_l_thick 144 ).pack('fill'=>'x', 'anchor'=>'w') 145 f.place('relx'=>@rel_list[idx], 'y'=>0, 'anchor'=>'nw', 'width'=>1, 146 'relwidth'=>@rel_list[idx+1] - @rel_list[idx]) 147 148=begin 149 @lbox_list[idx].xscrollcommand proc{|first, last| 150 @hscr_list[idx].set first, last 151 } 152 @hscr_list[idx].command proc{|*args| @lbox_list[idx].xview *args} 153=end 154 @lbox_list[idx].xscrollbar(@hscr_list[idx]) 155 156 # add new base 157 @base_list << base 158 } 159 160 # pad 161 # @f_title_pad = TkFrame.new(@frame) 162 @f_title_pad = TkFrame.new(@frame, 'relief'=>'raised', 163 'borderwidth'=>@title_border, 164 'highlightthickness'=>@h_l_thick) 165 166 @f_scr_pad = TkFrame.new(@frame, 'relief'=>'sunken', 167 'borderwidth'=>1, 168 'highlightthickness'=>@h_l_thick) 169 170 # height check 171 title_height = 0 172 @title_list.each{|w| 173 h = w.winfo_reqheight 174 title_height = h if title_height < h 175 } 176 177 hscr_height = 0 178 @hscr_list.each{|w| 179 h = w.winfo_reqheight 180 hscr_height = h if hscr_height < h 181 } 182 183 @f_title.height title_height 184 @f_lbox.height lbox_height 185 @f_hscr.height hscr_height 186 187 # set control procedure for virtical scroll 188=begin 189 @lbox_list.each{|lbox| 190 lbox.yscrollcommand proc{|first, last| 191 @v_scroll.set first, last 192 } 193 } 194 @v_scroll.command proc{|*args| @lbox_list.each{|lbox| lbox.yview *args} } 195=end 196 @v_scroll.assign(*@lbox_list) 197 198 # set control procedure for horizoncal scroll 199=begin 200 @c_title.xscrollcommand proc{|first, last| 201 @h_scroll.set first, last 202 } 203 @c_lbox.xscrollcommand proc{|first, last| 204 @h_scroll.set first, last 205 } 206 @c_hscr.xscrollcommand proc{|first, last| 207 @h_scroll.set first, last 208 } 209 @h_scroll.command proc{|*args| 210 @c_title.xview *args 211 @c_lbox.xview *args 212 @c_hscr.xview *args if @show_each_hscr 213 } 214=end 215 @h_scroll.assign(@c_title, @c_lbox, @c_hscr) 216 217 # binding for listboxes 218 @lbox_mode = {} 219 @lbox_mode['browse'] = browse_mode_bindtag 220 @lbox_mode['single'] = single_mode_bindtag 221 @lbox_mode['extended'] = extended_mode_bindtag 222 @lbox_mode['multiple'] = multiple_mode_bindtag 223 @current_mode = 'browse' 224 @lbox_list.each_with_index{|l, idx| 225 l.bind('Shift-Key-Left', 226 proc{|w| focus_shift(w, -1); Tk.callback_break}, '%W') 227 l.bind('Shift-Key-Right', 228 proc{|w| focus_shift(w, 1); Tk.callback_break}, '%W') 229 230 l.bind('Button-2', proc{|x, y| 231 @lbox_mark_x = x 232 @lbox_list.each{|lbox| lbox.scan_mark(x, y)} 233 }, '%x %y') 234 l.bind('B2-Motion', proc{|x, y| 235 @lbox_list.each{|lbox| lbox.scan_dragto(@lbox_mark_x, y)} 236 l.scan_dragto(x, y) 237 }, '%x %y') 238 239 l.bindtags(l.bindtags.unshift(@lbox_mode[@current_mode])) 240 } 241 242 bbox = @w_title.bbox 243 @c_title.height(bbox[3]) 244 @c_title.scrollregion(bbox) 245 246 bbox = @w_lbox.bbox 247 @c_lbox.height(bbox[3]) 248 @c_lbox.scrollregion(bbox) 249 250 if @show_each_hscr 251 bbox = @w_hscr.bbox 252 @c_hscr.height(bbox[3]) 253 @c_hscr.scrollregion(bbox) 254 end 255 256 # alignment 257 TkGrid.rowconfigure(@frame, 0, 'weight'=>0) 258 TkGrid.rowconfigure(@frame, 1, 'weight'=>1) 259 TkGrid.rowconfigure(@frame, 2, 'weight'=>0) 260 TkGrid.rowconfigure(@frame, 3, 'weight'=>0) 261 TkGrid.columnconfigure(@frame, 0, 'weight'=>1) 262 TkGrid.columnconfigure(@frame, 1, 'weight'=>0) 263 TkGrid.columnconfigure(@frame, 2, 'weight'=>0) 264 @v_scroll.grid('row'=>1, 'column'=>2, 'sticky'=>'ns') 265 @c_title.grid('row'=>0, 'column'=>0, 'sticky'=>'news') 266 @f_title_pad.grid('row'=>0, 'column'=>2, 'sticky'=>'news') 267 @c_lbox.grid('row'=>1, 'column'=>0, 'sticky'=>'news') 268 @c_hscr.grid('row'=>2, 'column'=>0, 'sticky'=>'ew') if @show_each_hscr 269 @h_scroll.grid('row'=>3, 'column'=>0, 'sticky'=>'ew') if @show_win_hscr 270 @f_scr_pad.grid('row'=>2, 'rowspan'=>2, 'column'=>2, 'sticky'=>'news') 271 272 # binding for 'Configure' event 273 @c_lbox.bind('Configure', 274 proc{|height, width| reconstruct(height, width)}, 275 '%h %w') 276 277 # set default receiver of method calls 278 @path = @lbox_list[0].path 279 280 # configure options 281 keys = {} unless keys 282 keys = _symbolkey2str(keys) 283 284 # 'mode' option of listboxes 285 sel_mode = keys.delete('mode') 286 mode(sel_mode) if sel_mode 287 288 # 'scrollbarwidth' option == 'width' option of scrollbars 289 width = keys.delete('scrollbarwidth') 290 scrollbarwidth(width) if width 291 292 # options for listbox titles 293 title_font = keys.delete('titlefont') 294 titlefont(title_font) if title_font 295 296 title_fg = keys.delete('titleforeground') 297 titleforeground(title_fg) if title_fg 298 299 title_bg = keys.delete('titlebackground') 300 titlebackground(title_bg) if title_bg 301 302 # set receivers for configure methods 303 delegate('DEFAULT', *@lbox_list) 304 delegate('activebackground', @v_scroll, @h_scroll, *@hscr_list) 305 delegate('troughcolor', @v_scroll, @h_scroll, *@hscr_list) 306 delegate('repeatdelay', @v_scroll, @h_scroll, *@hscr_list) 307 delegate('repeatinterval', @v_scroll, @h_scroll, *@hscr_list) 308 delegate('borderwidth', @frame) 309 delegate('width', @c_lbox, @c_title, @c_hscr) 310 delegate('relief', @frame) 311 312 # configure 313 configure(keys) if keys.size > 0 314 end 315 private :initialize_composite 316 317 # set 'mode' option of listboxes 318 def mode(sel_mode) 319 @lbox_list.each{|l| 320 tags = l.bindtags 321 tags = tags - [ @lbox_mode[@current_mode] ] 322 l.bindtags(tags.unshift(@lbox_mode[sel_mode])) 323 @current_mode = sel_mode 324 } 325 end 326 327 # keep_minsize? 328 def keep_minsize? 329 @keep_minsize 330 end 331 def keep_minsize(bool) 332 @keep_minsize = bool 333 end 334 335 # each hscr 336 def show_each_hscr 337 @show_each_hscr = true 338 @c_hscr.grid('row'=>2, 'column'=>0, 'sticky'=>'ew') 339 end 340 def hide_each_hscr 341 @show_each_hscr = false 342 @c_hscr.ungrid 343 end 344 345 # window hscroll 346 def show_win_hscr 347 @show_win_hscr = true 348 @h_scroll.grid('row'=>3, 'column'=>0, 'sticky'=>'ew') 349 end 350 def hide_win_hscr 351 @show_each_hscr = false 352 @h_scroll.ungrid 353 end 354 355 # set scrollbar width 356 def scrollbarwidth(width) 357 @scrbar_width = width 358 @v_scroll['width'] = @scrbar_width 359 @h_scroll['width'] = @scrbar_width 360 @hscr_list.each{|hscr| hscr['width'] = @scrbar_width} 361 self 362 end 363 364 # set scrollbar border 365 def scrollbarborder(width) 366 @scrbar_border = width 367 @v_scroll['border'] = @scrbar_border 368 @h_scroll['border'] = @scrbar_border 369 @hscr_list.each{|hscr| hscr['border'] = @scrbar_border} 370 self 371 end 372 373 # set listbox borders 374 def listboxborder(width) 375 @lbox_border = width 376 @lbox_list.each{|w| w['border'] = @lbox_border} 377 self 378 end 379 380 # set listbox relief 381 def listboxrelief(relief) 382 @lbox_list.each{|w| w['relief'] = relief} 383 self 384 end 385 386 # set title borders 387 def titleborder(width) 388 @title_border = width 389 @f_title_pad['border'] = @title_border 390 @title_list.each{|label| label['border'] = @title_border} 391 self 392 end 393 394 # set title font 395 def titlefont(font) 396 @title_list.each{|label| label['font'] = font} 397 title_height = 0 398 @title_list.each{|w| 399 h = w.winfo_reqheight 400 title_height = h if title_height < h 401 } 402 @f_title.height title_height 403 bbox = @w_title.bbox 404 @c_title.height(bbox[3]) 405 @c_title.scrollregion(bbox) 406 self 407 end 408 409 # set title foreground color 410 def titleforeground(fg) 411 @title_list.each{|label| label['foreground'] = fg} 412 self 413 end 414 415 # set title background color 416 def titlebackground(bg) 417 @f_title_pad['background'] = bg 418 @title_list.each{|label| label['background'] = bg} 419 self 420 end 421 422 # set title cmds 423 def titlecommand(idx, cmd=Proc.new) 424 @title_cmd[idx] = cmd 425 end 426 427 # call title cmds 428 def titleinvoke(idx) 429 @title_cmd[idx].call if @title_cmd[idx] 430 end 431 432 # get label widgets of listbox titles 433 def titlelabels(*indices) 434 @title_list[*indices] 435 end 436 437 # get listbox widgets 438 def columns(*indices) 439 @lbox_list[*indices] 440 end 441 442 def activate(idx) 443 @lbox_list.each{|lbox| lbox.activate(idx)} 444 end 445 446 def bbox(idx) 447 @lbox_list.collect{|lbox| lbox.bbox(idx)} 448 end 449 450 def delete(*idx) 451 @lbox_list.collect{|lbox| lbox.delete(*idx)} 452 end 453 454 def get(*idx) 455 if idx.size == 1 456 @lbox_list.collect{|lbox| lbox.get(*idx)} 457 else 458 list = @lbox_list.collect{|lbox| lbox.get(*idx)} 459 result = [] 460 list[0].each_with_index{|line, index| 461 result << list.collect{|lines| lines[index]} 462 } 463 result 464 end 465 end 466 467 def _line_array_to_hash(line) 468 result = {} 469 @name_index.each_pair{|label, indices| 470 if indices.size == 1 471 result[label] = line[indices[0]] 472 else 473 result[label] = indices.collect{|index| line[index]} 474 end 475 } 476 result 477 end 478 private :_line_array_to_hash 479 480 def get_by_hash(*idx) 481 get_result = get(*idx) 482 if idx.size == 1 483 _line_array_to_hash(get_result) 484 else 485 get_result.collect{|line| _line_array_to_hash(line)} 486 end 487 end 488 489 def insert(idx, *lines) 490 lbox_ins = [] 491 (0..@lbox_list.size).each{lbox_ins << []} 492 493 lines.each{|line| 494 if line.kind_of? Hash 495 array = [] 496 @name_index.each_pair{|label, indices| 497 if indices.size == 1 498 array[indices[0]] = line[label] 499 else 500 if line[label].kind_of? Array 501 indices.each_with_index{|index, num| 502 array[index] = line[label][num] 503 } 504 else 505 array[indices[0]] = line[label] 506 end 507 end 508 } 509 line = array 510 end 511 512 @name_index.each_pair{|label, indices| 513 if indices.size == 1 514 lbox_ins[indices[0]] << line[indices[0]] 515 else 516 indices.each{|index| lbox_ins[index] << line[index]} 517 end 518 } 519 } 520 521 @lbox_list.each_with_index{|lbox, index| 522 lbox.insert(idx, *lbox_ins[index]) if lbox_ins[index] 523 } 524 end 525 526 def selection_anchor(index) 527 @lbox_list.each{|lbox| lbox.selection_anchor(index)} 528 end 529 530 def selection_clear(first, last=None) 531 @lbox_list.each{|lbox| lbox.selection_clear(first, last=None)} 532 end 533 534 def selection_set(first, last=None) 535 @lbox_list.each{|lbox| lbox.selection_set(first, last=None)} 536 end 537 538 ########################################### 539 private 540 541 def reconstruct(height, width) 542 if @keep_minsize && width <= @width_total 543 @f_title.width(@width_total) 544 @f_lbox.width(@width_total) 545 @f_hscr.width(@width_total) if @show_each_hscr 546 @window_width = @width_total 547 else 548 @f_title.width(width) 549 @f_lbox.width(width) 550 @f_hscr.width(width) if @show_each_hscr 551 @window_width = width 552 end 553 554 @f_lbox.height(height) 555 556 @c_title.scrollregion(@w_title.bbox) 557 @c_lbox.scrollregion(@w_lbox.bbox) 558 @c_hscr.scrollregion(@w_hscr.bbox) if @show_each_hscr 559 560 (0..(@rel_list.size - 2)).each{|idx| 561 title, lbox, hscr = @base_list[idx] 562 title.place('relwidth'=>@rel_list[idx+1] - @rel_list[idx]) 563 lbox.place('relwidth'=>@rel_list[idx+1] - @rel_list[idx], 564 'relheight'=>1.0) 565 hscr.place('relwidth'=>@rel_list[idx+1] - @rel_list[idx]) 566 } 567 end 568 569 def resize(x) 570 idx = @sel_sash 571 return if idx == 0 572 573 # adjustment of relative positioning 574 delta = (x - @x) / @frame_width 575 if delta < @rel_list[idx-1] - @rel_list[idx] + (2*@sash/@frame_width) 576 delta = @rel_list[idx-1] - @rel_list[idx] + (2*@sash/@frame_width) 577 elsif delta > @rel_list[idx+1] - @rel_list[idx] - (2*@sash/@frame_width) 578 delta = @rel_list[idx+1] - @rel_list[idx] - (2*@sash/@frame_width) 579 end 580 @rel_list[idx] += delta 581 582 # adjustment of leftside widget of the sash 583 title, lbox, hscr = @base_list[idx - 1] 584 title.place('relwidth'=>@rel_list[idx] - @rel_list[idx-1]) 585 lbox.place('relwidth'=>@rel_list[idx] - @rel_list[idx-1], 'relheight'=>1.0) 586 hscr.place('relwidth'=>@rel_list[idx] - @rel_list[idx-1]) 587 588 # adjustment of rightside widget of the sash 589 title, lbox, hscr = @base_list[idx] 590 title.place('relwidth'=>@rel_list[idx+1] - @rel_list[idx], 591 'relx'=>@rel_list[idx]) 592 lbox.place('relwidth'=>@rel_list[idx+1] - @rel_list[idx], 593 'relx'=>@rel_list[idx], 'relheight'=>1.0) 594 hscr.place('relwidth'=>@rel_list[idx+1] - @rel_list[idx], 595 'relx'=>@rel_list[idx]) 596 597 # update reference position 598 @x = x 599 end 600 601 def motion_cb(w, x, idx) 602 if x <= @sash && idx > 0 603 w.cursor 'sb_h_double_arrow' 604 @mode = :sash 605 @sel_sash = idx 606 elsif x >= w.winfo_width - @sash && idx < @lbox_total - 1 607 w.cursor 'sb_h_double_arrow' 608 @mode = :sash 609 @sel_sash = idx + 1 610 else 611 w.cursor "" 612 @mode = :title 613 @sel_sash = 0 614 end 615 end 616 617 def title_binding(title, index) 618 title.bind('Motion', proc{|w, x, idx| motion_cb(w, x, idx.to_i)}, 619 "%W %x #{index}") 620 621 title.bind('Enter', proc{|w, x, idx| motion_cb(w, x, idx.to_i)}, 622 "%W %x #{index}") 623 624 title.bind('Leave', proc{|w| w.cursor ""}, "%W") 625 626 title.bind('Button-1', 627 proc{|w, x| 628 if @mode == :sash 629 @x = x 630 @frame_width = TkWinfo.width(@f_title).to_f 631 else 632 title.relief 'sunken' 633 end 634 }, 635 '%W %X') 636 637 title.bind('ButtonRelease-1', 638 proc{|w, x, idx| 639 i = idx.to_i 640 if @mode == :title && @title_cmd[i].kind_of?(Proc) 641 @title_cmd[i].call 642 end 643 title.relief 'raised' 644 motion_cb(w,x,i) 645 }, 646 "%W %x #{index}") 647 648 title.bind('B1-Motion', proc{|x| resize(x) if @mode == :sash}, "%X") 649 end 650 651 ################################# 652 def browse_mode_bindtag 653 t = TkBindTag.new 654 t.bind('Button-1', 655 proc{|w, y| w.focus; select_line(w, w.nearest(y))}, '%W %y') 656 t.bind('B1-Motion', proc{|w, y| select_line(w, w.nearest(y))}, '%W %y') 657 658 t.bind('Shift-Button-1', 659 proc{|w, y| active_line(w, w.nearest(y))}, '%W %y') 660 661 t.bind('Key-Up', proc{|w| select_shift(w, -1)}, '%W') 662 t.bind('Key-Down', proc{|w| select_shift(w, 1)}, '%W') 663 664 t.bind('Control-Home', proc{|w| select_line(w, 0)}, '%W') 665 t.bind('Control-End', proc{|w| select_line(w, 'end')}, '%W') 666 667 t.bind('space', proc{|w| select_line(w, w.index('active').to_i)}, '%W') 668 t.bind('Select', proc{|w| select_line(w, w.index('active').to_i)}, '%W') 669 t.bind('Control-slash', 670 proc{|w| select_line(w, w.index('active').to_i)}, '%W') 671 672 t 673 end 674 675 ######################## 676 def single_mode_bindtag 677 t = TkBindTag.new 678 t.bind('Button-1', 679 proc{|w, y| w.focus; select_only(w, w.nearest(y))}, '%W %y') 680 t.bind('ButtonRelease-1', 681 proc{|w, y| active_line(w, w.nearest(y))}, '%W %y') 682 683 t.bind('Shift-Button-1', 684 proc{|w, y| active_line(w, w.nearest(y))}, '%W %y') 685 686 t.bind('Key-Up', proc{|w| select_shift(w, -1)}, '%W') 687 t.bind('Key-Down', proc{|w| select_shift(w, 1)}, '%W') 688 689 t.bind('Control-Home', proc{|w| select_line(w, 0)}, '%W') 690 t.bind('Control-End', proc{|w| select_line(w, 'end')}, '%W') 691 692 t.bind('space', proc{|w| select_line(w, w.index('active').to_i)}, '%W') 693 t.bind('Select', proc{|w| select_line(w, w.index('active').to_i)}, '%W') 694 t.bind('Control-slash', 695 proc{|w| select_line(w, w.index('active').to_i)}, '%W') 696 t.bind('Control-backslash', 697 proc{@lbox_list.each{|l| l.selection_clear(0, 'end')}}) 698 699 t 700 end 701 702 ######################## 703 def extended_mode_bindtag 704 t = TkBindTag.new 705 t.bind('Button-1', 706 proc{|w, y| w.focus; select_only(w, w.nearest(y))}, '%W %y') 707 t.bind('B1-Motion', proc{|w, y| select_range(w, w.nearest(y))}, '%W %y') 708 709 t.bind('ButtonRelease-1', 710 proc{|w, y| active_line(w, w.nearest(y))}, '%W %y') 711 712 t.bind('Shift-Button-1', 713 proc{|w, y| select_range(w, w.nearest(y))}, '%W %y') 714 t.bind('Shift-B1-Motion', 715 proc{|w, y| select_range(w, w.nearest(y))}, '%W %y') 716 717 t.bind('Control-Button-1', 718 proc{|w, y| select_toggle(w, w.nearest(y))}, '%W %y') 719 720 t.bind('Control-B1-Motion', 721 proc{|w, y| select_drag(w, w.nearest(y))}, '%W %y') 722 723 t.bind('Key-Up', proc{|w| active_shift(w, -1)}, '%W') 724 t.bind('Key-Down', proc{|w| active_shift(w, 1)}, '%W') 725 726 t.bind('Shift-Up', proc{|w| select_expand(w, -1)}, '%W') 727 t.bind('Shift-Down', proc{|w| select_expand(w, 1)}, '%W') 728 729 t.bind('Control-Home', proc{|w| select_line2(w, 0)}, '%W') 730 t.bind('Control-End', proc{|w| select_line2(w, 'end')}, '%W') 731 732 t.bind('Control-Shift-Home', proc{|w| select_range(w, 0)}, '%W') 733 t.bind('Control-Shift-End', proc{|w| select_range(w, 'end')}, '%W') 734 735 t.bind('space', proc{|w| select_active(w)}, '%W') 736 t.bind('Select', proc{|w| select_active(w)}, '%W') 737 t.bind('Control-slash', proc{|w| select_all}, '%W') 738 t.bind('Control-backslash', proc{|w| clear_all}, '%W') 739 740 t 741 end 742 743 ######################## 744 def multiple_mode_bindtag 745 t = TkBindTag.new 746 t.bind('Button-1', 747 proc{|w, y| w.focus; select_line3(w, w.nearest(y))}, '%W %y') 748 t.bind('ButtonRelease-1', 749 proc{|w, y| active_line(w, w.nearest(y))}, '%W %y') 750 751 t.bind('Key-Up', proc{|w| active_shift(w, -1)}, '%W') 752 t.bind('Key-Down', proc{|w| active_shift(w, 1)}, '%W') 753 754 t.bind('Control-Home', proc{|w| select_line2(w, 0)}, '%W') 755 t.bind('Control-End', proc{|w| select_line2(w, 'end')}, '%W') 756 757 t.bind('Control-Shift-Home', proc{|w| active_line(w, 0)}, '%W') 758 t.bind('Control-Shift-End', proc{|w| active_line(w, 'end')}, '%W') 759 760 t.bind('space', proc{|w| select_active(w)}, '%W') 761 t.bind('Select', proc{|w| select_active(w)}, '%W') 762 t.bind('Control-slash', proc{|w| select_all}, '%W') 763 t.bind('Control-backslash', proc{|w| clear_all}, '%W') 764 765 t 766 end 767 768 ######################## 769 def active_line(w, idx) 770 @lbox_list.each{|l| l.activate(idx)} 771 end 772 773 def select_only(w, idx) 774 @lbox_list.each{|l| 775 l.selection_clear(0, 'end') 776 l.selection_anchor(idx) 777 l.selection_set('anchor') 778 } 779 end 780 781 def select_range(w, idx) 782 @lbox_list.each{|l| 783 l.selection_clear(0, 'end') 784 l.selection_set('anchor', idx) 785 } 786 end 787 788 def select_toggle(w, idx) 789 st = w.selection_includes(idx) 790 @lbox_list.each{|l| 791 l.selection_anchor(idx) 792 if st == 1 793 l.selection_clear(idx) 794 else 795 l.selection_set(idx) 796 end 797 } 798 end 799 800 def select_drag(w, idx) 801 st = w.selection_includes('anchor') 802 @lbox_list.each{|l| 803 if st == 1 804 l.selection_set('anchor', idx) 805 else 806 l.selection_clear('anchor', idx) 807 end 808 } 809 end 810 811 def select_line(w, idx) 812 @lbox_list.each{|l| 813 l.selection_clear(0, 'end') 814 l.activate(idx) 815 l.selection_anchor(idx) 816 l.selection_set('anchor') 817 } 818 w.selection_set('anchor') 819 end 820 821 def select_line2(w, idx) 822 @lbox_list.each{|l| 823 l.activate(idx) 824 l.selection_anchor(idx) 825 l.selection_set('anchor') 826 } 827 end 828 829 def select_line3(w, idx) 830 @lbox_list.each{|l| 831 l.selection_set(idx) 832 } 833 end 834 835 def select_active(w) 836 idx = l.activate(idx) 837 @lbox_list.each{|l| 838 l.selection_set(idx) 839 } 840 end 841 842 def select_expand(w, dir) 843 idx = w.index('active').to_i + dir 844 if idx < 0 845 idx = 0 846 elsif idx >= w.size 847 idx = w.size - 1 848 end 849 @lbox_list.each{|l| 850 l.activate(idx) 851 l.selection_set(idx) 852 } 853 end 854 855 def active_shift(w, dir) 856 idx = w.index('active').to_i + dir 857 if idx < 0 858 idx = 0 859 elsif idx >= w.size 860 idx = w.size - 1 861 end 862 @lbox_list.each{|l| 863 l.activate(idx) 864 l.selection_anchor(idx) 865 } 866 end 867 868 def select_shift(w, dir) 869 idx = w.index('anchor').to_i + dir 870 if idx < 0 871 idx = 0 872 elsif idx >= w.size 873 idx = w.size - 1 874 end 875 @lbox_list.each{|l| 876 l.selection_clear(0, 'end') 877 l.activate(idx) 878 l.selection_anchor(idx) 879 l.selection_set('anchor') 880 } 881 end 882 883 def select_all 884 @lbox_list.each{|l| 885 l.selection_set(0, 'end') 886 } 887 end 888 889 def clear_all 890 @lbox_list.each{|l| 891 l.selection_clear(0, 'end') 892 } 893 end 894 895 def focus_shift(w, dir) 896 idx = @lbox_list.index(w) + dir 897 return if idx < 0 898 return if idx >= @lbox_list.size 899 @lbox_list[idx].focus 900 end 901 ######################## 902end 903 904################################################ 905# test 906################################################ 907if __FILE__ == $0 908 l = TkMultiListFrame.new(nil, 200, 909 [ ['L1', 200, proc{p 'click L1'}], 910 ['L2', 100], 911 ['L3', 200] ], 912 'width'=>350, 913 #'titleforeground'=>'yellow', 914 'titleforeground'=>'white', 915 #'titlebackground'=>'navy', 916 'titlebackground'=>'blue', 917 'titlefont'=>'courier' 918 ).pack('fill'=>'both', 'expand'=>true) 919 l.insert('end', [1,2,3]) 920 l.insert('end', [4,5,6]) 921 l.insert('end', [4,5,6], [4,5,6]) 922 l.insert('end', ['aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa', 923 'bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb', 924 'cccccccccccccccccccccccccccccccccccccccccccccccccccc']) 925 l.insert('end', [1,2,3]) 926 l.insert('end', [4,5,6], [4,5,6]) 927 l.insert('end', ['aaaaaaaaaaaaaaa','bbbbbbbbbbbbbb','ccccccccccccccccc']) 928 l.insert('end', [1,2,3]) 929 l.insert('end', [4,5,6], [4,5,6]) 930 l.insert('end', ['aaaaaaaaaaaaaaa','bbbbbbbbbbbbbb','ccccccccccccccccc']) 931 l.insert('end', [1,2,3]) 932 l.insert('end', [4,5,6], [4,5,6]) 933 l.insert('end', ['aaaaaaaaaaaaaaa','bbbbbbbbbbbbbb','ccccccccccccccccc']) 934 l.insert('end', [1,2,3]) 935 l.insert('end', [4,5,6], [4,5,6]) 936 p l.columns(1) 937 p l.columns(1..3) 938 p l.columns(1,2) 939 Tk.mainloop 940end 941