diff options
Diffstat (limited to 'ruby_1_8_5/ext/tk/sample/tkmulticolumnlist.rb')
-rw-r--r-- | ruby_1_8_5/ext/tk/sample/tkmulticolumnlist.rb | 743 |
1 files changed, 743 insertions, 0 deletions
diff --git a/ruby_1_8_5/ext/tk/sample/tkmulticolumnlist.rb b/ruby_1_8_5/ext/tk/sample/tkmulticolumnlist.rb new file mode 100644 index 0000000000..255eb691e5 --- /dev/null +++ b/ruby_1_8_5/ext/tk/sample/tkmulticolumnlist.rb @@ -0,0 +1,743 @@ +# +# tkmulticolumnlist.rb : multiple column list widget on scrollable frame +# by Hidetoshi NAGAI (nagai@ai.kyutech.ac.jp) +# +require 'tk' + +class TkMultiColumnList < TkText + include TkComposite + + # lbox_height : height of listboxes (pixel) + # title_info : array [ [<title_string>,<init_width>], ... ] + # keys : hash {<option>=><value>, ... } + def initialize_composite(lbox_height, title_info, keys={}) + # argument check + if (! title_info.kind_of? Array) or (title_info.size < 2) + raise + end + + # mode + @keep_minsize = true + @show_each_hscr = true + @show_win_hscr = true + + # init arrays + @base_list = [] + @rel_list = [] + @title_list = [] + @title_cmd = [] + @lbox_list = [] + @hscr_list = [] + + # decide total width + @lbox_total = title_info.size + @width_total = 0 + title_info.each{|title, width, cmd| + @width_total += width.to_f + @title_cmd << cmd + } + + # rel-table of label=>index + @name_index = {} + + # size definition + @window_width = @width_total + @sash = 5 + @scrbar_width = 15 + @scrbar_border = 3 + @lbox_border = 1 + @title_border = 3 + @h_l_thick = 0 + + # init status + @mode = :title + @command = nil + + # virtical scrollbar + @v_scroll = TkYScrollbar.new(@frame, 'highlightthickness'=>@h_l_thick, + 'borderwidth'=>@scrbar_border, + 'width'=>@scrbar_width) + + # horizontal scrollbar + @h_scroll = TkXScrollbar.new(@frame, 'highlightthickness'=>@h_l_thick, + 'borderwidth'=>@scrbar_border, + 'width'=>@scrbar_width) + + # create base flames + @c_title = TkCanvas.new(@frame, 'highlightthickness'=>@h_l_thick, + 'width'=>@window_width) + @f_title = TkFrame.new(@c_title, 'width'=>@width_total) + @w_title = TkcWindow.new(@c_title, 0, 0, + 'window'=>@f_title, 'anchor'=>'nw') + + @c_lbox = TkCanvas.new(@frame, 'highlightthickness'=>@h_l_thick, + 'width'=>@window_width) + @f_lbox = TkFrame.new(@c_lbox, 'width'=>@width_total) + @w_lbox = TkcWindow.new(@c_lbox, 0, 0, 'window'=>@f_lbox, 'anchor'=>'nw') + + @c_hscr = TkCanvas.new(@frame, 'highlightthickness'=>@h_l_thick, + 'width'=>@window_width) + @f_hscr = TkFrame.new(@c_hscr, 'width'=>@width_total) + @w_hscr = TkcWindow.new(@c_hscr, 0, 0, 'window'=>@f_hscr, 'anchor'=>'nw') + + # create each listbox + sum = 0.0 + @rel_list << sum/@width_total + title_info.each_with_index{|(label, width), idx| + # set relation between label and index + if @name_index.include?(label) + @name_index[label] << idx + else + @name_index[label] = [idx] + end + + # calculate relative positioning + sum += width + @rel_list << sum/@width_total + + # title field + f = TkFrame.new(@f_title, 'width'=>width) + base = [f] + + title = TkLabel.new(f, 'text'=>label, 'borderwidth'=>@title_border, + 'relief'=>'raised', 'highlightthickness'=>@h_l_thick) + title_binding(title, idx) + title.pack('fill'=>'x') + + @title_list << title + + f.place('relx'=>@rel_list[idx], 'y'=>0, 'anchor'=>'nw', 'width'=>1, + 'relheight'=>1.0, + 'relwidth'=>@rel_list[idx+1] - @rel_list[idx]) + + # listbox field + f = TkFrame.new(@f_lbox, 'width'=>width) + base << f + @lbox_list << TkText.new(f, 'highlightthickness'=>@h_l_thick, + 'borderwidth'=>@lbox_border, + 'takefocus'=>false, + 'wrap'=>'none') { + + bindtags(bindtags - [TkText]) + + @seltag = TkTextTag.new(self, 'background'=>'#b3b3b3', + 'borderwidth'=>1, 'relief'=>'raised') + def self.nearest(y) + self.index("@1,#{y}").split('.')[0].to_i + end + + def self.select_clear(first, last=nil) + first = "#{first}.0" if first.kind_of?(Integer) + first = self.index(first.to_s + ' linestart') + last = first unless last + last = "#{last}.0" if first.kind_of?(Integer) + last = self.index(last.to_s + ' + 1 lines linestart') + @seltag.remove(first, last) + end + + def self.select_set(first, last=nil) + first = "#{first}.0" if first.kind_of?(Integer) + first = self.index(first.to_s + ' linestart') + last = first unless last + last = "#{last}.0" if first.kind_of?(Integer) + last = self.index(last.to_s + ' + 1 lines linestart') + @seltag.add(first, last) + end + + def self.select_index + self.index(@seltag.first).split('.')[0].to_i + end + + pack('fill'=>'both', 'expand'=>true) + } + + f.place('relx'=>@rel_list[idx], 'y'=>0, 'anchor'=>'nw', 'width'=>1, + 'relwidth'=>@rel_list[idx+1] - @rel_list[idx], 'relheight'=>1.0) + + # scrollbar field + f = TkFrame.new(@f_hscr, 'width'=>width) + base << f + @hscr_list << TkXScrollbar.new(f, 'width'=>@scrbar_width, + 'borderwidth'=>@scrbar_border, + 'highlightthickness'=>@h_l_thick + ).pack('fill'=>'x', 'anchor'=>'w') + f.place('relx'=>@rel_list[idx], 'y'=>0, 'anchor'=>'nw', 'width'=>1, + 'relwidth'=>@rel_list[idx+1] - @rel_list[idx]) + + @lbox_list[idx].xscrollbar(@hscr_list[idx]) + + # add new base + @base_list << base + } + + # pad + @f_title_pad = TkFrame.new(@frame, 'relief'=>'raised', + 'borderwidth'=>@title_border, + 'highlightthickness'=>@h_l_thick) + + @f_scr_pad = TkFrame.new(@frame, 'relief'=>'sunken', + 'borderwidth'=>1, + 'highlightthickness'=>@h_l_thick) + + # height check + title_height = 0 + @title_list.each{|w| + h = w.winfo_reqheight + title_height = h if title_height < h + } + + hscr_height = 0 + @hscr_list.each{|w| + h = w.winfo_reqheight + hscr_height = h if hscr_height < h + } + + @f_title.height title_height + @f_lbox.height lbox_height + @f_hscr.height hscr_height + + # set control procedure for virtical scroll + @v_scroll.assign(*@lbox_list) + + # set control procedure for horizoncal scroll + @h_scroll.assign(@c_title, @c_lbox, @c_hscr) + + # binding for listboxes + @lbox_list.each_with_index{|l, idx| + l.bind('Button-1', proc{|w, y| + @frame.focus + select_line(w, w.nearest(y)) + }, '%W %y') + l.bind('B1-Motion', proc{|w, y| + select_line(w, w.nearest(y)) + }, '%W %y') + l.bind('Double-Button-1', proc{ + @command.call(get_select) if @command + }) + + l.bind('Control-Home', proc{|w| select_line(w, 0)}, '%W') + l.bind('Control-End', proc{|w| select_line(w, 'end')}, '%W') + + l.bind('Button-2', proc{|x, y| + @lbox_mark_x = x + @lbox_list.each{|lbox| lbox.scan_mark(x, y)} + }, '%x %y') + l.bind('B2-Motion', proc{|x, y| + @lbox_list.each{|lbox| lbox.scan_dragto(@lbox_mark_x, y)} + l.scan_dragto(x, y) + }, '%x %y') + } + + bbox = @w_title.bbox + @c_title.height(bbox[3]) + @c_title.scrollregion(bbox) + + bbox = @w_lbox.bbox + @c_lbox.height(bbox[3]) + @c_lbox.scrollregion(bbox) + + if @show_each_hscr + bbox = @w_hscr.bbox + @c_hscr.height(bbox[3]) + @c_hscr.scrollregion(bbox) + end + + # binding + @frame.takefocus(true) + @frame.bind('Key-Up', proc{select_shift(@lbox_list[0], -1)}) + @frame.bind('Key-Down', proc{select_shift(@lbox_list[0], 1)}) + @frame.bind('Return', proc{@command.call(get_select) if @command}) + + # alignment + TkGrid.rowconfigure(@frame, 0, 'weight'=>0) + TkGrid.rowconfigure(@frame, 1, 'weight'=>1) + TkGrid.rowconfigure(@frame, 2, 'weight'=>0) + TkGrid.rowconfigure(@frame, 3, 'weight'=>0) + TkGrid.columnconfigure(@frame, 0, 'weight'=>1) + TkGrid.columnconfigure(@frame, 1, 'weight'=>0) + TkGrid.columnconfigure(@frame, 2, 'weight'=>0) + @v_scroll.grid('row'=>1, 'column'=>2, 'sticky'=>'ns') + @c_title.grid('row'=>0, 'column'=>0, 'sticky'=>'news') + @f_title_pad.grid('row'=>0, 'column'=>2, 'sticky'=>'news') + @c_lbox.grid('row'=>1, 'column'=>0, 'sticky'=>'news') + @c_hscr.grid('row'=>2, 'column'=>0, 'sticky'=>'ew') if @show_each_hscr + @h_scroll.grid('row'=>3, 'column'=>0, 'sticky'=>'ew') if @show_win_hscr + @f_scr_pad.grid('row'=>2, 'rowspan'=>2, 'column'=>2, 'sticky'=>'news') + + # binding for 'Configure' event + @c_lbox.bind('Configure', + proc{|height, width| reconstruct(height, width)}, + '%h %w') + + # set default receiver of method calls + @path = @frame.path + + # configure options + keys = {} unless keys + keys = _symbolkey2str(keys) + + # command + cmd = keys.delete('command') + command(cmd) if cmd + + # 'scrollbarwidth' option == 'width' option of scrollbars + width = keys.delete('scrollbarwidth') + scrollbarwidth(width) if width + + # options for listbox titles + title_font = keys.delete('titlefont') + titlefont(title_font) if title_font + + title_fg = keys.delete('titleforeground') + titleforeground(title_fg) if title_fg + + title_bg = keys.delete('titlebackground') + titlebackground(title_bg) if title_bg + + # set receivers for configure methods + delegate('DEFAULT', *@lbox_list) + delegate('activebackground', @v_scroll, @h_scroll, *@hscr_list) + delegate('troughcolor', @v_scroll, @h_scroll, *@hscr_list) + delegate('repeatdelay', @v_scroll, @h_scroll, *@hscr_list) + delegate('repeatinterval', @v_scroll, @h_scroll, *@hscr_list) + delegate('borderwidth', @frame) + delegate('width', @c_lbox, @c_title, @c_hscr) + delegate('relief', @frame) + + # configure + configure(keys) if keys.size > 0 + end + private :initialize_composite + + # keep_minsize? + def keep_minsize? + @keep_minsize + end + def keep_minsize(bool) + @keep_minsize = bool + end + + # each hscr + def show_each_hscr + @show_each_hscr = true + @c_hscr.grid('row'=>2, 'column'=>0, 'sticky'=>'ew') + end + def hide_each_hscr + @show_each_hscr = false + @c_hscr.ungrid + end + + # window hscroll + def show_win_hscr + @show_win_hscr = true + @h_scroll.grid('row'=>3, 'column'=>0, 'sticky'=>'ew') + end + def hide_win_hscr + @show_each_hscr = false + @h_scroll.ungrid + end + + # set command + def command(cmd) + @command = cmd + self + end + + # set scrollbar width + def scrollbarwidth(width) + @scrbar_width = width + @v_scroll['width'] = @scrbar_width + @h_scroll['width'] = @scrbar_width + @hscr_list.each{|hscr| hscr['width'] = @scrbar_width} + self + end + + # set scrollbar border + def scrollbarborder(width) + @scrbar_border = width + @v_scroll['border'] = @scrbar_border + @h_scroll['border'] = @scrbar_border + @hscr_list.each{|hscr| hscr['border'] = @scrbar_border} + self + end + + # set listbox borders + def listboxborder(width) + @lbox_border = width + @lbox_list.each{|w| w['border'] = @lbox_border} + self + end + + # set listbox relief + def listboxrelief(relief) + @lbox_list.each{|w| w['relief'] = relief} + self + end + + # set title borders + def titleborder(width) + @title_border = width + @f_title_pad['border'] = @title_border + @title_list.each{|label| label['border'] = @title_border} + self + end + + # set title font + def titlefont(font) + @title_list.each{|label| label['font'] = font} + title_height = 0 + @title_list.each{|w| + h = w.winfo_reqheight + title_height = h if title_height < h + } + @f_title.height title_height + bbox = @w_title.bbox + @c_title.height(bbox[3]) + @c_title.scrollregion(bbox) + self + end + + # set title foreground color + def titleforeground(fg) + @title_list.each{|label| label['foreground'] = fg} + self + end + + # set title background color + def titlebackground(bg) + @f_title_pad['background'] = bg + @title_list.each{|label| label['background'] = bg} + self + end + + # set title cmds + def titlecommand(idx, cmd=Proc.new) + @title_cmd[idx] = cmd + end + + # call title cmds + def titleinvoke(idx) + @title_cmd[idx].call if @title_cmd[idx] + end + + # get label widgets of listbox titles + def titlelabels(*indices) + @title_list[*indices] + end + + # get listbox widgets + def columns(*indices) + @lbox_list[*indices] + end + + def delete(*idx) + idx = idx.collect{|i| + if i.kind_of?(Integer) + "#{i}.0" + else + i.to_s + end + } + @lbox_list.collect{|lbox| lbox.delete(*idx)} + end + + def get(idx_s, idx_e=nil) + unless idx_e + if idx_s.kind_of?(Integer) + idx_s = "#{idx_s}.0" + idx_e = "#{idx_s} lineend" + else + idx_s = idx_s.to_s + idx_e = "#{idx_s} lineend" + end + @lbox_list.collect{|lbox| + lbox.get(idx_s, idx_e) + } + else + if idx_s.kind_of?(Integer) + idx_s = "#{idx_s}.0" + else + idx_s = idx_s.to_s + end + if idx_e.kind_of?(Integer) + idx_e = "#{idx_e}.end" + else + idx_e = "#{idx_e} lineend" + end + list = @lbox_list.collect{|lbox| lbox.get(idx_s, idx_e).split(/\n/)} + result = [] + list[0].each_with_index{|line, index| + result << list.collect{|lines| lines[index]} + } + result + end + end + + def get_select + get(@lbox_list[0].select_index) + end + + def _line_array_to_hash(line) + result = {} + @name_index.each_pair{|label, indices| + if indices.size == 1 + result[label] = line[indices[0]] + else + result[label] = indices.collect{|index| line[index]} + end + } + result + end + private :_line_array_to_hash + + def get_by_hash(*idx) + get_result = get(*idx) + if idx.size == 1 + _line_array_to_hash(get_result) + else + get_result.collect{|line| _line_array_to_hash(line)} + end + end + + def insert(idx, *lines) + lbox_ins = [] + (0..@lbox_list.size).each{lbox_ins << []} + + if idx.kind_of?(Integer) + idx = "#{idx}.0" + else + idx = idx.to_s + end + + if @lbox_list[0].index('1.0 + 1 char') == @lbox_list[0].index('end') + cr = "" + else + cr = "\n" + end + + lines.each{|line| + if line.kind_of? Hash + array = [] + @name_index.each_pair{|label, indices| + if indices.size == 1 + array[indices[0]] = line[label] + else + if line[label].kind_of? Array + indices.each_with_index{|index, num| + array[index] = line[label][num] + } + else + array[indices[0]] = line[label] + end + end + } + line = array + end + + @name_index.each_pair{|label, indices| + if indices.size == 1 + lbox_ins[indices[0]] << line[indices[0]] + else + indices.each{|index| lbox_ins[index] << line[index]} + end + } + } + + @lbox_list.each_with_index{|lbox, index| + lbox.insert(idx, cr + lbox_ins[index].join("\n")) if lbox_ins[index] + } + end + + def select_clear(first, last=None) + @lbox_list.each{|lbox| lbox.sel_clear(first, last=None)} + end + + def select_set(first, last=None) + @lbox_list.each{|lbox| lbox.sel_set(first, last=None)} + end + + ########################################### + private + + def reconstruct(height, width) + if @keep_minsize && width <= @width_total + @f_title.width(@width_total) + @f_lbox.width(@width_total) + @f_hscr.width(@width_total) if @show_each_hscr + @window_width = @width_total + else + @f_title.width(width) + @f_lbox.width(width) + @f_hscr.width(width) if @show_each_hscr + @window_width = width + end + + @f_lbox.height(height) + + @c_title.scrollregion(@w_title.bbox) + @c_lbox.scrollregion(@w_lbox.bbox) + @c_hscr.scrollregion(@w_hscr.bbox) if @show_each_hscr + + (0..(@rel_list.size - 2)).each{|idx| + title, lbox, hscr = @base_list[idx] + title.place('relwidth'=>@rel_list[idx+1] - @rel_list[idx]) + lbox.place('relwidth'=>@rel_list[idx+1] - @rel_list[idx], + 'relheight'=>1.0) + hscr.place('relwidth'=>@rel_list[idx+1] - @rel_list[idx]) + } + end + + def resize(x) + idx = @sel_sash + return if idx == 0 + + # adjustment of relative positioning + delta = (x - @x) / @frame_width + if delta < @rel_list[idx-1] - @rel_list[idx] + (2*@sash/@frame_width) + delta = @rel_list[idx-1] - @rel_list[idx] + (2*@sash/@frame_width) + elsif delta > @rel_list[idx+1] - @rel_list[idx] - (2*@sash/@frame_width) + delta = @rel_list[idx+1] - @rel_list[idx] - (2*@sash/@frame_width) + end + @rel_list[idx] += delta + + # adjustment of leftside widget of the sash + title, lbox, hscr = @base_list[idx - 1] + title.place('relwidth'=>@rel_list[idx] - @rel_list[idx-1]) + lbox.place('relwidth'=>@rel_list[idx] - @rel_list[idx-1], 'relheight'=>1.0) + hscr.place('relwidth'=>@rel_list[idx] - @rel_list[idx-1]) + + # adjustment of rightside widget of the sash + title, lbox, hscr = @base_list[idx] + title.place('relwidth'=>@rel_list[idx+1] - @rel_list[idx], + 'relx'=>@rel_list[idx]) + lbox.place('relwidth'=>@rel_list[idx+1] - @rel_list[idx], + 'relx'=>@rel_list[idx], 'relheight'=>1.0) + hscr.place('relwidth'=>@rel_list[idx+1] - @rel_list[idx], + 'relx'=>@rel_list[idx]) + + # update reference position + @x = x + end + + def motion_cb(w, x, idx) + if x <= @sash && idx > 0 + w.cursor 'sb_h_double_arrow' + @mode = :sash + @sel_sash = idx + elsif x >= w.winfo_width - @sash && idx < @lbox_total - 1 + w.cursor 'sb_h_double_arrow' + @mode = :sash + @sel_sash = idx + 1 + else + w.cursor "" + @mode = :title + @sel_sash = 0 + end + end + + def title_binding(title, index) + title.bind('Motion', proc{|w, x, idx| motion_cb(w, x, idx.to_i)}, + "%W %x #{index}") + + title.bind('Enter', proc{|w, x, idx| motion_cb(w, x, idx.to_i)}, + "%W %x #{index}") + + title.bind('Leave', proc{|w| w.cursor ""}, "%W") + + title.bind('Button-1', + proc{|w, x| + if @mode == :sash + @x = x + @frame_width = TkWinfo.width(@f_title).to_f + else + title.relief 'sunken' + end + }, + '%W %X') + + title.bind('ButtonRelease-1', + proc{|w, x, idx| + i = idx.to_i + if @mode == :title && @title_cmd[i].kind_of?(Proc) + @title_cmd[i].call + end + title.relief 'raised' + motion_cb(w,x,i) + }, + "%W %x #{index}") + + title.bind('B1-Motion', proc{|x| resize(x) if @mode == :sash}, "%X") + end + + ######################## + def select_line(w, idx) + @lbox_list.each{|l| + l.select_clear(1, 'end') + l.select_set(idx) + } + w.select_set(idx) + end + + def select_shift(w, dir) + head = w.index('@1,1').split('.')[0].to_i + tail = w.index("@1,#{w.winfo_height - 1}").split('.')[0].to_i - 1 + idx = w.select_index + dir + last = w.index('end - 1 char').split('.')[0].to_i + if idx < 1 + idx = 1 + elsif idx > last + idx = last + end + @lbox_list.each{|l| + l.select_clear(1, 'end') + l.select_set(idx) + } + if head > idx + @lbox_list.each{|l| l.yview('scroll', -1, 'units')} + elsif tail < idx + @lbox_list.each{|l| l.yview('scroll', 1, 'units')} + end + end + ######################## +end + +################################################ +# test +################################################ +if __FILE__ == $0 + l = TkMultiColumnList.new(nil, 200, + [ ['L1', 200, proc{p 'click L1'}], + ['L2', 100], + ['L3', 200] ], + 'width'=>350, + #'titleforeground'=>'yellow', + 'titleforeground'=>'white', + #'titlebackground'=>'navy', + 'titlebackground'=>'blue', + 'titlefont'=>'courier' + ).pack('fill'=>'both', 'expand'=>true) + l.insert('end', [1,2,3]) + l.insert('end', [4,5,6]) + l.insert('end', [4,5,6], [4,5,6]) + l.insert('end', ['aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa', + 'bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb', + 'cccccccccccccccccccccccccccccccccccccccccccccccccccc']) + l.insert('end', [1,2,3]) + l.insert('end', [4,5,6], [4,5,6]) + l.insert('end', ['aaaaaaaaaaaaaaa','bbbbbbbbbbbbbb','ccccccccccccccccc']) + l.insert('end', [1,2,3]) + l.insert('end', [4,5,6], [4,5,6]) + l.insert('end', ['aaaaaaaaaaaaaaa','bbbbbbbbbbbbbb','ccccccccccccccccc']) + l.insert('end', [1,2,3]) + l.insert('end', [4,5,6], [4,5,6]) + l.insert('end', ['aaaaaaaaaaaaaaa','bbbbbbbbbbbbbb','ccccccccccccccccc']) + l.insert('end', [1,2,3]) + l.insert('end', [4,5,6], [4,5,6]) + p l.columns(1) + p l.columns(1..3) + p l.columns(1,2) + + l.command proc{|line_info| p line_info} + + Tk.mainloop +end |