1# 2# tkmulticolumnlist.rb : multiple column list widget on scrollable frame 3# by Hidetoshi NAGAI (nagai@ai.kyutech.ac.jp) 4# 5require 'tk' 6 7class TkMultiColumnList < TkText 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 @command = nil 55 56 # virtical scrollbar 57 @v_scroll = TkYScrollbar.new(@frame, 'highlightthickness'=>@h_l_thick, 58 'borderwidth'=>@scrbar_border, 59 'width'=>@scrbar_width) 60 61 # horizontal scrollbar 62 @h_scroll = TkXScrollbar.new(@frame, 'highlightthickness'=>@h_l_thick, 63 'borderwidth'=>@scrbar_border, 64 'width'=>@scrbar_width) 65 66 # create base flames 67 @c_title = TkCanvas.new(@frame, 'highlightthickness'=>@h_l_thick, 68 'width'=>@window_width) 69 @f_title = TkFrame.new(@c_title, 'width'=>@width_total) 70 @w_title = TkcWindow.new(@c_title, 0, 0, 71 'window'=>@f_title, 'anchor'=>'nw') 72 73 @c_lbox = TkCanvas.new(@frame, 'highlightthickness'=>@h_l_thick, 74 'width'=>@window_width) 75 @f_lbox = TkFrame.new(@c_lbox, 'width'=>@width_total) 76 @w_lbox = TkcWindow.new(@c_lbox, 0, 0, 'window'=>@f_lbox, 'anchor'=>'nw') 77 78 @c_hscr = TkCanvas.new(@frame, 'highlightthickness'=>@h_l_thick, 79 'width'=>@window_width) 80 @f_hscr = TkFrame.new(@c_hscr, 'width'=>@width_total) 81 @w_hscr = TkcWindow.new(@c_hscr, 0, 0, 'window'=>@f_hscr, 'anchor'=>'nw') 82 83 # create each listbox 84 sum = 0.0 85 @rel_list << sum/@width_total 86 title_info.each_with_index{|(label, width), idx| 87 # set relation between label and index 88 if @name_index.include?(label) 89 @name_index[label] << idx 90 else 91 @name_index[label] = [idx] 92 end 93 94 # calculate relative positioning 95 sum += width 96 @rel_list << sum/@width_total 97 98 # title field 99 f = TkFrame.new(@f_title, 'width'=>width) 100 base = [f] 101 102 title = TkLabel.new(f, 'text'=>label, 'borderwidth'=>@title_border, 103 'relief'=>'raised', 'highlightthickness'=>@h_l_thick) 104 title_binding(title, idx) 105 title.pack('fill'=>'x') 106 107 @title_list << title 108 109 f.place('relx'=>@rel_list[idx], 'y'=>0, 'anchor'=>'nw', 'width'=>1, 110 'relheight'=>1.0, 111 'relwidth'=>@rel_list[idx+1] - @rel_list[idx]) 112 113 # listbox field 114 f = TkFrame.new(@f_lbox, 'width'=>width) 115 base << f 116 @lbox_list << TkText.new(f, 'highlightthickness'=>@h_l_thick, 117 'borderwidth'=>@lbox_border, 118 'takefocus'=>false, 119 'wrap'=>'none') { 120 121 bindtags(bindtags - [TkText]) 122 123 @seltag = TkTextTag.new(self, 'background'=>'#b3b3b3', 124 'borderwidth'=>1, 'relief'=>'raised') 125 def self.nearest(y) 126 self.index("@1,#{y}").split('.')[0].to_i 127 end 128 129 def self.select_clear(first, last=nil) 130 first = "#{first}.0" if first.kind_of?(Integer) 131 first = self.index(first.to_s + ' linestart') 132 last = first unless last 133 last = "#{last}.0" if first.kind_of?(Integer) 134 last = self.index(last.to_s + ' + 1 lines linestart') 135 @seltag.remove(first, last) 136 end 137 138 def self.select_set(first, last=nil) 139 first = "#{first}.0" if first.kind_of?(Integer) 140 first = self.index(first.to_s + ' linestart') 141 last = first unless last 142 last = "#{last}.0" if first.kind_of?(Integer) 143 last = self.index(last.to_s + ' + 1 lines linestart') 144 @seltag.add(first, last) 145 end 146 147 def self.select_index 148 self.index(@seltag.first).split('.')[0].to_i 149 end 150 151 pack('fill'=>'both', 'expand'=>true) 152 } 153 154 f.place('relx'=>@rel_list[idx], 'y'=>0, 'anchor'=>'nw', 'width'=>1, 155 'relwidth'=>@rel_list[idx+1] - @rel_list[idx], 'relheight'=>1.0) 156 157 # scrollbar field 158 f = TkFrame.new(@f_hscr, 'width'=>width) 159 base << f 160 @hscr_list << TkXScrollbar.new(f, 'width'=>@scrbar_width, 161 'borderwidth'=>@scrbar_border, 162 'highlightthickness'=>@h_l_thick 163 ).pack('fill'=>'x', 'anchor'=>'w') 164 f.place('relx'=>@rel_list[idx], 'y'=>0, 'anchor'=>'nw', 'width'=>1, 165 'relwidth'=>@rel_list[idx+1] - @rel_list[idx]) 166 167 @lbox_list[idx].xscrollbar(@hscr_list[idx]) 168 169 # add new base 170 @base_list << base 171 } 172 173 # pad 174 @f_title_pad = TkFrame.new(@frame, 'relief'=>'raised', 175 'borderwidth'=>@title_border, 176 'highlightthickness'=>@h_l_thick) 177 178 @f_scr_pad = TkFrame.new(@frame, 'relief'=>'sunken', 179 'borderwidth'=>1, 180 'highlightthickness'=>@h_l_thick) 181 182 # height check 183 title_height = 0 184 @title_list.each{|w| 185 h = w.winfo_reqheight 186 title_height = h if title_height < h 187 } 188 189 hscr_height = 0 190 @hscr_list.each{|w| 191 h = w.winfo_reqheight 192 hscr_height = h if hscr_height < h 193 } 194 195 @f_title.height title_height 196 @f_lbox.height lbox_height 197 @f_hscr.height hscr_height 198 199 # set control procedure for virtical scroll 200 @v_scroll.assign(*@lbox_list) 201 202 # set control procedure for horizoncal scroll 203 @h_scroll.assign(@c_title, @c_lbox, @c_hscr) 204 205 # binding for listboxes 206 @lbox_list.each_with_index{|l, idx| 207 l.bind('Button-1', proc{|w, y| 208 @frame.focus 209 select_line(w, w.nearest(y)) 210 }, '%W %y') 211 l.bind('B1-Motion', proc{|w, y| 212 select_line(w, w.nearest(y)) 213 }, '%W %y') 214 l.bind('Double-Button-1', proc{ 215 @command.call(get_select) if @command 216 }) 217 218 l.bind('Control-Home', proc{|w| select_line(w, 0)}, '%W') 219 l.bind('Control-End', proc{|w| select_line(w, 'end')}, '%W') 220 221 l.bind('Button-2', proc{|x, y| 222 @lbox_mark_x = x 223 @lbox_list.each{|lbox| lbox.scan_mark(x, y)} 224 }, '%x %y') 225 l.bind('B2-Motion', proc{|x, y| 226 @lbox_list.each{|lbox| lbox.scan_dragto(@lbox_mark_x, y)} 227 l.scan_dragto(x, y) 228 }, '%x %y') 229 } 230 231 bbox = @w_title.bbox 232 @c_title.height(bbox[3]) 233 @c_title.scrollregion(bbox) 234 235 bbox = @w_lbox.bbox 236 @c_lbox.height(bbox[3]) 237 @c_lbox.scrollregion(bbox) 238 239 if @show_each_hscr 240 bbox = @w_hscr.bbox 241 @c_hscr.height(bbox[3]) 242 @c_hscr.scrollregion(bbox) 243 end 244 245 # binding 246 @frame.takefocus(true) 247 @frame.bind('Key-Up', proc{select_shift(@lbox_list[0], -1)}) 248 @frame.bind('Key-Down', proc{select_shift(@lbox_list[0], 1)}) 249 @frame.bind('Return', proc{@command.call(get_select) if @command}) 250 251 # alignment 252 TkGrid.rowconfigure(@frame, 0, 'weight'=>0) 253 TkGrid.rowconfigure(@frame, 1, 'weight'=>1) 254 TkGrid.rowconfigure(@frame, 2, 'weight'=>0) 255 TkGrid.rowconfigure(@frame, 3, 'weight'=>0) 256 TkGrid.columnconfigure(@frame, 0, 'weight'=>1) 257 TkGrid.columnconfigure(@frame, 1, 'weight'=>0) 258 TkGrid.columnconfigure(@frame, 2, 'weight'=>0) 259 @v_scroll.grid('row'=>1, 'column'=>2, 'sticky'=>'ns') 260 @c_title.grid('row'=>0, 'column'=>0, 'sticky'=>'news') 261 @f_title_pad.grid('row'=>0, 'column'=>2, 'sticky'=>'news') 262 @c_lbox.grid('row'=>1, 'column'=>0, 'sticky'=>'news') 263 @c_hscr.grid('row'=>2, 'column'=>0, 'sticky'=>'ew') if @show_each_hscr 264 @h_scroll.grid('row'=>3, 'column'=>0, 'sticky'=>'ew') if @show_win_hscr 265 @f_scr_pad.grid('row'=>2, 'rowspan'=>2, 'column'=>2, 'sticky'=>'news') 266 267 # binding for 'Configure' event 268 @c_lbox.bind('Configure', 269 proc{|height, width| reconstruct(height, width)}, 270 '%h %w') 271 272 # set default receiver of method calls 273 @path = @frame.path 274 275 # configure options 276 keys = {} unless keys 277 keys = _symbolkey2str(keys) 278 279 # command 280 cmd = keys.delete('command') 281 command(cmd) if cmd 282 283 # 'scrollbarwidth' option == 'width' option of scrollbars 284 width = keys.delete('scrollbarwidth') 285 scrollbarwidth(width) if width 286 287 # options for listbox titles 288 title_font = keys.delete('titlefont') 289 titlefont(title_font) if title_font 290 291 title_fg = keys.delete('titleforeground') 292 titleforeground(title_fg) if title_fg 293 294 title_bg = keys.delete('titlebackground') 295 titlebackground(title_bg) if title_bg 296 297 # set receivers for configure methods 298 delegate('DEFAULT', *@lbox_list) 299 delegate('activebackground', @v_scroll, @h_scroll, *@hscr_list) 300 delegate('troughcolor', @v_scroll, @h_scroll, *@hscr_list) 301 delegate('repeatdelay', @v_scroll, @h_scroll, *@hscr_list) 302 delegate('repeatinterval', @v_scroll, @h_scroll, *@hscr_list) 303 delegate('borderwidth', @frame) 304 delegate('width', @c_lbox, @c_title, @c_hscr) 305 delegate('relief', @frame) 306 307 # configure 308 configure(keys) if keys.size > 0 309 end 310 private :initialize_composite 311 312 # keep_minsize? 313 def keep_minsize? 314 @keep_minsize 315 end 316 def keep_minsize(bool) 317 @keep_minsize = bool 318 end 319 320 # each hscr 321 def show_each_hscr 322 @show_each_hscr = true 323 @c_hscr.grid('row'=>2, 'column'=>0, 'sticky'=>'ew') 324 end 325 def hide_each_hscr 326 @show_each_hscr = false 327 @c_hscr.ungrid 328 end 329 330 # window hscroll 331 def show_win_hscr 332 @show_win_hscr = true 333 @h_scroll.grid('row'=>3, 'column'=>0, 'sticky'=>'ew') 334 end 335 def hide_win_hscr 336 @show_each_hscr = false 337 @h_scroll.ungrid 338 end 339 340 # set command 341 def command(cmd) 342 @command = cmd 343 self 344 end 345 346 # set scrollbar width 347 def scrollbarwidth(width) 348 @scrbar_width = width 349 @v_scroll['width'] = @scrbar_width 350 @h_scroll['width'] = @scrbar_width 351 @hscr_list.each{|hscr| hscr['width'] = @scrbar_width} 352 self 353 end 354 355 # set scrollbar border 356 def scrollbarborder(width) 357 @scrbar_border = width 358 @v_scroll['border'] = @scrbar_border 359 @h_scroll['border'] = @scrbar_border 360 @hscr_list.each{|hscr| hscr['border'] = @scrbar_border} 361 self 362 end 363 364 # set listbox borders 365 def listboxborder(width) 366 @lbox_border = width 367 @lbox_list.each{|w| w['border'] = @lbox_border} 368 self 369 end 370 371 # set listbox relief 372 def listboxrelief(relief) 373 @lbox_list.each{|w| w['relief'] = relief} 374 self 375 end 376 377 # set title borders 378 def titleborder(width) 379 @title_border = width 380 @f_title_pad['border'] = @title_border 381 @title_list.each{|label| label['border'] = @title_border} 382 self 383 end 384 385 # set title font 386 def titlefont(font) 387 @title_list.each{|label| label['font'] = font} 388 title_height = 0 389 @title_list.each{|w| 390 h = w.winfo_reqheight 391 title_height = h if title_height < h 392 } 393 @f_title.height title_height 394 bbox = @w_title.bbox 395 @c_title.height(bbox[3]) 396 @c_title.scrollregion(bbox) 397 self 398 end 399 400 # set title foreground color 401 def titleforeground(fg) 402 @title_list.each{|label| label['foreground'] = fg} 403 self 404 end 405 406 # set title background color 407 def titlebackground(bg) 408 @f_title_pad['background'] = bg 409 @title_list.each{|label| label['background'] = bg} 410 self 411 end 412 413 # set title cmds 414 def titlecommand(idx, cmd=Proc.new) 415 @title_cmd[idx] = cmd 416 end 417 418 # call title cmds 419 def titleinvoke(idx) 420 @title_cmd[idx].call if @title_cmd[idx] 421 end 422 423 # get label widgets of listbox titles 424 def titlelabels(*indices) 425 @title_list[*indices] 426 end 427 428 # get listbox widgets 429 def columns(*indices) 430 @lbox_list[*indices] 431 end 432 433 def delete(*idx) 434 idx = idx.collect{|i| 435 if i.kind_of?(Integer) 436 "#{i}.0" 437 else 438 i.to_s 439 end 440 } 441 @lbox_list.collect{|lbox| lbox.delete(*idx)} 442 end 443 444 def get(idx_s, idx_e=nil) 445 unless idx_e 446 if idx_s.kind_of?(Integer) 447 idx_s = "#{idx_s}.0" 448 idx_e = "#{idx_s} lineend" 449 else 450 idx_s = idx_s.to_s 451 idx_e = "#{idx_s} lineend" 452 end 453 @lbox_list.collect{|lbox| 454 lbox.get(idx_s, idx_e) 455 } 456 else 457 if idx_s.kind_of?(Integer) 458 idx_s = "#{idx_s}.0" 459 else 460 idx_s = idx_s.to_s 461 end 462 if idx_e.kind_of?(Integer) 463 idx_e = "#{idx_e}.end" 464 else 465 idx_e = "#{idx_e} lineend" 466 end 467 list = @lbox_list.collect{|lbox| lbox.get(idx_s, idx_e).split(/\n/)} 468 result = [] 469 list[0].each_with_index{|line, index| 470 result << list.collect{|lines| lines[index]} 471 } 472 result 473 end 474 end 475 476 def get_select 477 get(@lbox_list[0].select_index) 478 end 479 480 def _line_array_to_hash(line) 481 result = {} 482 @name_index.each_pair{|label, indices| 483 if indices.size == 1 484 result[label] = line[indices[0]] 485 else 486 result[label] = indices.collect{|index| line[index]} 487 end 488 } 489 result 490 end 491 private :_line_array_to_hash 492 493 def get_by_hash(*idx) 494 get_result = get(*idx) 495 if idx.size == 1 496 _line_array_to_hash(get_result) 497 else 498 get_result.collect{|line| _line_array_to_hash(line)} 499 end 500 end 501 502 def insert(idx, *lines) 503 lbox_ins = [] 504 (0..@lbox_list.size).each{lbox_ins << []} 505 506 if idx.kind_of?(Integer) 507 idx = "#{idx}.0" 508 else 509 idx = idx.to_s 510 end 511 512 if @lbox_list[0].index('1.0 + 1 char') == @lbox_list[0].index('end') 513 cr = "" 514 else 515 cr = "\n" 516 end 517 518 lines.each{|line| 519 if line.kind_of? Hash 520 array = [] 521 @name_index.each_pair{|label, indices| 522 if indices.size == 1 523 array[indices[0]] = line[label] 524 else 525 if line[label].kind_of? Array 526 indices.each_with_index{|index, num| 527 array[index] = line[label][num] 528 } 529 else 530 array[indices[0]] = line[label] 531 end 532 end 533 } 534 line = array 535 end 536 537 @name_index.each_pair{|label, indices| 538 if indices.size == 1 539 lbox_ins[indices[0]] << line[indices[0]] 540 else 541 indices.each{|index| lbox_ins[index] << line[index]} 542 end 543 } 544 } 545 546 @lbox_list.each_with_index{|lbox, index| 547 lbox.insert(idx, cr + lbox_ins[index].join("\n")) if lbox_ins[index] 548 } 549 end 550 551 def select_clear(first, last=None) 552 @lbox_list.each{|lbox| lbox.sel_clear(first, last=None)} 553 end 554 555 def select_set(first, last=None) 556 @lbox_list.each{|lbox| lbox.sel_set(first, last=None)} 557 end 558 559 ########################################### 560 private 561 562 def reconstruct(height, width) 563 if @keep_minsize && width <= @width_total 564 @f_title.width(@width_total) 565 @f_lbox.width(@width_total) 566 @f_hscr.width(@width_total) if @show_each_hscr 567 @window_width = @width_total 568 else 569 @f_title.width(width) 570 @f_lbox.width(width) 571 @f_hscr.width(width) if @show_each_hscr 572 @window_width = width 573 end 574 575 @f_lbox.height(height) 576 577 @c_title.scrollregion(@w_title.bbox) 578 @c_lbox.scrollregion(@w_lbox.bbox) 579 @c_hscr.scrollregion(@w_hscr.bbox) if @show_each_hscr 580 581 (0..(@rel_list.size - 2)).each{|idx| 582 title, lbox, hscr = @base_list[idx] 583 title.place('relwidth'=>@rel_list[idx+1] - @rel_list[idx]) 584 lbox.place('relwidth'=>@rel_list[idx+1] - @rel_list[idx], 585 'relheight'=>1.0) 586 hscr.place('relwidth'=>@rel_list[idx+1] - @rel_list[idx]) 587 } 588 end 589 590 def resize(x) 591 idx = @sel_sash 592 return if idx == 0 593 594 # adjustment of relative positioning 595 delta = (x - @x) / @frame_width 596 if delta < @rel_list[idx-1] - @rel_list[idx] + (2*@sash/@frame_width) 597 delta = @rel_list[idx-1] - @rel_list[idx] + (2*@sash/@frame_width) 598 elsif delta > @rel_list[idx+1] - @rel_list[idx] - (2*@sash/@frame_width) 599 delta = @rel_list[idx+1] - @rel_list[idx] - (2*@sash/@frame_width) 600 end 601 @rel_list[idx] += delta 602 603 # adjustment of leftside widget of the sash 604 title, lbox, hscr = @base_list[idx - 1] 605 title.place('relwidth'=>@rel_list[idx] - @rel_list[idx-1]) 606 lbox.place('relwidth'=>@rel_list[idx] - @rel_list[idx-1], 'relheight'=>1.0) 607 hscr.place('relwidth'=>@rel_list[idx] - @rel_list[idx-1]) 608 609 # adjustment of rightside widget of the sash 610 title, lbox, hscr = @base_list[idx] 611 title.place('relwidth'=>@rel_list[idx+1] - @rel_list[idx], 612 'relx'=>@rel_list[idx]) 613 lbox.place('relwidth'=>@rel_list[idx+1] - @rel_list[idx], 614 'relx'=>@rel_list[idx], 'relheight'=>1.0) 615 hscr.place('relwidth'=>@rel_list[idx+1] - @rel_list[idx], 616 'relx'=>@rel_list[idx]) 617 618 # update reference position 619 @x = x 620 end 621 622 def motion_cb(w, x, idx) 623 if x <= @sash && idx > 0 624 w.cursor 'sb_h_double_arrow' 625 @mode = :sash 626 @sel_sash = idx 627 elsif x >= w.winfo_width - @sash && idx < @lbox_total - 1 628 w.cursor 'sb_h_double_arrow' 629 @mode = :sash 630 @sel_sash = idx + 1 631 else 632 w.cursor "" 633 @mode = :title 634 @sel_sash = 0 635 end 636 end 637 638 def title_binding(title, index) 639 title.bind('Motion', proc{|w, x, idx| motion_cb(w, x, idx.to_i)}, 640 "%W %x #{index}") 641 642 title.bind('Enter', proc{|w, x, idx| motion_cb(w, x, idx.to_i)}, 643 "%W %x #{index}") 644 645 title.bind('Leave', proc{|w| w.cursor ""}, "%W") 646 647 title.bind('Button-1', 648 proc{|w, x| 649 if @mode == :sash 650 @x = x 651 @frame_width = TkWinfo.width(@f_title).to_f 652 else 653 title.relief 'sunken' 654 end 655 }, 656 '%W %X') 657 658 title.bind('ButtonRelease-1', 659 proc{|w, x, idx| 660 i = idx.to_i 661 if @mode == :title && @title_cmd[i].kind_of?(Proc) 662 @title_cmd[i].call 663 end 664 title.relief 'raised' 665 motion_cb(w,x,i) 666 }, 667 "%W %x #{index}") 668 669 title.bind('B1-Motion', proc{|x| resize(x) if @mode == :sash}, "%X") 670 end 671 672 ######################## 673 def select_line(w, idx) 674 @lbox_list.each{|l| 675 l.select_clear(1, 'end') 676 l.select_set(idx) 677 } 678 w.select_set(idx) 679 end 680 681 def select_shift(w, dir) 682 head = w.index('@1,1').split('.')[0].to_i 683 tail = w.index("@1,#{w.winfo_height - 1}").split('.')[0].to_i - 1 684 idx = w.select_index + dir 685 last = w.index('end - 1 char').split('.')[0].to_i 686 if idx < 1 687 idx = 1 688 elsif idx > last 689 idx = last 690 end 691 @lbox_list.each{|l| 692 l.select_clear(1, 'end') 693 l.select_set(idx) 694 } 695 if head > idx 696 @lbox_list.each{|l| l.yview('scroll', -1, 'units')} 697 elsif tail < idx 698 @lbox_list.each{|l| l.yview('scroll', 1, 'units')} 699 end 700 end 701 ######################## 702end 703 704################################################ 705# test 706################################################ 707if __FILE__ == $0 708 l = TkMultiColumnList.new(nil, 200, 709 [ ['L1', 200, proc{p 'click L1'}], 710 ['L2', 100], 711 ['L3', 200] ], 712 'width'=>350, 713 #'titleforeground'=>'yellow', 714 'titleforeground'=>'white', 715 #'titlebackground'=>'navy', 716 'titlebackground'=>'blue', 717 'titlefont'=>'courier' 718 ).pack('fill'=>'both', 'expand'=>true) 719 l.insert('end', [1,2,3]) 720 l.insert('end', [4,5,6]) 721 l.insert('end', [4,5,6], [4,5,6]) 722 l.insert('end', ['aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa', 723 'bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb', 724 'cccccccccccccccccccccccccccccccccccccccccccccccccccc']) 725 l.insert('end', [1,2,3]) 726 l.insert('end', [4,5,6], [4,5,6]) 727 l.insert('end', ['aaaaaaaaaaaaaaa','bbbbbbbbbbbbbb','ccccccccccccccccc']) 728 l.insert('end', [1,2,3]) 729 l.insert('end', [4,5,6], [4,5,6]) 730 l.insert('end', ['aaaaaaaaaaaaaaa','bbbbbbbbbbbbbb','ccccccccccccccccc']) 731 l.insert('end', [1,2,3]) 732 l.insert('end', [4,5,6], [4,5,6]) 733 l.insert('end', ['aaaaaaaaaaaaaaa','bbbbbbbbbbbbbb','ccccccccccccccccc']) 734 l.insert('end', [1,2,3]) 735 l.insert('end', [4,5,6], [4,5,6]) 736 p l.columns(1) 737 p l.columns(1..3) 738 p l.columns(1,2) 739 740 l.command proc{|line_info| p line_info} 741 742 Tk.mainloop 743end 744