From 3124427ccbf02a5bd20f169ad6a328286e05c262 Mon Sep 17 00:00:00 2001 From: nagai Date: Wed, 2 Mar 2005 07:06:52 +0000 Subject: * ext/tk/tcltklib.c: enforce thread-check and exception-handling to avoid SEGV trouble. * ext/tk/tkutil/tkutil.c; fix a bug on converting a SJIS string array to a Tcl's list string. * ext/tk/tcltklib.c: wrap Tcl's original "namespace" command to protect from namespace crash. * ext/tk/lib/multi-tk.rb: enforce exception-handling. * ext/tk/lib/multi-tk.rb: catch IRB_EXIT to work on irb. * ext/tk/lib/tk.rb: ditto. * ext/tk/tcltklib.c: add TclTkLib.mainloop_thread? * ext/tk/lib/multi-tk.rb: (bug fix) callback returns a value. * ext/tk/lib/tk/canvas.rb (delete): bug fix when multiple arguments. * ext/tk/lib/clock.rb: fix 'no method error'. * ext/tk/lib/clock.rb (self.clicks): accept a Symbol argument. * ext/tk/lib/variable.rb: be able to set default_value_type; :numeric, :bool, :string, :symbol, :list, :numlist or nil (default; same to :string). If set a type, TkVariable#value returns a value of the type. * ext/tk/lib/tkextlib/tclx/tclx.rb: add Tk::TclX.signal to warn the risk of using TclX extension's 'signal' command. * ext/tk/sample/irbtk.rb: irb with Ruby/Tk. * ext/tk/sample/demos-*/anilabel.rb: bug fix on 'show code' * ext/tk/sample/demos-*/aniwave.rb: new Ruby/Tk animation demo. * ext/tk/sample/demos-*/pendulum.rb: ditto. * ext/tk/sample/demos-*/goldberg.rb: ditto. * ext/tk/sample/demos-*/widget: add entries of animation demos. git-svn-id: svn+ssh://ci.ruby-lang.org/ruby/trunk@8046 b2dd03c8-39d4-4d8f-98ff-823fe69b080e --- ChangeLog | 47 + ext/tk/ChangeLog.tkextlib | 4 + ext/tk/MANUAL_tcltklib.eng | 12 +- ext/tk/MANUAL_tcltklib.eucj | 11 +- ext/tk/lib/multi-tk.rb | 277 +++- ext/tk/lib/tk.rb | 89 +- ext/tk/lib/tk/canvas.rb | 6 +- ext/tk/lib/tk/clock.rb | 6 +- ext/tk/lib/tk/timer.rb | 1 + ext/tk/lib/tk/variable.rb | 247 ++- ext/tk/lib/tkextlib/tclx/tclx.rb | 10 + ext/tk/sample/demos-en/anilabel.rb | 2 +- ext/tk/sample/demos-en/aniwave.rb | 115 ++ ext/tk/sample/demos-en/goldberg.rb | 1968 ++++++++++++++++++++++++ ext/tk/sample/demos-en/pendulum.rb | 223 +++ ext/tk/sample/demos-en/widget | 8 +- ext/tk/sample/demos-jp/anilabel.rb | 2 +- ext/tk/sample/demos-jp/aniwave.rb | 116 ++ ext/tk/sample/demos-jp/goldberg.rb | 1974 ++++++++++++++++++++++++ ext/tk/sample/demos-jp/pendulum.rb | 224 +++ ext/tk/sample/demos-jp/widget | 8 +- ext/tk/sample/irbtk.rb | 30 + ext/tk/tcltklib.c | 2925 +++++++++++++++++++++++++++++------- ext/tk/tkutil/tkutil.c | 98 +- 24 files changed, 7696 insertions(+), 707 deletions(-) create mode 100644 ext/tk/sample/demos-en/aniwave.rb create mode 100644 ext/tk/sample/demos-en/goldberg.rb create mode 100644 ext/tk/sample/demos-en/pendulum.rb create mode 100644 ext/tk/sample/demos-jp/aniwave.rb create mode 100644 ext/tk/sample/demos-jp/goldberg.rb create mode 100644 ext/tk/sample/demos-jp/pendulum.rb create mode 100644 ext/tk/sample/irbtk.rb diff --git a/ChangeLog b/ChangeLog index 056a4506e3..92a5908d7e 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,3 +1,50 @@ +Wed Mar 2 16:03:08 2005 Hidetoshi NAGAI + + * ext/tk/tcltklib.c: enforce thread-check and exception-handling to + avoid SEGV trouble. + + * ext/tk/tkutil/tkutil.c; fix a bug on converting a SJIS string array + to a Tcl's list string. + + * ext/tk/tcltklib.c: wrap Tcl's original "namespace" command to + protect from namespace crash. + + * ext/tk/lib/multi-tk.rb: enforce exception-handling. + + * ext/tk/lib/multi-tk.rb: catch IRB_EXIT to work on irb. + + * ext/tk/lib/tk.rb: ditto. + + * ext/tk/tcltklib.c: add TclTkLib.mainloop_thread? + + * ext/tk/lib/multi-tk.rb: (bug fix) callback returns a value. + + * ext/tk/lib/tk/canvas.rb (delete): bug fix when multiple arguments. + + * ext/tk/lib/clock.rb: fix 'no method error'. + + * ext/tk/lib/clock.rb (self.clicks): accept a Symbol argument. + + * ext/tk/lib/variable.rb: be able to set default_value_type; :numeric, + :bool, :string, :symbol, :list, :numlist or nil (default; same to + :string). If set a type, TkVariable#value returns a value of the + type. + + * ext/tk/lib/tkextlib/tclx/tclx.rb: add Tk::TclX.signal to warn the + risk of using TclX extension's 'signal' command. + + * ext/tk/sample/irbtk.rb: irb with Ruby/Tk. + + * ext/tk/sample/demos-*/anilabel.rb: bug fix on 'show code' + + * ext/tk/sample/demos-*/aniwave.rb: new Ruby/Tk animation demo. + + * ext/tk/sample/demos-*/pendulum.rb: ditto. + + * ext/tk/sample/demos-*/goldberg.rb: ditto. + + * ext/tk/sample/demos-*/widget: add entries of animation demos. + Wed Mar 2 12:21:18 2005 Nobuyoshi Nakada * eval.c (rb_eval): [EXPERIMENTAL] NODE_LAMBDA implemented. diff --git a/ext/tk/ChangeLog.tkextlib b/ext/tk/ChangeLog.tkextlib index 53e3dd69ee..d99ceece7e 100644 --- a/ext/tk/ChangeLog.tkextlib +++ b/ext/tk/ChangeLog.tkextlib @@ -1,3 +1,7 @@ +2005-02-20 Hidetoshi NAGAI + + * ext/tk/lib/tclx/tclx.rb: warning TclX's 'signal' command. + 2005-01-25 Hidetoshi NAGAI * ext/tk/lib/tkextlib/blt/component.rb: bug fix. cannot accept diff --git a/ext/tk/MANUAL_tcltklib.eng b/ext/tk/MANUAL_tcltklib.eng index d3417f5dff..43211d951f 100644 --- a/ext/tk/MANUAL_tcltklib.eng +++ b/ext/tk/MANUAL_tcltklib.eng @@ -126,12 +126,22 @@ module TclTklib : defined as 0. [module methods] - mainloop(check_root = true) + mainloop(check_root = true) : Starts the eventloop. If 'check_root' is true, this method : doesn't return when a root widget exists. : If 'check_root' is false, doen't return by the other : reasons than exceptions. + mainloop_thread? + : Returns whether the current thread executes the eventloop. + : If true, the eventloop is working on the current thread. + : If no eventloop is working, this method returns nil. + : And if the other thread executes the eventloop, returns false. + : + : *** ATTENTION *** + : When this methods returns false, it is dangerous to call a Tk + : interpreter directly. + mainloop_watchdog(check_root = true) : On the normal eventloop, some kinds of callback operations : cause deadlock. To avoid some of such deadlocks, this diff --git a/ext/tk/MANUAL_tcltklib.eucj b/ext/tk/MANUAL_tcltklib.eucj index a0d7e42307..a2a14cb19f 100644 --- a/ext/tk/MANUAL_tcltklib.eucj +++ b/ext/tk/MANUAL_tcltklib.eucj @@ -230,6 +230,15 @@ require "tcltklib" : WINDOW 以外のイベントは発生しうるため ).終了には,外部 : からの働き掛け ( スレッドを活用するなど ) が必要. + mainloop_thread? + : カレントスレッドがイベントループを実行しているスレッド + : かどうかを返す. + : イベントループを実行しているスレッドであれば true を, + : どのスレッドでもイベントループが実行されていない場合は + : nil を,他のスレッドでイベントループが実行されている場 + : 合は false を返す. + : false の際に Tk インタープリタを直接呼ぶのは危険である. + mainloop_watchdog(check_root = true) : 通常のイベントループでは,イベント処理の内容によっては : デッドロックを引き起こす可能性がある (例えばイベントに @@ -377,7 +386,7 @@ require "tcltklib" : 失敗した場合は RuntimeError の例外を発生する. safe? - : Tcl/Tk インタープリタを safe インタープリタであるかを調べる. + : Tcl/Tk インタープリタが safe インタープリタであるかを調べる. : safe インタープリタであれば true を返す. allow_ruby_exit? diff --git a/ext/tk/lib/multi-tk.rb b/ext/tk/lib/multi-tk.rb index cfe4887c4e..268246fa73 100644 --- a/ext/tk/lib/multi-tk.rb +++ b/ext/tk/lib/multi-tk.rb @@ -77,17 +77,22 @@ class MultiTkIp cmd.inspect end def call(*args) - begin - unless @ip.deleted? - @ip.cb_eval(@cmd, *args) - end - rescue TkCallbackBreak, TkCallbackContinue => e - fail e - rescue Exception => e - if @ip.safe? - # ignore - else + unless @ip.deleted? + current = Thread.current + backup_ip = current['callback_ip'] + current['callback_ip'] = @ip + begin + @ip.cb_eval(@cmd, *args) + rescue TkCallbackBreak, TkCallbackContinue => e fail e + rescue Exception => e + if @ip.safe? + nil # ignore + else + fail e + end + ensure + current['callback_ip'] = backup_ip end end end @@ -105,19 +110,23 @@ class MultiTkIp def _check_and_return(thread, exception, wait=0) unless thread - unless exception.kind_of?(MultiTkIp_OK) || safe? + unless exception.kind_of?(MultiTkIp_OK) msg = "#{exception.class}: #{exception.message}" + + if @interp.deleted? + warn("Warning (#{self}): " + msg) + return nil + end + + if safe? + warn("Warning (#{self}): " + msg) if $DEBUG + return nil + end + begin - if @interp.deleted? - warn('Warning: ' + msg) - elsif @interp._eval_without_enc('info command bgerror').size != 0 - @interp._eval(@interp._merge_tklist('bgerror', msg)) - else - warn('Warning: ' + msg) - end + @interp._eval_without_enc(@interp._merge_tklist('bgerror', msg)) rescue Exception => e - warn('Warning: ' + msg) - warn('Warning: ' + e.message) + warn("Warning (#{self}): " + msg) end end return nil @@ -230,8 +239,18 @@ class MultiTkIp def _receiver_eval_proc_core(safe_level, thread, cmd, *args) begin #ret = proc{$SAFE = safe_level; cmd.call(*args)}.call - ret = cmd.call(safe_level, *args) - + #ret = cmd.call(safe_level, *args) + normal_ret = false + ret = catch(:IRB_EXIT) do # IRB hack + retval = cmd.call(safe_level, *args) + normal_ret = true + retval + end + unless normal_ret + # catch IRB_EXIT + exit(ret) + end + ret rescue SystemExit => e # delete IP unless @interp.deleted? @@ -297,7 +316,8 @@ class MultiTkIp _check_and_return(thread, MultiTkIp_OK.new(nil)) end - if master? && !safe? && allow_ruby_exit? + # if master? && !safe? && allow_ruby_exit? + if !@interp.deleted? && master? && !safe? && allow_ruby_exit? =begin ObjectSpace.each_object(TclTkIp){|obj| obj.delete unless obj.deleted? @@ -380,6 +400,16 @@ class MultiTkIp rescue Exception => e # raise exception + begin + bt = _toUTF8(e.backtrace.join("\n")) + bt.instance_variable_set(:@encoding, 'utf-8') + rescue Exception + bt = e.backtrace.join("\n") + end + begin + @interp._set_global_var('errorInfo', bt) + rescue Exception + end _check_and_return(thread, e) else @@ -411,7 +441,8 @@ class MultiTkIp def _receiver_mainloop(check_root) Thread.new{ while !@interp.deleted? - break if @interp._invoke_without_enc('info', 'command', '.').size == 0 + inf = @interp._invoke_without_enc('info', 'command', '.') + break if !inf.kind_of?(String) || inf != '.' sleep 0.5 end } @@ -742,8 +773,8 @@ class MultiTkIp # create toplevel widget begin top = TkToplevel.new(toplevel_keys) - rescue NameError - fail unless @interp.safe? + rescue NameError => e + fail e unless @interp.safe? fail SecurityError, "unable create toplevel on the safe interpreter" end msg = "Untrusted Ruby/Tk applet (#{slave_name})" @@ -870,7 +901,11 @@ class MultiTkIp fail SecurityError, "cannot create a master-ip at level #{$SAFE}" end - if !master.master? && master.safe? + if master.deleted? && safeip == nil + fail RuntimeError, "cannot create a slave of a deleted interpreter" + end + + if !master.deleted? && !master.master? && master.safe? fail SecurityError, "safe-slave-ip cannot create a new interpreter" end @@ -964,15 +999,20 @@ class MultiTkIp undef :instance_eval end + # dummy call for initialization + self.eval_proc{ Tk.tk_call('set', 'tcl_patchLevel') } + self.freeze # defend against modification end ###################################### def _default_delete_hook(slave) - if @slave_ip_top[slave].kind_of?(String) + @slave_ip_tbl.delete(slave) + top = @slave_ip_top.delete(slave) + if top.kind_of?(String) # call default hook of safetk.tcl (ignore exceptions) - if @slave_ip_top[slave] == '' + if top == '' begin @interp._eval("::safe::disallowTk #{slave}") rescue @@ -980,20 +1020,19 @@ class MultiTkIp end else # toplevel path begin - @interp._eval("::safe::tkDelete {} #{@slave_ip_top[slave]} #{slave}") + @interp._eval("::safe::tkDelete {} #{top} #{slave}") rescue warn("Waring: fail to call '::safe::tkDelete'") if $DEBUG begin - @interp._eval("destroy #{@slave_ip_top[slave]}") + @interp._eval("destroy #{top}") rescue warn("Waring: fail to destroy toplevel") if $DEBUG end end end end - @slave_ip_tbl.delete(slave) - @slave_ip_top.delete(slave) end + end @@ -1007,10 +1046,14 @@ class MultiTkIp end def self.__getip - if Thread.current.group == ThreadGroup::Default + current = Thread.current + if TclTkLib.mainloop_thread? != false && current['callback_ip'] + return current['callback_ip'] + end + if current.group == ThreadGroup::Default @@DEFAULT_MASTER else - ip = @@IP_TABLE[Thread.current.group] + ip = @@IP_TABLE[current.group] unless ip fail SecurityError, "cannot call Tk methods on #{Thread.current.inspect}" @@ -1093,9 +1136,15 @@ class MultiTkIp def inspect s = self.to_s.chop! if master? - s << ':master' + if @interp.deleted? + s << ':deleted-master' + else + s << ':master' + end else - if @interp.safe? + if @interp.deleted? + s << ':deleted-slave' + elsif @interp.safe? s << ':safe-slave' else s << ':trusted-slave' @@ -1281,11 +1330,13 @@ class MultiTkIp #self.eval_callback{ TkComm._get_eval_string(TkUtil.eval_cmd(cmd, *args)) } #ret = self.eval_callback{ TkComm._get_eval_string(TkUtil.eval_cmd(cmd, *args)) } ret = self.eval_callback(*args){|safe, *params| - $SAFE=safe; TkComm._get_eval_string(TkUtil.eval_cmd(cmd, *params)) + $SAFE=safe + TkComm._get_eval_string(TkUtil.eval_cmd(cmd, *params)) } if ret.kind_of?(Exception) - raise ret + raise ret end + ret end end @@ -1300,10 +1351,11 @@ class MultiTkIp end # on IP thread - if (@cmd_receiver == Thread.current) + if @cmd_receiver == Thread.current || + (!req_val && TclTkLib.mainloop_thread? != false) # callback begin ret = cmd.call(*args) - rescue SystemExit + rescue SystemExit => e # exit IP warn("Warning: "+ $! + " on " + self.inspect) if $DEBUG begin @@ -1318,6 +1370,18 @@ class MultiTkIp ((e.message.length > 0)? ' "' + e.message + '"': '') + " on " + self.inspect) end +=begin + begin + bt = _toUTF8(e.backtrace.join("\n")) + bt.instance_variable_set(:@encoding, 'utf-8') + rescue Exception + bt = e.backtrace.join("\n") + end + begin + @interp._set_global_var('errorInfo', bt) + rescue Exception + end +=end ret = e end return ret @@ -1353,7 +1417,7 @@ class MultiTkIp self._eval_without_enc('exit') rescue Exception end - if !safe? && allow_ruby_exit? + if !self.deleted? && !safe? && allow_ruby_exit? self.delete fail e else @@ -1380,11 +1444,34 @@ class MultiTkIp end end =end +=begin def eval_callback(*args) if block_given? eval_proc_core(false, Proc.new, *args) +# eval_proc_core(Thread.current, Proc.new, *args) else + cmd = args.shift eval_proc_core(false, *args) +# eval_proc_core(Thread.current, *args) + end + end +=end + def eval_callback(*args) + if block_given? + cmd = Proc.new + else + cmd = args.shift + end + if TclTkLib.mainloop_thread? != false + args.unshift(safe_level) + end + current = Thread.current + backup_ip = current['callback_ip'] + current['callback_ip'] = self + begin + eval_proc_core(false, cmd, *args) + ensure + current['callback_ip'] = backup_ip end end @@ -1399,7 +1486,7 @@ class MultiTkIp =end def eval_proc(*args) # The scope of the eval-block of 'eval_proc' method is different from - # the enternal. If you want to pass local values to the eval-block, + # the external. If you want to pass local values to the eval-block, # use arguments of eval_proc method. They are passed to block-arguments. if block_given? cmd = Proc.new @@ -1408,11 +1495,24 @@ class MultiTkIp fail ArgumentError, "A Proc or Method object is expected for 1st argument" end end - eval_proc_core(true, - proc{|safe, *params| - $SAFE=safe; Thread.new(*params, &cmd).value - }, - *args) + if TclTkLib.mainloop_thread? == true + # call from eventloop + current = Thread.current + backup_ip = current['callback_ip'] + current['callback_ip'] = self + begin + eval_proc_core(false, cmd, safe_level, *args) + ensure + current['callback_ip'] = backup_ip + end + else + eval_proc_core(true, + proc{|safe, *params| + $SAFE=safe + Thread.new(*params, &cmd).value + }, + *args) + end end alias call eval_proc @@ -1739,7 +1839,7 @@ end # depend on TclTkIp class MultiTkIp - def mainloop(check_root = true, restart_on_dead = false) + def mainloop(check_root = true, restart_on_dead = true) #return self if self.slave? #return self if self != @@DEFAULT_MASTER if self != @@DEFAULT_MASTER @@ -1752,7 +1852,11 @@ class MultiTkIp rescue MultiTkIp_OK => ret # return value @wait_on_mainloop[1] = false - return ret.value.value + if ret.value.kind_of?(Thread) + return ret.value.value + else + return ret.value + end rescue SystemExit # exit IP warn("Warning: " + $! + " on " + self.inspect) if $DEBUG @@ -1762,7 +1866,7 @@ class MultiTkIp rescue Exception end self.delete - rescue Exception => e + rescue StandardError => e if $DEBUG warn("Warning: " + e.class.inspect + ((e.message.length > 0)? ' "' + e.message + '"': '') + @@ -1779,31 +1883,59 @@ class MultiTkIp unless restart_on_dead @wait_on_mainloop[1] = true +=begin + begin + @interp.mainloop(check_root) + rescue StandardError => e + if $DEBUG + warn("Warning: " + e.class.inspect + + ((e.message.length > 0)? ' "' + e.message + '"': '') + + " on " + self.inspect) + end + end +=end @interp.mainloop(check_root) @wait_on_mainloop[1] = false else - begin + loop do @wait_on_mainloop[1] = true - loop do - break unless self.alive? - if check_root - begin - break if TclTkLib.num_of_mainwindows == 0 - rescue Exception - break - end + break unless self.alive? + if check_root + begin + break if TclTkLib.num_of_mainwindows == 0 + rescue StandardError + break end - @interp.mainloop(check_root) end - #rescue StandardError - rescue Exception - if TclTkLib.mainloop_abort_on_exception != nil - STDERR.print("Warning: Tk mainloop receives ", $!.class.inspect, - " exception (ignore) : ", $!.message, "\n"); + break if @interp.deleted? + begin + @interp.mainloop(check_root) + rescue StandardError => e + if TclTkLib.mainloop_abort_on_exception != nil + #STDERR.print("Warning: Tk mainloop receives ", $!.class.inspect, + # " exception (ignore) : ", $!.message, "\n"); + if $DEBUG + warn("Warning: Tk mainloop receives " << e.class.inspect << + " exception (ignore) : " << e.message); + end + end + #raise e + rescue Exception => e +=begin + if TclTkLib.mainloop_abort_on_exception != nil + #STDERR.print("Warning: Tk mainloop receives ", $!.class.inspect, + # " exception (ignore) : ", $!.message, "\n"); + if $DEBUG + warn("Warning: Tk mainloop receives " << e.class.inspect << + " exception (ignore) : " << e.message); + end + end +=end + raise e + ensure + @wait_on_mainloop[1] = false + Thread.pass # avoid eventloop conflict end - retry - ensure - @wait_on_mainloop[1] = false end end self @@ -1875,18 +2007,17 @@ class MultiTkIp @interp._eval_without_enc("foreach i {#{after_ids}} {after cancel $i}") rescue Exception end -=begin + begin @interp._invoke('destroy', '.') unless @interp.deleted? rescue Exception end -=end + if @safe_base && !@interp.deleted? # do 'exit' to call the delete_hook procedure @interp._eval_without_enc('exit') - else - @interp.delete unless @interp.deleted? end + @interp.delete self end diff --git a/ext/tk/lib/tk.rb b/ext/tk/lib/tk.rb index e7217ff975..95e26d7264 100644 --- a/ext/tk/lib/tk.rb +++ b/ext/tk/lib/tk.rb @@ -565,6 +565,15 @@ end module_function :bool, :number, :num_or_str, :string module_function :list, :simplelist, :window, :image_obj, :procedure + def subst(str, *opts) + # opts := :nobackslashes | :nocommands | novariables + tk_call('subst', + *(opts.collect{|opt| + opt = opt.to_s + (opt[0] == ?-)? opt: '-' << opt + } << str)) + end + def _toUTF8(str, encoding = nil) TkCore::INTERP._toUTF8(str, encoding) end @@ -1110,13 +1119,14 @@ module TkCore INTERP._invoke_without_enc('bind', 'all', "<#{WIDGET_DESTROY_HOOK}>", install_cmd(proc{|path| unless TkCore::INTERP.deleted? - if (widget = TkCore::INTERP.tk_windows[path]) - if widget.respond_to?(:__destroy_hook__) - begin + begin + if (widget=TkCore::INTERP.tk_windows[path]) + if widget.respond_to?(:__destroy_hook__) widget.__destroy_hook__ - rescue Exception end end + rescue Exception=>e + p e if $DEBUG end end }) << ' %W') @@ -1175,11 +1185,24 @@ module TkCore def TkCore.callback(*arg) begin - TkCore::INTERP.tk_cmd_tbl[arg.shift].call(*arg) - rescue SystemExit - exit(0) - rescue Interrupt - exit!(1) + if TkCore::INTERP.tk_cmd_tbl.kind_of?(Hash) + #TkCore::INTERP.tk_cmd_tbl[arg.shift].call(*arg) + normal_ret = false + ret = catch(:IRB_EXIT) do # IRB hack + retval = TkCore::INTERP.tk_cmd_tbl[arg.shift].call(*arg) + normal_ret = true + retval + end + unless normal_ret + # catch IRB_EXIT + exit(ret) + end + ret + end + rescue SystemExit=>e + exit(e.status) + rescue Interrupt=>e + fail(e) rescue Exception => e begin msg = _toUTF8(e.class.inspect) + ': ' + @@ -1194,6 +1217,8 @@ module TkCore e.backtrace.join("\n") + "\n---< backtrace of Tk side >-------" end + # TkCore::INTERP._set_global_var('errorInfo', msg) + # fail(e) fail(e, msg) end end @@ -1383,6 +1408,22 @@ module TkCore TclTkLib.mainloop(check_root) end + def mainloop_thread? + # true : current thread is mainloop + # nil : there is no mainloop + # false : mainloop is running on the other thread + # ( At then, it is dangerous to call Tk interpreter directly. ) + TclTkLib.mainloop_thread? + end + + def mainloop_exist? + TclTkLib.mainloop_thread? != nil + end + + def is_mainloop? + TclTkLib.mainloop_thread? == true + end + def mainloop_watchdog(check_root = true) # watchdog restarts mainloop when mainloop is dead TclTkLib.mainloop_watchdog(check_root) @@ -1738,13 +1779,34 @@ module Tk end def Tk.pack(*args) - #TkPack.configure(*args) - TkPack(*args) + TkPack.configure(*args) + end + def Tk.pack_forget(*args) + TkPack.forget(*args) + end + def Tk.unpack(*args) + TkPack.forget(*args) end def Tk.grid(*args) TkGrid.configure(*args) end + def Tk.grid_forget(*args) + TkGrid.forget(*args) + end + def Tk.ungrid(*args) + TkGrid.forget(*args) + end + + def Tk.place(*args) + TkPlace.configure(*args) + end + def Tk.place_forget(*args) + TkPlace.forget(*args) + end + def Tk.unplace(*args) + TkPlace.forget(*args) + end def Tk.update(idle=nil) if idle @@ -3541,7 +3603,7 @@ class TkWindow e case @def_default when :proc @@ -365,7 +462,7 @@ else # use Ruby script version of set tkvariable (traditional methods) ########################################################################### - def value + def _value begin INTERP._eval(Kernel.format('global %s; set %s', @id, @id)) #INTERP._eval(Kernel.format('set %s', @id)) @@ -436,7 +533,8 @@ else def [](*idxs) index = idxs.collect{|idx| _get_eval_string(idx)}.join(',') begin - INTERP._eval(Kernel.format('global %s; set %s(%s)', @id, @id, index)) + # INTERP._eval(Kernel.format('global %s; set %s(%s)', @id, @id, index)) + _to_default_type(INTERP._eval(Kernel.format('global %s; set %s(%s)', @id, @id, index))) rescue => e case @def_default when :proc @@ -483,8 +581,19 @@ else end + protected :_value + + def value + _to_default_type(_value) + end + + def value_type=(val) + self.default_value_type = val + self.value=(val) + end + def numeric - number(value) + number(_value) end def numeric=(val) case val @@ -497,17 +606,20 @@ end end val end + def numeric_type=(val) + @type = :numeric + self.numeric=(val) + end def bool # see Tcl_GetBoolean man-page - case value.downcase + case _value.downcase when '0', 'false', 'no', 'off' false else true end end - def bool=(val) if ! val self.value = '0' @@ -520,30 +632,48 @@ end end end end + def bool_type=(val) + @type = :bool + self.bool=(val) + end def to_i - number(value).to_i + number(_value).to_i end def to_f - number(value).to_f + number(_value).to_f end def to_s #string(value).to_s - value + _value + end + alias string= value= + def string_type=(val) + @type = :string + self.value=(val) end def to_sym - value.intern + _value.intern + end + alias symbol= value= + def symbol_type=(val) + @type = :symbol + self.value=(val) end def list #tk_split_list(value) - tk_split_simplelist(value) + tk_split_simplelist(_value) end alias to_a list + def numlist + list.collect!{|val| number(val)} + end + def list=(val) case val when Array @@ -555,6 +685,39 @@ end end val end + alias numlist= list= + + def list_type=(val) + @type = :list + self.list=(val) + end + def numlist_type=(val) + @type = :numlist + self.numlist=(val) + end + + def lappend(*elems) + tk_call('lappend', @id, *elems) + self + end + + def lindex(idx) + tk_call('lindex', self._value, idx) + end + alias lget lindex + + def lget_i(idx) + number(lget(idx)).to_i + end + + def lget_f(idx) + number(lget(idx)).to_f + end + + def lset(idx, val) + tk_call('lset', @id, idx, val) + self + end def inspect #Kernel.format "#", @id @@ -564,7 +727,7 @@ end def coerce(other) case other when TkVariable - [other.value, self.value] + [other._value, self._value] when String [other, self.to_s] when Symbol @@ -576,7 +739,7 @@ end when Array [other, self.to_a] else - [other, self.value] + [other, self._value] end end @@ -599,12 +762,12 @@ end when Array self.to_a + other when String - self.value + other + self._value + other else begin - number(self.value) + other + number(self._value) + other rescue - self.value + other.to_s + self._value + other.to_s end end end @@ -612,37 +775,40 @@ end if other.kind_of?(Array) self.to_a - other else - number(self.value) - other + number(self._value) - other end end def *(other) - begin - number(self.value) * other - rescue - self.value * other - end + num_or_str(self._value) * other.to_i + #begin + # number(self._value) * other + #rescue + # self._value * other + #end end def /(other) - number(self.value) / other + number(self._value) / other end def %(other) - begin - number(self.value) % other - rescue - self.value % other - end + num_or_str(self._value) % other.to_i + #begin + # number(self._value) % other + #rescue + # self._value % other + #end end def **(other) - number(self.value) ** other + number(self._value) ** other end def =~(other) - self.value =~ other + self._value =~ other end def ==(other) case other when TkVariable - self.equal?(other) + #self.equal?(other) + self._value == other._value when String self.to_s == other when Symbol @@ -654,7 +820,8 @@ end when Array self.to_a == other when Hash - self.value == other + # false if self is not an assoc array + self._value == other else false end @@ -673,17 +840,17 @@ end val = other.numeric other = val rescue - other = other.value + other = other._value end end if other.kind_of?(Numeric) begin return self.numeric <=> other rescue - return self.value <=> other.to_s + return self._value <=> other.to_s end else - return self.value <=> other + return self._value <=> other end end diff --git a/ext/tk/lib/tkextlib/tclx/tclx.rb b/ext/tk/lib/tkextlib/tclx/tclx.rb index 760ebd92b1..44799acbc9 100644 --- a/ext/tk/lib/tkextlib/tclx/tclx.rb +++ b/ext/tk/lib/tkextlib/tclx/tclx.rb @@ -27,6 +27,16 @@ module Tk Tk.tk_call('infox', *args) end + def self.signal(*args) + warn("Warning: Don't recommend to use TclX's 'signal' command. Please use Ruby's 'Signal.trap' method") + Tk.tk_call('signal', *args) + end + + def self.signal_restart(*args) + warn("Warning: Don't recommend to use TclX's 'signal' command. Please use Ruby's 'Signal.trap' method") + Tk.tk_call('signal', '-restart', *args) + end + ############################## class XPG3_MsgCat diff --git a/ext/tk/sample/demos-en/anilabel.rb b/ext/tk/sample/demos-en/anilabel.rb index 36989c5c91..f063bc53a4 100644 --- a/ext/tk/sample/demos-en/anilabel.rb +++ b/ext/tk/sample/demos-en/anilabel.rb @@ -37,7 +37,7 @@ TkFrame.new($anilabel_demo) {|frame| TkButton.new(frame) { text 'See Code' - command proc{showCode 'label'} + command proc{showCode 'anilabel'} }.pack('side'=>'left', 'expand'=>'yes') }.pack('side'=>'bottom', 'fill'=>'x', 'pady'=>'2m') diff --git a/ext/tk/sample/demos-en/aniwave.rb b/ext/tk/sample/demos-en/aniwave.rb new file mode 100644 index 0000000000..ebe27b875f --- /dev/null +++ b/ext/tk/sample/demos-en/aniwave.rb @@ -0,0 +1,115 @@ +# +# animated wave demo (called by 'widget') +# +# based on Tcl/Tk8.5a2 widget demos + +# destroy toplevel widget for this demo script +if defined?($aniwave_demo) && $aniwave_demo + $aniwave_demo.destroy + $aniwave_demo = nil +end + +# create toplevel widget +$aniwave_demo = TkToplevel.new {|w| + title("Animated Wave Demonstration") + iconname("aniwave") + positionWindow(w) +} + +# create label +msg = TkLabel.new($aniwave_demo) { + font $font + wraplength '4i' + justify 'left' + text 'This demonstration contains a canvas widget with a line item inside it. The animation routines work by adjusting the coordinates list of the line.' +} +msg.pack('side'=>'top') + +# create frame +TkFrame.new($aniwave_demo) {|frame| + TkButton.new(frame) { + text 'Dismiss' + command proc{ + tmppath = $aniwave_demo + $aniwave_demo = nil + tmppath.destroy + } + }.pack('side'=>'left', 'expand'=>'yes') + + TkButton.new(frame) { + text 'See Code' + command proc{showCode 'aniwave'} + }.pack('side'=>'left', 'expand'=>'yes') + +}.pack('side'=>'bottom', 'fill'=>'x', 'pady'=>'2m') + +# animated wave +class AnimatedWaveDemo + def initialize(frame, dir=:left) + @direction = dir + + # create canvas widget + @c = TkCanvas.new(frame, :width=>300, :height=>200, + :background=>'black') + @c.pack(:padx=>10, :pady=>10, :expand=>true) + + # Creates a coordinates list of a wave. + @waveCoords = [] + @backupCoords = [] + n = 0 + (-10..300).step(5){|n| @waveCoords << [n, 100]; @backupCoords << [n, 100] } + @waveCoords << [n, 0]; @backupCoords << [n, 0] + @waveCoords << [n+5, 200]; @backupCoords << [n+5, 200] + @coordsLen = @waveCoords.length + + # Create a smoothed line and arrange for its coordinates to be the + # contents of the variable waveCoords. + @line = TkcLine.new(@c, @waveCoords, + :width=>1, :fill=>'green', :smooth=>true) + + # Main animation "loop". + # Theoretically 100 frames-per-second (==10ms between frames) + @timer = TkTimer.new(10){ basicMotion; reverser } + + # Arrange for the animation loop to stop when the canvas is deleted + @c.bindtags_unshift(TkBindTag.new('Destroy'){ @timer.stop }) + end + + # Basic motion handler. Given what direction the wave is travelling + # in, it advances the y coordinates in the coordinate-list one step in + # that direction. + def basicMotion + @backupCoords, @waveCoords = @waveCoords, @backupCoords + (0...@coordsLen).each{|idx| + if @direction == :left + @waveCoords[idx][1] = @backupCoords[(idx+1 == @coordsLen)? 0: idx+1][1] + else + @waveCoords[idx][1] = @backupCoords[(idx == 0)? -1: idx-1][1] + end + } + @line.coords(@waveCoords) + end + + # Oscillation handler. This detects whether to reverse the direction + # of the wave by checking to see if the peak of the wave has moved off + # the screen (whose size we know already.) + def reverser + if @waveCoords[0][1] < 10 + @direction = :right + elsif @waveCoords[-1][1] < 10 + @direction = :left + end + end + + # animation control + def move + @timer.start + end + + def stop + @timer.stop + end +end + +# Start the animation processing +AnimatedWaveDemo.new($aniwave_demo, :left).move diff --git a/ext/tk/sample/demos-en/goldberg.rb b/ext/tk/sample/demos-en/goldberg.rb new file mode 100644 index 0000000000..dd1d979025 --- /dev/null +++ b/ext/tk/sample/demos-en/goldberg.rb @@ -0,0 +1,1968 @@ +# +# Ruby/Tk Goldverg demo (called by 'widget') +# +# Based on Tcl/Tk8.5a2 widget demos. +# The following is the original comment of TkGoldberg.tcl. +# +#>>##+################################################################# +#>># +#>># TkGoldberg.tcl +#>># by Keith Vetter, March 13, 2003 +#>># +#>># "Man will always find a difficult means to perform a simple task" +#>># Rube Goldberg +#>># +#>># Reproduced here with permission. +#>># +#>>##+################################################################# +#>># +#>># Keith Vetter 2003-03-21: this started out as a simple little program +#>># but was so much fun that it grew and grew. So I apologize about the +#>># size but I just couldn't resist sharing it. +#>># +#>># This is a whizzlet that does a Rube Goldberg type animation, the +#>># design of which comes from an New Years e-card from IncrediMail. +#>># That version had nice sound effects which I eschewed. On the other +#>># hand, that version was in black and white (actually dark blue and +#>># light blue) and this one is fully colorized. +#>># +#>># One thing I learned from this project is that drawing filled complex +#>># objects on a canvas is really hard. More often than not I had to +#>># draw each item twice--once with the desired fill color but no +#>># outline, and once with no fill but with the outline. Another trick +#>># is erasing by drawing with the background color. Having a flood fill +#>># command would have been extremely helpful. +#>># +#>># Two wiki pages were extremely helpful: Drawing rounded rectangles +#>># which I generalized into Drawing rounded polygons, and regular +#>># polygons which allowed me to convert ovals and arcs into polygons +#>># which could then be rotated (see Canvas Rotation). I also wrote +#>># Named Colors to aid in the color selection. +#>># +#>># I could comment on the code, but it's just 26 state machines with +#>># lots of canvas create and move calls. + +if defined?($goldberg_demo) && $goldberg_demo + $goldberg_demo.destroy + $goldberg_demo = nil +end + +# demo toplevel widget +$goldberg_demo = TkToplevel.new {|w| + title("Tk Goldberg (demonstration)") + iconname("goldberg") +# positionWindow(w) +} + +# label +msg = TkLabel.new($goldberg_demo) { + font 'Arial 10' + wraplength '4i' + justify 'left' + text "This is a demonstration of just how complex you can make your animations become. Click the ball to start things moving!\n\n\"Man will always find a difficult means to perform a simple task\"\n - Rube Goldberg" +} +msg.pack('side'=>'top') + +# frame +TkFrame.new($goldberg_demo) {|frame| + TkButton.new(frame) { + text 'Dismiss' + command proc{ + tmppath = $goldberg_demo + $goldberg_demo = nil + tmppath.destroy + } + }.pack('side'=>'left', 'expand'=>'yes') + + TkButton.new(frame) { + text 'See Code' + command proc{showCode 'goldberg'} + }.pack('side'=>'left', 'expand'=>'yes') + +}.pack('side'=>'bottom', 'fill'=>'x', 'pady'=>'2m') + +######################################### + +class TkGoldberg_Demo + def initialize(parent) + @parent = parent + + @S = {} + @S['title'] = 'Tk Goldberg' + @S['speed'] = TkVariable.new(5) + @S['cnt'] = TkVariable.new(0) + @S['message'] = TkVariable.new("\\nWelcome\\nto\\nRuby/Tk") + @S['pause'] = TkVariable.new + @S['details'] = TkVariable.new(true) + + @S['mode'] = TkVariable.new(:MSTART, :symbol) + # :MSTART, :MGO, :MPAUSE, :MSSTEP, :MBSTEP, :MDONE, :MDEBUG + + # 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 + @speed = [1, 10, 20, 50, 80, 100, 150, 200, 300, 400, 500] + + # colors + @C = {} + @C['fg'] = 'black' + # @C['bg'] = 'gray75' + @C['bg'] = 'cornflowerblue' + + @C['0'] = 'white'; @C['1a'] = 'darkgreen'; @C['1b'] = 'yellow' + @C['2'] = 'red'; @C['3a'] = 'green'; @C['3b'] = 'darkblue' + @C['4'] = @C['fg']; @C['5a'] = 'brown'; @C['5b'] = 'white' + @C['6'] = 'magenta'; @C['7'] = 'green'; @C['8'] = @C['fg'] + @C['9'] = 'blue4'; @C['10a'] = 'white'; @C['10b'] = 'cyan' + @C['11a'] = 'yellow'; @C['11b'] = 'mediumblue'; @C['12'] = 'tan2' + @C['13a'] = 'yellow'; @C['13b'] = 'red'; @C['14'] = 'white' + @C['15a'] = 'green'; @C['15b'] = 'yellow'; @C['16'] = 'gray65' + @C['17'] = '#A65353'; @C['18'] = @C['fg']; @C['19'] = 'gray50' + @C['20'] = 'cyan'; @C['21'] = 'gray65'; @C['22'] = @C['20'] + @C['23a'] = 'blue'; @C['23b'] = 'red'; @C['23c'] = 'yellow' + @C['24a'] = 'red'; @C['24b'] = 'white'; + + @STEP = TkVariable.new_hash + @STEP.default_value_type = :numeric + + @XY = {} + + @XY6 = { + '-1'=>[366, 207], '-2'=>[349, 204], '-3'=>[359, 193], '-4'=>[375, 192], + '-5'=>[340, 190], '-6'=>[349, 177], '-7'=>[366, 177], '-8'=>[380, 176], + '-9'=>[332, 172], '-10'=>[342, 161], '-11'=>[357, 164], + '-12'=>[372, 163], '-13'=>[381, 149], '-14'=>[364, 151], + '-15'=>[349, 146], '-16'=>[333, 148], '0'=>[357, 219], + '1'=>[359, 261], '2'=>[359, 291], '3'=>[359, 318], '4'=>[361, 324], + '5'=>[365, 329], '6'=>[367, 334], '7'=>[367, 340], '8'=>[366, 346], + '9'=>[364, 350], '10'=>[361, 355], '11'=>[359, 370], '12'=>[359, 391], + '13,0'=>[360, 456], '13,1'=>[376, 456], '13,2'=>[346, 456], + '13,3'=>[330, 456], '13,4'=>[353, 444], '13,5'=>[368, 443], + '13,6'=>[339, 442], '13,7'=>[359, 431], '13,8'=>[380, 437], + '13,9'=>[345, 428], '13,10'=>[328, 434], '13,11'=>[373, 424], + '13,12'=>[331, 420], '13,13'=>[360, 417], '13,14'=>[345, 412], + '13,15'=>[376, 410], '13,16'=>[360, 403] + } + + @timer = TkTimer.new(@speed[@S['speed'].numeric]){|timer| + timer.set_interval(go) + } + + do_display + reset + + # Start everything going + @timer.start + end + + def do_display() + @ctrl = TkFrame.new(@parent, :relief=>:ridge, :bd=>2, :padx=>5, :pady=>5) + @screen = TkFrame.new(@parent, :bd=>2, + :relief=>:raised).pack(:side=>:left, :fill=>:both, + :expand=>true) + + @canvas = TkCanvas.new(@parent, :width=>860, :height=>730, + :bg=>@C['bg'], :highlightthickness=>0){ + scrollregion([0, 0, 1000, 1000]) # Kludge to move everything up + yview_moveto(0.05) + }.pack(:in=>@screen, :side=>:top, :fill=>:both, :expand=>true) + + @canvas.bind('3'){ @pause.invoke } + @canvas.bind('Destroy'){ @timer.stop } + + do_ctrl_frame + do_detail_frame + + @show = TkButton.new(@parent, :text=>'>>', :command=>proc{show_ctrl}, + :bg=>@C['bg'], :activebackground=>@C['bg']) + @show.place(:in=>@canvas, :relx=>1, :rely=>0, :anchor=>:ne) + + Tk.update + end + + def do_ctrl_frame + @start = TkButton.new(@parent, :text=>'Start', :bd=>6, + :command=>proc{do_button(0)}) + @start.font(@start['font'].weight('bold')) + font = @start['font'] + + @pause = TkCheckbutton.new(@parent, :text=>'Pause', :font=>font, + :command=>proc{do_button(1)}, :relief=>:raised, + :variable=>@S['pause']) + + @step = TkButton.new(@parent, :text=>'Single Step', :font=>font, + :command=>proc{do_button(2)}) + @bstep = TkButton.new(@parent, :text=>'Big Step', :font=>font, + :command=>proc{do_button(4)}) + @reset = TkButton.new(@parent, :text=>'Reset', :font=>font, + :command=>proc{do_button(3)}) + + @details = TkFrame.new(@parent, :bd=>2, :relief=>:ridge) + @detail = TkCheckbutton.new(@parent, :text=>'Details', :font=>font, + :relief=>:raised, :variable=>@S['details']) + + @msg_entry = TkEntry.new(@parent, :textvariable=>@S['message'], + :justify=>:center) + @speed_scale = TkScale.new(@parent, :orient=>:horizontal, + :from=>1, :to=>10, :font=>font, + :variable=>@S['speed'], :bd=>2, + :relief=>:ridge, :showvalue=>false) + @about = TkButton.new(@parent, :text=>'About', + :command=>proc{about}, :font=>font) + + Tk.grid(@start, :in=>@ctrl, :row=>0, :sticky=>:ew) + @ctrl.grid_rowconfigure(1, :minsize=>10) + Tk.grid(@pause, :in=>@ctrl, :row=>2, :sticky=>:ew) + Tk.grid(@step, :in=>@ctrl, :sticky=>:ew) + Tk.grid(@bstep, :in=>@ctrl, :sticky=>:ew) + Tk.grid(@reset, :in=>@ctrl, :sticky=>:ew) + @ctrl.grid_rowconfigure(10, :minsize=>20) + Tk.grid(@details, :in=>@ctrl, :row=>11, :sticky=>:ew) + Tk.grid(@detail, :in=>@details, :row=>0, :sticky=>:ew) + @ctrl.grid_rowconfigure(50, :weight=>1) + + @S['mode'].trace('w', proc{|*args| active_GUI(*args)}) + @S['details'].trace('w', proc{|*args| active_GUI(*args)}) + @S['speed'].trace('w', proc{|*args| active_GUI(*args)}) + + Tk.grid(@msg_entry, :in=>@ctrl, :row=>98, :sticky=>:ew, :pady=>5) + Tk.grid(@speed_scale, :in=>@ctrl, :row=>99, :sticky=>:ew) + Tk.grid(@about, :in=>@ctrl, :row=>100, :sticky=>:ew) + + @reset.bind('3'){@S['mode'].value = -1} # Debugging + end + + def do_detail_frame + @f_details = TkFrame.new(@details) + + @label = TkLabel.new(@f_details, :textvariable=>@S['cnt'], + :bd=>1, :relief=>:solid, :bg=>'white') + Tk.grid(@label, '-', '-', '-', :sticky=>:ew, :row=>0) + + idx = 1 + loop { + break unless respond_to?("move#{idx}") + l = TkLabel.new(@f_details, :text=>idx, :anchor=>:e, + :width=>2, :bd=>1, :relief=>:solid, :bg=>'white') + @STEP[idx] = 0 + ll = TkLabel.new(@f_details, :textvariable=>@STEP.ref(idx), + :width=>5, :bd=>1, :relief=>:solid, :bg=>'white') + row = (idx + 1)/2 + col = ((idx + 1) & 1) * 2 + Tk.grid(l, :sticky=>:ew, :row=>row, :column=>col) + Tk.grid(ll, :sticky=>:ew, :row=>row, :column=>(col + 1)) + idx += 1 + } + @f_details.grid_columnconfigure(1, :weight=>1) + end + + def show_ctrl + if @ctrl.winfo_mapped? + @ctrl.pack_forget + @show.text('>>') + else + @ctrl.pack(:side=>:right, :fill=>:both, :ipady=>5) + @show.text('<<') + end + end + + def draw_all + reset_step + @canvas.delete(:all) + idx = 0 + loop{ + m = "draw#{idx}" + break unless respond_to?(m) + send(m) + idx += 1 + } + end + + def active_GUI(var1, var2, op) + st = {false=>:disabled, true=>:normal} + + m = @S['mode'].to_sym + @S['pause'].value = (m == :MPAUSE) + @start.state(st[m != :MGO]) + @pause.state(st[m != :MSTART && m != :MDONE]) + @step.state(st[m != :MGO && m != :MDONE]) + @bstep.state(st[m != :MGO && m != :MDONE]) + @reset.state(st[m != :MSTART]) + + if @S['details'].bool + Tk.grid(@f_details, :in=>@details, :row=>2, :sticky=>:ew) + else + Tk.grid_forget(@f_details) + end + @speed_scale.label("Speed: #{@S['speed'].value}") + end + + def start + @S['mode'].value = :MGO + end + + def do_button(what) + case what + when 0 # Start + reset if @S['mode'].to_sym == :MDONE + @S['mode'].value = :MGO + + when 1 # Pause + @S['mode'].value = ((@S['pause'].bool)? :MPAUSE: :MGO) + + when 2 # Step + @S['mode'].value = :MSSTEP + + when 3 # Reset + reset + + when 4 # Big step + @S['mode'].value = :MBSTEP + end + end + + def go(who = nil) + now = Tk::Clock.clicks(:miliseconds) + if who # Start here for debugging + @S['active'] = [who] + @S['mode'].value = :MGO + end + return if @S['mode'].to_sym == :MDEBUG # Debugging + # If not paused, do the next move + n = next_step if @S['mode'].to_sym != :MPAUSE + @S['mode'].value = :MPAUSE if @S['mode'].to_sym == :MSSTEP # Single step + @S['mode'].value = :MSSTEP if @S['mode'].to_sym == :MBSTEP && n # big step + elapsed = Tk::Clock.clicks(:miliseconds) - now + delay = @speed[@S['speed'].to_i] - elapsed + delay = 1 if delay <= 0 + return delay + end + + def next_step + retval = false # Return value + + if @S['mode'].to_sym != :MSTART && @S['mode'].to_sym != :MDONE + @S['cnt'].numeric += 1 + end + alive = [] + @S['active'].each{|who| + who = who.to_i + n = send("move#{who}") + if (n & 1).nonzero? # This guy still alive + alive << who + end + if (n & 2).nonzero? # Next guy is active + alive << (who + 1) + retval = true + end + if (n & 4).nonzero? # End of puzzle flag + @S['mode'].value = :MDONE # Done mode + @S['active'] = [] # No more animation + return true + end + } + @S['active'] = alive + return retval + end + + def about + msg = "#{@S['title']}\nby Keith Vetter, March 2003\n(Reproduced by kind permission of the author)\n\n" + msg += "Man will always find a difficult means to perform a simple task" + msg += "\nRube Goldberg" + Tk.messageBox(:message=>msg, :title=>'About') + end + + ################################################################ + # + # All the drawing and moving routines + # + + # START HERE! banner + def draw0 + color = @C['0'] + TkcText.new(@canvas, [579, 119], :text=>'START HERE!', + :fill=>color, :anchor=>:w, + :tag=>'I0', :font=>['Times Roman', 12, :italic, :bold]) + TkcLine.new(@canvas, [719, 119, 763, 119], :tag=>'I0', :fill=>color, + :width=>5, :arrow=>:last, :arrowshape=>[18, 18, 5]) + @canvas.itembind('I0', '1'){ start } + end + + def move0(step = nil) + step = get_step(0, step) + + if @S['mode'].to_sym != :MSTART # Start the ball rolling + move_abs('I0', [-100, -100]) # Hide the banner + return 2 + end + + pos = [ + [673, 119], [678, 119], [683, 119], [688, 119], + [693, 119], [688, 119], [683, 119], [678, 119] + ] + step = step % pos.length + move_abs('I0', pos[step]) + return 1 + end + + # Dropping ball + def draw1 + color = @C['1a'] + color2 = @C['1b'] + TkcPolygon.new(@canvas, + [ 844, 133, 800, 133, 800, 346, 820, 346, + 820, 168, 844, 168, 844, 133 ], + :width=>3, :fill=>color, :outline=>'') + TkcPolygon.new(@canvas, + [ 771, 133, 685, 133, 685, 168, 751, 168, + 751, 346, 771, 346, 771, 133 ], + :width=>3, :fill=>color, :outline=>'') + TkcOval.new(@canvas, box(812, 122, 9), + :tag=>'I1', :fill=>color2, :outline=>'') + + @canvas.itembind('I1', '1'){ start } + end + + def move1(step = nil) + step = get_step(1, step) + pos = [ + [807, 122], [802, 122], [797, 123], [793, 124], [789, 129], [785, 153], + [785, 203], [785, 278, :x], [785, 367], [810, 392], [816, 438], + [821, 503], [824, 585, :y], [838, 587], [848, 593], [857, 601], + [-100, -100] + ] + return 0 if step >= pos.length + where = pos[step] + move_abs('I1', where) + move15a if where[2] == :y + return 3 if where[2] == :x + return 1 + end + + # Lighting the match + def draw2 + color = @C['2'] + + # Fulcrum + TkcPolygon.new(@canvas, [750, 369, 740, 392, 760, 392], + :fill=>@C['fg'], :outline=>@C['fg']) + + # Strike box + TkcRectangle.new(@canvas, [628, 335, 660, 383], + :fill=>'', :outline=>@C['fg']) + (0..2).each{|y| + yy = 335 + y*16 + TkcBitmap.new(@canvas, [628, yy], :bitmap=>'gray25', + :anchor=>:nw, :foreground=>@C['fg']) + TkcBitmap.new(@canvas, [644, yy], :bitmap=>'gray25', + :anchor=>:nw, :foreground=>@C['fg']) + } + + # Lever + TkcLine.new(@canvas, [702, 366, 798, 366], + :fill=>@C['fg'], :width=>6, :tag=>'I2_0') + + # R strap + TkcLine.new(@canvas, [712, 363, 712, 355], + :fill=>@C['fg'], :width=>3, :tag=>'I2_1') + + # L strap + TkcLine.new(@canvas, [705, 363, 705, 355], + :fill=>@C['fg'], :width=>3, :tag=>'I2_2') + + # Match stick + TkcLine.new(@canvas, [679, 356, 679, 360, 717, 360, 717, 356, 679, 356], + :fill=>@C['fg'], :width=>3, :tag=>'I2_3') + + # Match head + TkcPolygon.new(@canvas, + [ 671, 352, 677.4, 353.9, 680, 358.5, 677.4, 363.1, + 671, 365, 664.6, 363.1, 662, 358.5, 664.6, 353.9 ], + :fill=>color, :outline=>color, :tag=>'I2_4') + end + + def move2(step = nil) + step = get_step(2, step) + + stages = [0, 0, 1, 2, 0, 2, 1, 0, 1, 2, 0, 2, 1] + xy = [] + xy[0] = [ + 686, 333, 692, 323, 682, 316, 674, 309, 671, 295, 668, 307, + 662, 318, 662, 328, 671, 336 + ] + xy[1] = [ + 687, 331, 698, 322, 703, 295, 680, 320, 668, 297, 663, 311, + 661, 327, 671, 335 + ] + xy[2] = [ + 686, 331, 704, 322, 688, 300, 678, 283, 678, 283, 674, 298, + 666, 309, 660, 324, 672, 336 + ] + + if step >= stages.length + @canvas.delete('I2') + return 0 + end + + if step == 0 # Rotate the match + beta = 20 + + ox, oy = anchor('I2_0', :s) # Where to pivot + + i = 0 + until @canvas.find_withtag("I2_#{i}").empty? + rotate_item("I2_#{i}", ox, oy, beta) + i += 1 + end + + # For the flame + TkcPolygon.new(@canvas, [], :tag=>'I2', :smooth=>true, :fill=>@C['2']) + + return 1 + end + @canvas.coords('I2', xy[stages[step]]) + return ((step == 7)? 3: 1) + end + + # Weight and pulleys + def draw3 + color = @C['3a'] + color2 = @C['3b'] + + xy = [ [602, 296], [577, 174], [518, 174] ] + xy.each{|x, y| # 3 Pulleys + TkcOval.new(@canvas, box(x, y, 13), + :fill=>color, :outline=>@C['fg'], :width=>3) + TkcOval.new(@canvas, box(x, y, 2), :fill=>@C['fg'], :outline=>@C['fg']) + } + + # Wall to flame + TkcLine.new(@canvas, [750, 309, 670, 309], :tag=>'I3_s', + :width=>3, :fill=>@C['fg'], :smooth=>true) + + # Flame to pulley 1 + TkcLine.new(@canvas, [670, 309, 650, 309], :tag=>'I3_0', + :width=>3, :fill=>@C['fg'], :smooth=>true) + TkcLine.new(@canvas, [650, 309, 600, 309], :tag=>'I3_1', + :width=>3, :fill=>@C['fg'], :smooth=>true) + + # Pulley 1 half way to 2 + TkcLine.new(@canvas, [589, 296, 589, 235], :tag=>'I3_2', + :width=>3, :fill=>@C['fg']) + + # Pulley 1 other half to 2 + TkcLine.new(@canvas, [589, 235, 589, 174], :width=>3, :fill=>@C['fg']) + + # Across the top + TkcLine.new(@canvas, [577, 161, 518, 161], :width=>3, :fill=>@C['fg']) + + # Down to weight + TkcLine.new(@canvas, [505, 174, 505, 205], :tag=>'I3_w', + :width=>3, :fill=>@C['fg']) + + # Draw the weight as 2 circles, two rectangles and 1 rounded rectangle + x1, y1, x2, y2 = [515, 207, 495, 207] + TkcOval.new(@canvas, box(x1, y1, 6), + :tag=>'I3_', :fill=>color2, :outline=>color2) + TkcOval.new(@canvas, box(x2, y2, 6), + :tag=>'I3_', :fill=>color2, :outline=>color2) + TkcRectangle.new(@canvas, x1, y1 - 6, x2, y2 + 6, + :tag=>'I3_', :fill=>color2, :outline=>color2) + + TkcPolygon.new(@canvas, round_rect([492, 220, 518, 263], 15), + :smooth=>true, :tag=>'I3_', :fill=>color2, :outline=>color2) + + TkcLine.new(@canvas, [500, 217, 511, 217], + :tag=>'I3_', :fill=>color2, :width=>10) + + # Bottom weight target + TkcLine.new(@canvas, [502, 393, 522, 393, 522, 465], + :tag=>'I3__', :fill=>@C['fg'], :joinstyle=>:miter, :width=>10) + end + + def move3(step = nil) + step = get_step(3, step) + + pos = [ [505, 247], [505, 297], [505, 386.5], [505, 386.5] ] + rope = [] + rope[0] = [750, 309, 729, 301, 711, 324, 690, 300] + rope[1] = [750, 309, 737, 292, 736, 335, 717, 315, 712, 320] + rope[2] = [750, 309, 737, 309, 740, 343, 736, 351, 725, 340] + rope[3] = [750, 309, 738, 321, 746, 345, 742, 356] + + return 0 if step >= pos.length + + @canvas.delete("I3_#{step}") # Delete part of the rope + move_abs('I3_', pos[step]) # Move weight down + @canvas.coords('I3_s', rope[step]) # Flapping rope end + @canvas.coords('I3_w', [505, 174].concat(pos[step])) + if step == 2 + @canvas.move('I3__', 0, 30) + return 2 + end + return 1 + end + + # Cage and door + def draw4 + color = @C['4'] + x0, y0, x1, y1 = [527, 356, 611, 464] + + # Horizontal bars + y0.step(y1, 12){|y| + TkcLine.new(@canvas, [x0, y, x1, y], :fill=>color, :width=>1) + } + + # Vertical bars + x0.step(x1, 12){|x| + TkcLine.new(@canvas, [x, y0, x, y1], :fill=>color, :width=>1) + } + + # Swing gate + TkcLine.new(@canvas, [518, 464, 518, 428], + :tag=>'I4', :fill=>color, :width=>1) + end + + def move4(step = nil) + step = get_step(4, step) + + angles = [-10, -20, -30, -30] + return 0 if step >= angles.length + + rotate_item('I4', 518, 464, angles[step]) + @canvas.raise('I4') + + return((step == 3)? 3: 1) + end + + # Mouse + def draw5 + color = @C['5a'] + color2 = @C['5b'] + + xy = [377, 248, 410, 248, 410, 465, 518, 465] # Mouse course + xy.concat [518, 428, 451, 428, 451, 212, 377, 212] + + TkcPolygon.new(@canvas, xy, :fill=>color2, :outline=>@C['fg'], :width=>3) + + xy = [ + 534.5, 445.5, 541, 440, 552, 436, 560, 436, 569, 440, 574, 446, + 575, 452, 574, 454, 566, 456, 554, 456, 545, 456, 537, 454, 530, 452 + ] + TkcPolygon.new(@canvas, xy, :tag=>['I5', 'I5_0'], :fill=>color) + + TkcLine.new(@canvas, [573, 452, 592, 458, 601, 460, 613, 456], # Tail + :tag=>['I5', 'I5_1'], :fill=>color, :smooth=>true, :width=>3) + + xy = box(540, 446, 2) # Eye + xy = [540, 444, 541, 445, 541, 447, 540, 448, 538, 447, 538, 445] + TkcPolygon.new(@canvas, xy, :tag=>['I5', 'I5_2'], :fill=>@C['bg'], + :outline=>'', :smooth=>true) + + xy = [538, 454, 535, 461] # Front leg + TkcLine.new(@canvas, xy, :tag=>['I5', 'I5_3'], :fill=>color, :width=>2) + + xy = [566, 455, 569, 462] # Back leg + TkcLine.new(@canvas, xy, :tag=>['I5', 'I5_4'], :fill=>color, :width=>2) + + xy = [544, 455, 545, 460] # 2nd front leg + TkcLine.new(@canvas, xy, :tag=>['I5', 'I5_5'], :fill=>color, :width=>2) + + xy = [560, 455, 558, 460] # 2nd back leg + TkcLine.new(@canvas, xy, :tag=>['I5', 'I5_6'], :fill=>color, :width=>2) + end + + def move5(step = nil) + step = get_step(5, step) + + pos = [ + [553, 452], [533, 452], [513, 452], [493, 452], [473, 452], + [463, 442, 30], [445.5, 441.5, 30], [425.5, 434.5, 30], [422, 414], + [422, 394], [422, 374], [422, 354], [422, 334], [422, 314], [422, 294], + [422, 274, -30], [422, 260.5, -30, :x], [422.5, 248.5, -28], [425, 237] + ] + + return 0 if step >= pos.length + + x, y, beta, nxt = pos[step] + move_abs('I5', [x, y]) + if beta + ox, oy = centroid('I5_0') + (0..6).each{|id| rotate_item("I5_#{id}", ox, oy, beta) } + end + return 3 if nxt == :x + return 1 + end + + # Dropping gumballs + def draw6 + color = @C['6'] + xy = [324, 130, 391, 204] # Ball holder + xy = round_rect(xy, 10) + TkcPolygon.new(@canvas, xy, :smooth=>true, + :outline=>@C['fg'], :width=>3, :fill=>color) + xy = [339, 204, 376, 253] # Below the ball holder + TkcRectangle.new(@canvas, xy, :outline=>@C['fg'], :width=>3, + :fill=>color, :tag=>'I6c') + xy = box(346, 339, 28) + TkcOval.new(@canvas, xy, :fill=>color, :outline=>'') # Roter + TkcArc.new(@canvas, xy, :outline=>@C['fg'], :width=>2, :style=>:arc, + :start=>80, :extent=>205) + TkcArc.new(@canvas, xy, :outline=>@C['fg'], :width=>2, :style=>:arc, + :start=>-41, :extent=>85) + + xy = box(346, 339, 15) # Center of rotor + TkcOval.new(@canvas, xy, :outline=>@C['fg'], :fill=>@C['fg'], :tag=>'I6m') + xy = [352, 312, 352, 254, 368, 254, 368, 322] # Top drop to rotor + TkcPolygon.new(@canvas, xy, :fill=>color, :outline=>'') + TkcLine.new(@canvas, xy, :fill=>@C['fg'], :width=>2) + + xy = [353, 240, 367, 300] # Poke bottom hole + TkcRectangle.new(@canvas, xy, :fill=>color, :outline=>'') + xy = [341, 190, 375, 210] # Poke another hole + TkcRectangle.new(@canvas, xy, :fill=>color, :outline=>'') + + xy = [ + 368, 356, 368, 403, 389, 403, 389, 464, 320, 464, 320, 403, + 352, 403, 352, 366 + ] + TkcPolygon.new(@canvas, xy, :fill=>color, :outline=>'', + :width=>2) # Below rotor + TkcLine.new(@canvas, xy, :fill=>@C['fg'], :width=>2) + xy = box(275, 342, 7) # On/off rotor + TkcOval.new(@canvas, xy, :outline=>@C['fg'], :fill=>@C['fg']) + xy = [276, 334, 342, 325] # Fan belt top + TkcLine.new(@canvas, xy, :fill=>@C['fg'], :width=>3) + xy = [276, 349, 342, 353] # Fan belt bottom + TkcLine.new(@canvas, xy, :fill=>@C['fg'], :width=>3) + + xy = [337, 212, 337, 247] # What the mouse pushes + TkcLine.new(@canvas, xy, :fill=>@C['fg'], :width=>3, :tag=>'I6_') + xy = [392, 212, 392, 247] + TkcLine.new(@canvas, xy, :fill=>@C['fg'], :width=>3, :tag=>'I6_') + xy = [337, 230, 392, 230] + TkcLine.new(@canvas, xy, :fill=>@C['fg'], :width=>7, :tag=>'I6_') + + who = -1 # All the balls + colors = %w(red cyan orange green blue darkblue) + colors *= 3 + + (0..16).each{|i| + loc = -i + color = colors[i] + x, y = @XY6["#{loc}"] + TkcOval.new(@canvas, box(x, y, 5), + :fill=>color, :outline=>color, :tag=>"I6_b#{i}") + } + draw6a(12) # The wheel + end + + def draw6a(beta) + @canvas.delete('I6_0') + ox, oy = [346, 339] + (0..3).each{|i| + b = beta + i * 45 + x, y = rotate_c(28, 0, 0, 0, b) + xy = [ox + x, oy + y, ox - x, oy - y] + TkcLine.new(@canvas, xy, :tag=>'I6_0', :fill=>@C['fg'], :width=>2) + } + end + + def move6(step = nil) + step = get_step(6, step) + + return 0 if step > 62 + + if step < 2 # Open gate for balls to drop + @canvas.move('I6_', -7, 0) + if step == 1 # Poke a hole + xy = [348, 226, 365, 240] + TkcRectangle.new(@canvas, xy, :fill=>@canvas.itemcget('I6c', :fill), + :outline=>'') + end + return 1 + end + + s = step - 1 # Do the gumball drop dance + (0..(((s - 1)/3).to_i)).each{|i| + tag = "I6_b#{i}" + break if @canvas.find_withtag(tag).empty? + loc = s - 3*i + + if @XY6["#{loc},#{i}"] + move_abs(tag, @XY6["#{loc},#{i}"]) + elsif @XY6["#{loc}"] + move_abs(tag, @XY6["#{loc}"]) + end + } + if s % 3 == 1 + first = (s + 2)/3 + i = first + loop { + tag = "I6_b#{i}" + break if @canvas.find_withtag(tag).empty? + loc = first - i + move_abs(tag, @XY6["#{loc}"]) + i += 1 + } + end + if s >= 3 # Rotate the motor + idx = s % 3 + draw6a(12 + s * 15) + end + return((s == 3)? 3 : 1) + end + + # On/off switch + def draw7 + color = @C['7'] + xy = [198, 306, 277, 374] # Box + TkcRectangle.new(@canvas, xy, :outline=>@C['fg'], :width=>2, + :fill=>color, :tag=>'I7z') + @canvas.lower('I7z') + xy = [275, 343, 230, 349] + TkcLine.new(@canvas, xy, :tag=>'I7', :fill=>@C['fg'], :arrow=>:last, + :arrowshape=>[23, 23, 8], :width=>6) + xy = [225, 324] # On button + x, y = xy + TkcOval.new(@canvas, box(x, y, 3), :fill=>@C['fg'], :outline=>@C['fg']) + xy = [218, 323] # On text + font = ['Times Roman', 8] + TkcText.new(@canvas, xy, :text=>'on', :anchor=>:e, + :fill=>@C['fg'], :font=>font) + xy = [225, 350] # Off button + x, y = xy + TkcOval.new(@canvas, box(x, y, 3), :fill=>@C['fg'], :outline=>@C['fg']) + xy = [218, 349] # Off text + TkcText.new(@canvas, xy, :text=>'off', :anchor=>:e, + :fill=>@C['fg'], :font=>font) + end + + def move7(step = nil) + step = get_step(7, step) + + numsteps = 30 + return 0 if step > numsteps + beta = 30.0 / numsteps + rotate_item('I7', 275, 343, beta) + + return((step == numsteps)? 3: 1) + end + + # Electricity to the fan + def draw8 + sine([271, 248, 271, 306], 5, 8, :tag=>'I8_s', :fill=>@C['8'], :width=>3) + end + + def move8(step = nil) + step = get_step(8, step) + + return 0 if step > 3 + if step == 0 + sparkle(anchor('I8_s', :s), 'I8') + return 1 + elsif step == 1 + move_abs('I8', anchor('I8_s', :c)) + elsif step == 2 + move_abs('I8', anchor('I8_s', :n)) + else + @canvas.delete('I8') + end + return((step == 2)? 3: 1) + end + + # Fan + def draw9 + color = @C['9'] + xy = [266, 194, 310, 220] + TkcOval.new(@canvas, xy, :outline=>color, :fill=>color) + xy = [280, 209, 296, 248] + TkcOval.new(@canvas, xy, :outline=>color, :fill=>color) + xy = [ + 288, 249, 252, 249, 260, 240, 280, 234, + 296, 234, 316, 240, 324, 249, 288, 249 + ] + TkcPolygon.new(@canvas, xy, :fill=>color, :smooth=>true) + + xy = [248, 205, 265, 214, 264, 205, 265, 196] # Spinner + TkcPolygon.new(@canvas, xy, :fill=>color) + + xy = [255, 206, 265, 234] # Fan blades + TkcOval.new(@canvas, xy, :fill=>'', :outline=>@C['fg'], + :width=>3, :tag=>'I9_0') + xy = [255, 176, 265, 204] + TkcOval.new(@canvas, xy, :fill=>'', :outline=>@C['fg'], + :width=>3, :tag=>'I9_0') + xy = [255, 206, 265, 220] + TkcOval.new(@canvas, xy, :fill=>'', :outline=>@C['fg'], + :width=>1, :tag=>'I9_1') + xy = [255, 190, 265, 204] + TkcOval.new(@canvas, xy, :fill=>'', :outline=>@C['fg'], + :width=>1, :tag=>'I9_1') + end + + def move9(step = nil) + step = get_step(9, step) + + if (step & 1).nonzero? + @canvas.itemconfigure('I9_0', :width=>4) + @canvas.itemconfigure('I9_1', :width=>1) + @canvas.lower('I9_1', 'I9_0') + else + @canvas.itemconfigure('I9_0', :width=>1) + @canvas.itemconfigure('I9_1', :width=>4) + @canvas.lower('I9_0', 'I9_1') + end + return 3 if step == 0 + return 1 + end + + # Boat + def draw10 + color = @C['10a'] + color2 = @C['10b'] + xy = [191, 230, 233, 230, 233, 178, 191, 178] # Sail + TkcPolygon.new(@canvas, xy, :fill=>color, :width=>3, :outline=>@C['fg'], + :tag=>'I10') + xy = box(209, 204, 31) # Front + TkcArc.new(@canvas, xy, :outline=>'', :fill=>color, :style=>:pie, + :start=>120, :extent=>120, :tag=>'I10') + TkcArc.new(@canvas, xy, :outline=>@C['fg'], :width=>3, :style=>:arc, + :start=>120, :extent=>120, :tag=>'I10') + xy = box(249, 204, 31) # Back + TkcArc.new(@canvas, xy, :outline=>'', :fill=>@C['bg'], :width=>3, + :style=>:pie, :start=>120, :extent=>120, :tag=>'I10') + TkcArc.new(@canvas, xy, :outline=>@C['fg'], :width=>3, :style=>:arc, + :start=>120, :extent=>120, :tag=>'I10') + + xy = [200, 171, 200, 249] # Mast + TkcLine.new(@canvas, xy, :fill=>@C['fg'], :width=>3, :tag=>'I10') + xy = [159, 234, 182, 234] # Bow sprit + TkcLine.new(@canvas, xy, :fill=>@C['fg'], :width=>3, :tag=>'I10') + xy = [180, 234, 180, 251, 220, 251] # Hull + TkcLine.new(@canvas, xy, :fill=>@C['fg'], :width=>6, :tag=>'I10') + + xy = [92, 255, 221, 255] # Waves + sine(xy, 2, 25, :fill=>color2, :width=>1, :tag=>'I10w') + + xy = @canvas.coords('I10w')[4..-5] # Water + xy.concat([222, 266, 222, 277, 99, 277]) + TkcPolygon.new(@canvas, xy, :fill=>color2, :outline=>color2) + xy = [222, 266, 222, 277, 97, 277, 97, 266] # Water bottom + TkcLine.new(@canvas, xy, :fill=>@C['fg'], :width=>3) + + xy = box(239, 262, 17) + TkcArc.new(@canvas, xy, :outline=>@C['fg'], :width=>3, :style=>:arc, + :start=>95, :extent=>103) + xy = box(76, 266, 21) + TkcArc.new(@canvas, xy, :outline=>@C['fg'], :width=>3, :style=>:arc, + :extent=>190) + end + + def move10(step = nil) + step = get_step(10, step) + + pos = [ + [195, 212], [193, 212], [190, 212], [186, 212], [181, 212], [176, 212], + [171, 212], [166, 212], [161, 212], [156, 212], [151, 212], [147, 212], + [142, 212], [137, 212], [132, 212, :x], [127, 212], [121, 212], + [116, 212], [111, 212] + ] + + return 0 if step >= pos.length + + where = pos[step] + move_abs('I10', where) + + return 3 if where[2] == :x + return 1 + end + + # 2nd ball drop + def draw11 + color = @C['11a'] + color2 = @C['11b'] + xy = [23, 264, 55, 591] # Color the down tube + TkcRectangle.new(@canvas, xy, :fill=>color, :outline=>'') + xy = box(71, 460, 48) # Color the outer loop + TkcOval.new(@canvas, xy, :fill=>color, :outline=>'') + + xy = [55, 264, 55, 458] # Top right side + TkcLine.new(@canvas, xy, :fill=>@C['fg'], :width=>3) + xy = [55, 504, 55, 591] # Bottom right side + TkcLine.new(@canvas, xy, :fill=>@C['fg'], :width=>3) + xy = box(71, 460, 48) # Outer loop + TkcArc.new(@canvas, xy, :outline=>@C['fg'], :width=>3, :style=>:arc, + :start=>110, :extent=>-290, :tag=>'I11i') + xy = box(71, 460, 16) # Inner loop + TkcOval.new(@canvas, xy, :outline=>@C['fg'], :fill=>'', + :width=>3, :tag=>'I11i') + TkcOval.new(@canvas, xy, :outline=>@C['fg'], :fill=>@C['bg'], :width=>3) + + xy = [23, 264, 23, 591] # Left side + TkcLine.new(@canvas, xy, :fill=>@C['fg'], :width=>3) + xy = box(1, 266, 23) # Top left curve + TkcArc.new(@canvas, xy, :outline=>@C['fg'], :width=>3, + :style=>:arc, :extent=>90) + + xy = box(75, 235, 9) # The ball + TkcOval.new(@canvas, xy, :fill=>color2, :outline=>'', + :width=>3, :tag=>'I11') + end + + def move11(step = nil) + step = get_step(11, step) + + pos = [ + [75, 235], [70, 235], [65, 237], [56, 240], [46, 247], [38, 266], + [38, 296], [38, 333], [38, 399], [38, 475], [74, 496], [105, 472], + [100, 437], [65, 423], [-100, -100], [38, 505], [38, 527, :x], [38, 591] + ] + + return 0 if step >= pos.length + where = pos[step] + move_abs('I11', where) + return 3 if where[2] == :x + return 1 + end + + # Hand + def draw12 + xy = [ + 20, 637, 20, 617, 20, 610, 20, 590, 40, 590, 40, 590, + 60, 590, 60, 610, 60, 610 + ] + xy.concat([60, 610, 65, 620, 60, 631]) # Thumb + xy.concat([60, 631, 60, 637, 60, 662, 60, 669, 52, 669, + 56, 669, 50, 669, 50, 662, 50, 637]) + + y0 = 637 # Bumps for fingers + y1 = 645 + 50.step(21, -10){|x| + x1 = x - 5 + x2 = x - 10 + xy << x << y0 << x1 << y1 << x2 << y0 + } + TkcPolygon.new(@canvas, xy, :fill=>@C['12'], :outline=>@C['fg'], + :smooth=>true, :tag=>'I12', :width=>3) + end + + def move12(step = nil) + step = get_step(12, step) + + pos = [[42.5, 641, :x]] + return 0 if step >= pos.length + where = pos[step] + move_abs('I12', where) + return 3 if where[2] == :x + return 1 + end + + # Fax + def draw13 + color = @C['13a'] + xy = [86, 663, 149, 663, 149, 704, 50, 704, 50, 681, 64, 681, 86, 671] + xy2 = [ + 784, 663, 721, 663, 721, 704, 820, 704, 820, 681, 806, 681, 784, 671 + ] + radii = [2, 9, 9, 8, 5, 5, 2] + + round_poly(@canvas, xy, radii, :width=>3, + :outline=>@C['fg'], :fill=>color) + round_poly(@canvas, xy2, radii, :width=>3, + :outline=>@C['fg'], :fill=>color) + + xy = [56, 677] + x, y = xy + TkcRectangle.new(@canvas, box(x, y, 4), :fill=>'', :outline=>@C['fg'], + :width=>3, :tag=>'I13') + xy = [809, 677] + x, y = xy + TkcRectangle.new(@canvas, box(x, y, 4), :fill=>'', :outline=>@C['fg'], + :width=>3, :tag=>'I13R') + + xy = [112, 687] # Label + TkcText.new(@canvas, xy, :text=>'FAX', :fill=>@C['fg'], + :font=>['Times Roman', 12, :bold]) + xy = [762, 687] + TkcText.new(@canvas, xy, :text=>'FAX', :fill=>@C['fg'], + :font=>['Times Roman', 12, :bold]) + + xy = [138, 663, 148, 636, 178, 636] # Paper guide + TkcLine.new(@canvas, xy, :smooth=>true, :fill=>@C['fg'], :width=>3) + xy = [732, 663, 722, 636, 692, 636] + TkcLine.new(@canvas, xy, :smooth=>true, :fill=>@C['fg'], :width=>3) + + sine([149, 688, 720, 688], 5, 15, + :tag=>'I13_s', :fill=>@C['fg'], :width=>3) + end + + def move13(step = nil) + step = get_step(13, step) + + numsteps = 7 + + if step == numsteps + 2 + move_abs('I13_star', [-100, -100]) + @canvas.itemconfigure('I13R', :fill=>@C['13b'], :width=>2) + return 2 + end + if step == 0 # Button down + @canvas.delete('I13') + sparkle([-100, -100], 'I13_star') # Create off screen + return 1 + end + x0, y0 = anchor('I13_s', :w) + x1, y1 = anchor('I13_s', :e) + x = x0 + (x1 - x0) * (step - 1) / numsteps.to_f + move_abs('I13_star', [x, y0]) + return 1 + end + + # Paper in fax + def draw14 + color = @C['14'] + xy = [102, 661, 113, 632, 130, 618] # Left paper edge + TkcLine.new(@canvas, xy, :smooth=>true, :fill=>color, + :width=>3, :tag=>'I14L_0') + xy = [148, 629, 125, 640, 124, 662] # Right paper edge + TkcLine.new(@canvas, xy, :smooth=>true, :fill=>color, + :width=>3, :tag=>'I14L_1') + draw14a('L') + + xy = [ + 768.0, 662.5, 767.991316225, 662.433786215, 767.926187912, 662.396880171 + ] + TkcLine.new(@canvas, xy, :smooth=>true, :fill=>color, + :width=>3, :tag=>'I14R_0') + @canvas.lower('I14R_0') + # NB. these numbers are VERY sensitive, you must start with final size + # and shrink down to get the values + xy = [ + 745.947897349, 662.428358855, 745.997829056, 662.452239237, 746.0, 662.5 + ] + TkcLine.new(@canvas, xy, :smooth=>true, :fill=>color, + :width=>3, :tag=>'I14R_1') + @canvas.lower('I14R_1') + end + + def draw14a(side) + color = @C['14'] + xy = @canvas.coords("I14#{side}_0") + xy2 = @canvas.coords("I14#{side}_1") + x0, y0, x1, y1, x2, y2 = xy + x3, y3, x4, y4, x5, y5 = xy2 + + zz = [ + x0, y0, x0, y0, xy, x2, y2, x2, y2, + x3, y3, x3, y3, xy2, x5, y5, x5, y5 + ].flatten + @canvas.delete("I14#{side}") + TkcPolygon.new(@canvas, zz, :tag=>"I14#{side}", :smooth=>true, + :fill=>color, :outline=>color, :width=>3) + @canvas.lower("I14#{side}") + end + + def move14(step = nil) + step = get_step(14, step) + + # Paper going down + sc = 0.9 - 0.05*step + if sc < 0.3 + @canvas.delete('I14L') + return 0 + end + + ox, oy = @canvas.coords('I14L_0') + @canvas.scale('I14L_0', ox, oy, sc, sc) + ox, oy = @canvas.coords('I14L_1')[-2..-1] + @canvas.scale('I14L_1', ox, oy, sc, sc) + draw14a('L') + + # Paper going up + sc = 0.35 + 0.05*step + sc = 1/sc + + ox, oy = @canvas.coords('I14R_0') + @canvas.scale('I14R_0', ox, oy, sc, sc) + ox, oy = @canvas.coords('I14R_1')[-2..-1] + @canvas.scale('I14R_1', ox, oy, sc, sc) + draw14a('R') + + return((step == 10)? 3: 1) + end + + # Light beam + def draw15 + color = @C['15a'] + xy = [824, 599, 824, 585, 820, 585, 829, 585] + TkcLine.new(@canvas, xy, :fill=>@C['fg'], :width=>3, :tag=>'I15a') + xy = [789, 599, 836, 643] + TkcRectangle.new(@canvas, xy, :fill=>color, :outline=>@C['fg'], :width=>3) + xy = [778, 610, 788, 632] + TkcRectangle.new(@canvas, xy, :fill=>color, :outline=>@C['fg'], :width=>3) + xy = [766, 617, 776, 625] + TkcRectangle.new(@canvas, xy, :fill=>color, :outline=>@C['fg'], :width=>3) + + xy = [633, 600, 681, 640] + TkcRectangle.new(@canvas, xy, :fill=>color, :outline=>@C['fg'], :width=>3) + xy = [635, 567, 657, 599] + TkcRectangle.new(@canvas, xy, :fill=>color, :outline=>@C['fg'], :width=>2) + xy = [765, 557, 784, 583] + TkcRectangle.new(@canvas, xy, :fill=>color, :outline=>@C['fg'], :width=>2) + + sine([658, 580, 765, 580], 3, 15, + :tag=>'I15_s', :fill=>@C['fg'], :width=>3) + end + + def move15a + color = @C['15b'] + @canvas.scale('I15a', 824, 599, 1, 0.3) # Button down + xy = [765, 621, 681, 621] + TkcLine.new(@canvas, xy, :dash=>'-', :width=>3, :fill=>color, :tag=>'I15') + end + + def move15(step = nil) + step = get_step(15, step) + + numsteps = 6 + + if step == numsteps + 2 + move_abs('I15_star', [-100, -100]) + return 2 + end + if step == 0 # Break the light beam + sparkle([-100, -100], 'I15_star') + xy = [765, 621, 745, 621] + @canvas.coords('I15', xy) + return 1 + end + x0, y0 = anchor('I15_s', :w) + x1, y1 = anchor('I15_s', :e) + x = x0 + (x1 - x0) * (step - 1) / numsteps.to_f + move_abs('I15_star', [x, y0]) + return 1 + end + + # Bell + def draw16 + color = @C['16'] + xy = [722, 485, 791, 556] + TkcRectangle.new(@canvas, xy, :fill=>'', :outline=>@C['fg'], :width=>3) + xy = box(752, 515, 25) # Bell + TkcOval.new(@canvas, xy, :fill=>color, :outline=>'black', + :tag=>'I16b', :width=>2) + xy = box(752, 515, 5) # Bell button + TkcOval.new(@canvas, xy, :fill=>'black', :outline=>'black', :tag=>'I16b') + + xy = [784, 523, 764, 549] # Clapper + TkcLine.new(@canvas, xy, :width=>3, :tag=>'I16c', :fill=>@C['fg']) + xy = box(784, 523, 4) + TkcOval.new(@canvas, xy, :fill=>@C['fg'], :outline=>@C['fg'], :tag=>'I16d') + end + + def move16(step = nil) + step = get_step(16, step) + + # Note: we never stop + ox, oy = [760, 553] + if (step & 1).nonzero? + beta = 12 + @canvas.move('I16b', 3, 0) + else + beta = -12 + @canvas.move('I16b', -3, 0) + end + rotate_item('I16c', ox, oy, beta) + rotate_item('I16d', ox, oy, beta) + + return ((step == 1)? 3: 1) + end + + # Cat + def draw17 + color = @C['17'] + + xy = [584, 556, 722, 556] + TkcLine.new(@canvas, xy, :fill=>@C['fg'], :width=>3) + xy = [584, 485, 722, 485] + TkcLine.new(@canvas, xy, :fill=>@C['fg'], :width=>3) + + xy = [664, 523, 717, 549] # Body + TkcArc.new(@canvas, xy, :outline=>@C['fg'], :fill=>color, :width=>3, + :style=>:chord, :start=>128, :extent=>260, :tag=>'I17') + + xy = [709, 554, 690, 543] # Paw + TkcOval.new(@canvas, xy, :outline=>@C['fg'], :fill=>color, + :width=>3, :tag=>'I17') + xy = [657, 544, 676, 555] + TkcOval.new(@canvas, xy, :outline=>@C['fg'], :fill=>color, + :width=>3, :tag=>'I17') + + xy = box(660, 535, 15) # Lower face + TkcArc.new(@canvas, xy, :outline=>@C['fg'], :width=>3, :style=>:arc, + :start=>150, :extent=>240, :tag=>'I17_') + TkcArc.new(@canvas, xy, :outline=>'', :fill=>color, :width=>1, + :style=>:chord, :start=>150, :extent=>240, :tag=>'I17_') + xy = [674, 529, 670, 513, 662, 521, 658, 521, 650, 513, 647, 529] # Ears + TkcLine.new(@canvas, xy, :fill=>@C['fg'], :width=>3, :tag=>'I17_') + TkcPolygon.new(@canvas, xy, :fill=>color, :outline=>'', :width=>1, + :tag=>['I17_', 'I17_c']) + xy = [652, 542, 628, 539] # Whiskers + TkcLine.new(@canvas, xy, :fill=>@C['fg'], :width=>3, :tag=>'I17_') + xy = [652, 543, 632, 545] + TkcLine.new(@canvas, xy, :fill=>@C['fg'], :width=>3, :tag=>'I17_') + xy = [652, 546, 632, 552] + TkcLine.new(@canvas, xy, :fill=>@C['fg'], :width=>3, :tag=>'I17_') + + xy = [668, 543, 687, 538] + TkcLine.new(@canvas, xy, :fill=>@C['fg'], :width=>3, + :tag=>['I17_', 'I17_w']) + xy = [668, 544, 688, 546] + TkcLine.new(@canvas, xy, :fill=>@C['fg'], :width=>3, + :tag=>['I17_', 'I17_w']) + xy = [668, 547, 688, 553] + TkcLine.new(@canvas, xy, :fill=>@C['fg'], :width=>3, + :tag=>['I17_', 'I17_w']) + + xy = [649, 530, 654, 538, 659, 530] # Left eye + TkcLine.new(@canvas, xy, :fill=>@C['fg'], :width=>2, + :smooth=>true, :tag=>'I17') + xy = [671, 530, 666, 538, 661, 530] # Right eye + TkcLine.new(@canvas, xy, :fill=>@C['fg'], :width=>2, + :smooth=>true, :tag=>'I17') + xy = [655, 543, 660, 551, 665, 543] # Mouth + TkcLine.new(@canvas, xy, :fill=>@C['fg'], :width=>2, + :smooth=>true, :tag=>'I17') + end + + def move17(step = nil) + step = get_step(17, step) + + if step == 0 + @canvas.delete('I17') # Delete most of the cat + xy = [655, 543, 660, 535, 665, 543] # Mouth + TkcLine.new(@canvas, xy, :fill=>@C['fg'], :width=>3, + :smooth=>true, :tag=>'I17_') + xy = box(654, 530, 4) # Left eye + TkcOval.new(@canvas, xy, :outline=>@C['fg'], :width=>3, :fill=>'', + :tag=>'I17_') + xy = box(666, 530, 4) # Right eye + TkcOval.new(@canvas, xy, :outline=>@C['fg'], :width=>3, :fill=>'', + :tag=>'I17_') + + @canvas.move('I17_', 0, -20) # Move face up + xy = [652, 528, 652, 554] # Front leg + TkcLine.new(@canvas, xy, :fill=>@C['fg'], :width=>3, :tag=>'I17_') + xy = [670, 528, 670, 554] # 2nd front leg + TkcLine.new(@canvas, xy, :fill=>@C['fg'], :width=>3, :tag=>'I17_') + + xy = [ # Body + 675, 506, 694, 489, 715, 513, 715, 513, 715, 513, 716, 525, + 716, 525, 716, 525, 706, 530, 695, 530, 679, 535, 668, 527, + 668, 527, 668, 527, 675, 522, 676, 517, 677, 512 + ] + TkcPolygon.new(@canvas, xy, :fill=>@canvas.itemcget('I17_c', :fill), + :outline=>@C['fg'], :width=>3, :smooth=>true, + :tag=>'I17_') + xy = [716, 514, 716, 554] # Back leg + TkcLine.new(@canvas, xy, :fill=>@C['fg'], :width=>3, :tag=>'I17_') + xy = [694, 532, 694, 554] # 2nd back leg + TkcLine.new(@canvas, xy, :fill=>@C['fg'], :width=>3, :tag=>'I17_') + xy = [715, 514, 718, 506, 719, 495, 716, 488] # Tail + TkcLine.new(@canvas, xy, :fill=>@C['fg'], :width=>3, + :smooth=>true, :tag=>'I17_') + + @canvas.raise('I17w') # Make whiskers visible + @canvas.move('I17_', -5, 0) # Move away from the wall a bit + return 2 + end + return 0 + end + + # Sling shot + def draw18 + color = @C['18'] + xy = [721, 506, 627, 506] # Sling hold + TkcLine.new(@canvas, xy, :width=>4, :fill=>@C['fg'], :tag=>'I18') + + xy = [607, 500, 628, 513] # Sling rock + TkcOval.new(@canvas, xy, :fill=>color, :outline=>'', :tag=>'I18a') + + xy = [526, 513, 606, 507, 494, 502] # Sling band + TkcLine.new(@canvas, xy, :fill=>@C['fg'], :width=>4, :tag=>'I18b') + xy = [485, 490, 510, 540, 510, 575, 510, 540, 535, 491] # Sling + TkcLine.new(@canvas, xy, :fill=>@C['fg'], :width=>6) + end + + def move18(step = nil) + step = get_step(18, step) + + pos = [ + [587, 506], [537, 506], [466, 506], [376, 506], [266, 506, :x], + [136, 506], [16, 506], [-100, -100] + ] + + b = [] + b[0] = [490, 502, 719, 507, 524, 512] # Band collapsing + b[1] = [ + 491, 503, 524, 557, 563, 505, 559, 496, 546, 506, 551, 525, + 553, 536, 538, 534, 532, 519, 529, 499 + ] + b[2] = [ + 491, 503, 508, 563, 542, 533, 551, 526, 561, 539, 549, 550, 530, 500 + ] + b[3] = [ + 491, 503, 508, 563, 530, 554, 541, 562, 525, 568, 519, 544, 530, 501 + ] + + return 0 if step >= pos.length + + if step == 0 + @canvas.delete('I18') + @canvas.itemconfigure('I18b', :smooth=>true) + end + if b[step] + @canvas.coords('I18b', b[step]) + end + + where = pos[step] + move_abs('I18a', where) + return 3 if where[2] == :x + return 1 + end + + # Water pipe + def draw19 + color = @C['19'] + xx = [[249, 181], [155, 118], [86, 55], [22, 0]] + xx.each{|x1, x2| + TkcRectangle.new(@canvas, x1, 453, x2, 467, + :fill=>color, :outline=>'', :tag=>'I19') + TkcLine.new(@canvas, x1, 453, x2, 453, + :fill=>@C['fg'], :width=>1) # Pipe top + TkcLine.new(@canvas, x1, 467, x2, 467, + :fill=>@C['fg'], :width=>1) # Pipe bottom + } + @canvas.raise('I11i') + + xy = box(168, 460, 16) # Bulge by the joint + TkcOval.new(@canvas, xy, :fill=>color, :outline=>'') + TkcArc.new(@canvas, xy, :outline=>@C['fg'], :width=>1, :style=>:arc, + :start=>21, :extent=>136) + TkcArc.new(@canvas, xy, :outline=>@C['fg'], :width=>1, :style=>:arc, + :start=>-21, :extent=>-130) + + xy = [249, 447, 255, 473] # First joint 26x6 + TkcRectangle.new(@canvas, xy, :fill=>color, :outline=>@C['fg'], :width=>1) + + xy = box(257, 433, 34) # Bend up + TkcArc.new(@canvas, xy, :outline=>'', :fill=>color, :width=>1, + :style=>:pie, :start=>0, :extent=>-91) + TkcArc.new(@canvas, xy, :outline=>@C['fg'], :width=>1, + :style=>:arc, :start=>0, :extent=>-90) + xy = box(257, 433, 20) + TkcArc.new(@canvas, xy, :outline=>'', :fill=>@C['bg'], :width=>1, + :style=>:pie, :start=>0, :extent=>-92) + TkcArc.new(@canvas, xy, :outline=>@C['fg'], :width=>1, + :style=>:arc, :start=>0, :extent=>-90) + xy = box(257, 421, 34) # Bend left + TkcArc.new(@canvas, xy, :outline=>'', :fill=>color, :width=>1, + :style=>:pie, :start=>0, :extent=>91) + TkcArc.new(@canvas, xy, :outline=>@C['fg'], :width=>1, + :style=>:arc, :start=>0, :extent=>90) + xy = box(257, 421, 20) + TkcArc.new(@canvas, xy, :outline=>'', :fill=>@C['bg'], :width=>1, + :style=>:pie, :start=>0, :extent=>90) + TkcArc.new(@canvas, xy, :outline=>@C['fg'], :width=>1, + :style=>:arc, :start=>0, :extent=>90) + xy = box(243, 421, 34) # Bend down + TkcArc.new(@canvas, xy, :outline=>'', :fill=>color, :width=>1, + :style=>:pie, :start=>90, :extent=>90) + TkcArc.new(@canvas, xy, :outline=>@C['fg'], :width=>1, + :style=>:arc, :start=>90, :extent=>90) + xy = box(243, 421, 20) + TkcArc.new(@canvas, xy, :outline=>'', :fill=>@C['bg'], :width=>1, + :style=>:pie, :start=>90, :extent=>90) + TkcArc.new(@canvas, xy, :outline=>@C['fg'], :width=>1, + :style=>:arc, :start=>90, :extent=>90) + + xy = [270, 427, 296, 433] # 2nd joint bottom + TkcRectangle.new(@canvas, xy, :fill=>color, :outline=>@C['fg'], :width=>1) + xy = [270, 421, 296, 427] # 2nd joint top + TkcRectangle.new(@canvas, xy, :fill=>color, :outline=>@C['fg'], :width=>1) + xy = [249, 382, 255, 408] # Third joint right + TkcRectangle.new(@canvas, xy, :fill=>color, :outline=>@C['fg'], :width=>1) + xy = [243, 382, 249, 408] # Third joint left + TkcRectangle.new(@canvas, xy, :fill=>color, :outline=>@C['fg'], :width=>1) + xy = [203, 420, 229, 426] # Last joint + TkcRectangle.new(@canvas, xy, :fill=>color, :outline=>@C['fg'], :width=>1) + + xy = box(168, 460, 6) # Handle joint + TkcOval.new(@canvas, xy, :fill=>@C['fg'], :outline=>'', :tag=>'I19a') + xy = [168, 460, 168, 512] # Handle bar + TkcLine.new(@canvas, xy, :fill=>@C['fg'], :width=>5, :tag=>'I19b') + end + + def move19(step = nil) + step = get_step(19, step) + + angles = [30, 30, 30] + return 2 if step == angles.length + ox, oy = centroid('I19a') + rotate_item('I19b', ox, oy, angles[step]) + + return 1 + end + + # Water pouring + def draw20 + # do nothing + end + + def move20(step = nil) + step = get_step(20, step) + + pos = [451, 462, 473, 484, 496, 504, 513, 523, 532] + freq = [20, 40, 40, 40, 40, 40, 40, 40, 40] + pos = [ + [451, 20], [462, 40], [473, 40], [484, 40], [496, 40], + [504, 40], [513, 40], [523, 40], [532, 40, :x] + ] + return 0 if step >= pos.length + + @canvas.delete('I20') + where = pos[step] + y, f = where + h20(y, f) + return 3 if where[2] == :x + return 1 + end + + def h20(y, f) + color = @C['20'] + @canvas.delete('I20') + + sine([208, 428, 208, y], 4, f, :tag=>['I20', 'I20s'], + :width=>3, :fill=>color, :smooth=>true) + TkcLine.new(@canvas, @canvas.coords('I20s'), :width=>3, + :fill=>color, :smooth=>1, :tag=>['I20', 'I20a']) + TkcLine.new(@canvas, @canvas.coords('I20s'), :width=>3, + :fill=>color, :smooth=>1, :tag=>['I20', 'I20b']) + @canvas.move('I20a', 8, 0) + @canvas.move('I20b', 16, 0) + end + + # Bucket + def draw21 + color = @C['21'] + xy = [217, 451, 244, 490] # Right handle + TkcLine.new(@canvas, xy, :fill=>@C['fg'], :width=>2, :tag=>'I21_a') + xy = [201, 467, 182, 490] # Left handle + TkcLine.new(@canvas, xy, :fill=>@C['fg'], :width=>2, :tag=>'I21_a') + + xy = [245, 490, 237, 535] # Right side + xy2 = [189, 535, 181, 490] # Left side + TkcPolygon.new(@canvas, xy + xy2, :fill=>color, :outline=>'', + :tag=>['I21', 'I21f']) + TkcLine.new(@canvas, xy, :fill=>@C['fg'], :width=>2, :tag=>'I21') + TkcLine.new(@canvas, xy2, :fill=>@C['fg'], :width=>2, :tag=>'I21') + + xy = [182, 486, 244, 498] # Top + TkcOval.new(@canvas, xy, :fill=>color, :outline=>'', :width=>2, + :tag=>['I21', 'I21f']) + TkcOval.new(@canvas, xy, :fill=>'', :outline=>@C['fg'], :width=>2, + :tag=>['I21', 'I21t']) + xy = [189, 532, 237, 540] # Bottom + TkcOval.new(@canvas, xy, :fill=>color, :outline=>@C['fg'], :width=>2, + :tag=>['I21', 'I21b']) + end + + def move21(step = nil) + step = get_step(21, step) + + numsteps = 30 + return 0 if step >= numsteps + + x1, y1, x2, y2 = @canvas.coords('I21b') + # lx1, ly1, lx2, ly2 = @canvas.coords('I21t') + lx1, ly1, lx2, ly2 = [183, 492, 243, 504] + + f = step / numsteps.to_f + y2 = y2 - 3 + xx1 = x1 + (lx1 - x1) * f + yy1 = y1 + (ly1 - y1) * f + xx2 = x2 + (lx2 - x2) * f + yy2 = y2 + (ly2 - y2) * f + + @canvas.itemconfigure('I21b', :fill=>@C['20']) + @canvas.delete('I21w') + TkcPolygon.new(@canvas, x2, y2, x1, y1, xx1, yy1, xx2, yy1, + :tag=>['I21', 'I21w'], :outline=>'', :fill=>@C['20']) + @canvas.lower('I21w', 'I21') + @canvas.raise('I21b') + @canvas.lower('I21f') + + return((step == numsteps - 1)? 3: 1) + end + + # Bucket drop + def draw22 + # do nothing + end + + def move22(step = nil) + step = get_step(22, step) + pos = [[213, 513], [213, 523], [213, 543, :x], [213, 583], [213, 593]] + + @canvas.itemconfigure('I21f', :fill=>@C['22']) if step == 0 + return 0 if step >= pos.length + where = pos[step] + move_abs('I21', where) + h20(where[1], 40) + @canvas.delete('I21_a') # Delete handles + + return 3 if where[2] == :x + return 1 + end + + # Blow dart + def draw23 + color = @C['23a'] + color2 = @C['23b'] + color3 = @C['23c'] + + xy = [185, 623, 253, 650] # Block + TkcRectangle.new(@canvas, xy, :fill=>'black', :outline=>@C['fg'], + :width=>2, :tag=>'I23a') + xy = [187, 592, 241, 623] # Balloon + TkcOval.new(@canvas, xy, :outline=>'', :fill=>color, :tag=>'I23b') + TkcArc.new(@canvas, xy, :outline=>@C['fg'], :width=>3, :tag=>'I23b', + :style=>:arc, :start=>12, :extent=>336) + xy = [239, 604, 258, 589, 258, 625, 239, 610] # Balloon nozzle + TkcPolygon.new(@canvas, xy, :outline=>'', :fill=>color, :tag=>'I23b') + TkcLine.new(@canvas, xy, :fill=>@C['fg'], :width=>3, :tag=>'I23b') + + xy = [285, 611, 250, 603] # Dart body + TkcOval.new(@canvas, xy, :fill=>color2, :outline=>@C['fg'], + :width=>3, :tag=>'I23d') + xy = [249, 596, 249, 618, 264, 607, 249, 596] # Dart tail + TkcPolygon.new(@canvas, xy, :fill=>color3, :outline=>@C['fg'], + :width=>3, :tag=>'I23d') + xy = [249, 607, 268, 607] # Dart detail + TkcLine.new(@canvas, xy, :fill=>@C['fg'], :width=>3, :tag=>'I23d') + xy = [285, 607, 305, 607] # Dart needle + TkcLine.new(@canvas, xy, :fill=>@C['fg'], :width=>3, :tag=>'I23d') + end + + def move23(step = nil) + step = get_step(23, step) + + pos = [ + [277, 607], [287, 607], [307, 607, :x], [347, 607], [407, 607], + [487, 607], [587, 607], [687, 607], [787, 607], [-100, -100] + ] + + return 0 if step >= pos.length + if step <= 1 + ox, oy = anchor('I23a', :n) + @canvas.scale('I23b', ox, oy, 0.9, 0.5) + end + where = pos[step] + move_abs('I23d', where) + + return 3 if where[2] == :x + return 1 + end + + # Balloon + def draw24 + color = @C['24a'] + xy = [366, 518, 462, 665] # Balloon + TkcOval.new(@canvas, xy, :fill=>color, :outline=>@C['fg'], + :width=>3, :tag=>'I24') + xy = [414, 666, 414, 729] # String + TkcLine.new(@canvas, xy, :fill=>@C['fg'], :width=>3, :tag=>'I24') + xy = [410, 666, 404, 673, 422, 673, 418, 666] # Nozzle + TkcPolygon.new(@canvas, xy, :fill=>color, :outline=>@C['fg'], + :width=>3, :tag=>'I24') + + xy = [387, 567, 390, 549, 404, 542] # Reflections + TkcLine.new(@canvas, xy, :fill=>@C['fg'], :smooth=>true, + :width=>2, :tag=>'I24') + xy = [395, 568, 399, 554, 413, 547] + TkcLine.new(@canvas, xy, :fill=>@C['fg'], :smooth=>true, + :width=>2, :tag=>'I24') + xy = [403, 570, 396, 555, 381, 553] + TkcLine.new(@canvas, xy, :fill=>@C['fg'], :smooth=>true, + :width=>2, :tag=>'I24') + xy = [408, 564, 402, 547, 386, 545] + TkcLine.new(@canvas, xy, :fill=>@C['fg'], :smooth=>true, + :width=>2, :tag=>'I24') + end + + def move24(step = nil) + step = get_step(24, step) + + return 0 if step > 4 + return 2 if step == 4 + + if step == 0 + @canvas.delete('I24') # Exploding balloon + xy = [ + 347, 465, 361, 557, 271, 503, 272, 503, 342, 574, 259, 594, + 259, 593, 362, 626, 320, 737, 320, 740, 398, 691, 436, 738, + 436, 739, 476, 679, 528, 701, 527, 702, 494, 627, 548, 613, + 548, 613, 480, 574, 577, 473, 577, 473, 474, 538, 445, 508, + 431, 441, 431, 440, 400, 502, 347, 465, 347, 465 + ] + TkcPolygon.new(@canvas, xy, :tag=>'I24', :fill=>@C['24b'], + :outline=>@C['24a'], :width=>10, :smooth=>true) + msg = Tk.subst(@S['message'].value) + TkcText.new(@canvas, centroid('I24'), :text=>msg, :tag=>['I24', 'I24t'], + :justify=>:center, :font=>['Times Roman', 18, :bold]) + return 1 + end + + @canvas.itemconfigure('I24t', :font=>['Times Roman', 18 + 6*step, :bold]) + @canvas.move('I24', 0, -60) + ox, oy = centroid('I24') + @canvas.scale('I24', ox, oy, 1.25, 1.25) + return 1 + end + + # Displaying the message + def move25(step = nil) + step = get_step(25, step) + + if step == 0 + @XY['25'] = Tk::Clock.clicks(:miliseconds) + return 1 + end + elapsed = Tk::Clock.clicks(:miliseconds) - @XY['25'] + return 1 if elapsed < 5000 + return 2 + end + + # Collapsing balloon + def move26(step = nil) + step = get_step(26, step) + + if step >= 3 + @canvas.delete('I24', 'I26') + TkcText.new(@canvas, 430, 755, :anchor=>:s, :tag=>'I26', + :text=>'click to continue', + :font=>['Times Roman', 24, :bold]) + @canvas.bind('1', proc{reset}) + return 4 + end + + ox, oy = centroid('I24') + @canvas.scale('I24', ox, oy, 0.8, 0.8) + @canvas.move('I24', 0, 60) + @canvas.itemconfigure('I24t', :font=>['Times Roman', 30 - 6*step, :bold]) + return 1 + end + + ################################################################ + # + # Helper functions + # + def box(x, y, r) + [x - r, y - r, x + r, y + r] + end + + def move_abs(item, xy) + x, y = xy + ox, oy = centroid(item) + dx = x - ox + dy = y - oy + @canvas.move(item, dx, dy) + end + + def rotate_item(item, ox, oy, beta) + xy = @canvas.coords(item) + xy2 = [] + 0.step(xy.length - 1, 2){|idx| + x, y = xy[idx, 2] + xy2.concat(rotate_c(x, y, ox, oy, beta)) + } + @canvas.coords(item, xy2) + end + + def rotate_c(x, y, ox, oy, beta) + # rotates vector (ox,oy)->(x,y) by beta degrees clockwise + + x -= ox # Shift to origin + y -= oy + + beta = beta * Math.atan(1) * 4 / 180.0 # Radians + xx = x * Math.cos(beta) - y * Math.sin(beta) # Rotate + yy = x * Math.sin(beta) + y * Math.cos(beta) + + xx += ox # Shift back + yy += oy + + [xx, yy] + end + + def reset + draw_all + @canvas.bind_remove('1') + @S['mode'].value = :MSTART + @S['active'] = [0] + end + + # Each Move## keeps its state info in STEP, this retrieves and increments it + def get_step(who, step) + if step + @STEP[who] = step + else + if !@STEP.exist?(who) || @STEP[who] == "" + @STEP[who] = 0 + else + @STEP[who] += 1 + end + end + @STEP[who] + end + + def reset_step + @S['cnt'].value = 0 + @STEP.keys.each{|k| @STEP[k] = ''} + end + + def sine(xy0, amp, freq, opts = {}) + x0, y0, x1, y1 = xy0 + step = 2 + xy = [] + if y0 == y1 # Horizontal + x0.step(x1, step){|x| + beta = (x - x0) * 2 * Math::PI / freq + y = y0 + amp * Math.sin(beta) + xy << x << y + } + else + y0.step(y1, step){|y| + beta = (y - y0) * 2 * Math::PI / freq + x = x0 + amp * Math.sin(beta) + xy << x << y + } + end + TkcLine.new(@canvas, xy, opts) + end + + def round_rect(xy, radius, opts={}) + x0, y0, x3, y3 = xy + r = @canvas.winfo_pixels(radius) + d = 2 * r + + # Make sure that the radius of the curve is less than 3/8 size of the box! + maxr = 0.75 + if d > maxr * (x3 - x0) + d = maxr * (x3 - x0) + end + if d > maxr * (y3 - y0) + d = maxr * (y3 - y0) + end + + x1 = x0 + d + x2 = x3 - d + y1 = y0 + d + y2 = y3 - d + + xy = [x0, y0, x1, y0, x2, y0, x3, y0, x3, y1, x3, y2] + xy.concat([x3, y3, x2, y3, x1, y3, x0, y3, x0, y2, x0, y1]) + return xy + end + + def round_poly(canv, xy, radii, opts) + lenXY = xy.length + lenR = radii.length + if lenXY != 2*lenR + raise "wrong number of vertices and radii" + end + + knots = [] + x0 = xy[-2]; y0 = xy[-1] + x1 = xy[0]; y1 = xy[1] + xy << xy[0] << xy[1] + + 0.step(lenXY - 1, 2){|i| + radius = radii[i/2] + r = canv.winfo_pixels(radius) + + x2 = xy[i+2]; y2 = xy[i+3] + z = _round_poly2(x0, y0, x1, y1, x2, y2, r) + knots.concat(z) + + x0 = x1; y0 = y1 + x1 = x2; y1 = y2 + } + TkcPolygon.new(canv, knots, {:smooth=>true}.update(opts)) + end + + def _round_poly2(x0, y0, x1, y1, x2, y2, radius) + d = 2 * radius + maxr = 0.75 + + v1x = x0 - x1 + v1y = y0 - y1 + v2x = x2 - x1 + v2y = y2 - y1 + + vlen1 = Math.sqrt(v1x*v1x + v1y*v1y) + vlen2 = Math.sqrt(v2x*v2x + v2y*v2y) + + if d > maxr * vlen1 + d = maxr * vlen1 + end + if d > maxr * vlen2 + d = maxr * vlen2 + end + + xy = [] + xy << (x1 + d * v1x / vlen1) << (y1 + d * v1y / vlen1) + xy << x1 << y1 + xy << (x1 + d * v2x / vlen2) << (y1 + d * v2y / vlen2) + + return xy + end + + def sparkle(oxy, tag) + xy = [ + [299, 283], [298, 302], [295, 314], [271, 331], + [239, 310], [242, 292], [256, 274], [281, 273] + ] + xy.each{|x, y| + TkcLine.new(@canvas, 271, 304, x, y, + :fill=>'white', :width=>3, :tag=>tag) + } + move_abs(tag, oxy) + end + + def centroid(item) + anchor(item, :c) + end + + def anchor(item, where) + x1, y1, x2, y2 = @canvas.bbox(item) + case(where) + when :n + y = y1 + when :s + y = y2 + else + y = (y1 + y2) / 2.0 + end + case(where) + when :w + x = x1 + when :e + x = x2 + else + x = (x1 + x2) / 2.0 + end + return [x, y] + end +end + +TkGoldberg_Demo.new($goldberg_demo) diff --git a/ext/tk/sample/demos-en/pendulum.rb b/ext/tk/sample/demos-en/pendulum.rb new file mode 100644 index 0000000000..36bb44edec --- /dev/null +++ b/ext/tk/sample/demos-en/pendulum.rb @@ -0,0 +1,223 @@ +# +# This demonstration illustrates how Tcl/Tk can be used to construct +# simulations of physical systems. +# (called by 'widget') +# +# based on Tcl/Tk8.5a2 widget demos + +# destroy toplevel widget for this demo script +if defined?($pendulum_demo) && $pendulum_demo + $pendulum_demo.destroy + $pendulum_demo = nil +end + +# create toplevel widget +$pendulum_demo = TkToplevel.new {|w| + title("Pendulum Animation Demonstration") + iconname("pendulum") + positionWindow(w) +} + +# create label +msg = TkLabel.new($pendulum_demo) { + font $font + wraplength '4i' + justify 'left' + text 'This demonstration shows how Ruby/Tk can be used to carry out animations that are linked to simulations of physical systems. In the left canvas is a graphical representation of the physical system itself, a simple pendulum, and in the right canvas is a graph of the phase space of the system, which is a plot of the angle (relative to the vertical) against the angular velocity. The pendulum bob may be repositioned by clicking and dragging anywhere on the left canvas.' +} +msg.pack('side'=>'top') + +# create frame +TkFrame.new($pendulum_demo) {|frame| + TkButton.new(frame) { + text 'Dismiss' + command proc{ + tmppath = $pendulum_demo + $pendulum_demo = nil + tmppath.destroy + } + }.pack('side'=>'left', 'expand'=>'yes') + + TkButton.new(frame) { + text 'See Code' + command proc{showCode 'pendulum'} + }.pack('side'=>'left', 'expand'=>'yes') + +}.pack('side'=>'bottom', 'fill'=>'x', 'pady'=>'2m') + +# animated wave +class PendulumAnimationDemo + def initialize(frame) + # Create some structural widgets + pane = TkPanedWindow.new(frame).pack(:fill=>:both, :expand=>true) + pane.add(@lf1 = TkLabelFrame.new(pane, :text=>'Pendulum Simulation')) + pane.add(@lf2 = TkLabelFrame.new(pane, :text=>'Phase Space')) + + # Create the canvas containing the graphical representation of the + # simulated system. + @c = TkCanvas.new(@lf1, :width=>320, :height=>200, :background=>'white', + :borderwidth=>2, :relief=>:sunken) + TkcText.new(@c, 5, 5, :anchor=>:nw, + :text=>'Click to Adjust Bob Start Position') + # Coordinates of these items don't matter; they will be set properly below + @plate = TkcLine.new(@c, 0, 25, 320, 25, :width=>2, :fill=>'grey50') + @rod = TkcLine.new(@c, 1, 1, 1, 1, :width=>3, :fill=>'black') + @bob = TkcOval.new(@c, 1, 1, 2, 2, + :width=>3, :fill=>'yellow', :outline=>'black') + TkcOval.new(@c, 155, 20, 165, 30, :fill=>'grey50', :outline=>'') + + # pack + @c.pack(:fill=>:both, :expand=>true) + + # Create the canvas containing the phase space graph; this consists of + # a line that gets gradually paler as it ages, which is an extremely + # effective visual trick. + @k = TkCanvas.new(@lf2, :width=>320, :height=>200, :background=>'white', + :borderwidth=>2, :relief=>:sunken) + @y_axis = TkcLine.new(@k, 160, 200, 160, 0, :fill=>'grey75', :arrow=>:last) + @x_axis = TkcLine.new(@k, 0, 100, 320, 100, :fill=>'grey75', :arrow=>:last) + + @graph = {} + 90.step(0, -10){|i| + # Coordinates of these items don't matter; + # they will be set properly below + @graph[i] = TkcLine.new(@k, 0, 0, 1, 1, :smooth=>true, :fill=>"grey#{i}") + } + + # labels + @label_theta = TkcText.new(@k, 0, 0, :anchor=>:ne, + :text=>'q', :font=>'Symbol 8') + @label_dtheta = TkcText.new(@k, 0, 0, :anchor=>:ne, + :text=>'dq', :font=>'Symbol 8') + + # pack + @k.pack(:fill=>:both, :expand=>true) + + # Initialize some variables + @points = [] + @theta = 45.0 + @dTheta = 0.0 + @length = 150 + + # init display + showPendulum + + # animation loop + @timer = TkTimer.new(15){ repeat } + + # binding + @c.bindtags_unshift(btag = TkBindTag.new) + btag.bind('Destroy'){ @timer.stop } + btag.bind('1', proc{|x, y| @timer.stop; showPendulum(x, y)}, '%x %y') + btag.bind('B1-Motion', proc{|x, y| showPendulum(x, y)}, '%x %y') + btag.bind('ButtonRelease-1', + proc{|x, y| showPendulum(x, y); @timer.start }, '%x %y') + + btag.bind('Configure', proc{|w| @plate.coords(0, 25, w, 25)}, '%w') + + @k.bind('Configure', proc{|h, w| + @psh = h/2; + @psw = w/2 + @x_axis.coords(2, @psh, w-2, @psh) + @y_axis.coords(@psw, h-2, @psw, 2) + @label_theta.coords(@psw-4, 6) + @label_dtheta.coords(w-6, @psh+4) + }, '%h %w') + + # animation start + @timer.start(500) + end + + # This procedure makes the pendulum appear at the correct place on the + # canvas. If the additional arguments x, y are passed instead of computing + # the position of the pendulum from the length of the pendulum rod and its + # angle, the length and angle are computed in reverse from the given + # location (which is taken to be the centre of the pendulum bob.) + def showPendulum(x=nil, y=nil) + if x && y && (x != 160 || y != 25) + @dTheta = 0.0 + x2 = x - 160 + y2 = y - 25 + @length = Math.hypot(x2, y2) + @theta = Math.atan2(x2,y2)*180/Math::PI + else + angle = @theta*Math::PI/180 + x = 160 + @length*Math.sin(angle) + y = 25 + @length*Math.cos(angle) + end + + @rod.coords(160, 25, x, y) + @bob.coords(x-15, y-15, x+15, y+15) + end + + # Update the phase-space graph according to the current angle and the + # rate at which the angle is changing (the first derivative with + # respect to time.) + def showPhase + @points << @theta + @psw << -20*@dTheta + @psh + if @points.length > 100 + @points = @points[-100..-1] + end + (0...100).step(10){|i| + first = - i + last = 11 - i + last = -1 if last >= 0 + next if first > last + lst = @points[first..last] + @graph[i].coords(lst) if lst && lst.length >= 4 + } + end + + # This procedure is the "business" part of the simulation that does + # simple numerical integration of the formula for a simple rotational + # pendulum. + def recomputeAngle + scaling = 3000.0/@length/@length + + # To estimate the integration accurately, we really need to + # compute the end-point of our time-step. But to do *that*, we + # need to estimate the integration accurately! So we try this + # technique, which is inaccurate, but better than doing it in a + # single step. What we really want is bound up in the + # differential equation: + # .. - sin theta + # theta + theta = ----------- + # length + # But my math skills are not good enough to solve this! + + # first estimate + firstDDTheta = -Math.sin(@theta * Math::PI/180) * scaling + midDTheta = @dTheta + firstDDTheta + midTheta = @theta + (@dTheta + midDTheta)/2 + # second estimate + midDDTheta = -Math.sin(midTheta * Math::PI/180) * scaling + midDTheta = @dTheta + (firstDDTheta + midDDTheta)/2 + midTheta = @theta + (@dTheta + midDTheta)/2 + # Now we do a double-estimate approach for getting the final value + # first estimate + midDDTheta = -Math.sin(midTheta * Math::PI/180) * scaling + lastDTheta = midDTheta + midDDTheta + lastTheta = midTheta + (midDTheta+ lastDTheta)/2 + # second estimate + lastDDTheta = -Math.sin(lastTheta * Math::PI/180) * scaling + lastDTheta = midDTheta + (midDDTheta + lastDDTheta)/2 + lastTheta = midTheta + (midDTheta + lastDTheta)/2 + # Now put the values back in our globals + @dTheta = lastDTheta + @theta = lastTheta + end + + # This method ties together the simulation engine and the graphical + # display code that visualizes it. + def repeat + # Simulate + recomputeAngle + + # Update the display + showPendulum + showPhase + end +end + +# Start the animation processing +PendulumAnimationDemo.new($pendulum_demo) diff --git a/ext/tk/sample/demos-en/widget b/ext/tk/sample/demos-en/widget index 1a4fb0b96d..b8073a05da 100644 --- a/ext/tk/sample/demos-en/widget +++ b/ext/tk/sample/demos-en/widget @@ -392,6 +392,12 @@ txt.insert('end', "\n") txt.insert('end', "Animation\n", tag_title) txt.insert('end', " \n ", tag_demospace) txt.insert('end', "1. Animated labels (if supported)\n", tag_demo, "demo-anilabel") +txt.insert('end', " \n ", tag_demospace) +txt.insert('end', "2. Animated wave (if supported)\n", tag_demo, "demo-aniwave") +txt.insert('end', " \n ", tag_demospace) +txt.insert('end', "3. Pendulum simulation (if supported)\n", tag_demo, "demo-pendulum") +txt.insert('end', " \n ", tag_demospace) +txt.insert('end', "4. A celebration of Rube Goldberg (if supported)\n", tag_demo, "demo-goldberg") txt.insert('end', "\n") txt.insert('end', "Miscellaneous\n", tag_title) @@ -785,7 +791,7 @@ end # def aboutBox Tk.messageBox('icon'=>'info', 'type'=>'ok', 'title'=>'About Widget Demo', - 'message'=>"Ruby/Tk widget demonstration Ver.1.5.0-en\n\n" + + 'message'=>"Ruby/Tk widget demonstration Ver.1.5.2-en\n\n" + "based on demos of Tk8.1 -- 8.5 " + "( Copyright:: " + "(c) 1996-1997 Sun Microsystems, Inc. / " + diff --git a/ext/tk/sample/demos-jp/anilabel.rb b/ext/tk/sample/demos-jp/anilabel.rb index 8cbec50167..97781fbe77 100644 --- a/ext/tk/sample/demos-jp/anilabel.rb +++ b/ext/tk/sample/demos-jp/anilabel.rb @@ -39,7 +39,7 @@ TkFrame.new($anilabel_demo) {|frame| TkButton.new(frame) { text 'コード参照' - command proc{showCode 'label'} + command proc{showCode 'anilabel'} }.pack('side'=>'left', 'expand'=>'yes') }.pack('side'=>'bottom', 'fill'=>'x', 'pady'=>'2m') diff --git a/ext/tk/sample/demos-jp/aniwave.rb b/ext/tk/sample/demos-jp/aniwave.rb new file mode 100644 index 0000000000..81e2d76b30 --- /dev/null +++ b/ext/tk/sample/demos-jp/aniwave.rb @@ -0,0 +1,116 @@ +# +# animated wave demo (called by 'widget') +# +# based on Tcl/Tk8.5a2 widget demos + +# destroy toplevel widget for this demo script +if defined?($aniwave_demo) && $aniwave_demo + $aniwave_demo.destroy + $aniwave_demo = nil +end + +# create toplevel widget +$aniwave_demo = TkToplevel.new {|w| + title("Animated Wave Demonstration") + iconname("aniwave") + positionWindow(w) +} + +# create label +msg = TkLabel.new($aniwave_demo) { + font $font + wraplength '4i' + justify 'left' + text 'このデモでは、ラインアイテムが一つだけ描かれたキャンバスウィジェットが表示されています。アニメーション処理は、そのラインアイテムの座標値を変更することで実現しています。' +} +msg.pack('side'=>'top') + +# create frame +TkFrame.new($aniwave_demo) {|frame| + TkButton.new(frame) { + #text '了解' + text '閉じる' + command proc{ + tmppath = $aniwave_demo + $aniwave_demo = nil + tmppath.destroy + } + }.pack('side'=>'left', 'expand'=>'yes') + + TkButton.new(frame) { + text 'コード参照' + command proc{showCode 'aniwave'} + }.pack('side'=>'left', 'expand'=>'yes') + +}.pack('side'=>'bottom', 'fill'=>'x', 'pady'=>'2m') + +# animated wave +class AnimatedWaveDemo + def initialize(frame, dir=:left) + @direction = dir + + # create canvas widget + @c = TkCanvas.new(frame, :width=>300, :height=>200, + :background=>'black') + @c.pack(:padx=>10, :pady=>10, :expand=>true) + + # Creates a coordinates list of a wave. + @waveCoords = [] + @backupCoords = [] + n = 0 + (-10..300).step(5){|n| @waveCoords << [n, 100]; @backupCoords << [n, 100] } + @waveCoords << [n, 0]; @backupCoords << [n, 0] + @waveCoords << [n+5, 200]; @backupCoords << [n+5, 200] + @coordsLen = @waveCoords.length + + # Create a smoothed line and arrange for its coordinates to be the + # contents of the variable waveCoords. + @line = TkcLine.new(@c, @waveCoords, + :width=>1, :fill=>'green', :smooth=>true) + + # Main animation "loop". + # Theoretically 100 frames-per-second (==10ms between frames) + @timer = TkTimer.new(10){ basicMotion; reverser } + + # Arrange for the animation loop to stop when the canvas is deleted + @c.bindtags_unshift(TkBindTag.new('Destroy'){ @timer.stop }) + end + + # Basic motion handler. Given what direction the wave is travelling + # in, it advances the y coordinates in the coordinate-list one step in + # that direction. + def basicMotion + @backupCoords, @waveCoords = @waveCoords, @backupCoords + (0...@coordsLen).each{|idx| + if @direction == :left + @waveCoords[idx][1] = @backupCoords[(idx+1 == @coordsLen)? 0: idx+1][1] + else + @waveCoords[idx][1] = @backupCoords[(idx == 0)? -1: idx-1][1] + end + } + @line.coords(@waveCoords) + end + + # Oscillation handler. This detects whether to reverse the direction + # of the wave by checking to see if the peak of the wave has moved off + # the screen (whose size we know already.) + def reverser + if @waveCoords[0][1] < 10 + @direction = :right + elsif @waveCoords[-1][1] < 10 + @direction = :left + end + end + + # animation control + def move + @timer.start + end + + def stop + @timer.stop + end +end + +# Start the animation processing +AnimatedWaveDemo.new($aniwave_demo, :left).move diff --git a/ext/tk/sample/demos-jp/goldberg.rb b/ext/tk/sample/demos-jp/goldberg.rb new file mode 100644 index 0000000000..4f4dfc222d --- /dev/null +++ b/ext/tk/sample/demos-jp/goldberg.rb @@ -0,0 +1,1974 @@ +# +# Ruby/Tk Goldverg demo (called by 'widget') +# +# Based on Tcl/Tk8.5a2 widget demos. +# The following is the original comment of TkGoldberg.tcl. +# +#>>##+################################################################# +#>># +#>># TkGoldberg.tcl +#>># by Keith Vetter, March 13, 2003 +#>># +#>># "Man will always find a difficult means to perform a simple task" +#>># Rube Goldberg +#>># +#>># Reproduced here with permission. +#>># +#>>##+################################################################# +#>># +#>># Keith Vetter 2003-03-21: this started out as a simple little program +#>># but was so much fun that it grew and grew. So I apologize about the +#>># size but I just couldn't resist sharing it. +#>># +#>># This is a whizzlet that does a Rube Goldberg type animation, the +#>># design of which comes from an New Years e-card from IncrediMail. +#>># That version had nice sound effects which I eschewed. On the other +#>># hand, that version was in black and white (actually dark blue and +#>># light blue) and this one is fully colorized. +#>># +#>># One thing I learned from this project is that drawing filled complex +#>># objects on a canvas is really hard. More often than not I had to +#>># draw each item twice--once with the desired fill color but no +#>># outline, and once with no fill but with the outline. Another trick +#>># is erasing by drawing with the background color. Having a flood fill +#>># command would have been extremely helpful. +#>># +#>># Two wiki pages were extremely helpful: Drawing rounded rectangles +#>># which I generalized into Drawing rounded polygons, and regular +#>># polygons which allowed me to convert ovals and arcs into polygons +#>># which could then be rotated (see Canvas Rotation). I also wrote +#>># Named Colors to aid in the color selection. +#>># +#>># I could comment on the code, but it's just 26 state machines with +#>># lots of canvas create and move calls. + +if defined?($goldberg_demo) && $goldberg_demo + $goldberg_demo.destroy + $goldberg_demo = nil +end + +# demo toplevel widget +$goldberg_demo = TkToplevel.new {|w| + title("Tk Goldberg (demonstration)") + iconname("goldberg") +# positionWindow(w) +} + +# label +msg = TkLabel.new($goldberg_demo) { + font 'Arial 10' + wraplength '4i' + justify 'left' + text "これは、あなたが自分のアニメーションをいかに入り組んだものにできるかを示すというだけのためのデモです。ボールをクリックすれば物が動き始めます!\n\n\"Man will always find a difficult means to perform a simple task\"\n - Rube Goldberg" +} +msg.pack('side'=>'top') + +# frame +TkFrame.new($goldberg_demo) {|frame| + TkButton.new(frame) { + text '閉じる' + command proc{ + tmppath = $goldberg_demo + $goldberg_demo = nil + tmppath.destroy + } + }.pack('side'=>'left', 'expand'=>'yes') + + TkButton.new(frame) { + text 'コード参照' + command proc{showCode 'goldberg'} + }.pack('side'=>'left', 'expand'=>'yes') + +}.pack('side'=>'bottom', 'fill'=>'x', 'pady'=>'2m') + +######################################### + +class TkGoldberg_Demo + def initialize(parent) + @parent = parent + + @S = {} + @S['title'] = 'Tk Goldberg' + @S['speed'] = TkVariable.new(5) + @S['cnt'] = TkVariable.new(0) + # @S['message'] = TkVariable.new("\\nWelcome\\nto\\nRuby/Tk") + @S['message'] = TkVariable.new("\\n ようこそ!\\nRuby/Tk\\nの\\n世界へ") + @S['pause'] = TkVariable.new + @S['details'] = TkVariable.new(true) + + @S['mode'] = TkVariable.new(:MSTART, :symbol) + # :MSTART, :MGO, :MPAUSE, :MSSTEP, :MBSTEP, :MDONE, :MDEBUG + + # 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 + @speed = [1, 10, 20, 50, 80, 100, 150, 200, 300, 400, 500] + + # colors + @C = {} + @C['fg'] = 'black' + # @C['bg'] = 'gray75' + @C['bg'] = 'cornflowerblue' + + @C['0'] = 'white'; @C['1a'] = 'darkgreen'; @C['1b'] = 'yellow' + @C['2'] = 'red'; @C['3a'] = 'green'; @C['3b'] = 'darkblue' + @C['4'] = @C['fg']; @C['5a'] = 'brown'; @C['5b'] = 'white' + @C['6'] = 'magenta'; @C['7'] = 'green'; @C['8'] = @C['fg'] + @C['9'] = 'blue4'; @C['10a'] = 'white'; @C['10b'] = 'cyan' + @C['11a'] = 'yellow'; @C['11b'] = 'mediumblue'; @C['12'] = 'tan2' + @C['13a'] = 'yellow'; @C['13b'] = 'red'; @C['14'] = 'white' + @C['15a'] = 'green'; @C['15b'] = 'yellow'; @C['16'] = 'gray65' + @C['17'] = '#A65353'; @C['18'] = @C['fg']; @C['19'] = 'gray50' + @C['20'] = 'cyan'; @C['21'] = 'gray65'; @C['22'] = @C['20'] + @C['23a'] = 'blue'; @C['23b'] = 'red'; @C['23c'] = 'yellow' + @C['24a'] = 'red'; @C['24b'] = 'white'; + + @STEP = TkVariable.new_hash + @STEP.default_value_type = :numeric + + @XY = {} + + @XY6 = { + '-1'=>[366, 207], '-2'=>[349, 204], '-3'=>[359, 193], '-4'=>[375, 192], + '-5'=>[340, 190], '-6'=>[349, 177], '-7'=>[366, 177], '-8'=>[380, 176], + '-9'=>[332, 172], '-10'=>[342, 161], '-11'=>[357, 164], + '-12'=>[372, 163], '-13'=>[381, 149], '-14'=>[364, 151], + '-15'=>[349, 146], '-16'=>[333, 148], '0'=>[357, 219], + '1'=>[359, 261], '2'=>[359, 291], '3'=>[359, 318], '4'=>[361, 324], + '5'=>[365, 329], '6'=>[367, 334], '7'=>[367, 340], '8'=>[366, 346], + '9'=>[364, 350], '10'=>[361, 355], '11'=>[359, 370], '12'=>[359, 391], + '13,0'=>[360, 456], '13,1'=>[376, 456], '13,2'=>[346, 456], + '13,3'=>[330, 456], '13,4'=>[353, 444], '13,5'=>[368, 443], + '13,6'=>[339, 442], '13,7'=>[359, 431], '13,8'=>[380, 437], + '13,9'=>[345, 428], '13,10'=>[328, 434], '13,11'=>[373, 424], + '13,12'=>[331, 420], '13,13'=>[360, 417], '13,14'=>[345, 412], + '13,15'=>[376, 410], '13,16'=>[360, 403] + } + + @timer = TkTimer.new(@speed[@S['speed'].numeric]){|timer| + timer.set_interval(go) + } + + do_display + reset + + # Start everything going + @timer.start + end + + def do_display() + @ctrl = TkFrame.new(@parent, :relief=>:ridge, :bd=>2, :padx=>5, :pady=>5) + @screen = TkFrame.new(@parent, :bd=>2, + :relief=>:raised).pack(:side=>:left, :fill=>:both, + :expand=>true) + + @canvas = TkCanvas.new(@parent, :width=>860, :height=>730, + :bg=>@C['bg'], :highlightthickness=>0){ + scrollregion([0, 0, 1000, 1000]) # Kludge to move everything up + yview_moveto(0.05) + }.pack(:in=>@screen, :side=>:top, :fill=>:both, :expand=>true) + + @canvas.bind('3'){ @pause.invoke } + @canvas.bind('Destroy'){ @timer.stop } + + do_ctrl_frame + do_detail_frame + + @show = TkButton.new(@parent, :text=>'>>', :command=>proc{show_ctrl}, + :bg=>@C['bg'], :activebackground=>@C['bg']) + @show.place(:in=>@canvas, :relx=>1, :rely=>0, :anchor=>:ne) + + Tk.update + end + + def do_ctrl_frame + @start = TkButton.new(@parent, :text=>'Start', :bd=>6, + :command=>proc{do_button(0)}) + @start.font(@start['font'].weight('bold')) + font = @start['font'] + + @pause = TkCheckbutton.new(@parent, :text=>'Pause', :font=>font, + :command=>proc{do_button(1)}, :relief=>:raised, + :variable=>@S['pause']) + + @step = TkButton.new(@parent, :text=>'Single Step', :font=>font, + :command=>proc{do_button(2)}) + @bstep = TkButton.new(@parent, :text=>'Big Step', :font=>font, + :command=>proc{do_button(4)}) + @reset = TkButton.new(@parent, :text=>'Reset', :font=>font, + :command=>proc{do_button(3)}) + + @details = TkFrame.new(@parent, :bd=>2, :relief=>:ridge) + @detail = TkCheckbutton.new(@parent, :text=>'Details', :font=>font, + :relief=>:raised, :variable=>@S['details']) + + @msg_entry = TkEntry.new(@parent, :textvariable=>@S['message'], + :justify=>:center) + @speed_scale = TkScale.new(@parent, :orient=>:horizontal, + :from=>1, :to=>10, :font=>font, + :variable=>@S['speed'], :bd=>2, + :relief=>:ridge, :showvalue=>false) + @about = TkButton.new(@parent, :text=>'About', + :command=>proc{about}, :font=>font) + + Tk.grid(@start, :in=>@ctrl, :row=>0, :sticky=>:ew) + @ctrl.grid_rowconfigure(1, :minsize=>10) + Tk.grid(@pause, :in=>@ctrl, :row=>2, :sticky=>:ew) + Tk.grid(@step, :in=>@ctrl, :sticky=>:ew) + Tk.grid(@bstep, :in=>@ctrl, :sticky=>:ew) + Tk.grid(@reset, :in=>@ctrl, :sticky=>:ew) + @ctrl.grid_rowconfigure(10, :minsize=>20) + Tk.grid(@details, :in=>@ctrl, :row=>11, :sticky=>:ew) + Tk.grid(@detail, :in=>@details, :row=>0, :sticky=>:ew) + @ctrl.grid_rowconfigure(50, :weight=>1) + + @S['mode'].trace('w', proc{|*args| active_GUI(*args)}) + @S['details'].trace('w', proc{|*args| active_GUI(*args)}) + @S['speed'].trace('w', proc{|*args| active_GUI(*args)}) + + Tk.grid(@msg_entry, :in=>@ctrl, :row=>98, :sticky=>:ew, :pady=>5) + Tk.grid(@speed_scale, :in=>@ctrl, :row=>99, :sticky=>:ew) + Tk.grid(@about, :in=>@ctrl, :row=>100, :sticky=>:ew) + + @reset.bind('3'){@S['mode'].value = -1} # Debugging + end + + def do_detail_frame + @f_details = TkFrame.new(@details) + + @label = TkLabel.new(@f_details, :textvariable=>@S['cnt'], + :bd=>1, :relief=>:solid, :bg=>'white') + Tk.grid(@label, '-', '-', '-', :sticky=>:ew, :row=>0) + + idx = 1 + loop { + break unless respond_to?("move#{idx}") + l = TkLabel.new(@f_details, :text=>idx, :anchor=>:e, + :width=>2, :bd=>1, :relief=>:solid, :bg=>'white') + @STEP[idx] = 0 + ll = TkLabel.new(@f_details, :textvariable=>@STEP.ref(idx), + :width=>5, :bd=>1, :relief=>:solid, :bg=>'white') + row = (idx + 1)/2 + col = ((idx + 1) & 1) * 2 + Tk.grid(l, :sticky=>:ew, :row=>row, :column=>col) + Tk.grid(ll, :sticky=>:ew, :row=>row, :column=>(col + 1)) + idx += 1 + } + @f_details.grid_columnconfigure(1, :weight=>1) + end + + def show_ctrl + if @ctrl.winfo_mapped? + @ctrl.pack_forget + @show.text('>>') + else + @ctrl.pack(:side=>:right, :fill=>:both, :ipady=>5) + @show.text('<<') + end + end + + def draw_all + reset_step + @canvas.delete(:all) + idx = 0 + loop{ + m = "draw#{idx}" + break unless respond_to?(m) + send(m) + idx += 1 + } + end + + def active_GUI(var1, var2, op) + st = {false=>:disabled, true=>:normal} + + m = @S['mode'].to_sym + @S['pause'].value = (m == :MPAUSE) + @start.state(st[m != :MGO]) + @pause.state(st[m != :MSTART && m != :MDONE]) + @step.state(st[m != :MGO && m != :MDONE]) + @bstep.state(st[m != :MGO && m != :MDONE]) + @reset.state(st[m != :MSTART]) + + if @S['details'].bool + Tk.grid(@f_details, :in=>@details, :row=>2, :sticky=>:ew) + else + Tk.grid_forget(@f_details) + end + @speed_scale.label("Speed: #{@S['speed'].value}") + end + + def start + @S['mode'].value = :MGO + end + + def do_button(what) + case what + when 0 # Start + reset if @S['mode'].to_sym == :MDONE + @S['mode'].value = :MGO + + when 1 # Pause + @S['mode'].value = ((@S['pause'].bool)? :MPAUSE: :MGO) + + when 2 # Step + @S['mode'].value = :MSSTEP + + when 3 # Reset + reset + + when 4 # Big step + @S['mode'].value = :MBSTEP + end + end + + def go(who = nil) + now = Tk::Clock.clicks(:miliseconds) + if who # Start here for debugging + @S['active'] = [who] + @S['mode'].value = :MGO + end + return if @S['mode'].to_sym == :MDEBUG # Debugging + # If not paused, do the next move + n = next_step if @S['mode'].to_sym != :MPAUSE + @S['mode'].value = :MPAUSE if @S['mode'].to_sym == :MSSTEP # Single step + @S['mode'].value = :MSSTEP if @S['mode'].to_sym == :MBSTEP && n # big step + elapsed = Tk::Clock.clicks(:miliseconds) - now + delay = @speed[@S['speed'].to_i] - elapsed + delay = 1 if delay <= 0 + return delay + end + + def next_step + retval = false # Return value + + if @S['mode'].to_sym != :MSTART && @S['mode'].to_sym != :MDONE + @S['cnt'].numeric += 1 + end + alive = [] + @S['active'].each{|who| + who = who.to_i + n = send("move#{who}") + if (n & 1).nonzero? # This guy still alive + alive << who + end + if (n & 2).nonzero? # Next guy is active + alive << (who + 1) + retval = true + end + if (n & 4).nonzero? # End of puzzle flag + @S['mode'].value = :MDONE # Done mode + @S['active'] = [] # No more animation + return true + end + } + @S['active'] = alive + return retval + end + + def about + msg = "Ruby/Tk Version ::\nby Hidetoshi NAGAI (nagai@ai.kyutech.ac.jp)\n\n" + msg += "Original Version ::\n" + msg += "#{@S['title']}\nby Keith Vetter, March 2003\n(Reproduced by kind permission of the author)\n\n" + msg += "Man will always find a difficult means to perform a simple task" + msg += "\nRube Goldberg" + Tk.messageBox(:message=>msg, :title=>'About') + end + + ################################################################ + # + # All the drawing and moving routines + # + + # START HERE! banner + def draw0 + color = @C['0'] + TkcText.new(@canvas, + # [579, 119], :text=>'START HERE!', + [558, 119], :text=>'ここからスタート!', + :fill=>color, :anchor=>:w, + :tag=>'I0', :font=>['Times Roman', 12, :italic, :bold]) + TkcLine.new(@canvas, [719, 119, 763, 119], :tag=>'I0', :fill=>color, + :width=>5, :arrow=>:last, :arrowshape=>[18, 18, 5]) + @canvas.itembind('I0', '1'){ start } + end + + def move0(step = nil) + step = get_step(0, step) + + if @S['mode'].to_sym != :MSTART # Start the ball rolling + move_abs('I0', [-100, -100]) # Hide the banner + return 2 + end + + pos = [ + [673, 119], [678, 119], [683, 119], [688, 119], + [693, 119], [688, 119], [683, 119], [678, 119] + ] + step = step % pos.length + move_abs('I0', pos[step]) + return 1 + end + + # Dropping ball + def draw1 + color = @C['1a'] + color2 = @C['1b'] + TkcPolygon.new(@canvas, + [ 844, 133, 800, 133, 800, 346, 820, 346, + 820, 168, 844, 168, 844, 133 ], + :width=>3, :fill=>color, :outline=>'') + TkcPolygon.new(@canvas, + [ 771, 133, 685, 133, 685, 168, 751, 168, + 751, 346, 771, 346, 771, 133 ], + :width=>3, :fill=>color, :outline=>'') + TkcOval.new(@canvas, box(812, 122, 9), + :tag=>'I1', :fill=>color2, :outline=>'') + + @canvas.itembind('I1', '1'){ start } + end + + def move1(step = nil) + step = get_step(1, step) + pos = [ + [807, 122], [802, 122], [797, 123], [793, 124], [789, 129], [785, 153], + [785, 203], [785, 278, :x], [785, 367], [810, 392], [816, 438], + [821, 503], [824, 585, :y], [838, 587], [848, 593], [857, 601], + [-100, -100] + ] + return 0 if step >= pos.length + where = pos[step] + move_abs('I1', where) + move15a if where[2] == :y + return 3 if where[2] == :x + return 1 + end + + # Lighting the match + def draw2 + color = @C['2'] + + # Fulcrum + TkcPolygon.new(@canvas, [750, 369, 740, 392, 760, 392], + :fill=>@C['fg'], :outline=>@C['fg']) + + # Strike box + TkcRectangle.new(@canvas, [628, 335, 660, 383], + :fill=>'', :outline=>@C['fg']) + (0..2).each{|y| + yy = 335 + y*16 + TkcBitmap.new(@canvas, [628, yy], :bitmap=>'gray25', + :anchor=>:nw, :foreground=>@C['fg']) + TkcBitmap.new(@canvas, [644, yy], :bitmap=>'gray25', + :anchor=>:nw, :foreground=>@C['fg']) + } + + # Lever + TkcLine.new(@canvas, [702, 366, 798, 366], + :fill=>@C['fg'], :width=>6, :tag=>'I2_0') + + # R strap + TkcLine.new(@canvas, [712, 363, 712, 355], + :fill=>@C['fg'], :width=>3, :tag=>'I2_1') + + # L strap + TkcLine.new(@canvas, [705, 363, 705, 355], + :fill=>@C['fg'], :width=>3, :tag=>'I2_2') + + # Match stick + TkcLine.new(@canvas, [679, 356, 679, 360, 717, 360, 717, 356, 679, 356], + :fill=>@C['fg'], :width=>3, :tag=>'I2_3') + + # Match head + TkcPolygon.new(@canvas, + [ 671, 352, 677.4, 353.9, 680, 358.5, 677.4, 363.1, + 671, 365, 664.6, 363.1, 662, 358.5, 664.6, 353.9 ], + :fill=>color, :outline=>color, :tag=>'I2_4') + end + + def move2(step = nil) + step = get_step(2, step) + + stages = [0, 0, 1, 2, 0, 2, 1, 0, 1, 2, 0, 2, 1] + xy = [] + xy[0] = [ + 686, 333, 692, 323, 682, 316, 674, 309, 671, 295, 668, 307, + 662, 318, 662, 328, 671, 336 + ] + xy[1] = [ + 687, 331, 698, 322, 703, 295, 680, 320, 668, 297, 663, 311, + 661, 327, 671, 335 + ] + xy[2] = [ + 686, 331, 704, 322, 688, 300, 678, 283, 678, 283, 674, 298, + 666, 309, 660, 324, 672, 336 + ] + + if step >= stages.length + @canvas.delete('I2') + return 0 + end + + if step == 0 # Rotate the match + beta = 20 + + ox, oy = anchor('I2_0', :s) # Where to pivot + + i = 0 + until @canvas.find_withtag("I2_#{i}").empty? + rotate_item("I2_#{i}", ox, oy, beta) + i += 1 + end + + # For the flame + TkcPolygon.new(@canvas, [], :tag=>'I2', :smooth=>true, :fill=>@C['2']) + + return 1 + end + @canvas.coords('I2', xy[stages[step]]) + return ((step == 7)? 3: 1) + end + + # Weight and pulleys + def draw3 + color = @C['3a'] + color2 = @C['3b'] + + xy = [ [602, 296], [577, 174], [518, 174] ] + xy.each{|x, y| # 3 Pulleys + TkcOval.new(@canvas, box(x, y, 13), + :fill=>color, :outline=>@C['fg'], :width=>3) + TkcOval.new(@canvas, box(x, y, 2), :fill=>@C['fg'], :outline=>@C['fg']) + } + + # Wall to flame + TkcLine.new(@canvas, [750, 309, 670, 309], :tag=>'I3_s', + :width=>3, :fill=>@C['fg'], :smooth=>true) + + # Flame to pulley 1 + TkcLine.new(@canvas, [670, 309, 650, 309], :tag=>'I3_0', + :width=>3, :fill=>@C['fg'], :smooth=>true) + TkcLine.new(@canvas, [650, 309, 600, 309], :tag=>'I3_1', + :width=>3, :fill=>@C['fg'], :smooth=>true) + + # Pulley 1 half way to 2 + TkcLine.new(@canvas, [589, 296, 589, 235], :tag=>'I3_2', + :width=>3, :fill=>@C['fg']) + + # Pulley 1 other half to 2 + TkcLine.new(@canvas, [589, 235, 589, 174], :width=>3, :fill=>@C['fg']) + + # Across the top + TkcLine.new(@canvas, [577, 161, 518, 161], :width=>3, :fill=>@C['fg']) + + # Down to weight + TkcLine.new(@canvas, [505, 174, 505, 205], :tag=>'I3_w', + :width=>3, :fill=>@C['fg']) + + # Draw the weight as 2 circles, two rectangles and 1 rounded rectangle + x1, y1, x2, y2 = [515, 207, 495, 207] + TkcOval.new(@canvas, box(x1, y1, 6), + :tag=>'I3_', :fill=>color2, :outline=>color2) + TkcOval.new(@canvas, box(x2, y2, 6), + :tag=>'I3_', :fill=>color2, :outline=>color2) + TkcRectangle.new(@canvas, x1, y1 - 6, x2, y2 + 6, + :tag=>'I3_', :fill=>color2, :outline=>color2) + + TkcPolygon.new(@canvas, round_rect([492, 220, 518, 263], 15), + :smooth=>true, :tag=>'I3_', :fill=>color2, :outline=>color2) + + TkcLine.new(@canvas, [500, 217, 511, 217], + :tag=>'I3_', :fill=>color2, :width=>10) + + # Bottom weight target + TkcLine.new(@canvas, [502, 393, 522, 393, 522, 465], + :tag=>'I3__', :fill=>@C['fg'], :joinstyle=>:miter, :width=>10) + end + + def move3(step = nil) + step = get_step(3, step) + + pos = [ [505, 247], [505, 297], [505, 386.5], [505, 386.5] ] + rope = [] + rope[0] = [750, 309, 729, 301, 711, 324, 690, 300] + rope[1] = [750, 309, 737, 292, 736, 335, 717, 315, 712, 320] + rope[2] = [750, 309, 737, 309, 740, 343, 736, 351, 725, 340] + rope[3] = [750, 309, 738, 321, 746, 345, 742, 356] + + return 0 if step >= pos.length + + @canvas.delete("I3_#{step}") # Delete part of the rope + move_abs('I3_', pos[step]) # Move weight down + @canvas.coords('I3_s', rope[step]) # Flapping rope end + @canvas.coords('I3_w', [505, 174].concat(pos[step])) + if step == 2 + @canvas.move('I3__', 0, 30) + return 2 + end + return 1 + end + + # Cage and door + def draw4 + color = @C['4'] + x0, y0, x1, y1 = [527, 356, 611, 464] + + # Horizontal bars + y0.step(y1, 12){|y| + TkcLine.new(@canvas, [x0, y, x1, y], :fill=>color, :width=>1) + } + + # Vertical bars + x0.step(x1, 12){|x| + TkcLine.new(@canvas, [x, y0, x, y1], :fill=>color, :width=>1) + } + + # Swing gate + TkcLine.new(@canvas, [518, 464, 518, 428], + :tag=>'I4', :fill=>color, :width=>1) + end + + def move4(step = nil) + step = get_step(4, step) + + angles = [-10, -20, -30, -30] + return 0 if step >= angles.length + + rotate_item('I4', 518, 464, angles[step]) + @canvas.raise('I4') + + return((step == 3)? 3: 1) + end + + # Mouse + def draw5 + color = @C['5a'] + color2 = @C['5b'] + + xy = [377, 248, 410, 248, 410, 465, 518, 465] # Mouse course + xy.concat [518, 428, 451, 428, 451, 212, 377, 212] + + TkcPolygon.new(@canvas, xy, :fill=>color2, :outline=>@C['fg'], :width=>3) + + xy = [ + 534.5, 445.5, 541, 440, 552, 436, 560, 436, 569, 440, 574, 446, + 575, 452, 574, 454, 566, 456, 554, 456, 545, 456, 537, 454, 530, 452 + ] + TkcPolygon.new(@canvas, xy, :tag=>['I5', 'I5_0'], :fill=>color) + + TkcLine.new(@canvas, [573, 452, 592, 458, 601, 460, 613, 456], # Tail + :tag=>['I5', 'I5_1'], :fill=>color, :smooth=>true, :width=>3) + + xy = box(540, 446, 2) # Eye + xy = [540, 444, 541, 445, 541, 447, 540, 448, 538, 447, 538, 445] + TkcPolygon.new(@canvas, xy, :tag=>['I5', 'I5_2'], :fill=>@C['bg'], + :outline=>'', :smooth=>true) + + xy = [538, 454, 535, 461] # Front leg + TkcLine.new(@canvas, xy, :tag=>['I5', 'I5_3'], :fill=>color, :width=>2) + + xy = [566, 455, 569, 462] # Back leg + TkcLine.new(@canvas, xy, :tag=>['I5', 'I5_4'], :fill=>color, :width=>2) + + xy = [544, 455, 545, 460] # 2nd front leg + TkcLine.new(@canvas, xy, :tag=>['I5', 'I5_5'], :fill=>color, :width=>2) + + xy = [560, 455, 558, 460] # 2nd back leg + TkcLine.new(@canvas, xy, :tag=>['I5', 'I5_6'], :fill=>color, :width=>2) + end + + def move5(step = nil) + step = get_step(5, step) + + pos = [ + [553, 452], [533, 452], [513, 452], [493, 452], [473, 452], + [463, 442, 30], [445.5, 441.5, 30], [425.5, 434.5, 30], [422, 414], + [422, 394], [422, 374], [422, 354], [422, 334], [422, 314], [422, 294], + [422, 274, -30], [422, 260.5, -30, :x], [422.5, 248.5, -28], [425, 237] + ] + + return 0 if step >= pos.length + + x, y, beta, nxt = pos[step] + move_abs('I5', [x, y]) + if beta + ox, oy = centroid('I5_0') + (0..6).each{|id| rotate_item("I5_#{id}", ox, oy, beta) } + end + return 3 if nxt == :x + return 1 + end + + # Dropping gumballs + def draw6 + color = @C['6'] + xy = [324, 130, 391, 204] # Ball holder + xy = round_rect(xy, 10) + TkcPolygon.new(@canvas, xy, :smooth=>true, + :outline=>@C['fg'], :width=>3, :fill=>color) + xy = [339, 204, 376, 253] # Below the ball holder + TkcRectangle.new(@canvas, xy, :outline=>@C['fg'], :width=>3, + :fill=>color, :tag=>'I6c') + xy = box(346, 339, 28) + TkcOval.new(@canvas, xy, :fill=>color, :outline=>'') # Roter + TkcArc.new(@canvas, xy, :outline=>@C['fg'], :width=>2, :style=>:arc, + :start=>80, :extent=>205) + TkcArc.new(@canvas, xy, :outline=>@C['fg'], :width=>2, :style=>:arc, + :start=>-41, :extent=>85) + + xy = box(346, 339, 15) # Center of rotor + TkcOval.new(@canvas, xy, :outline=>@C['fg'], :fill=>@C['fg'], :tag=>'I6m') + xy = [352, 312, 352, 254, 368, 254, 368, 322] # Top drop to rotor + TkcPolygon.new(@canvas, xy, :fill=>color, :outline=>'') + TkcLine.new(@canvas, xy, :fill=>@C['fg'], :width=>2) + + xy = [353, 240, 367, 300] # Poke bottom hole + TkcRectangle.new(@canvas, xy, :fill=>color, :outline=>'') + xy = [341, 190, 375, 210] # Poke another hole + TkcRectangle.new(@canvas, xy, :fill=>color, :outline=>'') + + xy = [ + 368, 356, 368, 403, 389, 403, 389, 464, 320, 464, 320, 403, + 352, 403, 352, 366 + ] + TkcPolygon.new(@canvas, xy, :fill=>color, :outline=>'', + :width=>2) # Below rotor + TkcLine.new(@canvas, xy, :fill=>@C['fg'], :width=>2) + xy = box(275, 342, 7) # On/off rotor + TkcOval.new(@canvas, xy, :outline=>@C['fg'], :fill=>@C['fg']) + xy = [276, 334, 342, 325] # Fan belt top + TkcLine.new(@canvas, xy, :fill=>@C['fg'], :width=>3) + xy = [276, 349, 342, 353] # Fan belt bottom + TkcLine.new(@canvas, xy, :fill=>@C['fg'], :width=>3) + + xy = [337, 212, 337, 247] # What the mouse pushes + TkcLine.new(@canvas, xy, :fill=>@C['fg'], :width=>3, :tag=>'I6_') + xy = [392, 212, 392, 247] + TkcLine.new(@canvas, xy, :fill=>@C['fg'], :width=>3, :tag=>'I6_') + xy = [337, 230, 392, 230] + TkcLine.new(@canvas, xy, :fill=>@C['fg'], :width=>7, :tag=>'I6_') + + who = -1 # All the balls + colors = %w(red cyan orange green blue darkblue) + colors *= 3 + + (0..16).each{|i| + loc = -i + color = colors[i] + x, y = @XY6["#{loc}"] + TkcOval.new(@canvas, box(x, y, 5), + :fill=>color, :outline=>color, :tag=>"I6_b#{i}") + } + draw6a(12) # The wheel + end + + def draw6a(beta) + @canvas.delete('I6_0') + ox, oy = [346, 339] + (0..3).each{|i| + b = beta + i * 45 + x, y = rotate_c(28, 0, 0, 0, b) + xy = [ox + x, oy + y, ox - x, oy - y] + TkcLine.new(@canvas, xy, :tag=>'I6_0', :fill=>@C['fg'], :width=>2) + } + end + + def move6(step = nil) + step = get_step(6, step) + + return 0 if step > 62 + + if step < 2 # Open gate for balls to drop + @canvas.move('I6_', -7, 0) + if step == 1 # Poke a hole + xy = [348, 226, 365, 240] + TkcRectangle.new(@canvas, xy, :fill=>@canvas.itemcget('I6c', :fill), + :outline=>'') + end + return 1 + end + + s = step - 1 # Do the gumball drop dance + (0..(((s - 1)/3).to_i)).each{|i| + tag = "I6_b#{i}" + break if @canvas.find_withtag(tag).empty? + loc = s - 3*i + + if @XY6["#{loc},#{i}"] + move_abs(tag, @XY6["#{loc},#{i}"]) + elsif @XY6["#{loc}"] + move_abs(tag, @XY6["#{loc}"]) + end + } + if s % 3 == 1 + first = (s + 2)/3 + i = first + loop { + tag = "I6_b#{i}" + break if @canvas.find_withtag(tag).empty? + loc = first - i + move_abs(tag, @XY6["#{loc}"]) + i += 1 + } + end + if s >= 3 # Rotate the motor + idx = s % 3 + draw6a(12 + s * 15) + end + return((s == 3)? 3 : 1) + end + + # On/off switch + def draw7 + color = @C['7'] + xy = [198, 306, 277, 374] # Box + TkcRectangle.new(@canvas, xy, :outline=>@C['fg'], :width=>2, + :fill=>color, :tag=>'I7z') + @canvas.lower('I7z') + xy = [275, 343, 230, 349] + TkcLine.new(@canvas, xy, :tag=>'I7', :fill=>@C['fg'], :arrow=>:last, + :arrowshape=>[23, 23, 8], :width=>6) + xy = [225, 324] # On button + x, y = xy + TkcOval.new(@canvas, box(x, y, 3), :fill=>@C['fg'], :outline=>@C['fg']) + xy = [218, 323] # On text + font = ['Times Roman', 8] + TkcText.new(@canvas, xy, :text=>'on', :anchor=>:e, + :fill=>@C['fg'], :font=>font) + xy = [225, 350] # Off button + x, y = xy + TkcOval.new(@canvas, box(x, y, 3), :fill=>@C['fg'], :outline=>@C['fg']) + xy = [218, 349] # Off text + TkcText.new(@canvas, xy, :text=>'off', :anchor=>:e, + :fill=>@C['fg'], :font=>font) + end + + def move7(step = nil) + step = get_step(7, step) + + numsteps = 30 + return 0 if step > numsteps + beta = 30.0 / numsteps + rotate_item('I7', 275, 343, beta) + + return((step == numsteps)? 3: 1) + end + + # Electricity to the fan + def draw8 + sine([271, 248, 271, 306], 5, 8, :tag=>'I8_s', :fill=>@C['8'], :width=>3) + end + + def move8(step = nil) + step = get_step(8, step) + + return 0 if step > 3 + if step == 0 + sparkle(anchor('I8_s', :s), 'I8') + return 1 + elsif step == 1 + move_abs('I8', anchor('I8_s', :c)) + elsif step == 2 + move_abs('I8', anchor('I8_s', :n)) + else + @canvas.delete('I8') + end + return((step == 2)? 3: 1) + end + + # Fan + def draw9 + color = @C['9'] + xy = [266, 194, 310, 220] + TkcOval.new(@canvas, xy, :outline=>color, :fill=>color) + xy = [280, 209, 296, 248] + TkcOval.new(@canvas, xy, :outline=>color, :fill=>color) + xy = [ + 288, 249, 252, 249, 260, 240, 280, 234, + 296, 234, 316, 240, 324, 249, 288, 249 + ] + TkcPolygon.new(@canvas, xy, :fill=>color, :smooth=>true) + + xy = [248, 205, 265, 214, 264, 205, 265, 196] # Spinner + TkcPolygon.new(@canvas, xy, :fill=>color) + + xy = [255, 206, 265, 234] # Fan blades + TkcOval.new(@canvas, xy, :fill=>'', :outline=>@C['fg'], + :width=>3, :tag=>'I9_0') + xy = [255, 176, 265, 204] + TkcOval.new(@canvas, xy, :fill=>'', :outline=>@C['fg'], + :width=>3, :tag=>'I9_0') + xy = [255, 206, 265, 220] + TkcOval.new(@canvas, xy, :fill=>'', :outline=>@C['fg'], + :width=>1, :tag=>'I9_1') + xy = [255, 190, 265, 204] + TkcOval.new(@canvas, xy, :fill=>'', :outline=>@C['fg'], + :width=>1, :tag=>'I9_1') + end + + def move9(step = nil) + step = get_step(9, step) + + if (step & 1).nonzero? + @canvas.itemconfigure('I9_0', :width=>4) + @canvas.itemconfigure('I9_1', :width=>1) + @canvas.lower('I9_1', 'I9_0') + else + @canvas.itemconfigure('I9_0', :width=>1) + @canvas.itemconfigure('I9_1', :width=>4) + @canvas.lower('I9_0', 'I9_1') + end + return 3 if step == 0 + return 1 + end + + # Boat + def draw10 + color = @C['10a'] + color2 = @C['10b'] + xy = [191, 230, 233, 230, 233, 178, 191, 178] # Sail + TkcPolygon.new(@canvas, xy, :fill=>color, :width=>3, :outline=>@C['fg'], + :tag=>'I10') + xy = box(209, 204, 31) # Front + TkcArc.new(@canvas, xy, :outline=>'', :fill=>color, :style=>:pie, + :start=>120, :extent=>120, :tag=>'I10') + TkcArc.new(@canvas, xy, :outline=>@C['fg'], :width=>3, :style=>:arc, + :start=>120, :extent=>120, :tag=>'I10') + xy = box(249, 204, 31) # Back + TkcArc.new(@canvas, xy, :outline=>'', :fill=>@C['bg'], :width=>3, + :style=>:pie, :start=>120, :extent=>120, :tag=>'I10') + TkcArc.new(@canvas, xy, :outline=>@C['fg'], :width=>3, :style=>:arc, + :start=>120, :extent=>120, :tag=>'I10') + + xy = [200, 171, 200, 249] # Mast + TkcLine.new(@canvas, xy, :fill=>@C['fg'], :width=>3, :tag=>'I10') + xy = [159, 234, 182, 234] # Bow sprit + TkcLine.new(@canvas, xy, :fill=>@C['fg'], :width=>3, :tag=>'I10') + xy = [180, 234, 180, 251, 220, 251] # Hull + TkcLine.new(@canvas, xy, :fill=>@C['fg'], :width=>6, :tag=>'I10') + + xy = [92, 255, 221, 255] # Waves + sine(xy, 2, 25, :fill=>color2, :width=>1, :tag=>'I10w') + + xy = @canvas.coords('I10w')[4..-5] # Water + xy.concat([222, 266, 222, 277, 99, 277]) + TkcPolygon.new(@canvas, xy, :fill=>color2, :outline=>color2) + xy = [222, 266, 222, 277, 97, 277, 97, 266] # Water bottom + TkcLine.new(@canvas, xy, :fill=>@C['fg'], :width=>3) + + xy = box(239, 262, 17) + TkcArc.new(@canvas, xy, :outline=>@C['fg'], :width=>3, :style=>:arc, + :start=>95, :extent=>103) + xy = box(76, 266, 21) + TkcArc.new(@canvas, xy, :outline=>@C['fg'], :width=>3, :style=>:arc, + :extent=>190) + end + + def move10(step = nil) + step = get_step(10, step) + + pos = [ + [195, 212], [193, 212], [190, 212], [186, 212], [181, 212], [176, 212], + [171, 212], [166, 212], [161, 212], [156, 212], [151, 212], [147, 212], + [142, 212], [137, 212], [132, 212, :x], [127, 212], [121, 212], + [116, 212], [111, 212] + ] + + return 0 if step >= pos.length + + where = pos[step] + move_abs('I10', where) + + return 3 if where[2] == :x + return 1 + end + + # 2nd ball drop + def draw11 + color = @C['11a'] + color2 = @C['11b'] + xy = [23, 264, 55, 591] # Color the down tube + TkcRectangle.new(@canvas, xy, :fill=>color, :outline=>'') + xy = box(71, 460, 48) # Color the outer loop + TkcOval.new(@canvas, xy, :fill=>color, :outline=>'') + + xy = [55, 264, 55, 458] # Top right side + TkcLine.new(@canvas, xy, :fill=>@C['fg'], :width=>3) + xy = [55, 504, 55, 591] # Bottom right side + TkcLine.new(@canvas, xy, :fill=>@C['fg'], :width=>3) + xy = box(71, 460, 48) # Outer loop + TkcArc.new(@canvas, xy, :outline=>@C['fg'], :width=>3, :style=>:arc, + :start=>110, :extent=>-290, :tag=>'I11i') + xy = box(71, 460, 16) # Inner loop + TkcOval.new(@canvas, xy, :outline=>@C['fg'], :fill=>'', + :width=>3, :tag=>'I11i') + TkcOval.new(@canvas, xy, :outline=>@C['fg'], :fill=>@C['bg'], :width=>3) + + xy = [23, 264, 23, 591] # Left side + TkcLine.new(@canvas, xy, :fill=>@C['fg'], :width=>3) + xy = box(1, 266, 23) # Top left curve + TkcArc.new(@canvas, xy, :outline=>@C['fg'], :width=>3, + :style=>:arc, :extent=>90) + + xy = box(75, 235, 9) # The ball + TkcOval.new(@canvas, xy, :fill=>color2, :outline=>'', + :width=>3, :tag=>'I11') + end + + def move11(step = nil) + step = get_step(11, step) + + pos = [ + [75, 235], [70, 235], [65, 237], [56, 240], [46, 247], [38, 266], + [38, 296], [38, 333], [38, 399], [38, 475], [74, 496], [105, 472], + [100, 437], [65, 423], [-100, -100], [38, 505], [38, 527, :x], [38, 591] + ] + + return 0 if step >= pos.length + where = pos[step] + move_abs('I11', where) + return 3 if where[2] == :x + return 1 + end + + # Hand + def draw12 + xy = [ + 20, 637, 20, 617, 20, 610, 20, 590, 40, 590, 40, 590, + 60, 590, 60, 610, 60, 610 + ] + xy.concat([60, 610, 65, 620, 60, 631]) # Thumb + xy.concat([60, 631, 60, 637, 60, 662, 60, 669, 52, 669, + 56, 669, 50, 669, 50, 662, 50, 637]) + + y0 = 637 # Bumps for fingers + y1 = 645 + 50.step(21, -10){|x| + x1 = x - 5 + x2 = x - 10 + xy << x << y0 << x1 << y1 << x2 << y0 + } + TkcPolygon.new(@canvas, xy, :fill=>@C['12'], :outline=>@C['fg'], + :smooth=>true, :tag=>'I12', :width=>3) + end + + def move12(step = nil) + step = get_step(12, step) + + pos = [[42.5, 641, :x]] + return 0 if step >= pos.length + where = pos[step] + move_abs('I12', where) + return 3 if where[2] == :x + return 1 + end + + # Fax + def draw13 + color = @C['13a'] + xy = [86, 663, 149, 663, 149, 704, 50, 704, 50, 681, 64, 681, 86, 671] + xy2 = [ + 784, 663, 721, 663, 721, 704, 820, 704, 820, 681, 806, 681, 784, 671 + ] + radii = [2, 9, 9, 8, 5, 5, 2] + + round_poly(@canvas, xy, radii, :width=>3, + :outline=>@C['fg'], :fill=>color) + round_poly(@canvas, xy2, radii, :width=>3, + :outline=>@C['fg'], :fill=>color) + + xy = [56, 677] + x, y = xy + TkcRectangle.new(@canvas, box(x, y, 4), :fill=>'', :outline=>@C['fg'], + :width=>3, :tag=>'I13') + xy = [809, 677] + x, y = xy + TkcRectangle.new(@canvas, box(x, y, 4), :fill=>'', :outline=>@C['fg'], + :width=>3, :tag=>'I13R') + + xy = [112, 687] # Label + TkcText.new(@canvas, xy, :text=>'FAX', :fill=>@C['fg'], + :font=>['Times Roman', 12, :bold]) + xy = [762, 687] + TkcText.new(@canvas, xy, :text=>'FAX', :fill=>@C['fg'], + :font=>['Times Roman', 12, :bold]) + + xy = [138, 663, 148, 636, 178, 636] # Paper guide + TkcLine.new(@canvas, xy, :smooth=>true, :fill=>@C['fg'], :width=>3) + xy = [732, 663, 722, 636, 692, 636] + TkcLine.new(@canvas, xy, :smooth=>true, :fill=>@C['fg'], :width=>3) + + sine([149, 688, 720, 688], 5, 15, + :tag=>'I13_s', :fill=>@C['fg'], :width=>3) + end + + def move13(step = nil) + step = get_step(13, step) + + numsteps = 7 + + if step == numsteps + 2 + move_abs('I13_star', [-100, -100]) + @canvas.itemconfigure('I13R', :fill=>@C['13b'], :width=>2) + return 2 + end + if step == 0 # Button down + @canvas.delete('I13') + sparkle([-100, -100], 'I13_star') # Create off screen + return 1 + end + x0, y0 = anchor('I13_s', :w) + x1, y1 = anchor('I13_s', :e) + x = x0 + (x1 - x0) * (step - 1) / numsteps.to_f + move_abs('I13_star', [x, y0]) + return 1 + end + + # Paper in fax + def draw14 + color = @C['14'] + xy = [102, 661, 113, 632, 130, 618] # Left paper edge + TkcLine.new(@canvas, xy, :smooth=>true, :fill=>color, + :width=>3, :tag=>'I14L_0') + xy = [148, 629, 125, 640, 124, 662] # Right paper edge + TkcLine.new(@canvas, xy, :smooth=>true, :fill=>color, + :width=>3, :tag=>'I14L_1') + draw14a('L') + + xy = [ + 768.0, 662.5, 767.991316225, 662.433786215, 767.926187912, 662.396880171 + ] + TkcLine.new(@canvas, xy, :smooth=>true, :fill=>color, + :width=>3, :tag=>'I14R_0') + @canvas.lower('I14R_0') + # NB. these numbers are VERY sensitive, you must start with final size + # and shrink down to get the values + xy = [ + 745.947897349, 662.428358855, 745.997829056, 662.452239237, 746.0, 662.5 + ] + TkcLine.new(@canvas, xy, :smooth=>true, :fill=>color, + :width=>3, :tag=>'I14R_1') + @canvas.lower('I14R_1') + end + + def draw14a(side) + color = @C['14'] + xy = @canvas.coords("I14#{side}_0") + xy2 = @canvas.coords("I14#{side}_1") + x0, y0, x1, y1, x2, y2 = xy + x3, y3, x4, y4, x5, y5 = xy2 + + zz = [ + x0, y0, x0, y0, xy, x2, y2, x2, y2, + x3, y3, x3, y3, xy2, x5, y5, x5, y5 + ].flatten + @canvas.delete("I14#{side}") + TkcPolygon.new(@canvas, zz, :tag=>"I14#{side}", :smooth=>true, + :fill=>color, :outline=>color, :width=>3) + @canvas.lower("I14#{side}") + end + + def move14(step = nil) + step = get_step(14, step) + + # Paper going down + sc = 0.9 - 0.05*step + if sc < 0.3 + @canvas.delete('I14L') + return 0 + end + + ox, oy = @canvas.coords('I14L_0') + @canvas.scale('I14L_0', ox, oy, sc, sc) + ox, oy = @canvas.coords('I14L_1')[-2..-1] + @canvas.scale('I14L_1', ox, oy, sc, sc) + draw14a('L') + + # Paper going up + sc = 0.35 + 0.05*step + sc = 1/sc + + ox, oy = @canvas.coords('I14R_0') + @canvas.scale('I14R_0', ox, oy, sc, sc) + ox, oy = @canvas.coords('I14R_1')[-2..-1] + @canvas.scale('I14R_1', ox, oy, sc, sc) + draw14a('R') + + return((step == 10)? 3: 1) + end + + # Light beam + def draw15 + color = @C['15a'] + xy = [824, 599, 824, 585, 820, 585, 829, 585] + TkcLine.new(@canvas, xy, :fill=>@C['fg'], :width=>3, :tag=>'I15a') + xy = [789, 599, 836, 643] + TkcRectangle.new(@canvas, xy, :fill=>color, :outline=>@C['fg'], :width=>3) + xy = [778, 610, 788, 632] + TkcRectangle.new(@canvas, xy, :fill=>color, :outline=>@C['fg'], :width=>3) + xy = [766, 617, 776, 625] + TkcRectangle.new(@canvas, xy, :fill=>color, :outline=>@C['fg'], :width=>3) + + xy = [633, 600, 681, 640] + TkcRectangle.new(@canvas, xy, :fill=>color, :outline=>@C['fg'], :width=>3) + xy = [635, 567, 657, 599] + TkcRectangle.new(@canvas, xy, :fill=>color, :outline=>@C['fg'], :width=>2) + xy = [765, 557, 784, 583] + TkcRectangle.new(@canvas, xy, :fill=>color, :outline=>@C['fg'], :width=>2) + + sine([658, 580, 765, 580], 3, 15, + :tag=>'I15_s', :fill=>@C['fg'], :width=>3) + end + + def move15a + color = @C['15b'] + @canvas.scale('I15a', 824, 599, 1, 0.3) # Button down + xy = [765, 621, 681, 621] + TkcLine.new(@canvas, xy, :dash=>'-', :width=>3, :fill=>color, :tag=>'I15') + end + + def move15(step = nil) + step = get_step(15, step) + + numsteps = 6 + + if step == numsteps + 2 + move_abs('I15_star', [-100, -100]) + return 2 + end + if step == 0 # Break the light beam + sparkle([-100, -100], 'I15_star') + xy = [765, 621, 745, 621] + @canvas.coords('I15', xy) + return 1 + end + x0, y0 = anchor('I15_s', :w) + x1, y1 = anchor('I15_s', :e) + x = x0 + (x1 - x0) * (step - 1) / numsteps.to_f + move_abs('I15_star', [x, y0]) + return 1 + end + + # Bell + def draw16 + color = @C['16'] + xy = [722, 485, 791, 556] + TkcRectangle.new(@canvas, xy, :fill=>'', :outline=>@C['fg'], :width=>3) + xy = box(752, 515, 25) # Bell + TkcOval.new(@canvas, xy, :fill=>color, :outline=>'black', + :tag=>'I16b', :width=>2) + xy = box(752, 515, 5) # Bell button + TkcOval.new(@canvas, xy, :fill=>'black', :outline=>'black', :tag=>'I16b') + + xy = [784, 523, 764, 549] # Clapper + TkcLine.new(@canvas, xy, :width=>3, :tag=>'I16c', :fill=>@C['fg']) + xy = box(784, 523, 4) + TkcOval.new(@canvas, xy, :fill=>@C['fg'], :outline=>@C['fg'], :tag=>'I16d') + end + + def move16(step = nil) + step = get_step(16, step) + + # Note: we never stop + ox, oy = [760, 553] + if (step & 1).nonzero? + beta = 12 + @canvas.move('I16b', 3, 0) + else + beta = -12 + @canvas.move('I16b', -3, 0) + end + rotate_item('I16c', ox, oy, beta) + rotate_item('I16d', ox, oy, beta) + + return ((step == 1)? 3: 1) + end + + # Cat + def draw17 + color = @C['17'] + + xy = [584, 556, 722, 556] + TkcLine.new(@canvas, xy, :fill=>@C['fg'], :width=>3) + xy = [584, 485, 722, 485] + TkcLine.new(@canvas, xy, :fill=>@C['fg'], :width=>3) + + xy = [664, 523, 717, 549] # Body + TkcArc.new(@canvas, xy, :outline=>@C['fg'], :fill=>color, :width=>3, + :style=>:chord, :start=>128, :extent=>260, :tag=>'I17') + + xy = [709, 554, 690, 543] # Paw + TkcOval.new(@canvas, xy, :outline=>@C['fg'], :fill=>color, + :width=>3, :tag=>'I17') + xy = [657, 544, 676, 555] + TkcOval.new(@canvas, xy, :outline=>@C['fg'], :fill=>color, + :width=>3, :tag=>'I17') + + xy = box(660, 535, 15) # Lower face + TkcArc.new(@canvas, xy, :outline=>@C['fg'], :width=>3, :style=>:arc, + :start=>150, :extent=>240, :tag=>'I17_') + TkcArc.new(@canvas, xy, :outline=>'', :fill=>color, :width=>1, + :style=>:chord, :start=>150, :extent=>240, :tag=>'I17_') + xy = [674, 529, 670, 513, 662, 521, 658, 521, 650, 513, 647, 529] # Ears + TkcLine.new(@canvas, xy, :fill=>@C['fg'], :width=>3, :tag=>'I17_') + TkcPolygon.new(@canvas, xy, :fill=>color, :outline=>'', :width=>1, + :tag=>['I17_', 'I17_c']) + xy = [652, 542, 628, 539] # Whiskers + TkcLine.new(@canvas, xy, :fill=>@C['fg'], :width=>3, :tag=>'I17_') + xy = [652, 543, 632, 545] + TkcLine.new(@canvas, xy, :fill=>@C['fg'], :width=>3, :tag=>'I17_') + xy = [652, 546, 632, 552] + TkcLine.new(@canvas, xy, :fill=>@C['fg'], :width=>3, :tag=>'I17_') + + xy = [668, 543, 687, 538] + TkcLine.new(@canvas, xy, :fill=>@C['fg'], :width=>3, + :tag=>['I17_', 'I17_w']) + xy = [668, 544, 688, 546] + TkcLine.new(@canvas, xy, :fill=>@C['fg'], :width=>3, + :tag=>['I17_', 'I17_w']) + xy = [668, 547, 688, 553] + TkcLine.new(@canvas, xy, :fill=>@C['fg'], :width=>3, + :tag=>['I17_', 'I17_w']) + + xy = [649, 530, 654, 538, 659, 530] # Left eye + TkcLine.new(@canvas, xy, :fill=>@C['fg'], :width=>2, + :smooth=>true, :tag=>'I17') + xy = [671, 530, 666, 538, 661, 530] # Right eye + TkcLine.new(@canvas, xy, :fill=>@C['fg'], :width=>2, + :smooth=>true, :tag=>'I17') + xy = [655, 543, 660, 551, 665, 543] # Mouth + TkcLine.new(@canvas, xy, :fill=>@C['fg'], :width=>2, + :smooth=>true, :tag=>'I17') + end + + def move17(step = nil) + step = get_step(17, step) + + if step == 0 + @canvas.delete('I17') # Delete most of the cat + xy = [655, 543, 660, 535, 665, 543] # Mouth + TkcLine.new(@canvas, xy, :fill=>@C['fg'], :width=>3, + :smooth=>true, :tag=>'I17_') + xy = box(654, 530, 4) # Left eye + TkcOval.new(@canvas, xy, :outline=>@C['fg'], :width=>3, :fill=>'', + :tag=>'I17_') + xy = box(666, 530, 4) # Right eye + TkcOval.new(@canvas, xy, :outline=>@C['fg'], :width=>3, :fill=>'', + :tag=>'I17_') + + @canvas.move('I17_', 0, -20) # Move face up + xy = [652, 528, 652, 554] # Front leg + TkcLine.new(@canvas, xy, :fill=>@C['fg'], :width=>3, :tag=>'I17_') + xy = [670, 528, 670, 554] # 2nd front leg + TkcLine.new(@canvas, xy, :fill=>@C['fg'], :width=>3, :tag=>'I17_') + + xy = [ # Body + 675, 506, 694, 489, 715, 513, 715, 513, 715, 513, 716, 525, + 716, 525, 716, 525, 706, 530, 695, 530, 679, 535, 668, 527, + 668, 527, 668, 527, 675, 522, 676, 517, 677, 512 + ] + TkcPolygon.new(@canvas, xy, :fill=>@canvas.itemcget('I17_c', :fill), + :outline=>@C['fg'], :width=>3, :smooth=>true, + :tag=>'I17_') + xy = [716, 514, 716, 554] # Back leg + TkcLine.new(@canvas, xy, :fill=>@C['fg'], :width=>3, :tag=>'I17_') + xy = [694, 532, 694, 554] # 2nd back leg + TkcLine.new(@canvas, xy, :fill=>@C['fg'], :width=>3, :tag=>'I17_') + xy = [715, 514, 718, 506, 719, 495, 716, 488] # Tail + TkcLine.new(@canvas, xy, :fill=>@C['fg'], :width=>3, + :smooth=>true, :tag=>'I17_') + + @canvas.raise('I17w') # Make whiskers visible + @canvas.move('I17_', -5, 0) # Move away from the wall a bit + return 2 + end + return 0 + end + + # Sling shot + def draw18 + color = @C['18'] + xy = [721, 506, 627, 506] # Sling hold + TkcLine.new(@canvas, xy, :width=>4, :fill=>@C['fg'], :tag=>'I18') + + xy = [607, 500, 628, 513] # Sling rock + TkcOval.new(@canvas, xy, :fill=>color, :outline=>'', :tag=>'I18a') + + xy = [526, 513, 606, 507, 494, 502] # Sling band + TkcLine.new(@canvas, xy, :fill=>@C['fg'], :width=>4, :tag=>'I18b') + xy = [485, 490, 510, 540, 510, 575, 510, 540, 535, 491] # Sling + TkcLine.new(@canvas, xy, :fill=>@C['fg'], :width=>6) + end + + def move18(step = nil) + step = get_step(18, step) + + pos = [ + [587, 506], [537, 506], [466, 506], [376, 506], [266, 506, :x], + [136, 506], [16, 506], [-100, -100] + ] + + b = [] + b[0] = [490, 502, 719, 507, 524, 512] # Band collapsing + b[1] = [ + 491, 503, 524, 557, 563, 505, 559, 496, 546, 506, 551, 525, + 553, 536, 538, 534, 532, 519, 529, 499 + ] + b[2] = [ + 491, 503, 508, 563, 542, 533, 551, 526, 561, 539, 549, 550, 530, 500 + ] + b[3] = [ + 491, 503, 508, 563, 530, 554, 541, 562, 525, 568, 519, 544, 530, 501 + ] + + return 0 if step >= pos.length + + if step == 0 + @canvas.delete('I18') + @canvas.itemconfigure('I18b', :smooth=>true) + end + if b[step] + @canvas.coords('I18b', b[step]) + end + + where = pos[step] + move_abs('I18a', where) + return 3 if where[2] == :x + return 1 + end + + # Water pipe + def draw19 + color = @C['19'] + xx = [[249, 181], [155, 118], [86, 55], [22, 0]] + xx.each{|x1, x2| + TkcRectangle.new(@canvas, x1, 453, x2, 467, + :fill=>color, :outline=>'', :tag=>'I19') + TkcLine.new(@canvas, x1, 453, x2, 453, + :fill=>@C['fg'], :width=>1) # Pipe top + TkcLine.new(@canvas, x1, 467, x2, 467, + :fill=>@C['fg'], :width=>1) # Pipe bottom + } + @canvas.raise('I11i') + + xy = box(168, 460, 16) # Bulge by the joint + TkcOval.new(@canvas, xy, :fill=>color, :outline=>'') + TkcArc.new(@canvas, xy, :outline=>@C['fg'], :width=>1, :style=>:arc, + :start=>21, :extent=>136) + TkcArc.new(@canvas, xy, :outline=>@C['fg'], :width=>1, :style=>:arc, + :start=>-21, :extent=>-130) + + xy = [249, 447, 255, 473] # First joint 26x6 + TkcRectangle.new(@canvas, xy, :fill=>color, :outline=>@C['fg'], :width=>1) + + xy = box(257, 433, 34) # Bend up + TkcArc.new(@canvas, xy, :outline=>'', :fill=>color, :width=>1, + :style=>:pie, :start=>0, :extent=>-91) + TkcArc.new(@canvas, xy, :outline=>@C['fg'], :width=>1, + :style=>:arc, :start=>0, :extent=>-90) + xy = box(257, 433, 20) + TkcArc.new(@canvas, xy, :outline=>'', :fill=>@C['bg'], :width=>1, + :style=>:pie, :start=>0, :extent=>-92) + TkcArc.new(@canvas, xy, :outline=>@C['fg'], :width=>1, + :style=>:arc, :start=>0, :extent=>-90) + xy = box(257, 421, 34) # Bend left + TkcArc.new(@canvas, xy, :outline=>'', :fill=>color, :width=>1, + :style=>:pie, :start=>0, :extent=>91) + TkcArc.new(@canvas, xy, :outline=>@C['fg'], :width=>1, + :style=>:arc, :start=>0, :extent=>90) + xy = box(257, 421, 20) + TkcArc.new(@canvas, xy, :outline=>'', :fill=>@C['bg'], :width=>1, + :style=>:pie, :start=>0, :extent=>90) + TkcArc.new(@canvas, xy, :outline=>@C['fg'], :width=>1, + :style=>:arc, :start=>0, :extent=>90) + xy = box(243, 421, 34) # Bend down + TkcArc.new(@canvas, xy, :outline=>'', :fill=>color, :width=>1, + :style=>:pie, :start=>90, :extent=>90) + TkcArc.new(@canvas, xy, :outline=>@C['fg'], :width=>1, + :style=>:arc, :start=>90, :extent=>90) + xy = box(243, 421, 20) + TkcArc.new(@canvas, xy, :outline=>'', :fill=>@C['bg'], :width=>1, + :style=>:pie, :start=>90, :extent=>90) + TkcArc.new(@canvas, xy, :outline=>@C['fg'], :width=>1, + :style=>:arc, :start=>90, :extent=>90) + + xy = [270, 427, 296, 433] # 2nd joint bottom + TkcRectangle.new(@canvas, xy, :fill=>color, :outline=>@C['fg'], :width=>1) + xy = [270, 421, 296, 427] # 2nd joint top + TkcRectangle.new(@canvas, xy, :fill=>color, :outline=>@C['fg'], :width=>1) + xy = [249, 382, 255, 408] # Third joint right + TkcRectangle.new(@canvas, xy, :fill=>color, :outline=>@C['fg'], :width=>1) + xy = [243, 382, 249, 408] # Third joint left + TkcRectangle.new(@canvas, xy, :fill=>color, :outline=>@C['fg'], :width=>1) + xy = [203, 420, 229, 426] # Last joint + TkcRectangle.new(@canvas, xy, :fill=>color, :outline=>@C['fg'], :width=>1) + + xy = box(168, 460, 6) # Handle joint + TkcOval.new(@canvas, xy, :fill=>@C['fg'], :outline=>'', :tag=>'I19a') + xy = [168, 460, 168, 512] # Handle bar + TkcLine.new(@canvas, xy, :fill=>@C['fg'], :width=>5, :tag=>'I19b') + end + + def move19(step = nil) + step = get_step(19, step) + + angles = [30, 30, 30] + return 2 if step == angles.length + ox, oy = centroid('I19a') + rotate_item('I19b', ox, oy, angles[step]) + + return 1 + end + + # Water pouring + def draw20 + # do nothing + end + + def move20(step = nil) + step = get_step(20, step) + + pos = [451, 462, 473, 484, 496, 504, 513, 523, 532] + freq = [20, 40, 40, 40, 40, 40, 40, 40, 40] + pos = [ + [451, 20], [462, 40], [473, 40], [484, 40], [496, 40], + [504, 40], [513, 40], [523, 40], [532, 40, :x] + ] + return 0 if step >= pos.length + + @canvas.delete('I20') + where = pos[step] + y, f = where + h20(y, f) + return 3 if where[2] == :x + return 1 + end + + def h20(y, f) + color = @C['20'] + @canvas.delete('I20') + + sine([208, 428, 208, y], 4, f, :tag=>['I20', 'I20s'], + :width=>3, :fill=>color, :smooth=>true) + TkcLine.new(@canvas, @canvas.coords('I20s'), :width=>3, + :fill=>color, :smooth=>1, :tag=>['I20', 'I20a']) + TkcLine.new(@canvas, @canvas.coords('I20s'), :width=>3, + :fill=>color, :smooth=>1, :tag=>['I20', 'I20b']) + @canvas.move('I20a', 8, 0) + @canvas.move('I20b', 16, 0) + end + + # Bucket + def draw21 + color = @C['21'] + xy = [217, 451, 244, 490] # Right handle + TkcLine.new(@canvas, xy, :fill=>@C['fg'], :width=>2, :tag=>'I21_a') + xy = [201, 467, 182, 490] # Left handle + TkcLine.new(@canvas, xy, :fill=>@C['fg'], :width=>2, :tag=>'I21_a') + + xy = [245, 490, 237, 535] # Right side + xy2 = [189, 535, 181, 490] # Left side + TkcPolygon.new(@canvas, xy + xy2, :fill=>color, :outline=>'', + :tag=>['I21', 'I21f']) + TkcLine.new(@canvas, xy, :fill=>@C['fg'], :width=>2, :tag=>'I21') + TkcLine.new(@canvas, xy2, :fill=>@C['fg'], :width=>2, :tag=>'I21') + + xy = [182, 486, 244, 498] # Top + TkcOval.new(@canvas, xy, :fill=>color, :outline=>'', :width=>2, + :tag=>['I21', 'I21f']) + TkcOval.new(@canvas, xy, :fill=>'', :outline=>@C['fg'], :width=>2, + :tag=>['I21', 'I21t']) + xy = [189, 532, 237, 540] # Bottom + TkcOval.new(@canvas, xy, :fill=>color, :outline=>@C['fg'], :width=>2, + :tag=>['I21', 'I21b']) + end + + def move21(step = nil) + step = get_step(21, step) + + numsteps = 30 + return 0 if step >= numsteps + + x1, y1, x2, y2 = @canvas.coords('I21b') + # lx1, ly1, lx2, ly2 = @canvas.coords('I21t') + lx1, ly1, lx2, ly2 = [183, 492, 243, 504] + + f = step / numsteps.to_f + y2 = y2 - 3 + xx1 = x1 + (lx1 - x1) * f + yy1 = y1 + (ly1 - y1) * f + xx2 = x2 + (lx2 - x2) * f + yy2 = y2 + (ly2 - y2) * f + + @canvas.itemconfigure('I21b', :fill=>@C['20']) + @canvas.delete('I21w') + TkcPolygon.new(@canvas, x2, y2, x1, y1, xx1, yy1, xx2, yy1, + :tag=>['I21', 'I21w'], :outline=>'', :fill=>@C['20']) + @canvas.lower('I21w', 'I21') + @canvas.raise('I21b') + @canvas.lower('I21f') + + return((step == numsteps - 1)? 3: 1) + end + + # Bucket drop + def draw22 + # do nothing + end + + def move22(step = nil) + step = get_step(22, step) + pos = [[213, 513], [213, 523], [213, 543, :x], [213, 583], [213, 593]] + + @canvas.itemconfigure('I21f', :fill=>@C['22']) if step == 0 + return 0 if step >= pos.length + where = pos[step] + move_abs('I21', where) + h20(where[1], 40) + @canvas.delete('I21_a') # Delete handles + + return 3 if where[2] == :x + return 1 + end + + # Blow dart + def draw23 + color = @C['23a'] + color2 = @C['23b'] + color3 = @C['23c'] + + xy = [185, 623, 253, 650] # Block + TkcRectangle.new(@canvas, xy, :fill=>'black', :outline=>@C['fg'], + :width=>2, :tag=>'I23a') + xy = [187, 592, 241, 623] # Balloon + TkcOval.new(@canvas, xy, :outline=>'', :fill=>color, :tag=>'I23b') + TkcArc.new(@canvas, xy, :outline=>@C['fg'], :width=>3, :tag=>'I23b', + :style=>:arc, :start=>12, :extent=>336) + xy = [239, 604, 258, 589, 258, 625, 239, 610] # Balloon nozzle + TkcPolygon.new(@canvas, xy, :outline=>'', :fill=>color, :tag=>'I23b') + TkcLine.new(@canvas, xy, :fill=>@C['fg'], :width=>3, :tag=>'I23b') + + xy = [285, 611, 250, 603] # Dart body + TkcOval.new(@canvas, xy, :fill=>color2, :outline=>@C['fg'], + :width=>3, :tag=>'I23d') + xy = [249, 596, 249, 618, 264, 607, 249, 596] # Dart tail + TkcPolygon.new(@canvas, xy, :fill=>color3, :outline=>@C['fg'], + :width=>3, :tag=>'I23d') + xy = [249, 607, 268, 607] # Dart detail + TkcLine.new(@canvas, xy, :fill=>@C['fg'], :width=>3, :tag=>'I23d') + xy = [285, 607, 305, 607] # Dart needle + TkcLine.new(@canvas, xy, :fill=>@C['fg'], :width=>3, :tag=>'I23d') + end + + def move23(step = nil) + step = get_step(23, step) + + pos = [ + [277, 607], [287, 607], [307, 607, :x], [347, 607], [407, 607], + [487, 607], [587, 607], [687, 607], [787, 607], [-100, -100] + ] + + return 0 if step >= pos.length + if step <= 1 + ox, oy = anchor('I23a', :n) + @canvas.scale('I23b', ox, oy, 0.9, 0.5) + end + where = pos[step] + move_abs('I23d', where) + + return 3 if where[2] == :x + return 1 + end + + # Balloon + def draw24 + color = @C['24a'] + xy = [366, 518, 462, 665] # Balloon + TkcOval.new(@canvas, xy, :fill=>color, :outline=>@C['fg'], + :width=>3, :tag=>'I24') + xy = [414, 666, 414, 729] # String + TkcLine.new(@canvas, xy, :fill=>@C['fg'], :width=>3, :tag=>'I24') + xy = [410, 666, 404, 673, 422, 673, 418, 666] # Nozzle + TkcPolygon.new(@canvas, xy, :fill=>color, :outline=>@C['fg'], + :width=>3, :tag=>'I24') + + xy = [387, 567, 390, 549, 404, 542] # Reflections + TkcLine.new(@canvas, xy, :fill=>@C['fg'], :smooth=>true, + :width=>2, :tag=>'I24') + xy = [395, 568, 399, 554, 413, 547] + TkcLine.new(@canvas, xy, :fill=>@C['fg'], :smooth=>true, + :width=>2, :tag=>'I24') + xy = [403, 570, 396, 555, 381, 553] + TkcLine.new(@canvas, xy, :fill=>@C['fg'], :smooth=>true, + :width=>2, :tag=>'I24') + xy = [408, 564, 402, 547, 386, 545] + TkcLine.new(@canvas, xy, :fill=>@C['fg'], :smooth=>true, + :width=>2, :tag=>'I24') + end + + def move24(step = nil) + step = get_step(24, step) + + return 0 if step > 4 + return 2 if step == 4 + + if step == 0 + @canvas.delete('I24') # Exploding balloon + xy = [ + 347, 465, 361, 557, 271, 503, 272, 503, 342, 574, 259, 594, + 259, 593, 362, 626, 320, 737, 320, 740, 398, 691, 436, 738, + 436, 739, 476, 679, 528, 701, 527, 702, 494, 627, 548, 613, + 548, 613, 480, 574, 577, 473, 577, 473, 474, 538, 445, 508, + 431, 441, 431, 440, 400, 502, 347, 465, 347, 465 + ] + TkcPolygon.new(@canvas, xy, :tag=>'I24', :fill=>@C['24b'], + :outline=>@C['24a'], :width=>10, :smooth=>true) + msg = Tk.subst(@S['message'].value) + TkcText.new(@canvas, centroid('I24'), :text=>msg, :tag=>['I24', 'I24t'], + :justify=>:center, :font=>['Times Roman', 18, :bold]) + return 1 + end + + @canvas.itemconfigure('I24t', :font=>['Times Roman', 18 + 6*step, :bold]) + @canvas.move('I24', 0, -60) + ox, oy = centroid('I24') + @canvas.scale('I24', ox, oy, 1.25, 1.25) + return 1 + end + + # Displaying the message + def move25(step = nil) + step = get_step(25, step) + + if step == 0 + @XY['25'] = Tk::Clock.clicks(:miliseconds) + return 1 + end + elapsed = Tk::Clock.clicks(:miliseconds) - @XY['25'] + return 1 if elapsed < 5000 + return 2 + end + + # Collapsing balloon + def move26(step = nil) + step = get_step(26, step) + + if step >= 3 + @canvas.delete('I24', 'I26') + TkcText.new(@canvas, 430, 755, :anchor=>:s, :tag=>'I26', + #:text=>'click to continue', + :text=>'クリックでリセットします', + :font=>['Times Roman', 24, :bold]) + @canvas.bind('1', proc{reset}) + return 4 + end + + ox, oy = centroid('I24') + @canvas.scale('I24', ox, oy, 0.8, 0.8) + @canvas.move('I24', 0, 60) + @canvas.itemconfigure('I24t', :font=>['Times Roman', 30 - 6*step, :bold]) + return 1 + end + + ################################################################ + # + # Helper functions + # + def box(x, y, r) + [x - r, y - r, x + r, y + r] + end + + def move_abs(item, xy) + x, y = xy + ox, oy = centroid(item) + dx = x - ox + dy = y - oy + @canvas.move(item, dx, dy) + end + + def rotate_item(item, ox, oy, beta) + xy = @canvas.coords(item) + xy2 = [] + 0.step(xy.length - 1, 2){|idx| + x, y = xy[idx, 2] + xy2.concat(rotate_c(x, y, ox, oy, beta)) + } + @canvas.coords(item, xy2) + end + + def rotate_c(x, y, ox, oy, beta) + # rotates vector (ox,oy)->(x,y) by beta degrees clockwise + + x -= ox # Shift to origin + y -= oy + + beta = beta * Math.atan(1) * 4 / 180.0 # Radians + xx = x * Math.cos(beta) - y * Math.sin(beta) # Rotate + yy = x * Math.sin(beta) + y * Math.cos(beta) + + xx += ox # Shift back + yy += oy + + [xx, yy] + end + + def reset + draw_all + @canvas.bind_remove('1') + @S['mode'].value = :MSTART + @S['active'] = [0] + end + + # Each Move## keeps its state info in STEP, this retrieves and increments it + def get_step(who, step) + if step + @STEP[who] = step + else + if !@STEP.exist?(who) || @STEP[who] == "" + @STEP[who] = 0 + else + @STEP[who] += 1 + end + end + @STEP[who] + end + + def reset_step + @S['cnt'].value = 0 + @STEP.keys.each{|k| @STEP[k] = ''} + end + + def sine(xy0, amp, freq, opts = {}) + x0, y0, x1, y1 = xy0 + step = 2 + xy = [] + if y0 == y1 # Horizontal + x0.step(x1, step){|x| + beta = (x - x0) * 2 * Math::PI / freq + y = y0 + amp * Math.sin(beta) + xy << x << y + } + else + y0.step(y1, step){|y| + beta = (y - y0) * 2 * Math::PI / freq + x = x0 + amp * Math.sin(beta) + xy << x << y + } + end + TkcLine.new(@canvas, xy, opts) + end + + def round_rect(xy, radius, opts={}) + x0, y0, x3, y3 = xy + r = @canvas.winfo_pixels(radius) + d = 2 * r + + # Make sure that the radius of the curve is less than 3/8 size of the box! + maxr = 0.75 + if d > maxr * (x3 - x0) + d = maxr * (x3 - x0) + end + if d > maxr * (y3 - y0) + d = maxr * (y3 - y0) + end + + x1 = x0 + d + x2 = x3 - d + y1 = y0 + d + y2 = y3 - d + + xy = [x0, y0, x1, y0, x2, y0, x3, y0, x3, y1, x3, y2] + xy.concat([x3, y3, x2, y3, x1, y3, x0, y3, x0, y2, x0, y1]) + return xy + end + + def round_poly(canv, xy, radii, opts) + lenXY = xy.length + lenR = radii.length + if lenXY != 2*lenR + raise "wrong number of vertices and radii" + end + + knots = [] + x0 = xy[-2]; y0 = xy[-1] + x1 = xy[0]; y1 = xy[1] + xy << xy[0] << xy[1] + + 0.step(lenXY - 1, 2){|i| + radius = radii[i/2] + r = canv.winfo_pixels(radius) + + x2 = xy[i+2]; y2 = xy[i+3] + z = _round_poly2(x0, y0, x1, y1, x2, y2, r) + knots.concat(z) + + x0 = x1; y0 = y1 + x1 = x2; y1 = y2 + } + TkcPolygon.new(canv, knots, {:smooth=>true}.update(opts)) + end + + def _round_poly2(x0, y0, x1, y1, x2, y2, radius) + d = 2 * radius + maxr = 0.75 + + v1x = x0 - x1 + v1y = y0 - y1 + v2x = x2 - x1 + v2y = y2 - y1 + + vlen1 = Math.sqrt(v1x*v1x + v1y*v1y) + vlen2 = Math.sqrt(v2x*v2x + v2y*v2y) + + if d > maxr * vlen1 + d = maxr * vlen1 + end + if d > maxr * vlen2 + d = maxr * vlen2 + end + + xy = [] + xy << (x1 + d * v1x / vlen1) << (y1 + d * v1y / vlen1) + xy << x1 << y1 + xy << (x1 + d * v2x / vlen2) << (y1 + d * v2y / vlen2) + + return xy + end + + def sparkle(oxy, tag) + xy = [ + [299, 283], [298, 302], [295, 314], [271, 331], + [239, 310], [242, 292], [256, 274], [281, 273] + ] + xy.each{|x, y| + TkcLine.new(@canvas, 271, 304, x, y, + :fill=>'white', :width=>3, :tag=>tag) + } + move_abs(tag, oxy) + end + + def centroid(item) + anchor(item, :c) + end + + def anchor(item, where) + x1, y1, x2, y2 = @canvas.bbox(item) + case(where) + when :n + y = y1 + when :s + y = y2 + else + y = (y1 + y2) / 2.0 + end + case(where) + when :w + x = x1 + when :e + x = x2 + else + x = (x1 + x2) / 2.0 + end + return [x, y] + end +end + +TkGoldberg_Demo.new($goldberg_demo) diff --git a/ext/tk/sample/demos-jp/pendulum.rb b/ext/tk/sample/demos-jp/pendulum.rb new file mode 100644 index 0000000000..d703c74d5a --- /dev/null +++ b/ext/tk/sample/demos-jp/pendulum.rb @@ -0,0 +1,224 @@ +# +# This demonstration illustrates how Tcl/Tk can be used to construct +# simulations of physical systems. +# (called by 'widget') +# +# based on Tcl/Tk8.5a2 widget demos + +# destroy toplevel widget for this demo script +if defined?($pendulum_demo) && $pendulum_demo + $pendulum_demo.destroy + $pendulum_demo = nil +end + +# create toplevel widget +$pendulum_demo = TkToplevel.new {|w| + title("Pendulum Animation Demonstration") + iconname("pendulum") + positionWindow(w) +} + +# create label +msg = TkLabel.new($pendulum_demo) { + font $font + wraplength '4i' + justify 'left' + text 'このデモは、物理系のシミュレーションに関わるようなアニメーション実行するために Ruby/Tk をどのように用いることができるかを示しています。左側のキャンバスは単純な振り子である物理系自体のグラフィカル表現であるのに対し、右側のキャンバスは系の位相空間のグラフ(角速度と角度とをプロットしたもの)になっています。左側のキャンバス上でクリックおよびドラッグを行って振り子の重りの位置を変えてみてください。' +} +msg.pack('side'=>'top') + +# create frame +TkFrame.new($pendulum_demo) {|frame| + TkButton.new(frame) { + #text '了解' + text '閉じる' + command proc{ + tmppath = $pendulum_demo + $pendulum_demo = nil + tmppath.destroy + } + }.pack('side'=>'left', 'expand'=>'yes') + + TkButton.new(frame) { + text 'コード参照' + command proc{showCode 'pendulum'} + }.pack('side'=>'left', 'expand'=>'yes') + +}.pack('side'=>'bottom', 'fill'=>'x', 'pady'=>'2m') + +# animated wave +class PendulumAnimationDemo + def initialize(frame) + # Create some structural widgets + pane = TkPanedWindow.new(frame).pack(:fill=>:both, :expand=>true) + pane.add(@lf1 = TkLabelFrame.new(pane, :text=>'Pendulum Simulation')) + pane.add(@lf2 = TkLabelFrame.new(pane, :text=>'Phase Space')) + + # Create the canvas containing the graphical representation of the + # simulated system. + @c = TkCanvas.new(@lf1, :width=>320, :height=>200, :background=>'white', + :borderwidth=>2, :relief=>:sunken) + TkcText.new(@c, 5, 5, :anchor=>:nw, + :text=>'Click to Adjust Bob Start Position') + # Coordinates of these items don't matter; they will be set properly below + @plate = TkcLine.new(@c, 0, 25, 320, 25, :width=>2, :fill=>'grey50') + @rod = TkcLine.new(@c, 1, 1, 1, 1, :width=>3, :fill=>'black') + @bob = TkcOval.new(@c, 1, 1, 2, 2, + :width=>3, :fill=>'yellow', :outline=>'black') + TkcOval.new(@c, 155, 20, 165, 30, :fill=>'grey50', :outline=>'') + + # pack + @c.pack(:fill=>:both, :expand=>true) + + # Create the canvas containing the phase space graph; this consists of + # a line that gets gradually paler as it ages, which is an extremely + # effective visual trick. + @k = TkCanvas.new(@lf2, :width=>320, :height=>200, :background=>'white', + :borderwidth=>2, :relief=>:sunken) + @y_axis = TkcLine.new(@k, 160, 200, 160, 0, :fill=>'grey75', :arrow=>:last) + @x_axis = TkcLine.new(@k, 0, 100, 320, 100, :fill=>'grey75', :arrow=>:last) + + @graph = {} + 90.step(0, -10){|i| + # Coordinates of these items don't matter; + # they will be set properly below + @graph[i] = TkcLine.new(@k, 0, 0, 1, 1, :smooth=>true, :fill=>"grey#{i}") + } + + # labels + @label_theta = TkcText.new(@k, 0, 0, :anchor=>:ne, + :text=>'q', :font=>'Symbol 8') + @label_dtheta = TkcText.new(@k, 0, 0, :anchor=>:ne, + :text=>'dq', :font=>'Symbol 8') + + # pack + @k.pack(:fill=>:both, :expand=>true) + + # Initialize some variables + @points = [] + @theta = 45.0 + @dTheta = 0.0 + @length = 150 + + # init display + showPendulum + + # animation loop + @timer = TkTimer.new(15){ repeat } + + # binding + @c.bindtags_unshift(btag = TkBindTag.new) + btag.bind('Destroy'){ @timer.stop } + btag.bind('1', proc{|x, y| @timer.stop; showPendulum(x, y)}, '%x %y') + btag.bind('B1-Motion', proc{|x, y| showPendulum(x, y)}, '%x %y') + btag.bind('ButtonRelease-1', + proc{|x, y| showPendulum(x, y); @timer.start }, '%x %y') + + btag.bind('Configure', proc{|w| @plate.coords(0, 25, w, 25)}, '%w') + + @k.bind('Configure', proc{|h, w| + @psh = h/2; + @psw = w/2 + @x_axis.coords(2, @psh, w-2, @psh) + @y_axis.coords(@psw, h-2, @psw, 2) + @label_theta.coords(@psw-4, 6) + @label_dtheta.coords(w-6, @psh+4) + }, '%h %w') + + # animation start + @timer.start(500) + end + + # This procedure makes the pendulum appear at the correct place on the + # canvas. If the additional arguments x, y are passed instead of computing + # the position of the pendulum from the length of the pendulum rod and its + # angle, the length and angle are computed in reverse from the given + # location (which is taken to be the centre of the pendulum bob.) + def showPendulum(x=nil, y=nil) + if x && y && (x != 160 || y != 25) + @dTheta = 0.0 + x2 = x - 160 + y2 = y - 25 + @length = Math.hypot(x2, y2) + @theta = Math.atan2(x2,y2)*180/Math::PI + else + angle = @theta*Math::PI/180 + x = 160 + @length*Math.sin(angle) + y = 25 + @length*Math.cos(angle) + end + + @rod.coords(160, 25, x, y) + @bob.coords(x-15, y-15, x+15, y+15) + end + + # Update the phase-space graph according to the current angle and the + # rate at which the angle is changing (the first derivative with + # respect to time.) + def showPhase + @points << @theta + @psw << -20*@dTheta + @psh + if @points.length > 100 + @points = @points[-100..-1] + end + (0...100).step(10){|i| + first = - i + last = 11 - i + last = -1 if last >= 0 + next if first > last + lst = @points[first..last] + @graph[i].coords(lst) if lst && lst.length >= 4 + } + end + + # This procedure is the "business" part of the simulation that does + # simple numerical integration of the formula for a simple rotational + # pendulum. + def recomputeAngle + scaling = 3000.0/@length/@length + + # To estimate the integration accurately, we really need to + # compute the end-point of our time-step. But to do *that*, we + # need to estimate the integration accurately! So we try this + # technique, which is inaccurate, but better than doing it in a + # single step. What we really want is bound up in the + # differential equation: + # .. - sin theta + # theta + theta = ----------- + # length + # But my math skills are not good enough to solve this! + + # first estimate + firstDDTheta = -Math.sin(@theta * Math::PI/180) * scaling + midDTheta = @dTheta + firstDDTheta + midTheta = @theta + (@dTheta + midDTheta)/2 + # second estimate + midDDTheta = -Math.sin(midTheta * Math::PI/180) * scaling + midDTheta = @dTheta + (firstDDTheta + midDDTheta)/2 + midTheta = @theta + (@dTheta + midDTheta)/2 + # Now we do a double-estimate approach for getting the final value + # first estimate + midDDTheta = -Math.sin(midTheta * Math::PI/180) * scaling + lastDTheta = midDTheta + midDDTheta + lastTheta = midTheta + (midDTheta+ lastDTheta)/2 + # second estimate + lastDDTheta = -Math.sin(lastTheta * Math::PI/180) * scaling + lastDTheta = midDTheta + (midDDTheta + lastDDTheta)/2 + lastTheta = midTheta + (midDTheta + lastDTheta)/2 + # Now put the values back in our globals + @dTheta = lastDTheta + @theta = lastTheta + end + + # This method ties together the simulation engine and the graphical + # display code that visualizes it. + def repeat + # Simulate + recomputeAngle + + # Update the display + showPendulum + showPhase + end +end + +# Start the animation processing +PendulumAnimationDemo.new($pendulum_demo) diff --git a/ext/tk/sample/demos-jp/widget b/ext/tk/sample/demos-jp/widget index 3be05c167c..59d6309d56 100644 --- a/ext/tk/sample/demos-jp/widget +++ b/ext/tk/sample/demos-jp/widget @@ -442,6 +442,12 @@ txt.insert('end', "\n") txt.insert('end', "アニメーション\n", tag_kanji_title) txt.insert('end', " \n ", tag_demospace) txt.insert('end', "1. アニメーションラベル (機能に対応したバージョンのTkが必要)\n", tag_demo, "demo-anilabel") +txt.insert('end', " \n ", tag_demospace) +txt.insert('end', "2. 波形のアニメーション (機能に対応したバージョンのTkが必要)\n", tag_demo, "demo-aniwave") +txt.insert('end', " \n ", tag_demospace) +txt.insert('end', "3. 振り子のシミュレーション (機能に対応したバージョンのTkが必要)\n", tag_demo, "demo-pendulum") +txt.insert('end', " \n ", tag_demospace) +txt.insert('end', "4. A celebration of Rube Goldberg (機能に対応したバージョンのTkが必要)\n", tag_demo, "demo-goldberg") txt.insert('end', "\n") #txt.insert('end', "その他\n", tag_middle) @@ -813,7 +819,7 @@ end # def aboutBox Tk.messageBox('icon'=>'info', 'type'=>'ok', 'title'=>'About Widget Demo', - 'message'=>"Ruby/Tk ウィジェットデモ Ver.1.5.0-jp\n\n" + + 'message'=>"Ruby/Tk ウィジェットデモ Ver.1.5.2-jp\n\n" + "based on demos of Tk8.1 -- 8.5 " + "( Copyright:: " + "(c) 1996-1997 Sun Microsystems, Inc. / " + diff --git a/ext/tk/sample/irbtk.rb b/ext/tk/sample/irbtk.rb new file mode 100644 index 0000000000..53ef10d0d1 --- /dev/null +++ b/ext/tk/sample/irbtk.rb @@ -0,0 +1,30 @@ +#!/usr/local/bin/ruby +# +# irbtk.rb - irb with Ruby/Tk +# +# If you want to use 'multi-tk.rb', give option '--multi-tk'. +# And if you want to use 'remote-tk.rb', give option '--remote-tk'. +# If you want both, you don't need to give both options, because +# 'remote-tk.rb' includes 'multi-tk.rb'. +# ( There is no trouble even if you give both options. ) +# +require 'remote-tk' if ARGV.delete('--remote-tk') +require 'multi-tk' if ARGV.delete('--multi-tk') + +require "tk" +module Tk + MAINLOOP = Thread.new{ mainloop } +end + +require "irb" + +if __FILE__ == $0 + IRB.start(__FILE__) +else + # check -e option + if /^-e$/ =~ $0 + IRB.start(__FILE__) + else + IRB.setup(__FILE__) + end +end diff --git a/ext/tk/tcltklib.c b/ext/tk/tcltklib.c index fae8e3da3e..2f94cf97fd 100644 --- a/ext/tk/tcltklib.c +++ b/ext/tk/tcltklib.c @@ -4,7 +4,7 @@ * Oct. 24, 1997 Y. Matsumoto */ -#define TCLTKLIB_RELEASE_DATE "2005-01-28" +#define TCLTKLIB_RELEASE_DATE "2005-03-02" #include "ruby.h" #include "rubysig.h" @@ -73,9 +73,7 @@ const char tcltklib_release_date[] = TCLTKLIB_RELEASE_DATE; /* finalize_proc_name */ static char *finalize_hook_name = "INTERP_FINALIZE_HOOK"; -/* to cancel remained after-scripts when deleting IP */ -#define CANCEL_AFTER_SCRIPTS "__ruby_tcltklib_cancel_after_scripts__" -#define DEF_CANCEL_AFTER_SCRIPTS_PROC "proc __ruby_tcltklib_cancel_after_scripts__ {} {foreach id [after info] {after cancel $id}}" +static void ip_finalize _((Tcl_Interp*)); /* for callback break & continue */ static VALUE eTkCallbackReturn; @@ -88,8 +86,10 @@ static ID ID_at_enc; static ID ID_at_interp; static ID ID_stop_p; +static ID ID_alive_p; static ID ID_kill; static ID ID_join; +static ID ID_value; static ID ID_call; static ID ID_backtrace; @@ -106,6 +106,9 @@ static ID ID_inspect; static VALUE ip_invoke_real _((int, VALUE*, VALUE)); static VALUE ip_invoke _((int, VALUE*, VALUE)); +static VALUE tk_funcall _((VALUE(), int, VALUE*, VALUE)); + + /* from tkAppInit.c */ #if TCL_MAJOR_VERSION < 8 || (TCL_MAJOR_VERSION == 8 && TCL_MINOR_VERSION < 4) @@ -148,6 +151,18 @@ struct eval_queue { VALUE thread; }; +struct call_queue { + Tcl_Event ev; + VALUE (*func)(); + int argc; + VALUE *argv; + VALUE interp; + int *done; + int safe_level; + VALUE result; + VALUE thread; +}; + void invoke_queue_mark(struct invoke_queue *q) { @@ -164,10 +179,28 @@ eval_queue_mark(struct eval_queue *q) rb_gc_mark(q->thread); } - +void +call_queue_mark(struct call_queue *q) +{ + int i; + + for(i = 0; i < q->argc; i++) { + rb_gc_mark(q->argv[i]); + } + + rb_gc_mark(q->interp); + rb_gc_mark(q->result); + rb_gc_mark(q->thread); +} + + static VALUE eventloop_thread; +static VALUE eventloop_stack; + static VALUE watchdog_thread; + Tcl_Interp *current_interp; + /* * 'event_loop_max' is a maximum events which the eventloop processes in one @@ -196,6 +229,8 @@ static int loop_counter = 0; static int check_rootwidget_flag = 0; + +/* call ruby interpreter */ #if TCL_MAJOR_VERSION >= 8 static int ip_ruby_eval _((ClientData, Tcl_Interp *, int, Tcl_Obj *CONST*)); static int ip_ruby_cmd _((ClientData, Tcl_Interp *, int, Tcl_Obj *CONST*)); @@ -204,6 +239,33 @@ static int ip_ruby_eval _((ClientData, Tcl_Interp *, int, char **)); static int ip_ruby_cmd _((ClientData, Tcl_Interp *, int, char **)); #endif +struct eval_body_arg { + char *string; + VALUE failed; +}; + +struct cmd_body_arg { + VALUE receiver; + ID method; + VALUE args; + VALUE failed; +}; + + +/*----------------------------*/ +/* use Tcl internal functions */ +/*----------------------------*/ +#ifndef TCL_NAMESPACE_DEBUG +#define TCL_NAMESPACE_DEBUG 0 +#endif + +#if TCL_NAMESPACE_DEBUG + +#if TCL_MAJOR_VERSION >= 8 +EXTERN struct TclIntStubs *tclIntStubsPtr; +#endif + +/*-- Tcl_GetCurrentNamespace --*/ #if TCL_MAJOR_VERSION == 8 && TCL_MINOR_VERSION < 5 /* Tcl7.x doesn't have namespace support. */ /* Tcl8.5+ has definition of Tcl_GetCurrentNamespace() in tclDecls.h */ @@ -213,47 +275,21 @@ EXTERN Tcl_Namespace * Tcl_GetCurrentNamespace _((Tcl_Interp *)); # if defined(USE_TCL_STUBS) && !defined(USE_TCL_STUB_PROCS) # ifndef Tcl_GetCurrentNamespace # ifndef FunctionNum_of_GetCurrentNamespace -# define FunctionNum_of_GetCurrentNamespace 124 +#define FunctionNum_of_GetCurrentNamespace 124 # endif -struct DummyTclIntStubs { - int magic; - struct TclIntStubHooks *hooks; - void (*func[FunctionNum_of_GetCurrentNamespace])(); - Tcl_Namespace * (*tcl_GetCurrentNamespace) _((Tcl_Interp *)); +struct DummyTclIntStubs_for_GetCurrentNamespace { + int magic; + struct TclIntStubHooks *hooks; + void (*func[FunctionNum_of_GetCurrentNamespace])(); + Tcl_Namespace * (*tcl_GetCurrentNamespace) _((Tcl_Interp *)); }; -EXTERN struct TclIntStubs *tclIntStubsPtr; + #define Tcl_GetCurrentNamespace \ - (((struct DummyTclIntStubs *)tclIntStubsPtr)->tcl_GetCurrentNamespace) + (((struct DummyTclIntStubs_for_GetCurrentNamespace *)tclIntStubsPtr)->tcl_GetCurrentNamespace) # endif # endif #endif - -/*---- class TclTkIp ----*/ -struct tcltkip { - Tcl_Interp *ip; /* the interpreter */ - Tcl_Namespace *default_ns; /* default namespace */ - int has_orig_exit; /* has original 'exit' command ? */ - Tcl_CmdInfo orig_exit_info; /* command info of original 'exit' command */ - int ref_count; /* reference count of rbtk_preserve_ip call */ - int allow_ruby_exit; /* allow exiting ruby by 'exit' function */ - int return_value; /* return value */ -}; - -static struct tcltkip * -get_ip(self) - VALUE self; -{ - struct tcltkip *ptr; - - Data_Get_Struct(self, struct tcltkip, ptr); - if (ptr == 0) { - rb_raise(rb_eTypeError, "uninitialized TclTkIp"); - } - return ptr; -} - - /* namespace check */ /* ip_null_namespace(Tcl_Interp *interp) */ #if TCL_MAJOR_VERSION < 8 @@ -271,6 +307,178 @@ get_ip(self) ((ptr)->default_ns == (Tcl_Namespace*)NULL || Tcl_GetCurrentNamespace((ptr)->ip) != (ptr)->default_ns) #endif +/*-- Tcl_PopCallFrame & Tcl_PushCallFrame --*/ +#if TCL_MAJOR_VERSION >= 8 +# ifndef CallFrame +typedef struct CallFrame { + Tcl_Namespace *nsPtr; + int dummy1; + int dummy2; + char *dummy3; + struct CallFrame *callerPtr; + struct CallFrame *callerVarPtr; + int level; + char *dummy7; + char *dummy8; + int dummy9; + char* dummy10; +} CallFrame; +# endif + +# if !defined(TclGetFrame) && !defined(TclGetFrame_TCL_DECLARED) +EXTERN int TclGetFrame _((Tcl_Interp *, CONST char *, CallFrame **)); +# endif +# if defined(USE_TCL_STUBS) && !defined(USE_TCL_STUB_PROCS) +# ifndef TclGetFrame +# ifndef FunctionNum_of_GetFrame +#define FunctionNum_of_GetFrame 32 +# endif +struct DummyTclIntStubs_for_GetFrame { + int magic; + struct TclIntStubHooks *hooks; + void (*func[FunctionNum_of_GetFrame])(); + int (*tclGetFrame) _((Tcl_Interp *, CONST char *, CallFrame **)); +}; +#define TclGetFrame \ + (((struct DummyTclIntStubs_for_GetFrame *)tclIntStubsPtr)->tclGetFrame) +# endif +# endif + +# if !defined(Tcl_PopCallFrame) && !defined(Tcl_PopCallFrame_TCL_DECLARED) +EXTERN void Tcl_PopCallFrame _((Tcl_Interp *)); +EXTERN int Tcl_PushCallFrame _((Tcl_Interp *, Tcl_CallFrame *, Tcl_Namespace *, int)); +# endif +# if defined(USE_TCL_STUBS) && !defined(USE_TCL_STUB_PROCS) +# ifndef Tcl_PopCallFrame +# ifndef FunctionNum_of_PopCallFrame +#define FunctionNum_of_PopCallFrame 128 +# endif +struct DummyTclIntStubs_for_PopCallFrame { + int magic; + struct TclIntStubHooks *hooks; + void (*func[FunctionNum_of_PopCallFrame])(); + void (*tcl_PopCallFrame) _((Tcl_Interp *)); + int (*tcl_PushCallFrame) _((Tcl_Interp *, Tcl_CallFrame *, Tcl_Namespace *, int)); +}; + +#define Tcl_PopCallFrame \ + (((struct DummyTclIntStubs_for_PopCallFrame *)tclIntStubsPtr)->tcl_PopCallFrame) +#define Tcl_PushCallFrame \ + (((struct DummyTclIntStubs_for_PopCallFrame *)tclIntStubsPtr)->tcl_PushCallFrame) +# endif +# endif + +#else /* Tcl7.x */ +# ifndef CallFrame +typedef struct CallFrame { + Tcl_HashTable varTable; + int level; + int argc; + char **argv; + struct CallFrame *callerPtr; + struct CallFrame *callerVarPtr; +} CallFrame; +# endif +# ifndef Tcl_CallFrame +#define Tcl_CallFrame CallFrame +# endif + +# if !defined(TclGetFrame) && !defined(TclGetFrame_TCL_DECLARED) +EXTERN int TclGetFrame _((Tcl_Interp *, CONST char *, CallFrame **)); +# endif + +# if !defined(Tcl_PopCallFrame) && !defined(Tcl_PopCallFrame_TCL_DECLARED) +typedef struct DummyInterp { + char *dummy1; + char *dummy2; + int dummy3; + Tcl_HashTable dummy4; + Tcl_HashTable dummy5; + Tcl_HashTable dummy6; + int numLevels; + int maxNestingDepth; + CallFrame *framePtr; + CallFrame *varFramePtr; +} DummyInterp; + +static void +Tcl_PopCallFrame(interp) + Tcl_Interp *interp; +{ + DummyInterp *iPtr = (DummyInterp*)interp; + CallFrame *frame = iPtr->varFramePtr; + + /* **** DUMMY **** */ + iPtr->framePtr = frame.callerPtr; + iPtr->varFramePtr = frame.callerVarPtr; + + return TCL_OK; +} + +/* dummy */ +#define Tcl_Namespace char + +static int +Tcl_PushCallFrame(interp, framePtr, nsPtr, isProcCallFrame) + Tcl_Interp *interp; + Tcl_CallFrame *framePtr; + Tcl_Namespace *nsPtr; + int isProcCallFrame; +{ + DummyInterp *iPtr = (DummyInterp*)interp; + CallFrame *frame = (CallFrame *)framePtr; + + /* **** DUMMY **** */ + Tcl_InitHashTable(&frame.varTable, TCL_STRING_KEYS); + if (iPtr->varFramePtr != NULL) { + frame.level = iPtr->varFramePtr->level + 1; + } else { + frame.level = 1; + } + frame.callerPtr = iPtr->framePtr; + frame.callerVarPtr = iPtr->varFramePtr; + iPtr->framePtr = &frame; + iPtr->varFramePtr = &frame; + + return TCL_OK; +} +# endif + +#endif + +#endif /* TCL_NAMESPACE_DEBUG */ + + +/*---- class TclTkIp ----*/ +struct tcltkip { + Tcl_Interp *ip; /* the interpreter */ +#if TCL_NAMESPACE_DEBUG + Tcl_Namespace *default_ns; /* default namespace */ +#endif + int has_orig_exit; /* has original 'exit' command ? */ + Tcl_CmdInfo orig_exit_info; /* command info of original 'exit' command */ + int ref_count; /* reference count of rbtk_preserve_ip call */ + int allow_ruby_exit; /* allow exiting ruby by 'exit' function */ + int return_value; /* return value */ +}; + +static struct tcltkip * +get_ip(self) + VALUE self; +{ + struct tcltkip *ptr; + + Data_Get_Struct(self, struct tcltkip, ptr); + if (ptr == 0) { + /* rb_raise(rb_eTypeError, "uninitialized TclTkIp"); */ + return((struct tcltkip *)NULL); + } + if (ptr->ip == (Tcl_Interp*)NULL) { + /* rb_raise(rb_eRuntimeError, "deleted IP"); */ + } + return ptr; +} + /* increment/decrement reference count of tcltkip */ static int @@ -278,7 +486,12 @@ rbtk_preserve_ip(ptr) struct tcltkip *ptr; { ptr->ref_count++; - Tcl_Preserve((ClientData)ptr->ip); + if (ptr->ip == (Tcl_Interp*)NULL) { + /* deleted IP */ + ptr->ref_count = 0; + } else { + Tcl_Preserve((ClientData)ptr->ip); + } return(ptr->ref_count); } @@ -289,12 +502,69 @@ rbtk_release_ip(ptr) ptr->ref_count--; if (ptr->ref_count < 0) { ptr->ref_count = 0; + } else if (ptr->ip == (Tcl_Interp*)NULL) { + /* deleted IP */ + ptr->ref_count = 0; } else { Tcl_Release((ClientData)ptr->ip); } return(ptr->ref_count); } + +/* treat excetiopn on Tcl side */ +static VALUE rbtk_pending_exception; +static VALUE rbtk_eventloop_depth = 0; + + +static int +pending_exception_check0() +{ + volatile VALUE exc = rbtk_pending_exception; + + if (!NIL_P(exc) && rb_obj_is_kind_of(exc, rb_eException)) { + DUMP1("find a pending exception"); + if (rbtk_eventloop_depth > 0) { + return 1; /* pending */ + } else { + rbtk_pending_exception = Qnil; + rb_exc_raise(exc); + } + } else { + return 0; + } +} + +static int +pending_exception_check1(thr_crit_bup, ptr) + int thr_crit_bup; + struct tcltkip *ptr; +{ + volatile VALUE exc = rbtk_pending_exception; + + if (!NIL_P(exc) && rb_obj_is_kind_of(exc, rb_eException)) { + DUMP1("find a pending exception"); + + if (rbtk_eventloop_depth > 0) { + return 1; /* pending */ + } else { + rbtk_pending_exception = Qnil; + + if (ptr != (struct tcltkip *)NULL) { + /* Tcl_Release(ptr->ip); */ + rbtk_release_ip(ptr); + } + + rb_thread_critical = thr_crit_bup; + + rb_exc_raise(exc); + } + } else { + return 0; + } +} + + /* call original 'exit' command */ static void call_original_exit(ptr, state) @@ -454,7 +724,7 @@ ip_set_eventloop_tick(self, tick) struct tcltkip *ptr = get_ip(self); /* ip is deleted? */ - if (Tcl_InterpDeleted(ptr->ip)) { + if (ptr == (struct tcltkip *)NULL || ptr->ip == (Tcl_Interp*)NULL || Tcl_InterpDeleted(ptr->ip)) { DUMP1("ip is deleted"); return get_eventloop_tick(self); } @@ -507,7 +777,7 @@ ip_set_no_event_wait(self, wait) struct tcltkip *ptr = get_ip(self); /* ip is deleted? */ - if (Tcl_InterpDeleted(ptr->ip)) { + if (ptr == (struct tcltkip *)NULL || ptr->ip == (Tcl_Interp*)NULL || Tcl_InterpDeleted(ptr->ip)) { DUMP1("ip is deleted"); return get_no_event_wait(self); } @@ -563,7 +833,7 @@ ip_set_eventloop_weight(self, loop_max, no_event) struct tcltkip *ptr = get_ip(self); /* ip is deleted? */ - if (Tcl_InterpDeleted(ptr->ip)) { + if (ptr == (struct tcltkip *)NULL || ptr->ip == (Tcl_Interp*)NULL || Tcl_InterpDeleted(ptr->ip)) { DUMP1("ip is deleted"); return get_eventloop_weight(self); } @@ -615,6 +885,19 @@ set_max_block_time(self, time) return Qnil; } +static VALUE +lib_evloop_thread_p(self) + VALUE self; +{ + if (NIL_P(eventloop_thread)) { + return Qnil; /* no eventloop */ + } else if (rb_thread_current() == eventloop_thread) { + return Qtrue; /* is eventloop */ + } else { + return Qfalse; /* not eventloop */ + } +} + static VALUE lib_evloop_abort_on_exc(self) VALUE self; @@ -659,7 +942,7 @@ ip_evloop_abort_on_exc_set(self, val) rb_secure(4); /* ip is deleted? */ - if (Tcl_InterpDeleted(ptr->ip)) { + if (ptr == (struct tcltkip *)NULL || ptr->ip == (Tcl_Interp*)NULL || Tcl_InterpDeleted(ptr->ip)) { DUMP1("ip is deleted"); return lib_evloop_abort_on_exc(self); } @@ -678,6 +961,48 @@ lib_num_of_mainwindows(self) return INT2FIX(Tk_GetNumMainWindows()); } + +static VALUE +call_DoOneEvent(flag_val) + VALUE flag_val; +{ + int flag; + + flag = FIX2INT(flag_val); + if (Tcl_DoOneEvent(flag)) { + return Qtrue; + } else { + return Qfalse; + } +} + +static VALUE +eventloop_sleep(dummy) + VALUE dummy; +{ + struct timeval t; + + t.tv_sec = (time_t)0; + t.tv_usec = (time_t)(no_event_wait*1000.0); + +#ifdef HAVE_NATIVETHREAD + if (!is_ruby_native_thread()) { + rb_bug("cross-thread violation on eventloop_sleep()"); + } +#endif + + rb_thread_wait_for(t); + +#ifdef HAVE_NATIVETHREAD + if (!is_ruby_native_thread()) { + rb_bug("cross-thread violation on eventloop_sleep()"); + } +#endif + + return Qnil; +} + + static int lib_eventloop_core(check_root, update_flag, check_var) int check_root; @@ -689,6 +1014,8 @@ lib_eventloop_core(check_root, update_flag, check_var) int event_flag; struct timeval t; int thr_crit_bup; + int status; + int depth = rbtk_eventloop_depth; if (update_flag) DUMP1("update loop start!!"); @@ -732,7 +1059,53 @@ lib_eventloop_core(check_root, update_flag, check_var) } } - found_event = Tcl_DoOneEvent(event_flag); + /* found_event = Tcl_DoOneEvent(event_flag); */ + found_event = RTEST(rb_protect(call_DoOneEvent, + INT2FIX(event_flag), &status)); + if (status) { + switch (status) { + case TAG_RAISE: + if (NIL_P(ruby_errinfo)) { + rbtk_pending_exception + = rb_exc_new2(rb_eException, "unknown exception"); + } else { + rbtk_pending_exception = ruby_errinfo; + + if (!NIL_P(rbtk_pending_exception)) { + if (rbtk_eventloop_depth == 0) { + VALUE exc = rbtk_pending_exception; + rbtk_pending_exception = 0; + rb_exc_raise(exc); + } else { + return 0; + } + } + } + break; + + case TAG_FATAL: + if (NIL_P(ruby_errinfo)) { + rb_exc_raise(rb_exc_new2(rb_eFatal, "FATAL")); + } else { + rb_exc_faise(ruby_errinfo); + } + } + } + + if (depth != rbtk_eventloop_depth) { + DUMP2("DoOneEvent(1) abnormal exit!! %d", + rbtk_eventloop_depth); + } + + if (check_var != (int*)NULL && !NIL_P(rbtk_pending_exception)) { + DUMP1("exception on wait"); + return 0; + } + + if (pending_exception_check0()) { + /* pending -> upper level */ + return 0; + } if (update_flag != 0) { if (found_event) { @@ -744,11 +1117,27 @@ lib_eventloop_core(check_root, update_flag, check_var) } } + DUMP1("trap check"); + if (rb_trap_pending) { + run_timer_flag = 0; + if (rb_prohibit_interrupt || check_var != (int*)NULL) { + /* pending or on wait command */ + return 0; + } else { + rb_trap_exec(); + } + } + DUMP1("check Root Widget"); if (check_root && Tk_GetNumMainWindows() == 0) { run_timer_flag = 0; - if (!rb_prohibit_interrupt) { - if (rb_trap_pending) rb_trap_exec(); + if (rb_trap_pending) { + if (rb_prohibit_interrupt || check_var != (int*)NULL) { + /* pending or on wait command */ + return 0; + } else { + rb_trap_exec(); + } } return 1; } @@ -781,34 +1170,152 @@ lib_eventloop_core(check_root, update_flag, check_var) } } - if (Tcl_DoOneEvent(event_flag)) { - tick_counter++; - } else { - if (update_flag != 0) { - DUMP1("update complete"); + if (NIL_P(eventloop_thread) || current == eventloop_thread) { + int st; + int status; + + /* st = Tcl_DoOneEvent(event_flag); */ + st = RTEST(rb_protect(call_DoOneEvent, + INT2FIX(event_flag), &status)); + if (status) { + switch (status) { + case TAG_RAISE: + if (NIL_P(ruby_errinfo)) { + rbtk_pending_exception + = rb_exc_new2(rb_eException, + "unknown exception"); + } else { + rbtk_pending_exception = ruby_errinfo; + + if (!NIL_P(rbtk_pending_exception)) { + if (rbtk_eventloop_depth == 0) { + VALUE exc = rbtk_pending_exception; + rbtk_pending_exception = 0; + rb_exc_raise(exc); + } else { + return 0; + } + } + } + break; + + case TAG_FATAL: + if (NIL_P(ruby_errinfo)) { + rb_exc_raise(rb_exc_new2(rb_eFatal, "FATAL")); + } else { + rb_exc_raise(ruby_errinfo); + } + } + } + + if (depth != rbtk_eventloop_depth) { + DUMP2("DoOneEvent(2) abnormal exit!! %d", + rbtk_eventloop_depth); return 0; } - tick_counter += no_event_tick; - rb_thread_wait_for(t); - } - if (watchdog_thread != 0 && eventloop_thread != current) { - return 1; - } + DUMP1("trap check"); + if (rb_trap_pending) { + run_timer_flag = 0; + if (rb_prohibit_interrupt || check_var != (int*)NULL) { + /* pending or on wait command */ + return 0; + } else { + rb_trap_exec(); + } + } - DUMP1("check Root Widget"); - if (check_root && Tk_GetNumMainWindows() == 0) { - run_timer_flag = 0; - if (!rb_prohibit_interrupt) { - if (rb_trap_pending) rb_trap_exec(); + if (check_var != (int*)NULL + && !NIL_P(rbtk_pending_exception)) { + DUMP1("exception on wait"); + return 0; } - return 1; - } - DUMP1("trap check"); - if (!rb_prohibit_interrupt) { - if (rb_trap_pending) rb_trap_exec(); - } + if (pending_exception_check0()) { + /* pending -> upper level */ + return 0; + } + + if (st) { + tick_counter++; + } else { + if (update_flag != 0) { + DUMP1("update complete"); + return 0; + } + + tick_counter += no_event_tick; + + /* rb_thread_wait_for(t); */ + rb_protect(eventloop_sleep, Qnil, &status); + + if (status) { + switch (status) { + case TAG_RAISE: + if (NIL_P(ruby_errinfo)) { + rbtk_pending_exception + = rb_exc_new2(rb_eException, + "unknown exception"); + } else { + rbtk_pending_exception = ruby_errinfo; + + if (!NIL_P(rbtk_pending_exception)) { + if (rbtk_eventloop_depth == 0) { + VALUE exc = rbtk_pending_exception; + rbtk_pending_exception = 0; + rb_exc_raise(exc); + } else { + return 0; + } + } + } + break; + + case TAG_FATAL: + if (NIL_P(ruby_errinfo)) { + rb_exc_raise(rb_exc_new2(rb_eFatal, + "FATAL")); + } else { + rb_exc_raise(ruby_errinfo); + } + } + } + } + + } else { + DUMP2("sleep eventloop %lx", current); + DUMP2("eventloop thread is %lx", eventloop_thread); + rb_thread_stop(); + } + + if (!NIL_P(watchdog_thread) && eventloop_thread != current) { + return 1; + } + + DUMP1("trap check"); + if (rb_trap_pending) { + run_timer_flag = 0; + if (rb_prohibit_interrupt || check_var != (int*)NULL) { + /* pending or on wait command */ + return 0; + } else { + rb_trap_exec(); + } + } + + DUMP1("check Root Widget"); + if (check_root && Tk_GetNumMainWindows() == 0) { + run_timer_flag = 0; + if (rb_trap_pending) { + if (rb_prohibit_interrupt || check_var != (int*)NULL) { + /* pending or on wait command */ + return 0; + } else { + rb_trap_exec(); + } + } + return 1; + } if (loop_counter++ > 30000) { /* fprintf(stderr, "loop_counter > 30000\n"); */ @@ -832,13 +1339,24 @@ lib_eventloop_core(check_root, update_flag, check_var) return 1; } + +struct evloop_params { + int check_root; + int update_flag; + int *check_var; +}; + VALUE -lib_eventloop_main(check_rootwidget) - VALUE check_rootwidget; +lib_eventloop_main_core(args) + VALUE args; { - check_rootwidget_flag = RTEST(check_rootwidget); + struct evloop_params *params = (struct evloop_params *)args; + + check_rootwidget_flag = params->check_root; - if (lib_eventloop_core(check_rootwidget_flag, 0, (int *)NULL)) { + if (lib_eventloop_core(params->check_root, + params->update_flag, + params->check_var)) { return Qtrue; } else { return Qfalse; @@ -846,35 +1364,121 @@ lib_eventloop_main(check_rootwidget) } VALUE -lib_eventloop_ensure(parent_evloop) - VALUE parent_evloop; +lib_eventloop_main(args) + VALUE args; +{ + volatile VALUE ret; + int status = 0; + + /* ret = rb_protect(lib_eventloop_main_core, args, &status); */ + ret = lib_eventloop_main_core(args); + + switch (status) { + case TAG_RAISE: + if (NIL_P(ruby_errinfo)) { + rbtk_pending_exception + = rb_exc_new2(rb_eException, "unknown exception"); + } else { + rbtk_pending_exception = ruby_errinfo; + } + return Qnil; + + case TAG_FATAL: + if (NIL_P(ruby_errinfo)) { + rbtk_pending_exception = rb_exc_new2(rb_eFatal, "FATAL"); + } else { + rbtk_pending_exception = ruby_errinfo; + } + return Qnil; + } + + return ret; +} + +VALUE +lib_eventloop_ensure(args) + VALUE args; { + struct evloop_params *ptr = (struct evloop_params *)args; + volatile VALUE current_evloop = rb_thread_current(); + Tk_DeleteTimerHandler(timer_token); timer_token = (Tcl_TimerToken)NULL; - DUMP2("eventloop-ensure: current-thread : %lx\n", rb_thread_current()); - DUMP2("eventloop-ensure: eventloop-thread : %lx\n", eventloop_thread); - if (eventloop_thread == rb_thread_current()) { - DUMP2("eventloop-thread -> %lx\n", parent_evloop); - eventloop_thread = parent_evloop; + + DUMP2("eventloop_ensure: current-thread : %lx", current_evloop); + DUMP2("eventloop_ensure: eventloop-thread : %lx", eventloop_thread); + if (eventloop_thread != current_evloop) { + DUMP2("finish eventloop %lx (NOT current eventloop)", current_evloop); + return Qnil; + } + + while(eventloop_thread = rb_ary_pop(eventloop_stack)) { + DUMP2("eventloop-ensure: new eventloop-thread -> %lx", + eventloop_thread); + + if (eventloop_thread == current_evloop) { + rbtk_eventloop_depth--; + DUMP2("eventloop %lx : back from recursive call", current_evloop); + break; + } + + if (NIL_P(eventloop_thread)) break; + + if (RTEST(rb_funcall(eventloop_thread, ID_alive_p, 0, 0))) { + DUMP2("eventloop-enshure: wake up parent %lx", eventloop_thread); + rb_thread_wakeup(eventloop_thread); + + break; + } } + + free(ptr); + + DUMP2("finish current eventloop %lx", current_evloop); return Qnil; } static VALUE -lib_eventloop_launcher(check_rootwidget) - VALUE check_rootwidget; +lib_eventloop_launcher(check_root, update_flag, check_var) + int check_root; + int update_flag; + int *check_var; { - VALUE parent_evloop = eventloop_thread; + volatile VALUE parent_evloop = eventloop_thread; + int depth = rbtk_eventloop_depth; + struct evloop_params *args = ALLOC(struct evloop_params); eventloop_thread = rb_thread_current(); + if (ruby_debug) { + if (parent_evloop == eventloop_thread) { + DUMP2("eventloop: recursive call on %lx", parent_evloop); + } + } + if (parent_evloop == eventloop_thread) rbtk_eventloop_depth++; + + if (!NIL_P(parent_evloop) && parent_evloop != eventloop_thread) { + DUMP2("wait for stop of parent_evloop %lx", parent_evloop); + while(!RTEST(rb_funcall(parent_evloop, ID_stop_p, 0))) { + DUMP2("parent_evloop %lx doesn't stop", parent_evloop); + rb_thread_run(parent_evloop); + } + DUMP1("succeed to stop parent"); + } + + rb_ary_push(eventloop_stack, parent_evloop); + if (ruby_debug) { fprintf(stderr, "tcltklib: eventloop-thread : %lx -> %lx\n", parent_evloop, eventloop_thread); } - return rb_ensure(lib_eventloop_main, check_rootwidget, - lib_eventloop_ensure, parent_evloop); + args->check_root = check_root; + args->update_flag = update_flag; + args->check_var = check_var; + + return rb_ensure(lib_eventloop_main, (VALUE)args, + lib_eventloop_ensure, (VALUE)args); } /* execute Tk_MainLoop */ @@ -894,7 +1498,7 @@ lib_mainloop(argc, argv, self) check_rootwidget = Qfalse; } - return lib_eventloop_launcher(check_rootwidget); + return lib_eventloop_launcher(RTEST(check_rootwidget), 0, (int*)NULL); } static VALUE @@ -906,7 +1510,8 @@ ip_mainloop(argc, argv, self) struct tcltkip *ptr = get_ip(self); /* ip is deleted? */ - if (Tcl_InterpDeleted(ptr->ip)) { + if (ptr == (struct tcltkip *)NULL || ptr->ip == (Tcl_Interp*)NULL + || Tcl_InterpDeleted(ptr->ip)) { DUMP1("ip is deleted"); return Qnil; } @@ -918,7 +1523,17 @@ ip_mainloop(argc, argv, self) return lib_mainloop(argc, argv, self); } -VALUE + +static VALUE +watchdog_evloop_launcher(check_rootwidget) + VALUE check_rootwidget; +{ + return lib_eventloop_launcher(RTEST(check_rootwidget), 0, (int*)NULL); +} + +#define EVLOOP_WAKEUP_CHANCE 3 + +static VALUE lib_watchdog_core(check_rootwidget) VALUE check_rootwidget; { @@ -934,7 +1549,7 @@ lib_watchdog_core(check_rootwidget) t1.tv_usec = (time_t)((WATCHDOG_INTERVAL)*1000.0); /* check other watchdog thread */ - if (watchdog_thread != 0) { + if (!NIL_P(watchdog_thread)) { if (RTEST(rb_funcall(watchdog_thread, ID_stop_p, 0))) { rb_funcall(watchdog_thread, ID_kill, 0); } else { @@ -945,23 +1560,24 @@ lib_watchdog_core(check_rootwidget) /* watchdog start */ do { - if (eventloop_thread == 0 - || (loop_counter == prev_val - && RTEST(rb_funcall(eventloop_thread, ID_stop_p, 0)) - && ++chance >= 3 ) - ) { + if (NIL_P(eventloop_thread) + || (loop_counter == prev_val && chance >= EVLOOP_WAKEUP_CHANCE)) { /* start new eventloop thread */ DUMP2("eventloop thread %lx is sleeping or dead", eventloop_thread); - evloop = rb_thread_create(lib_eventloop_launcher, + evloop = rb_thread_create(watchdog_evloop_launcher, (void*)&check_rootwidget); DUMP2("create new eventloop thread %lx", evloop); loop_counter = -1; chance = 0; rb_thread_run(evloop); } else { - loop_counter = prev_val; - chance = 0; + prev_val = loop_counter; + if (RTEST(rb_funcall(eventloop_thread, ID_stop_p, 0))) { + ++chance; + } else { + chance = 0; + } if (event_loop_wait_event) { rb_thread_wait_for(t0); } else { @@ -978,7 +1594,7 @@ VALUE lib_watchdog_ensure(arg) VALUE arg; { - eventloop_thread = 0; /* stop eventloops */ + eventloop_thread = Qnil; /* stop eventloops */ return Qnil; } @@ -1011,7 +1627,7 @@ ip_mainloop_watchdog(argc, argv, self) struct tcltkip *ptr = get_ip(self); /* ip is deleted? */ - if (Tcl_InterpDeleted(ptr->ip)) { + if (ptr == (struct tcltkip *)NULL || ptr->ip == (Tcl_Interp*)NULL || Tcl_InterpDeleted(ptr->ip)) { DUMP1("ip is deleted"); return Qnil; } @@ -1023,6 +1639,108 @@ ip_mainloop_watchdog(argc, argv, self) return lib_mainloop_watchdog(argc, argv, self); } + +/* thread-safe(?) interaction between Ruby and Tk */ +struct thread_call_proc_arg { + VALUE proc; + int *done; +}; + +void +_thread_call_proc_arg_mark(struct thread_call_proc_arg *q) +{ + rb_gc_mark(q->proc); +} + +static VALUE +_thread_call_proc_core(arg) + VALUE arg; +{ + struct thread_call_proc_arg *q = (struct thread_call_proc_arg*)arg; + return rb_funcall(q->proc, ID_call, 0); +} + +static VALUE +_thread_call_proc_ensure(arg) + VALUE arg; +{ + struct thread_call_proc_arg *q = (struct thread_call_proc_arg*)arg; + *(q->done) = 1; + return Qnil; +} + +static VALUE +_thread_call_proc(arg) + VALUE arg; +{ + struct thread_call_proc_arg *q = (struct thread_call_proc_arg*)arg; + + return rb_ensure(_thread_call_proc_core, (VALUE)q, + _thread_call_proc_ensure, (VALUE)q); +} + +static VALUE +_thread_call_proc_value(th) + VALUE th; +{ + return rb_funcall(th, ID_value, 0); +} + +static VALUE +lib_thread_callback(argc, argv, self) + int argc; + VALUE *argv; + VALUE self; +{ + struct thread_call_proc_arg *q; + VALUE proc, th, ret; + int status, foundEvent; + + if (rb_scan_args(argc, argv, "01", &proc) == 0) { + proc = rb_block_proc(); + } + + q = (struct thread_call_proc_arg *)ALLOC(struct thread_call_proc_arg); + q->proc = proc; + q->done = (int*)ALLOC(int); + *(q->done) = 0; + + /* create call-proc thread */ + th = rb_thread_create(_thread_call_proc, (void*)q); + + rb_thread_schedule(); + + /* start sub-eventloop */ + foundEvent = lib_eventloop_launcher(/* not check root-widget */0, 0, + q->done); + + if (RTEST(rb_funcall(th, ID_alive_p, 0))) { + rb_funcall(th, ID_kill, 0); + ret = Qnil; + } else { + ret = rb_protect(_thread_call_proc_value, th, &status); + } + + free(q->done); + free(q); + + if (NIL_P(rbtk_pending_exception)) { + /* return ruby_errinfo; */ + if (status) { + rb_exc_raise(ruby_errinfo); + } + } else { + VALUE exc = rbtk_pending_exception; + rbtk_pending_exception = Qnil; + /* return exc; */ + rb_exc_raise(exc); + } + + return ret; +} + + +/* do_one_event */ static VALUE lib_do_one_event_core(argc, argv, self, is_ip) int argc; @@ -1034,6 +1752,10 @@ lib_do_one_event_core(argc, argv, self, is_ip) int flags; int found_event; + if (eventloop_thread) { + rb_raise(rb_eRuntimeError, "eventloop is already running"); + } + if (rb_scan_args(argc, argv, "01", &vflags) == 0) { flags = TCL_ALL_EVENTS | TCL_DONT_WAIT; } else { @@ -1050,7 +1772,8 @@ lib_do_one_event_core(argc, argv, self, is_ip) struct tcltkip *ptr = get_ip(self); /* ip is deleted? */ - if (Tcl_InterpDeleted(ptr->ip)) { + if (ptr == (struct tcltkip *)NULL || ptr->ip == (Tcl_Interp*)NULL + || Tcl_InterpDeleted(ptr->ip)) { DUMP1("ip is deleted"); return Qfalse; } @@ -1064,6 +1787,10 @@ lib_do_one_event_core(argc, argv, self, is_ip) /* found_event = Tcl_DoOneEvent(TCL_ALL_EVENTS | TCL_DONT_WAIT); */ found_event = Tcl_DoOneEvent(flags); + if (pending_exception_check0()) { + return Qfalse; + } + if (found_event) { return Qtrue; } else { @@ -1187,11 +1914,6 @@ ip_ruby_eval_rescue(failed, einfo) return Qnil; } -struct eval_body_arg { - char *string; - VALUE failed; -}; - static VALUE ip_ruby_eval_body(arg) struct eval_body_arg *arg; @@ -1279,7 +2001,6 @@ ip_ruby_eval_body(arg) break; case TAG_RAISE: - case TAG_FATAL: if (NIL_P(ruby_errinfo)) { RARRAY(arg->failed)->ptr[0] = rb_exc_new2(rb_eException, "unknown exception"); @@ -1288,6 +2009,15 @@ ip_ruby_eval_body(arg) } break; + case TAG_FATAL: + if (NIL_P(ruby_errinfo)) { + RARRAY(arg->failed)->ptr[0] + = rb_exc_new2(rb_eFatal, "FATAL"); + } else { + RARRAY(arg->failed)->ptr[0] = ruby_errinfo; + } + break; + case TAG_THROW: if (NIL_P(ruby_errinfo)) { rb_jump_tag(TAG_THROW); @@ -1345,10 +2075,31 @@ ip_ruby_eval(clientData, interp, argc, argv) struct eval_body_arg *arg; int thr_crit_bup; + if (interp == (Tcl_Interp*)NULL) { + rbtk_pending_exception = rb_exc_new2(rb_eRuntimeError, + "IP is deleted"); + return TCL_ERROR; + } + /* ruby command has 1 arg. */ if (argc != 2) { - rb_raise(rb_eArgError, "wrong number of arguments (%d for 1)", - argc - 1); +#if 0 + rb_raise(rb_eArgError, + "wrong number of arguments (%d for 1)", argc - 1); +#else + char buf[sizeof(int)*8 + 1]; + Tcl_ResetResult(interp); + sprintf(buf, "%d", argc-1); + Tcl_AppendResult(interp, "wrong number of arguments (", + buf, " for 1)", (char *)NULL); +#if TCL_MAJOR_VERSION >= 8 + rbtk_pending_exception = rb_exc_new2(rb_eArgError, + Tcl_GetStringResult(interp)); +#else + rbtk_pending_exception = rb_exc_new2(rb_eArgError, interp->result); +#endif + return TCL_ERROR; +#endif } /* allocate */ @@ -1435,9 +2186,15 @@ ip_ruby_eval(clientData, interp, argc, argv) return TCL_CONTINUE; } else if (eclass == rb_eSystemExit) { + ip_set_exc_message(interp, res); + rbtk_pending_exception = res; + return TCL_RETURN; + +#if 0 thr_crit_bup = rb_thread_critical; rb_thread_critical = Qtrue; +#if 0 /* REMOVE : fail to rescue SystemExit */ /* Tcl_Eval(interp, "destroy ."); */ if (Tk_GetNumMainWindows() > 0) { Tk_Window main_win = Tk_MainWindow(interp); @@ -1445,6 +2202,7 @@ ip_ruby_eval(clientData, interp, argc, argv) Tk_DestroyWindow(main_win); } } +#endif /* StringValue(res); */ res = rb_funcall(res, ID_message, 0, 0); @@ -1454,6 +2212,11 @@ ip_ruby_eval(clientData, interp, argc, argv) rb_thread_critical = thr_crit_bup; rb_raise(rb_eSystemExit, RSTRING(res)->ptr); +#endif + } else if (eclass == rb_eInterrupt) { + ip_set_exc_message(interp, res); + rbtk_pending_exception = res; + return TCL_RETURN; } else if (rb_obj_is_kind_of(res, eLocalJumpError)) { VALUE reason = rb_ivar_get(res, ID_at_reason); @@ -1509,13 +2272,6 @@ ip_ruby_eval(clientData, interp, argc, argv) /* Tcl command `ruby_cmd' */ -struct cmd_body_arg { - VALUE receiver; - ID method; - VALUE args; - VALUE failed; -}; - static VALUE ip_ruby_cmd_core(arg) struct cmd_body_arg *arg; @@ -1632,7 +2388,6 @@ ip_ruby_cmd_body(arg) break; case TAG_RAISE: - case TAG_FATAL: if (NIL_P(ruby_errinfo)) { RARRAY(arg->failed)->ptr[0] = rb_exc_new2(rb_eException, "unknown exception"); @@ -1641,6 +2396,15 @@ ip_ruby_cmd_body(arg) } break; + case TAG_FATAL: + if (NIL_P(ruby_errinfo)) { + RARRAY(arg->failed)->ptr[0] + = rb_exc_new2(rb_eFatal, "FATAL"); + } else { + RARRAY(arg->failed)->ptr[0] = ruby_errinfo; + } + break; + case TAG_THROW: if (NIL_P(ruby_errinfo)) { rb_jump_tag(TAG_THROW); @@ -1706,8 +2470,26 @@ ip_ruby_cmd(clientData, interp, argc, argv) int thr_crit_bup; VALUE old_gc; + if (interp == (Tcl_Interp*)NULL) { + rbtk_pending_exception = rb_exc_new2(rb_eRuntimeError, + "IP is deleted"); + return TCL_ERROR; + } + if (argc < 3) { +#if 0 rb_raise(rb_eArgError, "too few arguments"); +#else + Tcl_ResetResult(interp); + Tcl_AppendResult(interp, "too few arguments", (char *)NULL); +#if TCL_MAJOR_VERSION >= 8 + rbtk_pending_exception = rb_exc_new2(rb_eArgError, + Tcl_GetStringResult(interp)); +#else + rbtk_pending_exception = rb_exc_new2(rb_eArgError, interp->result); +#endif + return TCL_ERROR; +#endif } /* allocate */ @@ -1744,8 +2526,21 @@ ip_ruby_cmd(clientData, interp, argc, argv) free(buf); } if (NIL_P(receiver)) { - rb_raise(rb_eArgError, "unknown class/module/global-variable '%s'", - str); +#if 0 + rb_raise(rb_eArgError, + "unknown class/module/global-variable '%s'", str); +#else + Tcl_ResetResult(interp); + Tcl_AppendResult(interp, "unknown class/module/global-variable '", + str, "'", (char *)NULL); +#if TCL_MAJOR_VERSION >= 8 + rbtk_pending_exception = rb_exc_new2(rb_eArgError, + Tcl_GetStringResult(interp)); +#else + rbtk_pending_exception = rb_exc_new2(rb_eArgError, interp->result); +#endif + return TCL_ERROR; +#endif } /* get metrhod */ @@ -1832,9 +2627,15 @@ ip_ruby_cmd(clientData, interp, argc, argv) return TCL_CONTINUE; } else if (eclass == rb_eSystemExit) { + ip_set_exc_message(interp, res); + rbtk_pending_exception = res; + return TCL_RETURN; + +#if 0 thr_crit_bup = rb_thread_critical; rb_thread_critical = Qtrue; +#if 0 /* REMOVE : fail to rescue SystemExit */ /* Tcl_Eval(interp, "destroy ."); */ if (Tk_GetNumMainWindows() > 0) { Tk_Window main_win = Tk_MainWindow(interp); @@ -1842,6 +2643,7 @@ ip_ruby_cmd(clientData, interp, argc, argv) Tk_DestroyWindow(main_win); } } +#endif /* StringValue(res); */ res = rb_funcall(res, ID_message, 0, 0); @@ -1851,6 +2653,11 @@ ip_ruby_cmd(clientData, interp, argc, argv) rb_thread_critical = thr_crit_bup; rb_raise(rb_eSystemExit, RSTRING(res)->ptr); +#endif + } else if (eclass == rb_eInterrupt) { + ip_set_exc_message(interp, res); + rbtk_pending_exception = res; + return TCL_RETURN; } else if (rb_obj_is_kind_of(res, eLocalJumpError)) { VALUE reason = rb_ivar_get(res, ID_at_reason); @@ -1911,6 +2718,9 @@ ip_ruby_cmd(clientData, interp, argc, argv) } +/*****************************/ +/* relpace of 'exit' command */ +/*****************************/ static int #if TCL_MAJOR_VERSION >= 8 ip_InterpExitObjCmd(clientData, interp, argc, argv) @@ -1926,9 +2736,17 @@ ip_InterpExitCommand(clientData, interp, argc, argv) char *argv[]; #endif { - if (!Tcl_InterpDeleted(interp) && !ip_null_namespace(interp)) { - Tcl_Preserve(interp); - Tcl_Eval(interp, "interp eval {} {destroy .}; interp delete {}"); + if (interp != (Tcl_Interp*)NULL + && !Tcl_InterpDeleted(interp) +#if TCL_NAMESPACE_DEBUG + && !ip_null_namespace(interp) +#endif + ) { + Tcl_ResetResult(interp); + /* Tcl_Preserve(interp); */ + /* Tcl_Eval(interp, "interp eval {} {destroy .}; interp delete {}"); */ + ip_finalize(interp); + Tcl_DeleteInterp(interp); Tcl_Release(interp); } return TCL_OK; @@ -1960,32 +2778,46 @@ ip_RubyExitCommand(clientData, interp, argc, argv) cmd = argv[0]; #endif + if (argc < 1 || argc > 2) { + /* arguemnt error */ + Tcl_AppendResult(interp, + "wrong number of arguments: should be \"", + cmd, " ?returnCode?\"", (char *)NULL); + return TCL_ERROR; + } + + if (interp == (Tcl_Interp*)NULL) return TCL_OK; + + Tcl_ResetResult(interp); + if (rb_safe_level() >= 4) { - rb_raise(rb_eSecurityError, - "Insecure operation `exit' at level %d", - rb_safe_level()); + ip_finalize(interp); + Tcl_DeleteInterp(interp); + Tcl_Release(interp); + return TCL_OK; + } else if (Tcl_IsSafe(interp)) { - rb_raise(rb_eSecurityError, - "Insecure operation `exit' on a safe interpreter"); -#if 0 - } else if (Tcl_GetMaster(interp) != (Tcl_Interp *)NULL) { - Tcl_Preserve(interp); - Tcl_Eval(interp, "interp eval {} {destroy .}"); - Tcl_Eval(interp, "interp delete {}"); + ip_finalize(interp); + Tcl_DeleteInterp(interp); Tcl_Release(interp); return TCL_OK; -#endif } - Tcl_ResetResult(interp); - switch(argc) { case 1: - rb_exit(0); /* not return if succeed */ - + /* rb_exit(0); */ /* not return if succeed */ Tcl_AppendResult(interp, "fail to call \"", cmd, "\"", (char *)NULL); - return TCL_ERROR; + +#if TCL_MAJOR_VERSION >= 8 + rbtk_pending_exception = rb_exc_new2(rb_eSystemExit, + Tcl_GetStringResult(interp)); +#else + rbtk_pending_exception = rb_exc_new2(rb_eSystemExit, interp->result); +#endif + rb_iv_set(rbtk_pending_exception, "status", INT2FIX(0)); + + return TCL_RETURN; case 2: #if TCL_MAJOR_VERSION >= 8 @@ -2002,11 +2834,21 @@ ip_RubyExitCommand(clientData, interp, argc, argv) } param = argv[1]; #endif - rb_exit(state); /* not return if succeed */ + /* rb_exit(state); */ /* not return if succeed */ Tcl_AppendResult(interp, "fail to call \"", cmd, " ", param, "\"", (char *)NULL); - return TCL_ERROR; + +#if TCL_MAJOR_VERSION >= 8 + rbtk_pending_exception = rb_exc_new2(rb_eSystemExit, + Tcl_GetStringResult(interp)); +#else + rbtk_pending_exception = rb_exc_new2(rb_eSystemExit, interp->result); +#endif + rb_iv_set(rbtk_pending_exception, "status", INT2FIX(state)); + + return TCL_RETURN; + default: /* arguemnt error */ Tcl_AppendResult(interp, @@ -2021,13 +2863,6 @@ ip_RubyExitCommand(clientData, interp, argc, argv) /* based on tclEvent.c */ /**************************/ -#if 0 /* - Disable the following "update" and "thread_update". Bcause, - they don't work in a callback-proc. After calling update in - a callback-proc, the callback proc never be worked. - If the problem will be fixed in the future, may enable the - functions. - */ /*********************/ /* replace of update */ /*********************/ @@ -2059,6 +2894,17 @@ ip_rbUpdateCommand(clientData, interp, objc, objv) int dummy; DUMP1("Ruby's 'update' is called"); + if (interp == (Tcl_Interp*)NULL) { + rbtk_pending_exception = rb_exc_new2(rb_eRuntimeError, + "IP is deleted"); + return TCL_ERROR; + } +#ifdef HAVE_NATIVETHREAD + if (!is_ruby_native_thread()) { + rb_bug("cross-thread violation on ip_ruby_eval()"); + } +#endif + if (objc == 1) { flags = TCL_ALL_EVENTS|TCL_DONT_WAIT; @@ -2093,12 +2939,33 @@ ip_rbUpdateCommand(clientData, interp, objc, objv) return TCL_ERROR; } + Tcl_Preserve(interp); + /* call eventloop */ -#if 1 - ret = lib_eventloop_core(0, flags, (int *)NULL); /* ignore result */ -#else - Tcl_UpdateObjCmd(clientData, interp, objc, objv); -#endif + /* ret = lib_eventloop_core(0, flags, (int *)NULL);*/ /* ignore result */ + ret = lib_eventloop_launcher(0, flags, (int *)NULL); /* ignore result */ + + /* exception check */ + if (!NIL_P(rbtk_pending_exception)) { + Tcl_Release(interp); + + /* + if (rb_obj_is_kind_of(rbtk_pending_exception, rb_eSystemExit)) { + */ + if (rb_obj_is_kind_of(rbtk_pending_exception, rb_eSystemExit) + || rb_obj_is_kind_of(rbtk_pending_exception, rb_eInterrupt)) { + return TCL_RETURN; + } else{ + return TCL_ERROR; + } + } + + /* trap check */ + if (rb_trap_pending) { + Tcl_Release(interp); + + return TCL_RETURN; + } /* * Must clear the interpreter's result because event handlers could @@ -2107,6 +2974,8 @@ ip_rbUpdateCommand(clientData, interp, objc, objv) DUMP2("last result '%s'", Tcl_GetStringResult(interp)); Tcl_ResetResult(interp); + Tcl_Release(interp); + DUMP1("finish Ruby's 'update'"); return TCL_OK; } @@ -2164,25 +3033,24 @@ ip_rb_threadUpdateCommand(clientData, interp, objc, objv) volatile VALUE current_thread = rb_thread_current(); DUMP1("Ruby's 'thread_update' is called"); + if (interp == (Tcl_Interp*)NULL) { + rbtk_pending_exception = rb_exc_new2(rb_eRuntimeError, + "IP is deleted"); + return TCL_ERROR; + } +#ifdef HAVE_NATIVETHREAD + if (!is_ruby_native_thread()) { + rb_bug("cross-thread violation on ip_ruby_eval()"); + } +#endif if (rb_thread_alone() || eventloop_thread == current_thread) { -#define USE_TCL_UPDATE 0 #if TCL_MAJOR_VERSION >= 8 -# if USE_TCL_UPDATE - DUMP1("call Tcl_UpdateObjCmd"); - return Tcl_UpdateObjCmd(clientData, interp, objc, objv); -# else DUMP1("call ip_rbUpdateObjCmd"); return ip_rbUpdateObjCmd(clientData, interp, objc, objv); -# endif #else /* TCL_MAJOR_VERSION < 8 */ -# if USE_TCL_UPDATE - DUMP1("call ip_rbUpdateCommand"); - return Tcl_UpdateCommand(clientData, interp, objc, objv); -# else DUMP1("call ip_rbUpdateCommand"); return ip_rbUpdateCommand(clientData, interp, objc, objv); -# endif #endif } @@ -2243,7 +3111,6 @@ ip_rb_threadUpdateCommand(clientData, interp, objc, objv) DUMP1("finish Ruby's 'thread_update'"); return TCL_OK; } -#endif /* update and thread_update don't work */ /***************************/ @@ -2276,6 +3143,7 @@ VwaitVarProc(clientData, interp, name1, name2, flags) return (char *) NULL; } + #if TCL_MAJOR_VERSION >= 8 static int ip_rbVwaitObjCmd _((ClientData, Tcl_Interp *, int, Tcl_Obj *CONST [])); @@ -2301,7 +3169,18 @@ ip_rbVwaitCommand(clientData, interp, objc, objv) int thr_crit_bup; DUMP1("Ruby's 'vwait' is called"); + if (interp == (Tcl_Interp*)NULL) { + rbtk_pending_exception = rb_exc_new2(rb_eRuntimeError, + "IP is deleted"); + return TCL_ERROR; + } + Tcl_Preserve(interp); +#ifdef HAVE_NATIVETHREAD + if (!is_ruby_native_thread()) { + rb_bug("cross-thread violation on ip_ruby_eval()"); + } +#endif if (objc != 2) { #ifdef Tcl_WrongNumArgs @@ -2357,8 +3236,11 @@ ip_rbVwaitCommand(clientData, interp, objc, objv) Tcl_Release(interp); return TCL_ERROR; } + done = 0; - foundEvent = lib_eventloop_core(/* not check root-widget */0, 0, &done); + + foundEvent + = lib_eventloop_launcher(/* not check root-widget */0, 0, &done); thr_crit_bup = rb_thread_critical; rb_thread_critical = Qtrue; @@ -2369,6 +3251,34 @@ ip_rbVwaitCommand(clientData, interp, objc, objv) rb_thread_critical = thr_crit_bup; + /* exception check */ + if (!NIL_P(rbtk_pending_exception)) { +#if TCL_MAJOR_VERSION >= 8 + Tcl_DecrRefCount(objv[1]); +#endif + Tcl_Release(interp); + +/* + if (rb_obj_is_kind_of(rbtk_pending_exception, rb_eSystemExit)) { +*/ + if (rb_obj_is_kind_of(rbtk_pending_exception, rb_eSystemExit) + || rb_obj_is_kind_of(rbtk_pending_exception, rb_eInterrupt)) { + return TCL_RETURN; + } else{ + return TCL_ERROR; + } + } + + /* trap check */ + if (rb_trap_pending) { +#if TCL_MAJOR_VERSION >= 8 + Tcl_DecrRefCount(objv[1]); +#endif + Tcl_Release(interp); + + return TCL_RETURN; + } + /* * Clear out the interpreter's result, since it may have been set * by event handlers. @@ -2489,6 +3399,11 @@ ip_rbTkWaitCommand(clientData, interp, objc, objv) int thr_crit_bup; DUMP1("Ruby's 'tkwait' is called"); + if (interp == (Tcl_Interp*)NULL) { + rbtk_pending_exception = rb_exc_new2(rb_eRuntimeError, + "IP is deleted"); + return TCL_ERROR; + } Tcl_Preserve(interp); @@ -2598,8 +3513,10 @@ ip_rbTkWaitCommand(clientData, interp, objc, objv) Tcl_Release(interp); return TCL_ERROR; } + done = 0; - lib_eventloop_core(check_rootwidget_flag, 0, &done); + /* lib_eventloop_core(check_rootwidget_flag, 0, &done); */ + lib_eventloop_launcher(check_rootwidget_flag, 0, &done); thr_crit_bup = rb_thread_critical; rb_thread_critical = Qtrue; @@ -2614,6 +3531,28 @@ ip_rbTkWaitCommand(clientData, interp, objc, objv) rb_thread_critical = thr_crit_bup; + /* exception check */ + if (!NIL_P(rbtk_pending_exception)) { + Tcl_Release(interp); + + /* + if (rb_obj_is_kind_of(rbtk_pending_exception, rb_eSystemExit)) { + */ + if (rb_obj_is_kind_of(rbtk_pending_exception, rb_eSystemExit) + || rb_obj_is_kind_of(rbtk_pending_exception, rb_eInterrupt)) { + return TCL_RETURN; + } else{ + return TCL_ERROR; + } + } + + /* trap check */ + if (rb_trap_pending) { + Tcl_Release(interp); + + return TCL_RETURN; + } + break; case TKWAIT_VISIBILITY: @@ -2642,7 +3581,37 @@ ip_rbTkWaitCommand(clientData, interp, objc, objv) rb_thread_critical = thr_crit_bup; done = 0; - lib_eventloop_core(check_rootwidget_flag, 0, &done); + /* lib_eventloop_core(check_rootwidget_flag, 0, &done); */ + lib_eventloop_launcher(check_rootwidget_flag, 0, &done); + + /* exception check */ + if (!NIL_P(rbtk_pending_exception)) { +#if TCL_MAJOR_VERSION >= 8 + Tcl_DecrRefCount(objv[2]); +#endif + Tcl_Release(interp); + + /* + if (rb_obj_is_kind_of(rbtk_pending_exception, rb_eSystemExit)) { + */ + if (rb_obj_is_kind_of(rbtk_pending_exception, rb_eSystemExit) + || rb_obj_is_kind_of(rbtk_pending_exception, rb_eInterrupt)) { + return TCL_RETURN; + } else{ + return TCL_ERROR; + } + } + + /* trap check */ + if (rb_trap_pending) { +#if TCL_MAJOR_VERSION >= 8 + Tcl_DecrRefCount(objv[2]); +#endif + Tcl_Release(interp); + + return TCL_RETURN; + } + if (done != 1) { /* * Note that we do not delete the event handler because it @@ -2706,7 +3675,31 @@ ip_rbTkWaitCommand(clientData, interp, objc, objv) rb_thread_critical = thr_crit_bup; done = 0; - lib_eventloop_core(check_rootwidget_flag, 0, &done); + /* lib_eventloop_core(check_rootwidget_flag, 0, &done); */ + lib_eventloop_launcher(check_rootwidget_flag, 0, &done); + + /* exception check */ + if (!NIL_P(rbtk_pending_exception)) { + Tcl_Release(interp); + + /* + if (rb_obj_is_kind_of(rbtk_pending_exception, rb_eSystemExit)) { + */ + if (rb_obj_is_kind_of(rbtk_pending_exception, rb_eSystemExit) + || rb_obj_is_kind_of(rbtk_pending_exception, rb_eInterrupt)) { + return TCL_RETURN; + } else{ + return TCL_ERROR; + } + } + + /* trap check */ + if (rb_trap_pending) { + Tcl_Release(interp); + + return TCL_RETURN; + } + /* * Note: there's no need to delete the event handler. It was * deleted automatically when the window was destroyed. @@ -2827,6 +3820,11 @@ ip_rb_threadVwaitCommand(clientData, interp, objc, objv) volatile VALUE current_thread = rb_thread_current(); DUMP1("Ruby's 'thread_vwait' is called"); + if (interp == (Tcl_Interp*)NULL) { + rbtk_pending_exception = rb_exc_new2(rb_eRuntimeError, + "IP is deleted"); + return TCL_ERROR; + } if (rb_thread_alone() || eventloop_thread == current_thread) { #if TCL_MAJOR_VERSION >= 8 @@ -2892,6 +3890,8 @@ ip_rb_threadVwaitCommand(clientData, interp, objc, objv) rb_thread_critical = thr_crit_bup; if (ret != TCL_OK) { + Tcl_Free((char *)param); + #if TCL_MAJOR_VERSION >= 8 Tcl_DecrRefCount(objv[1]); #endif @@ -2958,6 +3958,11 @@ ip_rb_threadTkWaitCommand(clientData, interp, objc, objv) volatile VALUE current_thread = rb_thread_current(); DUMP1("Ruby's 'thread_tkwait' is called"); + if (interp == (Tcl_Interp*)NULL) { + rbtk_pending_exception = rb_exc_new2(rb_eRuntimeError, + "IP is deleted"); + return TCL_ERROR; + } if (rb_thread_alone() || eventloop_thread == current_thread) { #if TCL_MAJOR_VERSION >= 8 @@ -3275,12 +4280,19 @@ ip_thread_vwait(self, var) VALUE self; VALUE var; { - VALUE argv[2]; + VALUE *argv; + VALUE retval; volatile VALUE cmd_str = rb_str_new2("thread_vwait"); + argv = ALLOC_N(VALUE, 2); argv[0] = cmd_str; argv[1] = var; - return ip_invoke_real(2, argv, self); + + retval = ip_invoke_real(2, argv, self); + + free(argv); + + return retval; } static VALUE @@ -3289,188 +4301,166 @@ ip_thread_tkwait(self, mode, target) VALUE mode; VALUE target; { - VALUE argv[3]; + VALUE *argv; + VALUE retval; volatile VALUE cmd_str = rb_str_new2("thread_tkwait"); + argv = ALLOC_N(VALUE, 3); argv[0] = cmd_str; argv[1] = mode; argv[2] = target; - return ip_invoke_real(3, argv, self); -} - -/* destroy interpreter */ -VALUE del_root(ip) - Tcl_Interp *ip; -{ - Tk_Window main_win; - if (!Tcl_InterpDeleted(ip)) { - Tcl_Preserve(ip); + retval = ip_invoke_real(3, argv, self); - if ( (main_win = Tk_MainWindow(ip)) != (Tk_Window)NULL - && !(((Tk_FakeWin*)main_win)->flags & TK_ALREADY_DEAD) ) { - DUMP1("wait main_win is destroyed"); - Tk_DestroyWindow(main_win); - } + free(argv); - Tcl_Release(ip); - } - return Qnil; + return retval; } +/* delete slave interpreters */ static void delete_slaves(ip) Tcl_Interp *ip; { + int thr_crit_bup; Tcl_Interp *slave; Tcl_Obj *slave_list, *elem; - Tcl_CmdInfo info; char *slave_name; int i, len; - if (Tcl_InterpDeleted(ip) || ip_null_namespace(ip)) { - DUMP2("call delete_slaves() for deleted ip(%lx)", ip); - return; - } + DUMP1("delete slaves"); + thr_crit_bup = rb_thread_critical; + rb_thread_critical = Qtrue; - DUMP2("delete slaves of ip(%lx)", ip); + if (!Tcl_InterpDeleted(ip) && Tcl_Eval(ip, "interp slaves") == TCL_OK) { + slave_list = Tcl_GetObjResult(ip); + Tcl_IncrRefCount(slave_list); - Tcl_Preserve(ip); + if (Tcl_ListObjLength((Tcl_Interp*)NULL, slave_list, &len) == TCL_OK) { + for(i = 0; i < len; i++) { + Tcl_ListObjIndex((Tcl_Interp*)NULL, slave_list, i, &elem); + Tcl_IncrRefCount(elem); - if (Tcl_Eval(ip, "info slaves") == TCL_ERROR) { - DUMP2("ip(%lx) cannot get a list of slave IPs", ip); - return; - } + if (elem == (Tcl_Obj*)NULL) continue; + + /* get slave */ + slave_name = Tcl_GetString(elem); + DUMP2("delete slave:'%s'", slave_name); - slave_list = Tcl_GetObjResult(ip); - Tcl_IncrRefCount(slave_list); + Tcl_DecrRefCount(elem); + + slave = Tcl_GetSlave(ip, slave_name); + if (slave == (Tcl_Interp*)NULL) continue; + + /* call ip_finalize */ + ip_finalize(slave); + + Tcl_DeleteInterp(slave); + Tcl_Release(slave); + } + } - if (Tcl_ListObjLength((Tcl_Interp*)NULL, slave_list, &len) == TCL_ERROR) { - DUMP1("slave_list is not a list object"); Tcl_DecrRefCount(slave_list); - return; } - for(i = 0; i < len; i++) { - Tcl_ListObjIndex((Tcl_Interp*)NULL, slave_list, i, &elem); - Tcl_IncrRefCount(elem); + rb_thread_critical = thr_crit_bup; +} - if (elem == (Tcl_Obj*)NULL) continue; - /* get slave */ - slave_name = Tcl_GetString(elem); - slave = Tcl_GetSlave(ip, slave_name); - if (slave == (Tcl_Interp*)NULL) { - DUMP2("slave \"%s\" does not exist", slave_name); - continue; - } +/* finalize operation */ +static void +ip_finalize(ip) + Tcl_Interp *ip; +{ + Tcl_CmdInfo info; + int thr_crit_bup; - Tcl_DecrRefCount(elem); + DUMP1("start ip_finalize"); - Tcl_Preserve(slave); + if (ip == (Tcl_Interp*)NULL) { + DUMP1("ip is NULL"); + return; + } - if (!Tcl_InterpDeleted(slave) && !ip_null_namespace(slave) && - Tcl_GetCommandInfo(slave, finalize_hook_name, &info)) { - DUMP2("call finalize hook proc '%s'", finalize_hook_name); - Tcl_Eval(slave, finalize_hook_name); - } +#if TCL_NAMESPACE_DEBUG + if (ip_null_namespace(ip)) { + DUMP2("ip(%lx) has null namespace", ip); + return; + } +#endif - if (!Tcl_InterpDeleted(slave) && - Tcl_Eval(slave, DEF_CANCEL_AFTER_SCRIPTS_PROC) == TCL_OK) { - if (!Tcl_InterpDeleted(slave) && !ip_null_namespace(slave) && - Tcl_GetCommandInfo(slave, CANCEL_AFTER_SCRIPTS, &info)) { - DUMP2("call cancel after scripts proc '%s'", - CANCEL_AFTER_SCRIPTS); - Tcl_Eval(slave, CANCEL_AFTER_SCRIPTS); - } - } + thr_crit_bup = rb_thread_critical; + rb_thread_critical = Qtrue; - /* delete slaves of slave */ - delete_slaves(slave); + Tcl_Preserve(ip); - /* delete slave */ - del_root(slave); - /* while(!rbtk_InterpDeleted(slave)) { */ - if (!Tcl_InterpDeleted(slave)) { - DUMP2("delete slave ip(%lx)", slave); - Tcl_DeleteInterp(slave); - } + /* delete slaves */ + delete_slaves(ip); - Tcl_Release(slave); + /* delete root widget */ + Tcl_GlobalEval(ip, "destroy ."); - /* delete slave_name command */ - Tcl_DeleteCommand(ip, slave_name); + /* call finalize-hook-proc */ + if (Tcl_GetCommandInfo(ip, finalize_hook_name, &info)) { + DUMP2("call finalize hook proc '%s'", finalize_hook_name); + Tcl_GlobalEval(ip, finalize_hook_name); } - Tcl_DecrRefCount(slave_list); + DUMP1("call cancel aftern scripts"); + Tcl_GlobalEval(ip, "foreach id [after info] {after cancel $id}"); Tcl_Release(ip); + + DUMP1("finish ip_finalize"); + rb_thread_critical = thr_crit_bup; } + +/* destroy interpreter */ static void ip_free(ptr) struct tcltkip *ptr; { - Tcl_CmdInfo info; - int thr_crit_bup; - char* argv[2]; + int thr_crit_bup; + struct ip_free_queue *q; DUMP2("free Tcl Interp %lx", ptr->ip); if (ptr) { thr_crit_bup = rb_thread_critical; rb_thread_critical = Qtrue; - DUMP2("IP ref_count = %d", ptr->ref_count); - - if (!Tcl_InterpDeleted(ptr->ip) && !rbtk_invalid_namespace(ptr)) { - DUMP2("IP(%lx) is not deleted", ptr->ip); - /* Tcl_Preserve(ptr->ip); */ - rbtk_preserve_ip(ptr); - - delete_slaves(ptr->ip); - - Tcl_ResetResult(ptr->ip); - - if (!Tcl_InterpDeleted(ptr->ip) && !rbtk_invalid_namespace(ptr) - && Tcl_GetCommandInfo(ptr->ip, finalize_hook_name, &info)) { - DUMP2("call finalize hook proc '%s'", finalize_hook_name); - Tcl_Eval(ptr->ip, finalize_hook_name); - } - - if (!Tcl_InterpDeleted(ptr->ip) && !rbtk_invalid_namespace(ptr) - && Tcl_Eval(ptr->ip, DEF_CANCEL_AFTER_SCRIPTS_PROC) == TCL_OK) { - if (!Tcl_InterpDeleted(ptr->ip) && !rbtk_invalid_namespace(ptr) - && Tcl_GetCommandInfo(ptr->ip, CANCEL_AFTER_SCRIPTS, &info)) { - DUMP2("call cancel after scripts proc '%s'", - CANCEL_AFTER_SCRIPTS); - Tcl_Eval(ptr->ip, CANCEL_AFTER_SCRIPTS); - } - } - - /* del_root(ptr->ip); */ - - DUMP1("delete interp"); - /* while(!rbtk_InterpDeleted(ptr->ip)) { */ - if (!Tcl_InterpDeleted(ptr->ip)) { - DUMP2("delete ip(%lx)", ptr->ip); - Tcl_DeleteInterp(ptr->ip); - } + if ( ptr->ip != (Tcl_Interp*)NULL + && !Tcl_InterpDeleted(ptr->ip) + && Tcl_GetMaster(ptr->ip) != (Tcl_Interp*)NULL + && !Tcl_InterpDeleted(Tcl_GetMaster(ptr->ip)) ) { + DUMP2("parent IP(%lx) is not deleted", Tcl_GetMaster(ptr->ip)); + DUMP2("slave IP(%lx) should not be deleted", ptr->ip); + free(ptr); + rb_thread_critical = thr_crit_bup; + return; + } - /* Tcl_Release(ptr->ip); */ - rbtk_release_ip(ptr); + if (ptr->ip == (Tcl_Interp*)NULL) { + DUMP1("ip_free is called for deleted IP"); + free(ptr); + rb_thread_critical = thr_crit_bup; + return; } - rbtk_release_ip(ptr); - DUMP2("IP ref_count = %d", ptr->ref_count); + ip_finalize(ptr->ip); + Tcl_DeleteInterp(ptr->ip); + Tcl_Release(ptr->ip); free(ptr); rb_thread_critical = thr_crit_bup; } + DUMP1("complete freeing Tcl Interp"); } + /* create and initialize interpreter */ static VALUE ip_alloc _((VALUE)); static VALUE @@ -3480,29 +4470,206 @@ ip_alloc(self) return Data_Wrap_Struct(self, 0, ip_free, 0); } -static VALUE -ip_init(argc, argv, self) - int argc; - VALUE *argv; - VALUE self; -{ - struct tcltkip *ptr; /* tcltkip data struct */ - VALUE argv0, opts; - int cnt; - int with_tk = 1; +static void +ip_replace_wait_commands(interp, mainWin) + Tcl_Interp *interp; Tk_Window mainWin; +{ + /* replace 'vwait' command */ +#if TCL_MAJOR_VERSION >= 8 + DUMP1("Tcl_CreateObjCommand(\"vwait\")"); + Tcl_CreateObjCommand(interp, "vwait", ip_rbVwaitObjCmd, + (ClientData)mainWin, (Tcl_CmdDeleteProc *)NULL); +#else /* TCL_MAJOR_VERSION < 8 */ + DUMP1("Tcl_CreateCommand(\"vwait\")"); + Tcl_CreateCommand(interp, "vwait", ip_rbVwaitCommand, + (ClientData)mainWin, (Tcl_CmdDeleteProc *)NULL); +#endif - /* security check */ - if (ruby_safe_level >= 4) { - rb_raise(rb_eSecurityError, "Cannot create a TclTkIp object at level %d", ruby_safe_level); - } + /* replace 'tkwait' command */ +#if TCL_MAJOR_VERSION >= 8 + DUMP1("Tcl_CreateObjCommand(\"tkwait\")"); + Tcl_CreateObjCommand(interp, "tkwait", ip_rbTkWaitObjCmd, + (ClientData)mainWin, (Tcl_CmdDeleteProc *)NULL); +#else /* TCL_MAJOR_VERSION < 8 */ + DUMP1("Tcl_CreateCommand(\"tkwait\")"); + Tcl_CreateCommand(interp, "tkwait", ip_rbTkWaitCommand, + (ClientData)mainWin, (Tcl_CmdDeleteProc *)NULL); +#endif - /* create object */ - Data_Get_Struct(self, struct tcltkip, ptr); - ptr = ALLOC(struct tcltkip); - DATA_PTR(self) = ptr; - ptr->ref_count = 0; - ptr->allow_ruby_exit = 1; + /* add 'thread_vwait' command */ +#if TCL_MAJOR_VERSION >= 8 + DUMP1("Tcl_CreateObjCommand(\"thread_vwait\")"); + Tcl_CreateObjCommand(interp, "thread_vwait", ip_rb_threadVwaitObjCmd, + (ClientData)mainWin, (Tcl_CmdDeleteProc *)NULL); +#else /* TCL_MAJOR_VERSION < 8 */ + DUMP1("Tcl_CreateCommand(\"thread_vwait\")"); + Tcl_CreateCommand(interp, "thread_vwait", ip_rb_threadVwaitCommand, + (ClientData)mainWin, (Tcl_CmdDeleteProc *)NULL); +#endif + + /* add 'thread_tkwait' command */ +#if TCL_MAJOR_VERSION >= 8 + DUMP1("Tcl_CreateObjCommand(\"thread_tkwait\")"); + Tcl_CreateObjCommand(interp, "thread_tkwait", ip_rb_threadTkWaitObjCmd, + (ClientData)mainWin, (Tcl_CmdDeleteProc *)NULL); +#else /* TCL_MAJOR_VERSION < 8 */ + DUMP1("Tcl_CreateCommand(\"thread_tkwait\")"); + Tcl_CreateCommand(interp, "thread_tkwait", ip_rb_threadTkWaitCommand, + (ClientData)mainWin, (Tcl_CmdDeleteProc *)NULL); +#endif + + /* replace 'update' command */ +#if TCL_MAJOR_VERSION >= 8 + DUMP1("Tcl_CreateObjCommand(\"update\")"); + Tcl_CreateObjCommand(interp, "update", ip_rbUpdateObjCmd, + (ClientData)mainWin, (Tcl_CmdDeleteProc *)NULL); +#else /* TCL_MAJOR_VERSION < 8 */ + DUMP1("Tcl_CreateCommand(\"update\")"); + Tcl_CreateCommand(interp, "update", ip_rbUpdateCommand, + (ClientData)mainWin, (Tcl_CmdDeleteProc *)NULL); +#endif + + /* add 'thread_update' command */ +#if TCL_MAJOR_VERSION >= 8 + DUMP1("Tcl_CreateObjCommand(\"thread_update\")"); + Tcl_CreateObjCommand(interp, "thread_update", ip_rb_threadUpdateObjCmd, + (ClientData)mainWin, (Tcl_CmdDeleteProc *)NULL); +#else /* TCL_MAJOR_VERSION < 8 */ + DUMP1("Tcl_CreateCommand(\"thread_update\")"); + Tcl_CreateCommand(interp, "thread_update", ip_rb_threadUpdateCommand, + (ClientData)mainWin, (Tcl_CmdDeleteProc *)NULL); +#endif +} + + + +#if TCL_MAJOR_VERSION >= 8 +static int ip_rbNamespaceObjCmd _((ClientData, Tcl_Interp *, int, + Tcl_Obj *CONST [])); +static int +ip_rbNamespaceObjCmd(clientData, interp, objc, objv) + ClientData clientData; + Tcl_Interp *interp; + int objc; + Tcl_Obj *CONST objv[]; +{ + Tcl_CmdInfo info; + int ret; + + if (!Tcl_GetCommandInfo(interp, "__orig_namespace_command__", &(info))) { + Tcl_ResetResult(interp); + Tcl_AppendResult(interp, + "invalid command name \"namespace\"", (char*)NULL); + return TCL_ERROR; + } + + rbtk_eventloop_depth++; + DUMP2("namespace wrapper enter depth == %d", rbtk_eventloop_depth); + + if (info.isNativeObjectProc) { + ret = (*(info.objProc))(info.objClientData, interp, objc, objv); + } else { + /* string interface */ + int i; + char **argv; + + argv = (char **)Tcl_Alloc(sizeof(char *) * (objc + 1)); + + for(i = 0; i < objc; i++) { + argv[i] = Tcl_GetString(objv[i]); + } + argv[objc] = (char *)NULL; + + ret = (*(info.proc))(info.clientData, interp, + objc, (CONST84 char **)argv); + + Tcl_Free((char*)argv); + } + + DUMP2("namespace wrapper exit depth == %d", rbtk_eventloop_depth); + rbtk_eventloop_depth--; + + return ret; +} +#endif + +static void +ip_wrap_namespace_command(interp) + Tcl_Interp *interp; +{ + Tcl_CmdInfo orig_info; + +#if TCL_MAJOR_VERSION < 8 + return; +#endif + + if (!Tcl_GetCommandInfo(interp, "namespace", &(orig_info))) { + return; + } + + if (orig_info.isNativeObjectProc) { + Tcl_CreateObjCommand(interp, "__orig_namespace_command__", + orig_info.objProc, orig_info.objClientData, + orig_info.deleteProc); + } else { + Tcl_CreateCommand(interp, "__orig_namespace_command__", + orig_info.proc, orig_info.clientData, + orig_info.deleteProc); + } + + Tcl_CreateObjCommand(interp, "namespace", ip_rbNamespaceObjCmd, + (ClientData) 0, (Tcl_CmdDeleteProc *)NULL); +} + + +/* call when interpreter is deleted */ +static void +ip_CallWhenDeleted(clientData, ip) + ClientData clientData; + Tcl_Interp *ip; +{ + int thr_crit_bup; + Tcl_CmdInfo info; + Tk_Window main_win = (Tk_Window) clientData; + + DUMP1("start ip_CallWhenDeleted"); + thr_crit_bup = rb_thread_critical; + rb_thread_critical = Qtrue; + + ip_finalize(ip); + + DUMP1("finish ip_CallWhenDeleted"); + rb_thread_critical = thr_crit_bup; +} + + +/* initialize interpreter */ +static VALUE +ip_init(argc, argv, self) + int argc; + VALUE *argv; + VALUE self; +{ + struct tcltkip *ptr; /* tcltkip data struct */ + VALUE argv0, opts; + int cnt; + int with_tk = 1; + Tk_Window mainWin; + + /* security check */ + if (ruby_safe_level >= 4) { + rb_raise(rb_eSecurityError, + "Cannot create a TclTkIp object at level %d", + ruby_safe_level); + } + + /* create object */ + Data_Get_Struct(self, struct tcltkip, ptr); + ptr = ALLOC(struct tcltkip); + DATA_PTR(self) = ptr; + ptr->ref_count = 0; + ptr->allow_ruby_exit = 1; ptr->return_value = 0; /* from Tk_Main() */ @@ -3513,11 +4680,13 @@ ip_init(argc, argv, self) } #if TCL_MAJOR_VERSION >= 8 +#if TCL_NAMESPACE_DEBUG DUMP1("get current namespace"); if ((ptr->default_ns = Tcl_GetCurrentNamespace(ptr->ip)) == (Tcl_Namespace*)NULL) { rb_raise(rb_eRuntimeError, "a new Tk interpreter has a NULL namespace"); } +#endif #endif rbtk_preserve_ip(ptr); @@ -3635,79 +4804,14 @@ ip_init(argc, argv, self) (ClientData)mainWin, (Tcl_CmdDeleteProc *)NULL); #endif -#if 0 /* - Disable the following "update" and "thread_update". Bcause, - they don't work in a callback-proc. After calling update in - a callback-proc, the callback proc never be worked. - If the problem will be fixed in the future, may enable the - functions. - */ - /* replace 'update' command */ -# if TCL_MAJOR_VERSION >= 8 - DUMP1("Tcl_CreateObjCommand(\"update\")"); - Tcl_CreateObjCommand(ptr->ip, "update", ip_rbUpdateObjCmd, - (ClientData)mainWin, (Tcl_CmdDeleteProc *)NULL); -# else /* TCL_MAJOR_VERSION < 8 */ - DUMP1("Tcl_CreateCommand(\"update\")"); - Tcl_CreateCommand(ptr->ip, "update", ip_rbUpdateCommand, - (ClientData)mainWin, (Tcl_CmdDeleteProc *)NULL); -# endif + /* replace vwait and tkwait */ + ip_replace_wait_commands(ptr->ip, mainWin); - /* add 'thread_update' command */ -# if TCL_MAJOR_VERSION >= 8 - DUMP1("Tcl_CreateObjCommand(\"thread_update\")"); - Tcl_CreateObjCommand(ptr->ip, "thread_update", ip_rb_threadUpdateObjCmd, - (ClientData)mainWin, (Tcl_CmdDeleteProc *)NULL); -# else /* TCL_MAJOR_VERSION < 8 */ - DUMP1("Tcl_CreateCommand(\"thread_update\")"); - Tcl_CreateCommand(ptr->ip, "thread_update", ip_rb_threadUpdateCommand, - (ClientData)mainWin, (Tcl_CmdDeleteProc *)NULL); -# endif -#endif + /* wrap namespace command */ + ip_wrap_namespace_command(ptr->ip); - /* replace 'vwait' command */ -#if TCL_MAJOR_VERSION >= 8 - DUMP1("Tcl_CreateObjCommand(\"vwait\")"); - Tcl_CreateObjCommand(ptr->ip, "vwait", ip_rbVwaitObjCmd, - (ClientData)mainWin, (Tcl_CmdDeleteProc *)NULL); -#else /* TCL_MAJOR_VERSION < 8 */ - DUMP1("Tcl_CreateCommand(\"vwait\")"); - Tcl_CreateCommand(ptr->ip, "vwait", ip_rbVwaitCommand, - (ClientData)mainWin, (Tcl_CmdDeleteProc *)NULL); -#endif - - /* replace 'tkwait' command */ -#if TCL_MAJOR_VERSION >= 8 - DUMP1("Tcl_CreateObjCommand(\"tkwait\")"); - Tcl_CreateObjCommand(ptr->ip, "tkwait", ip_rbTkWaitObjCmd, - (ClientData)mainWin, (Tcl_CmdDeleteProc *)NULL); -#else /* TCL_MAJOR_VERSION < 8 */ - DUMP1("Tcl_CreateCommand(\"tkwait\")"); - Tcl_CreateCommand(ptr->ip, "tkwait", ip_rbTkWaitCommand, - (ClientData)mainWin, (Tcl_CmdDeleteProc *)NULL); -#endif - - /* add 'thread_vwait' command */ -#if TCL_MAJOR_VERSION >= 8 - DUMP1("Tcl_CreateObjCommand(\"thread_vwait\")"); - Tcl_CreateObjCommand(ptr->ip, "thread_vwait", ip_rb_threadVwaitObjCmd, - (ClientData)mainWin, (Tcl_CmdDeleteProc *)NULL); -#else /* TCL_MAJOR_VERSION < 8 */ - DUMP1("Tcl_CreateCommand(\"thread_vwait\")"); - Tcl_CreateCommand(ptr->ip, "thread_vwait", ip_rb_threadVwaitCommand, - (ClientData)mainWin, (Tcl_CmdDeleteProc *)NULL); -#endif - - /* add 'thread_tkwait' command */ -#if TCL_MAJOR_VERSION >= 8 - DUMP1("Tcl_CreateObjCommand(\"thread_tkwait\")"); - Tcl_CreateObjCommand(ptr->ip, "thread_tkwait", ip_rb_threadTkWaitObjCmd, - (ClientData)mainWin, (Tcl_CmdDeleteProc *)NULL); -#else /* TCL_MAJOR_VERSION < 8 */ - DUMP1("Tcl_CreateCommand(\"thread_tkwait\")"); - Tcl_CreateCommand(ptr->ip, "thread_tkwait", ip_rb_threadTkWaitCommand, - (ClientData)mainWin, (Tcl_CmdDeleteProc *)NULL); -#endif + /* set finalizer */ + Tcl_CallWhenDeleted(ptr->ip, ip_CallWhenDeleted, (ClientData)mainWin); Tk_Release((ClientData)mainWin); @@ -3715,12 +4819,12 @@ ip_init(argc, argv, self) } static VALUE -ip_create_slave(argc, argv, self) +ip_create_slave_core(interp, argc, argv) + VALUE interp; int argc; VALUE *argv; - VALUE self; { - struct tcltkip *master = get_ip(self); + struct tcltkip *master = get_ip(interp); struct tcltkip *slave = ALLOC(struct tcltkip); VALUE safemode; VALUE name; @@ -3728,15 +4832,22 @@ ip_create_slave(argc, argv, self) int thr_crit_bup; Tk_Window mainWin; - /* safe-mode check */ - if (rb_scan_args(argc, argv, "11", &name, &safemode) == 1) { - safemode = Qfalse; + /* ip is deleted? */ + if (master == (struct tcltkip *)NULL || master->ip == (Tcl_Interp*)NULL + || Tcl_InterpDeleted(master->ip)) { + DUMP1("master-ip is deleted"); + return rb_exc_new2(rb_eRuntimeError, + "deleted master cannot create a new slave"); } + + name = argv[0]; + safemode = argv[1]; + if (Tcl_IsSafe(master->ip) == 1) { safe = 1; } else if (safemode == Qfalse || NIL_P(safemode)) { safe = 0; - rb_secure(4); + /* rb_secure(4); */ /* already checked */ } else { safe = 1; } @@ -3744,25 +4855,21 @@ ip_create_slave(argc, argv, self) thr_crit_bup = rb_thread_critical; rb_thread_critical = Qtrue; - /* ip is deleted? */ - if (Tcl_InterpDeleted(master->ip)) { - DUMP1("master-ip is deleted"); - rb_thread_critical = thr_crit_bup; - rb_raise(rb_eRuntimeError, "deleted master cannot create a new slave interpreter"); - } - /* create slave-ip */ slave->ref_count = 0; slave->allow_ruby_exit = 0; slave->return_value = 0; - slave->ip = Tcl_CreateSlave(master->ip, StringValuePtr(name), safe); + slave->ip = Tcl_CreateSlave(master->ip, RSTRING(name)->ptr, safe); if (slave->ip == NULL) { rb_thread_critical = thr_crit_bup; - rb_raise(rb_eRuntimeError, "fail to create the new slave interpreter"); + return rb_exc_new2(rb_eRuntimeError, + "fail to create the new slave interpreter"); } #if TCL_MAJOR_VERSION >= 8 +#if TCL_NAMESPACE_DEBUG slave->default_ns = Tcl_GetCurrentNamespace(slave->ip); +#endif #endif rbtk_preserve_ip(slave); @@ -3781,30 +4888,83 @@ ip_create_slave(argc, argv, self) (ClientData)mainWin, (Tcl_CmdDeleteProc *)NULL); #endif + /* replace vwait and tkwait */ + ip_replace_wait_commands(slave->ip, mainWin); + + /* wrap namespace command */ + ip_wrap_namespace_command(slave->ip); + + /* set finalizer */ + Tcl_CallWhenDeleted(slave->ip, ip_CallWhenDeleted, (ClientData)mainWin); + rb_thread_critical = thr_crit_bup; - return Data_Wrap_Struct(CLASS_OF(self), 0, ip_free, slave); + return Data_Wrap_Struct(CLASS_OF(interp), 0, ip_free, slave); } -/* make ip "safe" */ static VALUE -ip_make_safe(self) +ip_create_slave(argc, argv, self) + int argc; + VALUE *argv; VALUE self; { - struct tcltkip *ptr = get_ip(self); + struct tcltkip *master = get_ip(self); + VALUE safemode; + VALUE name; + VALUE *callargv; + VALUE retval; + + /* ip is deleted? */ + if (master == (struct tcltkip *)NULL || master->ip == (Tcl_Interp*)NULL + || Tcl_InterpDeleted(master->ip)) { + DUMP1("master-ip is deleted"); + rb_raise(rb_eRuntimeError, + "deleted master cannot create a new slave interpreter"); + } + + /* safe-mode check */ + if (rb_scan_args(argc, argv, "11", &name, &safemode) == 1) { + safemode = Qfalse; + } + if (Tcl_IsSafe(master->ip) != 1 + && (safemode == Qfalse || NIL_P(safemode))) { + rb_secure(4); + } + + callargv = ALLOC_N(VALUE, 2); + StringValue(name); + callargv[0] = name; + callargv[1] = safemode; + + retval = tk_funcall(ip_create_slave_core, 2, callargv, self); + + free(callargv); + + return retval; +} + +/* make ip "safe" */ +static VALUE +ip_make_safe_core(interp, argc, argv) + VALUE interp; + int argc; /* dummy */ + VALUE *argv; /* dummy */ +{ + struct tcltkip *ptr = get_ip(interp); Tk_Window mainWin; /* ip is deleted? */ - if (Tcl_InterpDeleted(ptr->ip)) { + if (ptr == (struct tcltkip *)NULL || ptr->ip == (Tcl_Interp*)NULL + || Tcl_InterpDeleted(ptr->ip)) { DUMP1("ip is deleted"); - rb_raise(rb_eRuntimeError, "interpreter is deleted"); + return rb_exc_new2(rb_eRuntimeError, "interpreter is deleted"); } if (Tcl_MakeSafe(ptr->ip) == TCL_ERROR) { #if TCL_MAJOR_VERSION >= 8 - rb_raise(rb_eRuntimeError, "%s", Tcl_GetStringResult(ptr->ip)); + return rb_exc_new2(rb_eRuntimeError, Tcl_GetStringResult(ptr->ip)); #else /* TCL_MAJOR_VERSION < 8 */ - rb_raise(rb_eRuntimeError, "%s", ptr->ip->result); + return rb_exc_new2(rb_eRuntimeError, ptr->ip->result); #endif } @@ -3822,7 +4982,23 @@ ip_make_safe(self) (ClientData)mainWin, (Tcl_CmdDeleteProc *)NULL); #endif - return self; + return interp; +} + +static VALUE +ip_make_safe(self) + VALUE self; +{ + struct tcltkip *ptr = get_ip(self); + + /* ip is deleted? */ + if (ptr == (struct tcltkip *)NULL || ptr->ip == (Tcl_Interp*)NULL + || Tcl_InterpDeleted(ptr->ip)) { + DUMP1("ip is deleted"); + rb_raise(rb_eRuntimeError, "interpreter is deleted"); + } + + return tk_funcall(ip_make_safe_core, 0, (VALUE*)NULL, self); } /* is safe? */ @@ -3833,7 +5009,8 @@ ip_is_safe_p(self) struct tcltkip *ptr = get_ip(self); /* ip is deleted? */ - if (Tcl_InterpDeleted(ptr->ip)) { + if (ptr == (struct tcltkip *)NULL || ptr->ip == (Tcl_Interp*)NULL + || Tcl_InterpDeleted(ptr->ip)) { DUMP1("ip is deleted"); rb_raise(rb_eRuntimeError, "interpreter is deleted"); } @@ -3853,7 +5030,8 @@ ip_allow_ruby_exit_p(self) struct tcltkip *ptr = get_ip(self); /* ip is deleted? */ - if (Tcl_InterpDeleted(ptr->ip)) { + if (ptr == (struct tcltkip *)NULL || ptr->ip == (Tcl_Interp*)NULL + || Tcl_InterpDeleted(ptr->ip)) { DUMP1("ip is deleted"); rb_raise(rb_eRuntimeError, "interpreter is deleted"); } @@ -3876,7 +5054,8 @@ ip_allow_ruby_exit_set(self, val) rb_secure(4); /* ip is deleted? */ - if (Tcl_InterpDeleted(ptr->ip)) { + if (ptr == (struct tcltkip *)NULL || ptr->ip == (Tcl_Interp*)NULL + || Tcl_InterpDeleted(ptr->ip)) { DUMP1("ip is deleted"); rb_raise(rb_eRuntimeError, "interpreter is deleted"); } @@ -3921,47 +5100,33 @@ static VALUE ip_delete(self) VALUE self; { - Tcl_CmdInfo info; + int thr_crit_bup; struct tcltkip *ptr = get_ip(self); + Tcl_CmdInfo info; - /* Tcl_Preserve(ptr->ip); */ - rbtk_preserve_ip(ptr); - - DUMP1("delete slaves"); - delete_slaves(ptr->ip); - - DUMP1("finalize operation"); - if (!Tcl_InterpDeleted(ptr->ip) && !rbtk_invalid_namespace(ptr) - && Tcl_GetCommandInfo(ptr->ip, finalize_hook_name, &info)) { - DUMP2("call finalize hook proc '%s'", finalize_hook_name); - Tcl_Eval(ptr->ip, finalize_hook_name); + if (ptr == (struct tcltkip *)NULL || ptr->ip == (Tcl_Interp*)NULL) { + DUMP1("delete deleted IP"); + return Qnil; } - if (!Tcl_InterpDeleted(ptr->ip) && !rbtk_invalid_namespace(ptr) - && Tcl_Eval(ptr->ip, DEF_CANCEL_AFTER_SCRIPTS_PROC) == TCL_OK) { - if (!Tcl_InterpDeleted(ptr->ip) && !rbtk_invalid_namespace(ptr) - && Tcl_GetCommandInfo(ptr->ip, CANCEL_AFTER_SCRIPTS, &info)) { - DUMP2("call cancel after scripts proc '%s'", - CANCEL_AFTER_SCRIPTS); - Tcl_Eval(ptr->ip, CANCEL_AFTER_SCRIPTS); - } - } + thr_crit_bup = rb_thread_critical; + rb_thread_critical = Qtrue; - del_root(ptr->ip); + DUMP1("call ip_finalize"); + ip_finalize(ptr->ip); DUMP1("delete interp"); - /* while(!rbtk_InterpDeleted(ptr->ip)) { */ - if (!Tcl_InterpDeleted(ptr->ip)) { - DUMP2("delete ip(%lx)", ptr->ip); - Tcl_DeleteInterp(ptr->ip); - } + Tcl_DeleteInterp(ptr->ip); + Tcl_Release(ptr->ip); - /* Tcl_Release(ptr->ip); */ - rbtk_release_ip(ptr); + ptr->ip = (Tcl_Interp*)NULL; + + rb_thread_critical = thr_crit_bup; return Qnil; } + /* is deleted? */ static VALUE ip_has_invalid_namespace_p(self) @@ -3969,11 +5134,20 @@ ip_has_invalid_namespace_p(self) { struct tcltkip *ptr = get_ip(self); + if (ptr == (struct tcltkip *)NULL || ptr->ip == (Tcl_Interp *)NULL) { + /* deleted IP */ + return Qtrue; + } + +#if TCL_NAMESPACE_DEBUG if (rbtk_invalid_namespace(ptr)) { return Qtrue; } else { return Qfalse; } +#else + return Qfalse; +#endif } static VALUE @@ -3982,7 +5156,8 @@ ip_is_deleted_p(self) { struct tcltkip *ptr = get_ip(self); - if (Tcl_InterpDeleted(ptr->ip) || rbtk_invalid_namespace(ptr)) { + if (ptr == (struct tcltkip *)NULL || ptr->ip == (Tcl_Interp *)NULL + || Tcl_InterpDeleted(ptr->ip)) { return Qtrue; } else { return Qfalse; @@ -4004,6 +5179,7 @@ create_ip_exc(interp, exc, fmt, va_alist) va_list args; char buf[BUFSIZ]; VALUE einfo; + struct tcltkip *ptr = get_ip(interp); va_init_list(args,fmt); vsnprintf(buf, BUFSIZ, fmt, args); @@ -4011,7 +5187,9 @@ create_ip_exc(interp, exc, fmt, va_alist) va_end(args); einfo = rb_exc_new2(exc, buf); rb_ivar_set(einfo, ID_at_interp, interp); - Tcl_ResetResult(get_ip(interp)->ip); + if (ptr) { + Tcl_ResetResult(ptr->ip); + } return einfo; } @@ -4073,7 +5251,192 @@ ip_get_result_string_obj(interp) #endif } + +/* call Tcl/Tk functions on the eventloop thread */ +static VALUE +callq_safelevel_handler(arg, callq) + VALUE arg; + VALUE callq; +{ + struct call_queue *q; + + Data_Get_Struct(callq, struct call_queue, q); + DUMP2("(safe-level handler) $SAFE = %d", q->safe_level); + rb_set_safe_level(q->safe_level); + return((q->func)(q->interp, q->argc, q->argv)); +} + +static int call_queue_handler _((Tcl_Event *, int)); +static int +call_queue_handler(evPtr, flags) + Tcl_Event *evPtr; + int flags; +{ + struct call_queue *q = (struct call_queue *)evPtr; + volatile VALUE ret; + volatile VALUE q_dat; + struct tcltkip *ptr; + + DUMP2("do_call_queue_handler : evPtr = %p", evPtr); + DUMP2("queue_handler thread : %lx", rb_thread_current()); + DUMP2("added by thread : %lx", q->thread); + + if (*(q->done)) { + DUMP1("processed by another event-loop"); + return 0; + } else { + DUMP1("process it on current event-loop"); + } + + /* process it */ + *(q->done) = 1; + + /* deleted ipterp ? */ + ptr = get_ip(q->interp); + if (ptr == (struct tcltkip *)NULL || ptr->ip == (Tcl_Interp*)NULL + || Tcl_InterpDeleted(ptr->ip)) { + /* deleted IP --> ignore */ + return 1; + } + + /* check safe-level */ + if (rb_safe_level() != q->safe_level) { + /* q_dat = Data_Wrap_Struct(rb_cData,0,0,q); */ + q_dat = Data_Wrap_Struct(rb_cData,call_queue_mark,0,q); + ret = rb_funcall(rb_proc_new(callq_safelevel_handler, q_dat), + ID_call, 0); + rb_gc_force_recycle(q_dat); + } else { + DUMP2("call function (for caller thread:%lx)", q->thread); + DUMP2("call function (current thread:%lx)", rb_thread_current()); + ret = (q->func)(q->interp, q->argc, q->argv); + } + + /* set result */ + RARRAY(q->result)->ptr[0] = ret; + + /* complete */ + *(q->done) = -1; + + /* back to caller */ + DUMP2("back to caller (caller thread:%lx)", q->thread); + DUMP2(" (current thread:%lx)", rb_thread_current()); + rb_thread_run(q->thread); + DUMP1("finish back to caller"); + + /* end of handler : remove it */ + return 1; +} + +static VALUE +tk_funcall(func, argc, argv, obj) + VALUE (*func)(); + int argc; + VALUE *argv; + VALUE obj; +{ + struct call_queue *callq; + int *alloc_done; + int thr_crit_bup; + volatile VALUE current = rb_thread_current(); + volatile VALUE ip_obj = obj; + volatile VALUE result; + volatile VALUE ret; + + + if (!NIL_P(ip_obj) && Tcl_InterpDeleted(get_ip(ip_obj)->ip)) { + return Qnil; + } + + if (NIL_P(eventloop_thread) || current == eventloop_thread) { + if (eventloop_thread) { + DUMP2("tk_funcall from current eventloop %lx", current); + } else { + DUMP2("tk_funcall from thread:%lx but no eventloop", current); + } + result = (func)(ip_obj, argc, argv); + if (rb_obj_is_kind_of(result, rb_eException)) { + rb_exc_raise(result); + } + return result; + } + + DUMP2("tk_funcall from thread %lx (NOT current eventloop)", current); + + thr_crit_bup = rb_thread_critical; + rb_thread_critical = Qtrue; + + /* allocate memory (keep result) */ + alloc_done = (int*)ALLOC(int); + *alloc_done = 0; + + /* allocate memory (freed by Tcl_ServiceEvent) */ + callq = (struct call_queue *)Tcl_Alloc(sizeof(struct call_queue)); + Tcl_Preserve(callq); + + /* allocate result obj */ + result = rb_ary_new2(1); + RARRAY(result)->ptr[0] = Qnil; + RARRAY(result)->len = 1; + + /* construct event data */ + callq->done = alloc_done; + callq->func = func; + callq->argc = argc; + callq->argv = argv; + callq->interp = ip_obj; + callq->result = result; + callq->thread = current; + callq->safe_level = rb_safe_level(); + callq->ev.proc = call_queue_handler; + + /* add the handler to Tcl event queue */ + DUMP1("add handler"); + Tcl_QueueEvent(&(callq->ev), TCL_QUEUE_HEAD); + + rb_thread_critical = thr_crit_bup; + + /* wait for the handler to be processed */ + DUMP2("wait for handler (current thread:%lx)", current); + while(*alloc_done >= 0) { + rb_thread_stop(); + } + DUMP2("back from handler (current thread:%lx)", current); + + /* get result & free allocated memory */ + ret = RARRAY(result)->ptr[0]; + free(alloc_done); + + Tcl_Release(callq); + + /* exception? */ + if (rb_obj_is_kind_of(ret, rb_eException)) { + DUMP1("raise exception"); + rb_exc_raise(ret); + } + + DUMP1("exit tk_funcall"); + return ret; +} + + /* eval string in tcl by Tcl_Eval() */ +struct call_eval_info { + struct tcltkip *ptr; + Tcl_Obj *cmd; +}; + +static VALUE +call_tcl_eval(arg) + VALUE arg; +{ + struct call_eval_info *inf = (struct call_eval_info *)arg; + + inf->ptr->return_value = Tcl_EvalObj(inf->ptr->ip, inf->cmd); + + return Qnil; +} + static VALUE ip_eval_real(self, cmd_str, cmd_len) VALUE self; @@ -4098,28 +5461,65 @@ ip_eval_real(self, cmd_str, cmd_len) Tcl_IncrRefCount(cmd); /* ip is deleted? */ - if (Tcl_InterpDeleted(ptr->ip) || rbtk_invalid_namespace(ptr)) { + if (ptr == (struct tcltkip *)NULL || ptr->ip == (Tcl_Interp*)NULL + || Tcl_InterpDeleted(ptr->ip) +#if TCL_NAMESPACE_DEBUG + || rbtk_invalid_namespace(ptr) +#endif + ) { DUMP1("ip is deleted"); Tcl_DecrRefCount(cmd); rb_thread_critical = thr_crit_bup; ptr->return_value = TCL_OK; return rb_tainted_str_new2(""); } else { + int status; + struct call_eval_info inf; + /* Tcl_Preserve(ptr->ip); */ rbtk_preserve_ip(ptr); - + +#if 0 ptr->return_value = Tcl_EvalObj(ptr->ip, cmd); /* ptr->return_value = Tcl_GlobalEvalObj(ptr->ip, cmd); */ +#else + inf.ptr = ptr; + inf.cmd = cmd; + ret = rb_protect(call_tcl_eval, (VALUE)&inf, &status); + switch(status) { + case TAG_RAISE: + if (NIL_P(ruby_errinfo)) { + rbtk_pending_exception = rb_exc_new2(rb_eException, + "unknown exception"); + } else { + rbtk_pending_exception = ruby_errinfo; + } + break; + + case TAG_FATAL: + if (NIL_P(ruby_errinfo)) { + rbtk_pending_exception = rb_exc_new2(rb_eFatal, "FATAL"); + } else { + rbtk_pending_exception = ruby_errinfo; + } + } +#endif } Tcl_DecrRefCount(cmd); } + if (pending_exception_check1(thr_crit_bup, ptr)) { + return rbtk_pending_exception; + } + if (ptr->return_value == TCL_ERROR) { volatile VALUE exc; + exc = create_ip_exc(self, rb_eRuntimeError, "%s", Tcl_GetStringResult(ptr->ip)); + /* Tcl_Release(ptr->ip); */ rbtk_release_ip(ptr); @@ -4139,7 +5539,12 @@ ip_eval_real(self, cmd_str, cmd_len) DUMP2("Tcl_Eval(%s)", cmd_str); /* ip is deleted? */ - if (Tcl_InterpDeleted(ptr->ip) || rbtk_invalid_namespace(ptr)) { + if (ptr == (struct tcltkip *)NULL || ptr->ip == (Tcl_Interp*)NULL + || Tcl_InterpDeleted(ptr->ip) +#if TCL_NAMESPACE_DEBUG + || rbtk_invalid_namespace(ptr) +#endif + ) { DUMP1("ip is deleted"); ptr->return_value = TCL_OK; return rb_tainted_str_new2(""); @@ -4150,9 +5555,15 @@ ip_eval_real(self, cmd_str, cmd_len) /* ptr->return_value = Tcl_GlobalEval(ptr->ip, cmd_str); */ } + if (pending_exception_check1(thr_crit_bup, ptr)) { + return rbtk_pending_exception; + } + if (ptr->return_value == TCL_ERROR) { volatile VALUE exc; + exc = create_ip_exc(self, rb_eRuntimeError, "%s", ptr->ip->result); + /* Tcl_Release(ptr->ip); */ rbtk_release_ip(ptr); rb_exc_raise(exc); @@ -4190,10 +5601,6 @@ eval_queue_handler(evPtr, flags) volatile VALUE ret; volatile VALUE q_dat; - DUMP2("do_eval_queue_handler : evPtr = %p", evPtr); - DUMP2("eval queue_thread : %lx", rb_thread_current()); - DUMP2("added by thread : %lx", q->thread); - if (*(q->done)) { DUMP1("processed by another event-loop"); return 0; @@ -4217,8 +5624,6 @@ eval_queue_handler(evPtr, flags) ID_call, 0); rb_gc_force_recycle(q_dat); } else { - DUMP2("call eval_real (for caller thread:%lx)", q->thread); - DUMP2("call eval_real (current thread:%lx)", rb_thread_current()); ret = ip_eval_real(q->interp, q->str, q->len); } @@ -4258,7 +5663,7 @@ ip_eval(self, str) StringValue(str); rb_thread_critical = thr_crit_bup; - if (eventloop_thread == 0 || current == eventloop_thread) { + if (NIL_P(eventloop_thread) || current == eventloop_thread) { if (eventloop_thread) { DUMP2("eval from current eventloop %lx", current); } else { @@ -4302,6 +5707,7 @@ ip_eval(self, str) evq->thread = current; evq->safe_level = rb_safe_level(); evq->ev.proc = eval_queue_handler; + position = TCL_QUEUE_TAIL; /* add the handler to Tcl event queue */ @@ -4334,19 +5740,26 @@ ip_eval(self, str) /* restart Tk */ static VALUE -lib_restart(self) - VALUE self; +lib_restart_core(interp, argc, argv) + VALUE interp; + int argc; /* dummy */ + VALUE *argv; /* dummy */ { volatile VALUE exc; - struct tcltkip *ptr = get_ip(self); + struct tcltkip *ptr = get_ip(interp); int thr_crit_bup; - rb_secure(4); + /* rb_secure(4); */ /* already checked */ /* ip is deleted? */ - if (Tcl_InterpDeleted(ptr->ip) || rbtk_invalid_namespace(ptr)) { + if (ptr == (struct tcltkip *)NULL || ptr->ip == (Tcl_Interp*)NULL + || Tcl_InterpDeleted(ptr->ip) +#if TCL_NAMESPACE_DEBUG + || rbtk_invalid_namespace(ptr) +#endif + ) { DUMP1("ip is deleted"); - rb_raise(rb_eRuntimeError, "interpreter is deleted"); + return rb_exc_new2(rb_eRuntimeError, "interpreter is deleted"); } thr_crit_bup = rb_thread_critical; @@ -4361,11 +5774,13 @@ lib_restart(self) DUMP2("(TCL_Eval result) %d", ptr->return_value); Tcl_ResetResult(ptr->ip); +#if TCL_MAJOR_VERSION >= 8 /* delete namespace ( tested on tk8.4.5 ) */ ptr->return_value = Tcl_Eval(ptr->ip, "namespace delete ::tk::msgcat"); /* ignore ERROR */ DUMP2("(TCL_Eval result) %d", ptr->return_value); Tcl_ResetResult(ptr->ip); +#endif /* delete trace proc ( tested on tk8.4.5 ) */ ptr->return_value = Tcl_Eval(ptr->ip, "trace vdelete ::tk_strictMotif w ::tk::EventMotifBindings"); @@ -4382,7 +5797,7 @@ lib_restart(self) /* Tcl_Release(ptr->ip); */ rbtk_release_ip(ptr); rb_thread_critical = thr_crit_bup; - rb_exc_raise(exc); + return exc; } } else { DUMP1("Tk_Init"); @@ -4391,7 +5806,7 @@ lib_restart(self) /* Tcl_Release(ptr->ip); */ rbtk_release_ip(ptr); rb_thread_critical = thr_crit_bup; - rb_exc_raise(exc); + return exc; } } #else /* TCL_MAJOR_VERSION < 8 */ @@ -4400,7 +5815,7 @@ lib_restart(self) exc = rb_exc_new2(rb_eRuntimeError, ptr->ip->result); /* Tcl_Release(ptr->ip); */ rbtk_release_ip(ptr); - rb_exc_raise(exc); + return exc; } #endif @@ -4409,7 +5824,32 @@ lib_restart(self) rb_thread_critical = thr_crit_bup; - return Qnil; + /* return Qnil; */ + return interp; +} + +static VALUE +lib_restart(self) + VALUE self; +{ + volatile VALUE exc; + struct tcltkip *ptr = get_ip(self); + int thr_crit_bup; + + rb_secure(4); + + /* ip is deleted? */ + if (ptr == (struct tcltkip *)NULL || ptr->ip == (Tcl_Interp*)NULL + || Tcl_InterpDeleted(ptr->ip) +#if TCL_NAMESPACE_DEBUG + || rbtk_invalid_namespace(ptr) +#endif + ) { + DUMP1("ip is deleted"); + rb_raise(rb_eRuntimeError, "interpreter is deleted"); + } + + return tk_funcall(lib_restart_core, 0, (VALUE*)NULL, self); } @@ -4422,7 +5862,8 @@ ip_restart(self) rb_secure(4); /* ip is deleted? */ - if (Tcl_InterpDeleted(ptr->ip)) { + if (ptr == (struct tcltkip *)NULL || ptr->ip == (Tcl_Interp*)NULL + || Tcl_InterpDeleted(ptr->ip)) { DUMP1("ip is deleted"); rb_raise(rb_eRuntimeError, "interpreter is deleted"); } @@ -4450,16 +5891,25 @@ lib_toUTF8_core(ip_obj, src, encodename) struct tcltkip *ptr; char *buf; int thr_crit_bup; +#endif + + if (NIL_P(src)) { + return rb_str_new2(""); + } +#ifdef TCL_UTF_MAX if (NIL_P(ip_obj)) { interp = (Tcl_Interp *)NULL; } else { - interp = get_ip(ip_obj)->ip; + ptr = get_ip(ip_obj); /* ip is deleted? */ - if (Tcl_InterpDeleted(interp)) { + if (ptr == (struct tcltkip *)NULL || ptr->ip == (Tcl_Interp*)NULL + || Tcl_InterpDeleted(ptr->ip)) { DUMP1("ip is deleted"); interp = (Tcl_Interp *)NULL; + } else { + interp = ptr->ip; } } @@ -4594,9 +6044,17 @@ lib_fromUTF8_core(ip_obj, src, encodename) int taint_flag = OBJ_TAINTED(str); char *buf; int thr_crit_bup; +#endif + + if (NIL_P(src)) { + return rb_str_new2(""); + } +#ifdef TCL_UTF_MAX if (NIL_P(ip_obj)) { interp = (Tcl_Interp *)NULL; + } else if (get_ip(ip_obj) == (struct tcltkip *)NULL) { + interp = (Tcl_Interp *)NULL; } else { interp = get_ip(ip_obj)->ip; } @@ -4794,6 +6252,73 @@ lib_Tcl_backslash(self, str) return lib_UTF_backslash_core(self, str, 1); } + +/* invoke Tcl proc */ +struct invoke_info { + struct tcltkip *ptr; + Tcl_CmdInfo cmdinfo; +#if TCL_MAJOR_VERSION >= 8 + int objc; + Tcl_Obj **objv; +#else + int argc; + char **argv; +#endif +}; + +static VALUE +invoke_tcl_proc(arg) + VALUE arg; +{ + struct invoke_info *inf = (struct invoke_info *)arg; + int i, len; +#if TCL_MAJOR_VERSION >= 8 + int argc = inf->objc; + char **argv = (char **)NULL; +#endif + + /* memory allocation for arguments of this command */ +#if TCL_MAJOR_VERSION >= 8 + if (!inf->cmdinfo.isNativeObjectProc) { + /* string interface */ + argv = (char **)ALLOC_N(char *, argc+1); + for (i = 0; i < argc; ++i) { + argv[i] = Tcl_GetStringFromObj(inf->objv[i], &len); + } + argv[argc] = (char *)NULL; + } +#endif + + Tcl_ResetResult(inf->ptr->ip); + + /* Invoke the C procedure */ +#if TCL_MAJOR_VERSION >= 8 + if (inf->cmdinfo.isNativeObjectProc) { + inf->ptr->return_value + = (*(inf->cmdinfo.objProc))(inf->cmdinfo.objClientData, + inf->ptr->ip, inf->objc, inf->objv); + } + else +#endif + { +#if TCL_MAJOR_VERSION >= 8 + inf->ptr->return_value + = (*(inf->cmdinfo.proc))(inf->cmdinfo.clientData, inf->ptr->ip, + argc, (CONST84 char **)argv); + + free(argv); + +#else /* TCL_MAJOR_VERSION < 8 */ + inf->ptr->return_value + = (*(inf->cmdinfo.proc))(inf->cmdinfo.clientData, inf->ptr->ip, + inf->argc, inf->argv); +#endif + } + + return Qnil; +} + + #if TCL_MAJOR_VERSION >= 8 static VALUE ip_invoke_core(interp, objc, objv) @@ -4815,6 +6340,9 @@ ip_invoke_core(interp, argc, argv) char *s; int len; int thr_crit_bup; + struct invoke_info inf; + int status; + VALUE ret; #if TCL_MAJOR_VERSION >= 8 int argc = objc; @@ -4833,17 +6361,27 @@ ip_invoke_core(interp, argc, argv) ptr = get_ip(interp); /* ip is deleted? */ - if (Tcl_InterpDeleted(ptr->ip) || rbtk_invalid_namespace(ptr)) { + if (ptr == (struct tcltkip *)NULL || ptr->ip == (Tcl_Interp*)NULL + || Tcl_InterpDeleted(ptr->ip) +#if TCL_NAMESPACE_DEBUG + || rbtk_invalid_namespace(ptr) +#endif + ) { DUMP1("ip is deleted"); return rb_tainted_str_new2(""); } + /* Tcl_Preserve(ptr->ip); */ + rbtk_preserve_ip(ptr); + /* map from the command name to a C procedure */ DUMP2("call Tcl_GetCommandInfo, %s", cmd); if (!Tcl_GetCommandInfo(ptr->ip, cmd, &info)) { DUMP1("error Tcl_GetCommandInfo"); /* if (event_loop_abort_on_exc || cmd[0] != '.') { */ if (event_loop_abort_on_exc > 0) { + /* Tcl_Release(ptr->ip); */ + rbtk_release_ip(ptr); /*rb_ip_raise(obj,rb_eNameError,"invalid command name `%s'",cmd);*/ return create_ip_exc(interp, rb_eNameError, "invalid command name `%s'", cmd); @@ -4854,6 +6392,8 @@ ip_invoke_core(interp, argc, argv) rb_warn("invalid command name `%s' (ignore)", cmd); } Tcl_ResetResult(ptr->ip); + /* Tcl_Release(ptr->ip); */ + rbtk_release_ip(ptr); return rb_tainted_str_new2(""); } } @@ -4862,6 +6402,41 @@ ip_invoke_core(interp, argc, argv) thr_crit_bup = rb_thread_critical; rb_thread_critical = Qtrue; + +#if 1 /* wrap tcl-proc call */ + /* setup params */ + inf.ptr = ptr; + inf.cmdinfo = info; +#if TCL_MAJOR_VERSION >= 8 + inf.objc = objc; + inf.objv = objv; +#else + inf.argc = argc; + inf.argv = argv; +#endif + + /* invoke tcl-proc */ + ret = rb_protect(invoke_tcl_proc, (VALUE)&inf, &status); + switch(status) { + case TAG_RAISE: + if (NIL_P(ruby_errinfo)) { + rbtk_pending_exception = rb_exc_new2(rb_eException, + "unknown exception"); + } else { + rbtk_pending_exception = ruby_errinfo; + } + break; + + case TAG_FATAL: + if (NIL_P(ruby_errinfo)) { + rbtk_pending_exception = rb_exc_new2(rb_eFatal, "FATAL"); + } else { + rbtk_pending_exception = ruby_errinfo; + } + } + +#else /* !wrap tcl-proc call */ + /* memory allocation for arguments of this command */ #if TCL_MAJOR_VERSION >= 8 if (!info.isNativeObjectProc) { @@ -4902,12 +6477,18 @@ ip_invoke_core(interp, argc, argv) argc, argv); #endif } +#endif /* ! wrap tcl-proc call */ + + /* exception on mainloop */ + if (pending_exception_check1(thr_crit_bup, ptr)) { + return rbtk_pending_exception; + } rb_thread_critical = thr_crit_bup; - /* exception on mainloop */ if (ptr->return_value == TCL_ERROR) { if (event_loop_abort_on_exc > 0 && !Tcl_InterpDeleted(ptr->ip)) { + #if TCL_MAJOR_VERSION >= 8 return create_ip_exc(interp, rb_eRuntimeError, "%s", Tcl_GetStringResult(ptr->ip)); @@ -5062,7 +6643,7 @@ ip_invoke_real(argc, argv, interp) ptr = get_ip(interp); /* ip is deleted? */ - if (Tcl_InterpDeleted(ptr->ip)) { + if (ptr == (struct tcltkip *)NULL || ptr->ip == (Tcl_Interp*)NULL || Tcl_InterpDeleted(ptr->ip)) { DUMP1("ip is deleted"); return rb_tainted_str_new2(""); } @@ -5170,7 +6751,7 @@ ip_invoke_with_position(argc, argv, obj, position) if (argc < 1) { rb_raise(rb_eArgError, "command name missing"); } - if (eventloop_thread == 0 || current == eventloop_thread) { + if (NIL_P(eventloop_thread) || current == eventloop_thread) { if (eventloop_thread) { DUMP2("invoke from current eventloop %lx", current); } else { @@ -5258,7 +6839,8 @@ ip_retval(self) ptr = get_ip(self); /* ip is deleted? */ - if (Tcl_InterpDeleted(ptr->ip)) { + if (ptr == (struct tcltkip *)NULL || ptr->ip == (Tcl_Interp*)NULL + || Tcl_InterpDeleted(ptr->ip)) { DUMP1("ip is deleted"); return rb_tainted_str_new2(""); } @@ -5284,21 +6866,22 @@ ip_invoke_immediate(argc, argv, obj) return ip_invoke_with_position(argc, argv, obj, TCL_QUEUE_HEAD); } + /* access Tcl variables */ static VALUE -ip_get_variable(self, varname_arg, flag_arg) - VALUE self; - VALUE varname_arg; - VALUE flag_arg; +ip_get_variable_core(interp, argc, argv) + VALUE interp; + int argc; + VALUE *argv; { - struct tcltkip *ptr = get_ip(self); + struct tcltkip *ptr = get_ip(interp); int thr_crit_bup; volatile VALUE varname, flag; - varname = varname_arg; - flag = flag_arg; + varname = argv[0]; + flag = argv[1]; - StringValue(varname); + /* StringValue(varname); */ #if TCL_MAJOR_VERSION >= 8 { @@ -5315,7 +6898,12 @@ ip_get_variable(self, varname_arg, flag_arg) Tcl_IncrRefCount(nameobj); /* ip is deleted? */ - if (Tcl_InterpDeleted(ptr->ip) || rbtk_invalid_namespace(ptr)) { + if (ptr == (struct tcltkip *)NULL || ptr->ip == (Tcl_Interp*)NULL + || Tcl_InterpDeleted(ptr->ip) +#if TCL_NAMESPACE_DEBUG + || rbtk_invalid_namespace(ptr) +#endif + ) { DUMP1("ip is deleted"); Tcl_DecrRefCount(nameobj); rb_thread_critical = thr_crit_bup; @@ -5339,7 +6927,7 @@ ip_get_variable(self, varname_arg, flag_arg) /* Tcl_Release(ptr->ip); */ rbtk_release_ip(ptr); rb_thread_critical = thr_crit_bup; - rb_exc_raise(exc); + return exc; } Tcl_IncrRefCount(ret); @@ -5379,7 +6967,12 @@ ip_get_variable(self, varname_arg, flag_arg) char *ret; /* ip is deleted? */ - if (Tcl_InterpDeleted(ptr->ip) || rbtk_invalid_namespace(ptr)) { + if (ptr == (struct tcltkip *)NULL || ptr->ip == (Tcl_Interp*)NULL + || Tcl_InterpDeleted(ptr->ip) +#if TCL_NAMESPACE_DEBUG + || rbtk_invalid_namespace(ptr) +#endif + ) { DUMP1("ip is deleted"); return rb_tainted_str_new2(""); } else { @@ -5399,7 +6992,7 @@ ip_get_variable(self, varname_arg, flag_arg) /* Tcl_Release(ptr->ip); */ rbtk_release_ip(ptr); rb_thread_critical = thr_crit_bup; - rb_exc_raise(exc); + return exc; } strval = rb_tainted_str_new2(ret); @@ -5413,26 +7006,48 @@ ip_get_variable(self, varname_arg, flag_arg) } static VALUE -ip_get_variable2(self, varname_arg, index_arg, flag_arg) +ip_get_variable(self, varname, flag) VALUE self; - VALUE varname_arg; - VALUE index_arg; - VALUE flag_arg; + VALUE varname; + VALUE flag; { - struct tcltkip *ptr = get_ip(self); - int thr_crit_bup; - volatile VALUE varname, index, flag; + VALUE *argv; + VALUE retval; + + argv = ALLOC_N(VALUE, 2); + StringValue(varname); + argv[0] = varname; + argv[1] = flag; + + retval = tk_funcall(ip_get_variable_core, 2, argv, self); - if (NIL_P(index_arg)) { - return ip_get_variable(self, varname_arg, flag_arg); + free(argv); + + if (NIL_P(retval)) { + return rb_tainted_str_new2(""); + } else { + return retval; } +} + +static VALUE +ip_get_variable2_core(interp, argc, argv) + VALUE interp; + int argc; + VALUE *argv; +{ + struct tcltkip *ptr = get_ip(interp); + int thr_crit_bup; + volatile VALUE varname, index, flag; - varname = varname_arg; - index = index_arg; - flag = flag_arg; + varname = argv[0]; + index = argv[1]; + flag = argv[2]; + /* StringValue(varname); StringValue(index); + */ #if TCL_MAJOR_VERSION >= 8 { @@ -5451,7 +7066,12 @@ ip_get_variable2(self, varname_arg, index_arg, flag_arg) Tcl_IncrRefCount(idxobj); /* ip is deleted? */ - if (Tcl_InterpDeleted(ptr->ip) || rbtk_invalid_namespace(ptr)) { + if (ptr == (struct tcltkip *)NULL || ptr->ip == (Tcl_Interp*)NULL + || Tcl_InterpDeleted(ptr->ip) +#if TCL_NAMESPACE_DEBUG + || rbtk_invalid_namespace(ptr) +#endif + ) { DUMP1("ip is deleted"); Tcl_DecrRefCount(nameobj); Tcl_DecrRefCount(idxobj); @@ -5476,7 +7096,7 @@ ip_get_variable2(self, varname_arg, index_arg, flag_arg) /* Tcl_Release(ptr->ip); */ rbtk_release_ip(ptr); rb_thread_critical = thr_crit_bup; - rb_exc_raise(exc); + return exc; } Tcl_IncrRefCount(ret); @@ -5516,7 +7136,12 @@ ip_get_variable2(self, varname_arg, index_arg, flag_arg) char *ret; /* ip is deleted? */ - if (Tcl_InterpDeleted(ptr->ip) || rbtk_invalid_namespace(ptr)) { + if (ptr == (struct tcltkip *)NULL || ptr->ip == (Tcl_Interp*)NULL + || Tcl_InterpDeleted(ptr->ip) +#if TCL_NAMESPACE_DEBUG + || rbtk_invalid_namespace(ptr) +#endif + ) { DUMP1("ip is deleted"); return rb_tainted_str_new2(""); } else { @@ -5536,7 +7161,7 @@ ip_get_variable2(self, varname_arg, index_arg, flag_arg) /* Tcl_Release(ptr->ip); */ rbtk_release_ip(ptr); rb_thread_critical = thr_crit_bup; - rb_exc_raise(exc); + return exc; } strval = rb_tainted_str_new2(ret); @@ -5550,22 +7175,56 @@ ip_get_variable2(self, varname_arg, index_arg, flag_arg) } static VALUE -ip_set_variable(self, varname_arg, value_arg, flag_arg) +ip_get_variable2(self, varname, index, flag) VALUE self; - VALUE varname_arg; - VALUE value_arg; - VALUE flag_arg; + VALUE varname; + VALUE index; + VALUE flag; { - struct tcltkip *ptr = get_ip(self); + VALUE *argv; + VALUE retval; + + argv = ALLOC_N(VALUE, 3); + StringValue(varname); + argv[0] = varname; + + if (NIL_P(index)) { + argv[1] = flag; + retval = tk_funcall(ip_get_variable_core, 2, argv, self); + } else { + StringValue(index); + argv[1] = index; + argv[2] = flag; + retval = tk_funcall(ip_get_variable2_core, 3, argv, self); + } + + free(argv); + + if (NIL_P(retval)) { + return rb_tainted_str_new2(""); + } else { + return retval; + } +} + +static VALUE +ip_set_variable_core(interp, argc, argv) + VALUE interp; + int argc; + VALUE *argv; +{ + struct tcltkip *ptr = get_ip(interp); int thr_crit_bup; volatile VALUE varname, value, flag; - varname = varname_arg; - value = value_arg; - flag = flag_arg; + varname = argv[0]; + value = argv[1]; + flag = argv[2]; + /* StringValue(varname); StringValue(value); + */ #if TCL_MAJOR_VERSION >= 8 { @@ -5613,7 +7272,12 @@ ip_set_variable(self, varname_arg, value_arg, flag_arg) # endif /* ip is deleted? */ - if (Tcl_InterpDeleted(ptr->ip) || rbtk_invalid_namespace(ptr)) { + if (ptr == (struct tcltkip *)NULL || ptr->ip == (Tcl_Interp*)NULL + || Tcl_InterpDeleted(ptr->ip) +#if TCL_NAMESPACE_DEBUG + || rbtk_invalid_namespace(ptr) +#endif + ) { DUMP1("ip is deleted"); Tcl_DecrRefCount(nameobj); Tcl_DecrRefCount(valobj); @@ -5639,7 +7303,7 @@ ip_set_variable(self, varname_arg, value_arg, flag_arg) /* Tcl_Release(ptr->ip); */ rbtk_release_ip(ptr); rb_thread_critical = thr_crit_bup; - rb_exc_raise(exc); + return exc; } Tcl_IncrRefCount(ret); @@ -5680,7 +7344,12 @@ ip_set_variable(self, varname_arg, value_arg, flag_arg) CONST char *ret; /* ip is deleted? */ - if (Tcl_InterpDeleted(ptr->ip) || rbtk_invalid_namespace(ptr)) { + if (ptr == (struct tcltkip *)NULL || ptr->ip == (Tcl_Interp*)NULL + || Tcl_InterpDeleted(ptr->ip) +#if TCL_NAMESPACE_DEBUG + || rbtk_invalid_namespace(ptr) +#endif + ) { DUMP1("ip is deleted"); return rb_tainted_str_new2(""); } else { @@ -5691,7 +7360,7 @@ ip_set_variable(self, varname_arg, value_arg, flag_arg) } if (ret == NULL) { - rb_raise(rb_eRuntimeError, "%s", ptr->ip->result); + return rb_exc_new2(rb_eRuntimeError, ptr->ip->result); } strval = rb_tainted_str_new2(ret); @@ -5705,29 +7374,54 @@ ip_set_variable(self, varname_arg, value_arg, flag_arg) } static VALUE -ip_set_variable2(self, varname_arg, index_arg, value_arg, flag_arg) +ip_set_variable(self, varname, value, flag) VALUE self; - VALUE varname_arg; - VALUE index_arg; - VALUE value_arg; - VALUE flag_arg; + VALUE varname; + VALUE value; + VALUE flag; { - struct tcltkip *ptr = get_ip(self); - int thr_crit_bup; - volatile VALUE varname, index, value, flag; + VALUE *argv; + VALUE retval; + + StringValue(varname); + StringValue(value); + + argv = ALLOC_N(VALUE, 3); + argv[0] = varname; + argv[1] = value; + argv[2] = flag; + + retval = tk_funcall(ip_set_variable_core, 3, argv, self); - if (NIL_P(index_arg)) { - return ip_set_variable(self, varname_arg, value_arg, flag_arg); + free(argv); + + if (NIL_P(retval)) { + return rb_tainted_str_new2(""); + } else { + return retval; } +} + +static VALUE +ip_set_variable2_core(interp, argc, argv) + VALUE interp; + int argc; + VALUE *argv; +{ + struct tcltkip *ptr = get_ip(interp); + int thr_crit_bup; + volatile VALUE varname, index, value, flag; - varname = varname_arg; - index = index_arg; - value = value_arg; - flag = flag_arg; + varname = argv[0]; + index = argv[1]; + value = argv[2]; + flag = argv[3]; + /* StringValue(varname); StringValue(index); StringValue(value); + */ #if TCL_MAJOR_VERSION >= 8 { @@ -5777,7 +7471,12 @@ ip_set_variable2(self, varname_arg, index_arg, value_arg, flag_arg) Tcl_IncrRefCount(valobj); /* ip is deleted? */ - if (Tcl_InterpDeleted(ptr->ip) || rbtk_invalid_namespace(ptr)) { + if (ptr == (struct tcltkip *)NULL || ptr->ip == (Tcl_Interp*)NULL + || Tcl_InterpDeleted(ptr->ip) +#if TCL_NAMESPACE_DEBUG + || rbtk_invalid_namespace(ptr) +#endif + ) { DUMP1("ip is deleted"); Tcl_DecrRefCount(nameobj); Tcl_DecrRefCount(idxobj); @@ -5805,7 +7504,7 @@ ip_set_variable2(self, varname_arg, index_arg, value_arg, flag_arg) /* Tcl_Release(ptr->ip); */ rbtk_release_ip(ptr); rb_thread_critical = thr_crit_bup; - rb_exc_raise(exc); + return exc; } Tcl_IncrRefCount(ret); @@ -5838,7 +7537,12 @@ ip_set_variable2(self, varname_arg, index_arg, value_arg, flag_arg) CONST char *ret; /* ip is deleted? */ - if (Tcl_InterpDeleted(ptr->ip) || rbtk_invalid_namespace(ptr)) { + if (ptr == (struct tcltkip *)NULL || ptr->ip == (Tcl_Interp*)NULL + || Tcl_InterpDeleted(ptr->ip) +#if TCL_NAMESPACE_DEBUG + || rbtk_invalid_namespace(ptr) +#endif + ) { DUMP1("ip is deleted"); return rb_tainted_str_new2(""); } else { @@ -5850,7 +7554,7 @@ ip_set_variable2(self, varname_arg, index_arg, value_arg, flag_arg) } if (ret == (char*)NULL) { - rb_raise(rb_eRuntimeError, "%s", ptr->ip->result); + return rb_exc_new2(rb_eRuntimeError, ptr->ip->result); } Tcl_IncrRefCount(ret); @@ -5868,21 +7572,66 @@ ip_set_variable2(self, varname_arg, index_arg, value_arg, flag_arg) } static VALUE -ip_unset_variable(self, varname_arg, flag_arg) +ip_set_variable2(self, varname, index, value, flag) VALUE self; - VALUE varname_arg; - VALUE flag_arg; + VALUE varname; + VALUE index; + VALUE value; + VALUE flag; { - struct tcltkip *ptr = get_ip(self); - volatile VALUE varname, value, flag; + VALUE *argv; + VALUE retval; + + argv = ALLOC_N(VALUE, 4); + StringValue(varname); + argv[0] = varname; + + if (NIL_P(index)) { + StringValue(value); + argv[1] = value; + argv[2] = flag; + retval = tk_funcall(ip_set_variable_core, 3, argv, self); + } else { + StringValue(index); + StringValue(value); + argv[1] = index; + argv[2] = value; + argv[3] = flag; + retval = tk_funcall(ip_set_variable2_core, 4, argv, self); + } + + free(argv); - varname = varname_arg; - flag = flag_arg; + if (NIL_P(retval)) { + return rb_tainted_str_new2(""); + } else { + return retval; + } +} + +static VALUE +ip_unset_variable_core(interp, argc, argv) + VALUE interp; + int argc; + VALUE *argv; +{ + struct tcltkip *ptr = get_ip(interp); + volatile VALUE varname, flag; + + varname = argv[0]; + flag = argv[1]; + /* StringValue(varname); + */ /* ip is deleted? */ - if (Tcl_InterpDeleted(ptr->ip) || rbtk_invalid_namespace(ptr)) { + if (ptr == (struct tcltkip *)NULL || ptr->ip == (Tcl_Interp*)NULL + || Tcl_InterpDeleted(ptr->ip) +#if TCL_NAMESPACE_DEBUG + || rbtk_invalid_namespace(ptr) +#endif + ) { DUMP1("ip is deleted"); return Qtrue; } @@ -5892,9 +7641,9 @@ ip_unset_variable(self, varname_arg, flag_arg) if (ptr->return_value == TCL_ERROR) { if (FIX2INT(flag) & TCL_LEAVE_ERR_MSG) { #if TCL_MAJOR_VERSION >= 8 - rb_raise(rb_eRuntimeError, "%s", Tcl_GetStringResult(ptr->ip)); + return rb_exc_new2(rb_eRuntimeError, Tcl_GetStringResult(ptr->ip)); #else /* TCL_MAJOR_VERSION < 8 */ - rb_raise(rb_eRuntimeError, "%s", ptr->ip->result); + return rb_exc_new2(rb_eRuntimeError, ptr->ip->result); #endif } return Qfalse; @@ -5903,28 +7652,55 @@ ip_unset_variable(self, varname_arg, flag_arg) } static VALUE -ip_unset_variable2(self, varname_arg, index_arg, flag_arg) +ip_unset_variable(self, varname, flag) VALUE self; - VALUE varname_arg; - VALUE index_arg; - VALUE flag_arg; + VALUE varname; + VALUE flag; { - struct tcltkip *ptr = get_ip(self); - volatile VALUE varname, index, value, flag; + VALUE *argv; + VALUE retval; + + argv = ALLOC_N(VALUE, 2); + StringValue(varname); + argv[0] = varname; + argv[1] = flag; + + retval = tk_funcall(ip_unset_variable_core, 2, argv, self); - if (NIL_P(index_arg)) { - return ip_unset_variable(self, varname_arg, flag_arg); + free(argv); + + if (NIL_P(retval)) { + return rb_tainted_str_new2(""); + } else { + return retval; } +} + +static VALUE +ip_unset_variable2_core(interp, argc, argv) + VALUE interp; + int argc; + VALUE *argv; +{ + struct tcltkip *ptr = get_ip(interp); + volatile VALUE varname, index, flag; - varname = varname_arg; - index = index_arg; - flag = flag_arg; + varname = argv[0]; + index = argv[1]; + flag = argv[2]; + /* StringValue(varname); StringValue(index); + */ /* ip is deleted? */ - if (Tcl_InterpDeleted(ptr->ip) || rbtk_invalid_namespace(ptr)) { + if (ptr == (struct tcltkip *)NULL || ptr->ip == (Tcl_Interp*)NULL + || Tcl_InterpDeleted(ptr->ip) +#if TCL_NAMESPACE_DEBUG + || rbtk_invalid_namespace(ptr) +#endif + ) { DUMP1("ip is deleted"); return Qtrue; } @@ -5934,9 +7710,9 @@ ip_unset_variable2(self, varname_arg, index_arg, flag_arg) if (ptr->return_value == TCL_ERROR) { if (FIX2INT(flag) & TCL_LEAVE_ERR_MSG) { #if TCL_MAJOR_VERSION >= 8 - rb_raise(rb_eRuntimeError, "%s", Tcl_GetStringResult(ptr->ip)); + return rb_exc_new2(rb_eRuntimeError, Tcl_GetStringResult(ptr->ip)); #else /* TCL_MAJOR_VERSION < 8 */ - rb_raise(rb_eRuntimeError, "%s", ptr->ip->result); + return rb_exc_new2(rb_eRuntimeError, ptr->ip->result); #endif } return Qfalse; @@ -5944,6 +7720,39 @@ ip_unset_variable2(self, varname_arg, index_arg, flag_arg) return Qtrue; } +static VALUE +ip_unset_variable2(self, varname, index, flag) + VALUE self; + VALUE varname; + VALUE index; + VALUE flag; +{ + VALUE *argv; + VALUE retval; + + argv = ALLOC_N(VALUE, 3); + StringValue(varname); + argv[0] = varname; + + if (NIL_P(index)) { + argv[1] = flag; + retval = tk_funcall(ip_unset_variable_core, 2, argv, self); + } else { + StringValue(index); + argv[1] = index; + argv[2] = flag; + retval = tk_funcall(ip_unset_variable2_core, 3, argv, self); + } + + free(argv); + + if (NIL_P(retval)) { + return rb_tainted_str_new2(""); + } else { + return retval; + } +} + static VALUE ip_get_global_var(self, varname) VALUE self; @@ -6019,6 +7828,8 @@ lib_split_tklist_core(ip_obj, list_str) if (NIL_P(ip_obj)) { interp = (Tcl_Interp *)NULL; + } else if (get_ip(ip_obj) == (struct tcltkip *)NULL) { + interp = (Tcl_Interp *)NULL; } else { interp = get_ip(ip_obj)->ip; } @@ -6372,6 +8183,7 @@ tcltklib_compile_info() return ret; } + /*---- initialization ----*/ void Init_tcltklib() @@ -6401,8 +8213,11 @@ Init_tcltklib() rb_global_variable(&eTkCallbackContinue); rb_global_variable(&eventloop_thread); + rb_global_variable(&eventloop_stack); rb_global_variable(&watchdog_thread); + rb_global_variable(&rbtk_pending_exception); + /* --------------------------------------------------------------- */ rb_define_const(lib, "COMPILE_INFO", tcltklib_compile_info()); @@ -6456,8 +8271,10 @@ Init_tcltklib() ID_at_interp = rb_intern("@interp"); ID_stop_p = rb_intern("stop?"); + ID_alive_p = rb_intern("alive?"); ID_kill = rb_intern("kill"); ID_join = rb_intern("join"); + ID_value = rb_intern("value"); ID_call = rb_intern("call"); ID_backtrace = rb_intern("backtrace"); @@ -6474,8 +8291,12 @@ Init_tcltklib() /* --------------------------------------------------------------- */ rb_define_module_function(lib, "mainloop", lib_mainloop, -1); + rb_define_module_function(lib, "mainloop_thread?", + lib_evloop_thread_p, 0); rb_define_module_function(lib, "mainloop_watchdog", lib_mainloop_watchdog, -1); + rb_define_module_function(lib, "do_thread_callback", + lib_thread_callback, -1); rb_define_module_function(lib, "do_one_event", lib_do_one_event, -1); rb_define_module_function(lib, "mainloop_abort_on_exception", lib_evloop_abort_on_exc, 0); @@ -6567,8 +8388,16 @@ Init_tcltklib() /* --------------------------------------------------------------- */ - eventloop_thread = 0; - watchdog_thread = 0; + eventloop_thread = Qnil; + +#ifndef DEFAULT_EVENTLOOP_DEPTH +#define DEFAULT_EVENTLOOP_DEPTH 7 +#endif + eventloop_stack = rb_ary_new2(DEFAULT_EVENTLOOP_DEPTH); + + watchdog_thread = Qnil; + + rbtk_pending_exception = Qnil; /* --------------------------------------------------------------- */ diff --git a/ext/tk/tkutil/tkutil.c b/ext/tk/tkutil/tkutil.c index d88f7b9a72..bedf12642a 100644 --- a/ext/tk/tkutil/tkutil.c +++ b/ext/tk/tkutil/tkutil.c @@ -8,12 +8,20 @@ ************************************************/ -#define TKUTIL_RELEASE_DATE "2005-01-25" +#define TKUTIL_RELEASE_DATE "2005-03-02" #include "ruby.h" #include "rubysig.h" #include "st.h" +/* check ruby_version */ +#include "version.h" +#if RUBY_VERSION_MINOR == 9 +#define ST_FOREACH_PASS_ERR_ARG 1 /* Ruby 1.9 */ +#else +#define ST_FOREACH_PASS_ERR_ARG 0 /* Ruby 1.8 (from 2005/02/08) */ +#endif + static VALUE cMethod; static VALUE cTclTkLib; @@ -200,23 +208,35 @@ fromUTF8_toDefaultEnc(str, self) } +#if ST_FOREACH_PASS_ERR_ARG static void hash_check(err) int err; { if (err) { - rb_raise(rb_eRuntimeError, "hash modified"); + rb_raise(rb_eRuntimeError, "hash modified during iteration"); } } +#endif +#if ST_FOREACH_PASS_ERR_ARG static int to_strkey(key, value, hash, err) VALUE key; VALUE value; VALUE hash; int err; +#else +static int +to_strkey(key, value, hash) + VALUE key; + VALUE value; + VALUE hash; +#endif { +#if ST_FOREACH_PASS_ERR_ARG hash_check(err); +#endif if (key == Qundef) return ST_CONTINUE; rb_hash_aset(hash, rb_funcall(key, ID_to_s, 0, 0), value); return ST_CHECK; @@ -236,14 +256,16 @@ tk_symbolkey2str(self, keys) } static VALUE get_eval_string_core _((VALUE, VALUE, VALUE)); -static VALUE ary2list _((VALUE, VALUE)); -static VALUE ary2list2 _((VALUE, VALUE)); +static VALUE ary2list _((VALUE, VALUE, VALUE)); +static VALUE ary2list2 _((VALUE, VALUE, VALUE)); static VALUE hash2list _((VALUE, VALUE)); +static VALUE hash2list_enc _((VALUE, VALUE)); static VALUE hash2kv _((VALUE, VALUE, VALUE)); static VALUE -ary2list(ary, self) +ary2list(ary, enc_flag, self) VALUE ary; + VALUE enc_flag; VALUE self; { int idx, idx2, size, size2; @@ -266,29 +288,36 @@ ary2list(ary, self) val = RARRAY(ary)->ptr[idx]; switch(TYPE(val)) { case T_ARRAY: - RARRAY(dst)->ptr[RARRAY(dst)->len++] = ary2list(val, self); + RARRAY(dst)->ptr[RARRAY(dst)->len++] + = ary2list(val, enc_flag, self); break; case T_HASH: /* RARRAY(dst)->ptr[RARRAY(dst)->len++] = hash2list(val, self); */ - val = hash2kv(val, Qnil, self); + val = hash2kv(val, enc_flag, self); size2 = RARRAY(val)->len; for(idx2 = 0; idx2 < size2; idx2++) { val2 = RARRAY(val)->ptr[idx2]; switch(TYPE(val2)) { case T_ARRAY: RARRAY(dst)->ptr[RARRAY(dst)->len++] - = ary2list(val2, self); + = ary2list(val2, enc_flag, self); break; case T_HASH: - RARRAY(dst)->ptr[RARRAY(dst)->len++] - = hash2list(val2, self); + if (RTEST(enc_flag)) { + RARRAY(dst)->ptr[RARRAY(dst)->len++] + = hash2list_enc(val2, self); + } else { + RARRAY(dst)->ptr[RARRAY(dst)->len++] + = hash2list(val2, self); + } + break; default: if (val2 != TK_None) { RARRAY(dst)->ptr[RARRAY(dst)->len++] - = get_eval_string_core(val2, Qnil, self); + = get_eval_string_core(val2, enc_flag, self); } } } @@ -297,7 +326,7 @@ ary2list(ary, self) default: if (val != TK_None) { RARRAY(dst)->ptr[RARRAY(dst)->len++] - = get_eval_string_core(val, Qnil, self); + = get_eval_string_core(val, enc_flag, self); } } } @@ -305,8 +334,9 @@ ary2list(ary, self) } static VALUE -ary2list2(ary, self) +ary2list2(ary, enc_flag, self) VALUE ary; + VALUE enc_flag; VALUE self; { int idx, size; @@ -320,17 +350,23 @@ ary2list2(ary, self) val = RARRAY(ary)->ptr[idx]; switch(TYPE(val)) { case T_ARRAY: - RARRAY(dst)->ptr[RARRAY(dst)->len++] = ary2list(val, self); + RARRAY(dst)->ptr[RARRAY(dst)->len++] + = ary2list(val, enc_flag, self); break; case T_HASH: - RARRAY(dst)->ptr[RARRAY(dst)->len++] = hash2list(val, self); + if (RTEST(enc_flag)) { + RARRAY(dst)->ptr[RARRAY(dst)->len++] = hash2list(val, self); + } else { + RARRAY(dst)->ptr[RARRAY(dst)->len++] + = hash2list_enc(val, self); + } break; default: if (val != TK_None) { RARRAY(dst)->ptr[RARRAY(dst)->len++] - = get_eval_string_core(val, Qnil, self); + = get_eval_string_core(val, enc_flag, self); } } } @@ -448,16 +484,26 @@ assoc2kv_enc(assoc, ary, self) } } +#if ST_FOREACH_PASS_ERR_ARG static int push_kv(key, val, args, err) VALUE key; VALUE val; VALUE args; int err; +#else +static int +push_kv(key, val, args) + VALUE key; + VALUE val; + VALUE args; +#endif { volatile VALUE ary; +#if ST_FOREACH_PASS_ERR_ARG hash_check(err); +#endif ary = RARRAY(args)->ptr[0]; if (key == Qundef) return ST_CONTINUE; @@ -498,16 +544,26 @@ hash2kv(hash, ary, self) } } +#if ST_FOREACH_PASS_ERR_ARG static int push_kv_enc(key, val, args, err) VALUE key; VALUE val; VALUE args; int err; +#else +static int +push_kv_enc(key, val, args) + VALUE key; + VALUE val; + VALUE args; +#endif { volatile VALUE ary; +#if ST_FOREACH_PASS_ERR_ARG hash_check(err); +#endif ary = RARRAY(args)->ptr[0]; if (key == Qundef) return ST_CONTINUE; @@ -556,7 +612,7 @@ hash2list(hash, self) VALUE hash; VALUE self; { - return ary2list2(hash2kv(hash, Qnil, self), self); + return ary2list2(hash2kv(hash, Qnil, self), Qfalse, self); } @@ -565,7 +621,7 @@ hash2list_enc(hash, self) VALUE hash; VALUE self; { - return ary2list2(hash2kv_enc(hash, Qnil, self), self); + return ary2list2(hash2kv_enc(hash, Qnil, self), Qfalse, self); } static VALUE @@ -669,11 +725,7 @@ get_eval_string_core(obj, enc_flag, self) } case T_ARRAY: - if (RTEST(enc_flag)) { - return fromDefaultEnc_toUTF8(ary2list(obj, self), self); - } else { - return ary2list(obj, self); - } + return ary2list(obj, enc_flag, self); case T_FALSE: return rb_str_new2("0"); -- cgit v1.2.3