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