1#
2# tkballoonhelp.rb : simple balloon help widget
3#                       by Hidetoshi NAGAI (nagai@ai.kyutech.ac.jp)
4#
5# Add a balloon help to a widget.
6# This widget has only poor featureas. If you need more useful features,
7# please try to use the Tix extension of Tcl/Tk under Ruby/Tk.
8#
9# The interval time to display a balloon help is defined 'interval' option
10# (default is 750ms).
11#
12require 'tk'
13
14module Tk
15  module RbWidget
16    class BalloonHelp<TkLabel
17    end
18  end
19end
20class Tk::RbWidget::BalloonHelp<TkLabel
21  DEFAULT_FOREGROUND = 'black'
22  DEFAULT_BACKGROUND = 'white'
23  DEFAULT_INTERVAL   = 750
24
25  def _balloon_binding(interval)
26    @timer = TkAfter.new(interval, 1, proc{show})
27    def @timer.interval(val)
28      @sleep_time = val
29    end
30    @bindtag = TkBindTag.new
31    @bindtag.bind('Enter',  proc{@timer.start})
32    @bindtag.bind('Motion', proc{@timer.restart; erase})
33    @bindtag.bind('Any-ButtonPress', proc{@timer.restart; erase})
34    @bindtag.bind('Leave',  proc{@timer.stop; erase})
35    tags = @parent.bindtags
36    idx = tags.index(@parent)
37    unless idx
38      ppath = TkComm.window(@parent.path)
39      idx = tags.index(ppath) || 0
40    end
41    tags[idx,0] = @bindtag
42    @parent.bindtags(tags)
43  end
44  private :_balloon_binding
45
46  def initialize(parent=nil, keys={})
47    @parent = parent || Tk.root
48
49    @frame = TkToplevel.new(@parent)
50    @frame.withdraw
51    @frame.overrideredirect(true)
52    @frame.transient(TkWinfo.toplevel(@parent))
53    @epath = @frame.path
54
55    if keys
56      keys = _symbolkey2str(keys)
57    else
58      keys = {}
59    end
60
61    @command = keys.delete('command')
62
63    @interval = keys.delete('interval'){DEFAULT_INTERVAL}
64    _balloon_binding(@interval)
65
66    # @label = TkLabel.new(@frame, 'background'=>'bisque').pack
67    @label = TkLabel.new(@frame,
68                         'foreground'=>DEFAULT_FOREGROUND,
69                         'background'=>DEFAULT_BACKGROUND).pack
70    @label.configure(_symbolkey2str(keys)) unless keys.empty?
71    @path = @label
72  end
73
74  def epath
75    @epath
76  end
77
78  def interval(val)
79    if val
80      @timer.interval(val)
81    else
82      @interval
83    end
84  end
85
86  def command(cmd = Proc.new)
87    @command = cmd
88    self
89  end
90
91  def show
92    x = TkWinfo.pointerx(@parent)
93    y = TkWinfo.pointery(@parent)
94    @frame.geometry("+#{x+1}+#{y+1}")
95
96    if @command
97      case @command.arity
98      when 0
99        @command.call
100      when 2
101        @command.call(x - TkWinfo.rootx(@parent), y - TkWinfo.rooty(@parent))
102      when 3
103        @command.call(x - TkWinfo.rootx(@parent), y - TkWinfo.rooty(@parent),
104                      self)
105      else
106        @command.call(x - TkWinfo.rootx(@parent), y - TkWinfo.rooty(@parent),
107                      self, @parent)
108      end
109    end
110
111    @frame.deiconify
112    @frame.raise
113
114    begin
115      @org_cursor = @parent.cget('cursor')
116    rescue
117      @org_cursor = @parent['cursor']
118    end
119    begin
120      @parent.configure('cursor', 'crosshair')
121    rescue
122      @parent.cursor('crosshair')
123    end
124  end
125
126  def erase
127    begin
128      @parent.configure('cursor', @org_cursor)
129    rescue
130      @parent.cursor(@org_cursor)
131    end
132    @frame.withdraw
133  end
134
135  def destroy
136    @frame.destroy
137  end
138end
139
140################################################
141# test
142################################################
143if __FILE__ == $0
144  TkButton.new('text'=>'This button has a balloon help') {|b|
145    pack('fill'=>'x')
146    Tk::RbWidget::BalloonHelp.new(b, 'text'=>' Message ')
147  }
148  TkButton.new('text'=>'This button has another balloon help') {|b|
149    pack('fill'=>'x')
150    Tk::RbWidget::BalloonHelp.new(b,
151                        'text'=>"CONFIGURED MESSAGE\nchange colors, and so on",
152                        'interval'=>200, 'font'=>'courier',
153                        'background'=>'gray', 'foreground'=>'red')
154  }
155
156  sb = TkScrollbox.new.pack(:fill=>:x)
157  sb.insert(:end, *%w(aaa bbb ccc ddd eee fff ggg hhh iii jjj kkk lll mmm))
158=begin
159  # CASE1 : command takes no arguemnt
160  bh = Tk::RbWidget::BalloonHelp.new(sb, :interval=>500,
161                           :relief=>:ridge, :background=>'white',
162                           :command=>proc{
163                             y = TkWinfo.pointery(sb) - TkWinfo.rooty(sb)
164                             bh.text "current index == #{sb.nearest(y)}"
165                           })
166=end
167=begin
168  # CASE2 : command takes 2 arguemnts
169  bh = Tk::RbWidget::BalloonHelp.new(sb, :interval=>500,
170                           :relief=>:ridge, :background=>'white',
171                           :command=>proc{|x, y|
172                             bh.text "current index == #{sb.nearest(y)}"
173                           })
174=end
175=begin
176  # CASE3 : command takes 3 arguemnts
177  Tk::RbWidget::BalloonHelp.new(sb, :interval=>500,
178                      :relief=>:ridge, :background=>'white',
179                      :command=>proc{|x, y, bhelp|
180                        bhelp.text "current index == #{sb.nearest(y)}"
181                      })
182=end
183=begin
184  # CASE4a : command is a Proc object and takes 4 arguemnts
185  cmd = proc{|x, y, bhelp, parent|
186    bhelp.text "current index == #{parent.nearest(y)}"
187  }
188
189  Tk::RbWidget::BalloonHelp.new(sb, :interval=>500,
190                      :relief=>:ridge, :background=>'white',
191                      :command=>cmd)
192
193  sb2 = TkScrollbox.new.pack(:fill=>:x)
194  sb2.insert(:end, *%w(AAA BBB CCC DDD EEE FFF GGG HHH III JJJ KKK LLL MMM))
195  Tk::RbWidget::BalloonHelp.new(sb2, :interval=>500,
196                      :padx=>5, :relief=>:raised,
197                      :background=>'gray25', :foreground=>'white',
198                      :command=>cmd)
199=end
200#=begin
201  # CASE4b : command is a Method object and takes 4 arguemnts
202  def set_msg(x, y, bhelp, parent)
203    bhelp.text "current index == #{parent.nearest(y)}"
204  end
205  cmd = self.method(:set_msg)
206
207  Tk::RbWidget::BalloonHelp.new(sb, :interval=>500,
208                                :relief=>:ridge, :background=>'white',
209                                :command=>cmd)
210
211  sb2 = TkScrollbox.new.pack(:fill=>:x)
212  sb2.insert(:end, *%w(AAA BBB CCC DDD EEE FFF GGG HHH III JJJ KKK LLL MMM))
213  Tk::RbWidget::BalloonHelp.new(sb2, :interval=>500,
214                                :padx=>5, :relief=>:raised,
215                                :background=>'gray25', :foreground=>'white',
216                                :command=>cmd)
217#=end
218
219  Tk.mainloop
220end
221