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