* 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
This commit is contained in:
nagai 2005-03-02 07:06:52 +00:00
parent 16771ba23c
commit 3124427ccb
24 changed files with 7685 additions and 696 deletions

View File

@ -1,3 +1,50 @@
Wed Mar 2 16:03:08 2005 Hidetoshi NAGAI <nagai@ai.kyutech.ac.jp>
* 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 <nobu@ruby-lang.org> Wed Mar 2 12:21:18 2005 Nobuyoshi Nakada <nobu@ruby-lang.org>
* eval.c (rb_eval): [EXPERIMENTAL] NODE_LAMBDA implemented. * eval.c (rb_eval): [EXPERIMENTAL] NODE_LAMBDA implemented.

View File

@ -1,3 +1,7 @@
2005-02-20 Hidetoshi NAGAI <nagai@ai.kyutech.ac.jp>
* ext/tk/lib/tclx/tclx.rb: warning TclX's 'signal' command.
2005-01-25 Hidetoshi NAGAI <nagai@ai.kyutech.ac.jp> 2005-01-25 Hidetoshi NAGAI <nagai@ai.kyutech.ac.jp>
* ext/tk/lib/tkextlib/blt/component.rb: bug fix. cannot accept * ext/tk/lib/tkextlib/blt/component.rb: bug fix. cannot accept

View File

@ -132,6 +132,16 @@ module TclTklib
: If 'check_root' is false, doen't return by the other : If 'check_root' is false, doen't return by the other
: reasons than exceptions. : 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) mainloop_watchdog(check_root = true)
: On the normal eventloop, some kinds of callback operations : On the normal eventloop, some kinds of callback operations
: cause deadlock. To avoid some of such deadlocks, this : cause deadlock. To avoid some of such deadlocks, this

View File

@ -230,6 +230,15 @@ require "tcltklib"
: WINDOW 以外のイベントは発生しうるため ).終了には,外部 : WINDOW 以外のイベントは発生しうるため ).終了には,外部
: からの働き掛け ( スレッドを活用するなど ) が必要. : からの働き掛け ( スレッドを活用するなど ) が必要.
mainloop_thread?
: カレントスレッドがイベントループを実行しているスレッド
: かどうかを返す.
: イベントループを実行しているスレッドであれば true を,
: どのスレッドでもイベントループが実行されていない場合は
: nil を,他のスレッドでイベントループが実行されている場
: 合は false を返す.
: false の際に Tk インタープリタを直接呼ぶのは危険である.
mainloop_watchdog(check_root = true) mainloop_watchdog(check_root = true)
: 通常のイベントループでは,イベント処理の内容によっては : 通常のイベントループでは,イベント処理の内容によっては
: デッドロックを引き起こす可能性がある (例えばイベントに : デッドロックを引き起こす可能性がある (例えばイベントに
@ -377,7 +386,7 @@ require "tcltklib"
: 失敗した場合は RuntimeError の例外を発生する. : 失敗した場合は RuntimeError の例外を発生する.
safe? safe?
: Tcl/Tk インタープリタ safe インタープリタであるかを調べる. : Tcl/Tk インタープリタ safe インタープリタであるかを調べる.
: safe インタープリタであれば true を返す. : safe インタープリタであれば true を返す.
allow_ruby_exit? allow_ruby_exit?

View File

@ -77,18 +77,23 @@ class MultiTkIp
cmd.inspect cmd.inspect
end end
def call(*args) def call(*args)
begin
unless @ip.deleted? unless @ip.deleted?
current = Thread.current
backup_ip = current['callback_ip']
current['callback_ip'] = @ip
begin
@ip.cb_eval(@cmd, *args) @ip.cb_eval(@cmd, *args)
end
rescue TkCallbackBreak, TkCallbackContinue => e rescue TkCallbackBreak, TkCallbackContinue => e
fail e fail e
rescue Exception => e rescue Exception => e
if @ip.safe? if @ip.safe?
# ignore nil # ignore
else else
fail e fail e
end end
ensure
current['callback_ip'] = backup_ip
end
end end
end end
}.freeze }.freeze
@ -105,19 +110,23 @@ class MultiTkIp
def _check_and_return(thread, exception, wait=0) def _check_and_return(thread, exception, wait=0)
unless thread unless thread
unless exception.kind_of?(MultiTkIp_OK) || safe? unless exception.kind_of?(MultiTkIp_OK)
msg = "#{exception.class}: #{exception.message}" msg = "#{exception.class}: #{exception.message}"
begin
if @interp.deleted? if @interp.deleted?
warn('Warning: ' + msg) warn("Warning (#{self}): " + msg)
elsif @interp._eval_without_enc('info command bgerror').size != 0 return nil
@interp._eval(@interp._merge_tklist('bgerror', msg))
else
warn('Warning: ' + msg)
end end
if safe?
warn("Warning (#{self}): " + msg) if $DEBUG
return nil
end
begin
@interp._eval_without_enc(@interp._merge_tklist('bgerror', msg))
rescue Exception => e rescue Exception => e
warn('Warning: ' + msg) warn("Warning (#{self}): " + msg)
warn('Warning: ' + e.message)
end end
end end
return nil return nil
@ -230,8 +239,18 @@ class MultiTkIp
def _receiver_eval_proc_core(safe_level, thread, cmd, *args) def _receiver_eval_proc_core(safe_level, thread, cmd, *args)
begin begin
#ret = proc{$SAFE = safe_level; cmd.call(*args)}.call #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 rescue SystemExit => e
# delete IP # delete IP
unless @interp.deleted? unless @interp.deleted?
@ -297,7 +316,8 @@ class MultiTkIp
_check_and_return(thread, MultiTkIp_OK.new(nil)) _check_and_return(thread, MultiTkIp_OK.new(nil))
end end
if master? && !safe? && allow_ruby_exit? # if master? && !safe? && allow_ruby_exit?
if !@interp.deleted? && master? && !safe? && allow_ruby_exit?
=begin =begin
ObjectSpace.each_object(TclTkIp){|obj| ObjectSpace.each_object(TclTkIp){|obj|
obj.delete unless obj.deleted? obj.delete unless obj.deleted?
@ -380,6 +400,16 @@ class MultiTkIp
rescue Exception => e rescue Exception => e
# raise exception # 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) _check_and_return(thread, e)
else else
@ -411,7 +441,8 @@ class MultiTkIp
def _receiver_mainloop(check_root) def _receiver_mainloop(check_root)
Thread.new{ Thread.new{
while !@interp.deleted? 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 sleep 0.5
end end
} }
@ -742,8 +773,8 @@ class MultiTkIp
# create toplevel widget # create toplevel widget
begin begin
top = TkToplevel.new(toplevel_keys) top = TkToplevel.new(toplevel_keys)
rescue NameError rescue NameError => e
fail unless @interp.safe? fail e unless @interp.safe?
fail SecurityError, "unable create toplevel on the safe interpreter" fail SecurityError, "unable create toplevel on the safe interpreter"
end end
msg = "Untrusted Ruby/Tk applet (#{slave_name})" msg = "Untrusted Ruby/Tk applet (#{slave_name})"
@ -870,7 +901,11 @@ class MultiTkIp
fail SecurityError, "cannot create a master-ip at level #{$SAFE}" fail SecurityError, "cannot create a master-ip at level #{$SAFE}"
end 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" fail SecurityError, "safe-slave-ip cannot create a new interpreter"
end end
@ -964,15 +999,20 @@ class MultiTkIp
undef :instance_eval undef :instance_eval
end end
# dummy call for initialization
self.eval_proc{ Tk.tk_call('set', 'tcl_patchLevel') }
self.freeze # defend against modification self.freeze # defend against modification
end end
###################################### ######################################
def _default_delete_hook(slave) 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) # call default hook of safetk.tcl (ignore exceptions)
if @slave_ip_top[slave] == '' if top == ''
begin begin
@interp._eval("::safe::disallowTk #{slave}") @interp._eval("::safe::disallowTk #{slave}")
rescue rescue
@ -980,20 +1020,19 @@ class MultiTkIp
end end
else # toplevel path else # toplevel path
begin begin
@interp._eval("::safe::tkDelete {} #{@slave_ip_top[slave]} #{slave}") @interp._eval("::safe::tkDelete {} #{top} #{slave}")
rescue rescue
warn("Waring: fail to call '::safe::tkDelete'") if $DEBUG warn("Waring: fail to call '::safe::tkDelete'") if $DEBUG
begin begin
@interp._eval("destroy #{@slave_ip_top[slave]}") @interp._eval("destroy #{top}")
rescue rescue
warn("Waring: fail to destroy toplevel") if $DEBUG warn("Waring: fail to destroy toplevel") if $DEBUG
end end
end end
end end
end end
@slave_ip_tbl.delete(slave)
@slave_ip_top.delete(slave)
end end
end end
@ -1007,10 +1046,14 @@ class MultiTkIp
end end
def self.__getip 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 @@DEFAULT_MASTER
else else
ip = @@IP_TABLE[Thread.current.group] ip = @@IP_TABLE[current.group]
unless ip unless ip
fail SecurityError, fail SecurityError,
"cannot call Tk methods on #{Thread.current.inspect}" "cannot call Tk methods on #{Thread.current.inspect}"
@ -1093,9 +1136,15 @@ class MultiTkIp
def inspect def inspect
s = self.to_s.chop! s = self.to_s.chop!
if master? if master?
s << ':master' if @interp.deleted?
s << ':deleted-master'
else else
if @interp.safe? s << ':master'
end
else
if @interp.deleted?
s << ':deleted-slave'
elsif @interp.safe?
s << ':safe-slave' s << ':safe-slave'
else else
s << ':trusted-slave' s << ':trusted-slave'
@ -1281,11 +1330,13 @@ class MultiTkIp
#self.eval_callback{ TkComm._get_eval_string(TkUtil.eval_cmd(cmd, *args)) } #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{ TkComm._get_eval_string(TkUtil.eval_cmd(cmd, *args)) }
ret = self.eval_callback(*args){|safe, *params| 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) if ret.kind_of?(Exception)
raise ret raise ret
end end
ret
end end
end end
@ -1300,10 +1351,11 @@ class MultiTkIp
end end
# on IP thread # on IP thread
if (@cmd_receiver == Thread.current) if @cmd_receiver == Thread.current ||
(!req_val && TclTkLib.mainloop_thread? != false) # callback
begin begin
ret = cmd.call(*args) ret = cmd.call(*args)
rescue SystemExit rescue SystemExit => e
# exit IP # exit IP
warn("Warning: "+ $! + " on " + self.inspect) if $DEBUG warn("Warning: "+ $! + " on " + self.inspect) if $DEBUG
begin begin
@ -1318,6 +1370,18 @@ class MultiTkIp
((e.message.length > 0)? ' "' + e.message + '"': '') + ((e.message.length > 0)? ' "' + e.message + '"': '') +
" on " + self.inspect) " on " + self.inspect)
end 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 ret = e
end end
return ret return ret
@ -1353,7 +1417,7 @@ class MultiTkIp
self._eval_without_enc('exit') self._eval_without_enc('exit')
rescue Exception rescue Exception
end end
if !safe? && allow_ruby_exit? if !self.deleted? && !safe? && allow_ruby_exit?
self.delete self.delete
fail e fail e
else else
@ -1380,11 +1444,34 @@ class MultiTkIp
end end
end end
=end =end
=begin
def eval_callback(*args) def eval_callback(*args)
if block_given? if block_given?
eval_proc_core(false, Proc.new, *args) eval_proc_core(false, Proc.new, *args)
# eval_proc_core(Thread.current, Proc.new, *args)
else else
cmd = args.shift
eval_proc_core(false, *args) 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
end end
@ -1399,7 +1486,7 @@ class MultiTkIp
=end =end
def eval_proc(*args) def eval_proc(*args)
# The scope of the eval-block of 'eval_proc' method is different from # 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. # use arguments of eval_proc method. They are passed to block-arguments.
if block_given? if block_given?
cmd = Proc.new cmd = Proc.new
@ -1408,12 +1495,25 @@ class MultiTkIp
fail ArgumentError, "A Proc or Method object is expected for 1st argument" fail ArgumentError, "A Proc or Method object is expected for 1st argument"
end end
end end
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, eval_proc_core(true,
proc{|safe, *params| proc{|safe, *params|
$SAFE=safe; Thread.new(*params, &cmd).value $SAFE=safe
Thread.new(*params, &cmd).value
}, },
*args) *args)
end end
end
alias call eval_proc alias call eval_proc
def bg_eval_proc(*args) def bg_eval_proc(*args)
@ -1739,7 +1839,7 @@ end
# depend on TclTkIp # depend on TclTkIp
class MultiTkIp 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.slave?
#return self if self != @@DEFAULT_MASTER #return self if self != @@DEFAULT_MASTER
if self != @@DEFAULT_MASTER if self != @@DEFAULT_MASTER
@ -1752,7 +1852,11 @@ class MultiTkIp
rescue MultiTkIp_OK => ret rescue MultiTkIp_OK => ret
# return value # return value
@wait_on_mainloop[1] = false @wait_on_mainloop[1] = false
if ret.value.kind_of?(Thread)
return ret.value.value return ret.value.value
else
return ret.value
end
rescue SystemExit rescue SystemExit
# exit IP # exit IP
warn("Warning: " + $! + " on " + self.inspect) if $DEBUG warn("Warning: " + $! + " on " + self.inspect) if $DEBUG
@ -1762,7 +1866,7 @@ class MultiTkIp
rescue Exception rescue Exception
end end
self.delete self.delete
rescue Exception => e rescue StandardError => e
if $DEBUG if $DEBUG
warn("Warning: " + e.class.inspect + warn("Warning: " + e.class.inspect +
((e.message.length > 0)? ' "' + e.message + '"': '') + ((e.message.length > 0)? ' "' + e.message + '"': '') +
@ -1779,31 +1883,59 @@ class MultiTkIp
unless restart_on_dead unless restart_on_dead
@wait_on_mainloop[1] = true @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) @interp.mainloop(check_root)
@wait_on_mainloop[1] = false @wait_on_mainloop[1] = false
else else
begin
@wait_on_mainloop[1] = true
loop do loop do
@wait_on_mainloop[1] = true
break unless self.alive? break unless self.alive?
if check_root if check_root
begin begin
break if TclTkLib.num_of_mainwindows == 0 break if TclTkLib.num_of_mainwindows == 0
rescue Exception rescue StandardError
break break
end end
end end
break if @interp.deleted?
begin
@interp.mainloop(check_root) @interp.mainloop(check_root)
end rescue StandardError => e
#rescue StandardError
rescue Exception
if TclTkLib.mainloop_abort_on_exception != nil if TclTkLib.mainloop_abort_on_exception != nil
STDERR.print("Warning: Tk mainloop receives ", $!.class.inspect, #STDERR.print("Warning: Tk mainloop receives ", $!.class.inspect,
" exception (ignore) : ", $!.message, "\n"); # " exception (ignore) : ", $!.message, "\n");
if $DEBUG
warn("Warning: Tk mainloop receives " << e.class.inspect <<
" exception (ignore) : " << e.message);
end end
retry 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 ensure
@wait_on_mainloop[1] = false @wait_on_mainloop[1] = false
Thread.pass # avoid eventloop conflict
end
end end
end end
self self
@ -1875,18 +2007,17 @@ class MultiTkIp
@interp._eval_without_enc("foreach i {#{after_ids}} {after cancel $i}") @interp._eval_without_enc("foreach i {#{after_ids}} {after cancel $i}")
rescue Exception rescue Exception
end end
=begin
begin begin
@interp._invoke('destroy', '.') unless @interp.deleted? @interp._invoke('destroy', '.') unless @interp.deleted?
rescue Exception rescue Exception
end end
=end
if @safe_base && !@interp.deleted? if @safe_base && !@interp.deleted?
# do 'exit' to call the delete_hook procedure # do 'exit' to call the delete_hook procedure
@interp._eval_without_enc('exit') @interp._eval_without_enc('exit')
else
@interp.delete unless @interp.deleted?
end end
@interp.delete
self self
end end

View File

@ -565,6 +565,15 @@ end
module_function :bool, :number, :num_or_str, :string module_function :bool, :number, :num_or_str, :string
module_function :list, :simplelist, :window, :image_obj, :procedure 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) def _toUTF8(str, encoding = nil)
TkCore::INTERP._toUTF8(str, encoding) TkCore::INTERP._toUTF8(str, encoding)
end end
@ -1110,13 +1119,14 @@ module TkCore
INTERP._invoke_without_enc('bind', 'all', "<#{WIDGET_DESTROY_HOOK}>", INTERP._invoke_without_enc('bind', 'all', "<#{WIDGET_DESTROY_HOOK}>",
install_cmd(proc{|path| install_cmd(proc{|path|
unless TkCore::INTERP.deleted? 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__ widget.__destroy_hook__
rescue Exception
end end
end end
rescue Exception=>e
p e if $DEBUG
end end
end end
}) << ' %W') }) << ' %W')
@ -1175,11 +1185,24 @@ module TkCore
def TkCore.callback(*arg) def TkCore.callback(*arg)
begin begin
TkCore::INTERP.tk_cmd_tbl[arg.shift].call(*arg) if TkCore::INTERP.tk_cmd_tbl.kind_of?(Hash)
rescue SystemExit #TkCore::INTERP.tk_cmd_tbl[arg.shift].call(*arg)
exit(0) normal_ret = false
rescue Interrupt ret = catch(:IRB_EXIT) do # IRB hack
exit!(1) 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 rescue Exception => e
begin begin
msg = _toUTF8(e.class.inspect) + ': ' + msg = _toUTF8(e.class.inspect) + ': ' +
@ -1194,6 +1217,8 @@ module TkCore
e.backtrace.join("\n") + e.backtrace.join("\n") +
"\n---< backtrace of Tk side >-------" "\n---< backtrace of Tk side >-------"
end end
# TkCore::INTERP._set_global_var('errorInfo', msg)
# fail(e)
fail(e, msg) fail(e, msg)
end end
end end
@ -1383,6 +1408,22 @@ module TkCore
TclTkLib.mainloop(check_root) TclTkLib.mainloop(check_root)
end 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) def mainloop_watchdog(check_root = true)
# watchdog restarts mainloop when mainloop is dead # watchdog restarts mainloop when mainloop is dead
TclTkLib.mainloop_watchdog(check_root) TclTkLib.mainloop_watchdog(check_root)
@ -1738,13 +1779,34 @@ module Tk
end end
def Tk.pack(*args) def Tk.pack(*args)
#TkPack.configure(*args) TkPack.configure(*args)
TkPack(*args) end
def Tk.pack_forget(*args)
TkPack.forget(*args)
end
def Tk.unpack(*args)
TkPack.forget(*args)
end end
def Tk.grid(*args) def Tk.grid(*args)
TkGrid.configure(*args) TkGrid.configure(*args)
end 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) def Tk.update(idle=nil)
if idle if idle
@ -3940,7 +4002,7 @@ end
#Tk.freeze #Tk.freeze
module Tk module Tk
RELEASE_DATE = '2005-01-28'.freeze RELEASE_DATE = '2005-03-02'.freeze
autoload :AUTO_PATH, 'tk/variable' autoload :AUTO_PATH, 'tk/variable'
autoload :TCL_PACKAGE_PATH, 'tk/variable' autoload :TCL_PACKAGE_PATH, 'tk/variable'
@ -3950,7 +4012,6 @@ module Tk
autoload :TCL_PRECISION, 'tk/variable' autoload :TCL_PRECISION, 'tk/variable'
end end
# call setup script for Tk extension libraries (base configuration) # call setup script for Tk extension libraries (base configuration)
begin begin
require 'tkextlib/setup.rb' require 'tkextlib/setup.rb'

View File

@ -159,9 +159,11 @@ class TkCanvas<TkWindow
def delete(*args) def delete(*args)
if TkcItem::CItemID_TBL[self.path] if TkcItem::CItemID_TBL[self.path]
find('withtag', *args).each{|item| args.each{|tag|
find('withtag', tag).each{|item|
TkcItem::CItemID_TBL[self.path].delete(item.id) TkcItem::CItemID_TBL[self.path].delete(item.id)
} }
}
end end
tk_send_without_enc('delete', *args.collect{|t| tagid(t)}) tk_send_without_enc('delete', *args.collect{|t| tagid(t)})
self self

View File

@ -5,13 +5,17 @@ require 'tk'
module Tk module Tk
module Clock module Clock
include Tk
extend TkCore
def self.add(clk, *args) def self.add(clk, *args)
tk_call_without_enc('clock','add', clk, *args).to_i tk_call_without_enc('clock','add', clk, *args).to_i
end end
def self.clicks(ms=nil) def self.clicks(ms=nil)
ms = ms.to_s if ms.kind_of?(Symbol)
case ms case ms
when nil when nil, ''
tk_call_without_enc('clock','clicks').to_i tk_call_without_enc('clock','clicks').to_i
when /^mic/ when /^mic/
tk_call_without_enc('clock','clicks','-microseconds').to_i tk_call_without_enc('clock','clicks','-microseconds').to_i

View File

@ -420,6 +420,7 @@ class TkTimer
@wait_var.value = 0 @wait_var.value = 0
tk_call 'after', 'cancel', @after_id if @after_id tk_call 'after', 'cancel', @after_id if @after_id
@after_id = nil @after_id = nil
Tk_CBTBL.delete(@id) ;# for GC Tk_CBTBL.delete(@id) ;# for GC
self self
end end

View File

@ -126,7 +126,81 @@ TkCore::INTERP.add_tk_procs('rb_var', 'args', <<-'EOL')
self self
end end
def initialize(val="") def default_value_type
@type
end
def default_value_type=(type)
if type.kind_of?(Class)
if type == NilClass
@type = nil
elsif type == Numeric
@type = :numeric
elsif type == TrueClass || type == FalseClass
@type = :bool
elsif type == String
@type = :string
elsif type == Symbol
@type = :symbol
elsif type == Array
@type = :list
else
@type = nil
end
else
case(type)
when nil
@type = nil
when :numeric, 'numeric'
@type = :numeric
when true, false, :bool, 'bool'
@type = :bool
when :string, 'string'
@type = :string
when :symbol, 'symbol'
@type = :symbol
when :list, 'list'
@type = :list
when :numlist, 'numlist'
@type = :numlist
else
self.default_value_type = type.class
end
end
@type
end
def _to_default_type(val)
return val unless @type
if val.kind_of?(Hash)
val.keys.each{|k| val[k] = _to_default_type(val[k]) }
val
else
begin
case(@type)
when :numeric
number(val)
when :bool
TkComm
when :string
val
when :symbol
val.intern
when :list
tk_split_simplelist(val)
when :numlist
tk_split_simplelist(val).collect!{|v| number(v)}
else
val
end
rescue
val
end
end
end
private :_to_default_type
def initialize(val="", type=nil)
# @id = Tk_VARIABLE_ID.join('') # @id = Tk_VARIABLE_ID.join('')
@id = Tk_VARIABLE_ID.join(TkCore::INTERP._ip_id_) @id = Tk_VARIABLE_ID.join(TkCore::INTERP._ip_id_)
Tk_VARIABLE_ID[1].succ! Tk_VARIABLE_ID[1].succ!
@ -139,6 +213,8 @@ TkCore::INTERP.add_tk_procs('rb_var', 'args', <<-'EOL')
@trace_elem = nil @trace_elem = nil
@trace_opts = nil @trace_opts = nil
self.default_value_type = type
begin begin
INTERP._unset_global_var(@id) INTERP._unset_global_var(@id)
rescue rescue
@ -242,13 +318,25 @@ TkCore::INTERP.add_tk_procs('rb_var', 'args', <<-'EOL')
def is_hash? def is_hash?
#ITNERP._eval("global #{@id}; array exist #{@id}") == '1' #ITNERP._eval("global #{@id}; array exist #{@id}") == '1'
INTERP._invoke_without_enc('global', @id) INTERP._invoke_without_enc('global', @id)
INTERP._invoke_without_enc('array', 'exist', @id) == '1' # INTERP._invoke_without_enc('array', 'exist', @id) == '1'
TkComm.bool(INTERP._invoke_without_enc('array', 'exist', @id))
end end
def is_scalar? def is_scalar?
! is_hash? ! is_hash?
end end
def exist?(idx = nil)
INTERP._invoke_without_enc('global', @id)
if idx
# array
TkComm.bool(tk_call('info', 'exist', "#{@id}")) &&
TkComm.bool(tk_call('info', 'exist', "#{@id}(#{idx})"))
else
TkComm.bool(tk_call('info', 'exist', @id))
end
end
def keys def keys
if (is_scalar?) if (is_scalar?)
fail RuntimeError, 'cannot get keys from a scalar variable' fail RuntimeError, 'cannot get keys from a scalar variable'
@ -258,6 +346,11 @@ TkCore::INTERP.add_tk_procs('rb_var', 'args', <<-'EOL')
tk_split_simplelist(INTERP._fromUTF8(INTERP._invoke_without_enc('array', 'names', @id))) tk_split_simplelist(INTERP._fromUTF8(INTERP._invoke_without_enc('array', 'names', @id)))
end end
def size
INTERP._invoke_without_enc('global', @id)
TkComm.number(INTERP._invoke_without_enc('array', 'size', @id))
end
def clear def clear
if (is_scalar?) if (is_scalar?)
fail RuntimeError, 'cannot clear a scalar variable' fail RuntimeError, 'cannot clear a scalar variable'
@ -274,7 +367,6 @@ TkCore::INTERP.add_tk_procs('rb_var', 'args', <<-'EOL')
self self
end end
unless const_defined?(:USE_TCLs_SET_VARIABLE_FUNCTIONS) unless const_defined?(:USE_TCLs_SET_VARIABLE_FUNCTIONS)
USE_TCLs_SET_VARIABLE_FUNCTIONS = true USE_TCLs_SET_VARIABLE_FUNCTIONS = true
end end
@ -284,10 +376,11 @@ if USE_TCLs_SET_VARIABLE_FUNCTIONS
# use Tcl function version of set tkvariable # use Tcl function version of set tkvariable
########################################################################### ###########################################################################
def value def _value
#if INTERP._eval("global #{@id}; array exist #{@id}") == '1' #if INTERP._eval("global #{@id}; array exist #{@id}") == '1'
INTERP._invoke_without_enc('global', @id) INTERP._invoke_without_enc('global', @id)
if INTERP._invoke('array', 'exist', @id) == '1' # if INTERP._invoke('array', 'exist', @id) == '1'
if TkComm.bool(INTERP._invoke('array', 'exist', @id))
#Hash[*tk_split_simplelist(INTERP._eval("global #{@id}; array get #{@id}"))] #Hash[*tk_split_simplelist(INTERP._eval("global #{@id}; array get #{@id}"))]
Hash[*tk_split_simplelist(INTERP._invoke('array', 'get', @id))] Hash[*tk_split_simplelist(INTERP._invoke('array', 'get', @id))]
else else
@ -306,6 +399,7 @@ if USE_TCLs_SET_VARIABLE_FUNCTIONS
} }
self.value self.value
elsif val.kind_of?(Array) elsif val.kind_of?(Array)
=begin
INTERP._set_global_var(@id, '') INTERP._set_global_var(@id, '')
val.each{|v| val.each{|v|
#INTERP._set_variable(@id, _toUTF8(_get_eval_string(v)), #INTERP._set_variable(@id, _toUTF8(_get_eval_string(v)),
@ -316,6 +410,8 @@ if USE_TCLs_SET_VARIABLE_FUNCTIONS
TclTkLib::VarAccessFlag::LIST_ELEMENT) TclTkLib::VarAccessFlag::LIST_ELEMENT)
} }
self.value self.value
=end
_fromUTF8(INTERP._set_global_var(@id, array2tk_list(val)))
else else
#_fromUTF8(INTERP._set_global_var(@id, _toUTF8(_get_eval_string(val)))) #_fromUTF8(INTERP._set_global_var(@id, _toUTF8(_get_eval_string(val))))
_fromUTF8(INTERP._set_global_var(@id, _get_eval_string(val, true))) _fromUTF8(INTERP._set_global_var(@id, _get_eval_string(val, true)))
@ -325,7 +421,8 @@ if USE_TCLs_SET_VARIABLE_FUNCTIONS
def [](*idxs) def [](*idxs)
index = idxs.collect{|idx| _get_eval_string(idx, true)}.join(',') index = idxs.collect{|idx| _get_eval_string(idx, true)}.join(',')
begin begin
_fromUTF8(INTERP._get_global_var2(@id, index)) # _fromUTF8(INTERP._get_global_var2(@id, index))
_to_default_type(_fromUTF8(INTERP._get_global_var2(@id, index)))
rescue => e rescue => e
case @def_default case @def_default
when :proc when :proc
@ -365,7 +462,7 @@ else
# use Ruby script version of set tkvariable (traditional methods) # use Ruby script version of set tkvariable (traditional methods)
########################################################################### ###########################################################################
def value def _value
begin begin
INTERP._eval(Kernel.format('global %s; set %s', @id, @id)) INTERP._eval(Kernel.format('global %s; set %s', @id, @id))
#INTERP._eval(Kernel.format('set %s', @id)) #INTERP._eval(Kernel.format('set %s', @id))
@ -436,7 +533,8 @@ else
def [](*idxs) def [](*idxs)
index = idxs.collect{|idx| _get_eval_string(idx)}.join(',') index = idxs.collect{|idx| _get_eval_string(idx)}.join(',')
begin 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 rescue => e
case @def_default case @def_default
when :proc when :proc
@ -483,8 +581,19 @@ else
end 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 def numeric
number(value) number(_value)
end end
def numeric=(val) def numeric=(val)
case val case val
@ -497,17 +606,20 @@ end
end end
val val
end end
def numeric_type=(val)
@type = :numeric
self.numeric=(val)
end
def bool def bool
# see Tcl_GetBoolean man-page # see Tcl_GetBoolean man-page
case value.downcase case _value.downcase
when '0', 'false', 'no', 'off' when '0', 'false', 'no', 'off'
false false
else else
true true
end end
end end
def bool=(val) def bool=(val)
if ! val if ! val
self.value = '0' self.value = '0'
@ -520,30 +632,48 @@ end
end end
end end
end end
def bool_type=(val)
@type = :bool
self.bool=(val)
end
def to_i def to_i
number(value).to_i number(_value).to_i
end end
def to_f def to_f
number(value).to_f number(_value).to_f
end end
def to_s def to_s
#string(value).to_s #string(value).to_s
value _value
end
alias string= value=
def string_type=(val)
@type = :string
self.value=(val)
end end
def to_sym def to_sym
value.intern _value.intern
end
alias symbol= value=
def symbol_type=(val)
@type = :symbol
self.value=(val)
end end
def list def list
#tk_split_list(value) #tk_split_list(value)
tk_split_simplelist(value) tk_split_simplelist(_value)
end end
alias to_a list alias to_a list
def numlist
list.collect!{|val| number(val)}
end
def list=(val) def list=(val)
case val case val
when Array when Array
@ -555,6 +685,39 @@ end
end end
val val
end 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 def inspect
#Kernel.format "#<TkVariable: %s>", @id #Kernel.format "#<TkVariable: %s>", @id
@ -564,7 +727,7 @@ end
def coerce(other) def coerce(other)
case other case other
when TkVariable when TkVariable
[other.value, self.value] [other._value, self._value]
when String when String
[other, self.to_s] [other, self.to_s]
when Symbol when Symbol
@ -576,7 +739,7 @@ end
when Array when Array
[other, self.to_a] [other, self.to_a]
else else
[other, self.value] [other, self._value]
end end
end end
@ -599,12 +762,12 @@ end
when Array when Array
self.to_a + other self.to_a + other
when String when String
self.value + other self._value + other
else else
begin begin
number(self.value) + other number(self._value) + other
rescue rescue
self.value + other.to_s self._value + other.to_s
end end
end end
end end
@ -612,37 +775,40 @@ end
if other.kind_of?(Array) if other.kind_of?(Array)
self.to_a - other self.to_a - other
else else
number(self.value) - other number(self._value) - other
end end
end end
def *(other) def *(other)
begin num_or_str(self._value) * other.to_i
number(self.value) * other #begin
rescue # number(self._value) * other
self.value * other #rescue
end # self._value * other
#end
end end
def /(other) def /(other)
number(self.value) / other number(self._value) / other
end end
def %(other) def %(other)
begin num_or_str(self._value) % other.to_i
number(self.value) % other #begin
rescue # number(self._value) % other
self.value % other #rescue
end # self._value % other
#end
end end
def **(other) def **(other)
number(self.value) ** other number(self._value) ** other
end end
def =~(other) def =~(other)
self.value =~ other self._value =~ other
end end
def ==(other) def ==(other)
case other case other
when TkVariable when TkVariable
self.equal?(other) #self.equal?(other)
self._value == other._value
when String when String
self.to_s == other self.to_s == other
when Symbol when Symbol
@ -654,7 +820,8 @@ end
when Array when Array
self.to_a == other self.to_a == other
when Hash when Hash
self.value == other # false if self is not an assoc array
self._value == other
else else
false false
end end
@ -673,17 +840,17 @@ end
val = other.numeric val = other.numeric
other = val other = val
rescue rescue
other = other.value other = other._value
end end
end end
if other.kind_of?(Numeric) if other.kind_of?(Numeric)
begin begin
return self.numeric <=> other return self.numeric <=> other
rescue rescue
return self.value <=> other.to_s return self._value <=> other.to_s
end end
else else
return self.value <=> other return self._value <=> other
end end
end end

View File

@ -27,6 +27,16 @@ module Tk
Tk.tk_call('infox', *args) Tk.tk_call('infox', *args)
end 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 class XPG3_MsgCat

View File

@ -37,7 +37,7 @@ TkFrame.new($anilabel_demo) {|frame|
TkButton.new(frame) { TkButton.new(frame) {
text 'See Code' text 'See Code'
command proc{showCode 'label'} command proc{showCode 'anilabel'}
}.pack('side'=>'left', 'expand'=>'yes') }.pack('side'=>'left', 'expand'=>'yes')
}.pack('side'=>'bottom', 'fill'=>'x', 'pady'=>'2m') }.pack('side'=>'bottom', 'fill'=>'x', 'pady'=>'2m')

View File

@ -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

File diff suppressed because it is too large Load Diff

View File

@ -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)

View File

@ -392,6 +392,12 @@ txt.insert('end', "\n")
txt.insert('end', "Animation\n", tag_title) txt.insert('end', "Animation\n", tag_title)
txt.insert('end', " \n ", tag_demospace) txt.insert('end', " \n ", tag_demospace)
txt.insert('end', "1. Animated labels (if supported)\n", tag_demo, "demo-anilabel") 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', "\n")
txt.insert('end', "Miscellaneous\n", tag_title) txt.insert('end', "Miscellaneous\n", tag_title)
@ -785,7 +791,7 @@ end
# #
def aboutBox def aboutBox
Tk.messageBox('icon'=>'info', 'type'=>'ok', 'title'=>'About Widget Demo', 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 " + "based on demos of Tk8.1 -- 8.5 " +
"( Copyright:: " + "( Copyright:: " +
"(c) 1996-1997 Sun Microsystems, Inc. / " + "(c) 1996-1997 Sun Microsystems, Inc. / " +

View File

@ -39,7 +39,7 @@ TkFrame.new($anilabel_demo) {|frame|
TkButton.new(frame) { TkButton.new(frame) {
text 'コード参照' text 'コード参照'
command proc{showCode 'label'} command proc{showCode 'anilabel'}
}.pack('side'=>'left', 'expand'=>'yes') }.pack('side'=>'left', 'expand'=>'yes')
}.pack('side'=>'bottom', 'fill'=>'x', 'pady'=>'2m') }.pack('side'=>'bottom', 'fill'=>'x', 'pady'=>'2m')

View File

@ -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

File diff suppressed because it is too large Load Diff

View File

@ -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)

View File

@ -442,6 +442,12 @@ txt.insert('end', "\n")
txt.insert('end', "アニメーション\n", tag_kanji_title) txt.insert('end', "アニメーション\n", tag_kanji_title)
txt.insert('end', " \n ", tag_demospace) txt.insert('end', " \n ", tag_demospace)
txt.insert('end', "1. アニメーションラベル (機能に対応したバージョンのTkが必要)\n", tag_demo, "demo-anilabel") 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")
#txt.insert('end', "その他\n", tag_middle) #txt.insert('end', "その他\n", tag_middle)
@ -813,7 +819,7 @@ end
# #
def aboutBox def aboutBox
Tk.messageBox('icon'=>'info', 'type'=>'ok', 'title'=>'About Widget Demo', 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 " + "based on demos of Tk8.1 -- 8.5 " +
"( Copyright:: " + "( Copyright:: " +
"(c) 1996-1997 Sun Microsystems, Inc. / " + "(c) 1996-1997 Sun Microsystems, Inc. / " +

30
ext/tk/sample/irbtk.rb Normal file
View File

@ -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

File diff suppressed because it is too large Load Diff

View File

@ -8,12 +8,20 @@
************************************************/ ************************************************/
#define TKUTIL_RELEASE_DATE "2005-01-25" #define TKUTIL_RELEASE_DATE "2005-03-02"
#include "ruby.h" #include "ruby.h"
#include "rubysig.h" #include "rubysig.h"
#include "st.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 cMethod;
static VALUE cTclTkLib; static VALUE cTclTkLib;
@ -200,23 +208,35 @@ fromUTF8_toDefaultEnc(str, self)
} }
#if ST_FOREACH_PASS_ERR_ARG
static void static void
hash_check(err) hash_check(err)
int err; int err;
{ {
if (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 static int
to_strkey(key, value, hash, err) to_strkey(key, value, hash, err)
VALUE key; VALUE key;
VALUE value; VALUE value;
VALUE hash; VALUE hash;
int err; 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); hash_check(err);
#endif
if (key == Qundef) return ST_CONTINUE; if (key == Qundef) return ST_CONTINUE;
rb_hash_aset(hash, rb_funcall(key, ID_to_s, 0, 0), value); rb_hash_aset(hash, rb_funcall(key, ID_to_s, 0, 0), value);
return ST_CHECK; return ST_CHECK;
@ -236,14 +256,16 @@ tk_symbolkey2str(self, keys)
} }
static VALUE get_eval_string_core _((VALUE, VALUE, VALUE)); static VALUE get_eval_string_core _((VALUE, VALUE, VALUE));
static VALUE ary2list _((VALUE, VALUE)); static VALUE ary2list _((VALUE, VALUE, VALUE));
static VALUE ary2list2 _((VALUE, VALUE)); static VALUE ary2list2 _((VALUE, VALUE, VALUE));
static VALUE hash2list _((VALUE, VALUE)); static VALUE hash2list _((VALUE, VALUE));
static VALUE hash2list_enc _((VALUE, VALUE));
static VALUE hash2kv _((VALUE, VALUE, VALUE)); static VALUE hash2kv _((VALUE, VALUE, VALUE));
static VALUE static VALUE
ary2list(ary, self) ary2list(ary, enc_flag, self)
VALUE ary; VALUE ary;
VALUE enc_flag;
VALUE self; VALUE self;
{ {
int idx, idx2, size, size2; int idx, idx2, size, size2;
@ -266,29 +288,36 @@ ary2list(ary, self)
val = RARRAY(ary)->ptr[idx]; val = RARRAY(ary)->ptr[idx];
switch(TYPE(val)) { switch(TYPE(val)) {
case T_ARRAY: case T_ARRAY:
RARRAY(dst)->ptr[RARRAY(dst)->len++] = ary2list(val, self); RARRAY(dst)->ptr[RARRAY(dst)->len++]
= ary2list(val, enc_flag, self);
break; break;
case T_HASH: case T_HASH:
/* RARRAY(dst)->ptr[RARRAY(dst)->len++] = hash2list(val, self); */ /* RARRAY(dst)->ptr[RARRAY(dst)->len++] = hash2list(val, self); */
val = hash2kv(val, Qnil, self); val = hash2kv(val, enc_flag, self);
size2 = RARRAY(val)->len; size2 = RARRAY(val)->len;
for(idx2 = 0; idx2 < size2; idx2++) { for(idx2 = 0; idx2 < size2; idx2++) {
val2 = RARRAY(val)->ptr[idx2]; val2 = RARRAY(val)->ptr[idx2];
switch(TYPE(val2)) { switch(TYPE(val2)) {
case T_ARRAY: case T_ARRAY:
RARRAY(dst)->ptr[RARRAY(dst)->len++] RARRAY(dst)->ptr[RARRAY(dst)->len++]
= ary2list(val2, self); = ary2list(val2, enc_flag, self);
break; break;
case T_HASH: case T_HASH:
if (RTEST(enc_flag)) {
RARRAY(dst)->ptr[RARRAY(dst)->len++]
= hash2list_enc(val2, self);
} else {
RARRAY(dst)->ptr[RARRAY(dst)->len++] RARRAY(dst)->ptr[RARRAY(dst)->len++]
= hash2list(val2, self); = hash2list(val2, self);
}
break;
default: default:
if (val2 != TK_None) { if (val2 != TK_None) {
RARRAY(dst)->ptr[RARRAY(dst)->len++] 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: default:
if (val != TK_None) { if (val != TK_None) {
RARRAY(dst)->ptr[RARRAY(dst)->len++] 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 static VALUE
ary2list2(ary, self) ary2list2(ary, enc_flag, self)
VALUE ary; VALUE ary;
VALUE enc_flag;
VALUE self; VALUE self;
{ {
int idx, size; int idx, size;
@ -320,17 +350,23 @@ ary2list2(ary, self)
val = RARRAY(ary)->ptr[idx]; val = RARRAY(ary)->ptr[idx];
switch(TYPE(val)) { switch(TYPE(val)) {
case T_ARRAY: case T_ARRAY:
RARRAY(dst)->ptr[RARRAY(dst)->len++] = ary2list(val, self); RARRAY(dst)->ptr[RARRAY(dst)->len++]
= ary2list(val, enc_flag, self);
break; break;
case T_HASH: case T_HASH:
if (RTEST(enc_flag)) {
RARRAY(dst)->ptr[RARRAY(dst)->len++] = hash2list(val, self); RARRAY(dst)->ptr[RARRAY(dst)->len++] = hash2list(val, self);
} else {
RARRAY(dst)->ptr[RARRAY(dst)->len++]
= hash2list_enc(val, self);
}
break; break;
default: default:
if (val != TK_None) { if (val != TK_None) {
RARRAY(dst)->ptr[RARRAY(dst)->len++] 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 static int
push_kv(key, val, args, err) push_kv(key, val, args, err)
VALUE key; VALUE key;
VALUE val; VALUE val;
VALUE args; VALUE args;
int err; int err;
#else
static int
push_kv(key, val, args)
VALUE key;
VALUE val;
VALUE args;
#endif
{ {
volatile VALUE ary; volatile VALUE ary;
#if ST_FOREACH_PASS_ERR_ARG
hash_check(err); hash_check(err);
#endif
ary = RARRAY(args)->ptr[0]; ary = RARRAY(args)->ptr[0];
if (key == Qundef) return ST_CONTINUE; if (key == Qundef) return ST_CONTINUE;
@ -498,16 +544,26 @@ hash2kv(hash, ary, self)
} }
} }
#if ST_FOREACH_PASS_ERR_ARG
static int static int
push_kv_enc(key, val, args, err) push_kv_enc(key, val, args, err)
VALUE key; VALUE key;
VALUE val; VALUE val;
VALUE args; VALUE args;
int err; int err;
#else
static int
push_kv_enc(key, val, args)
VALUE key;
VALUE val;
VALUE args;
#endif
{ {
volatile VALUE ary; volatile VALUE ary;
#if ST_FOREACH_PASS_ERR_ARG
hash_check(err); hash_check(err);
#endif
ary = RARRAY(args)->ptr[0]; ary = RARRAY(args)->ptr[0];
if (key == Qundef) return ST_CONTINUE; if (key == Qundef) return ST_CONTINUE;
@ -556,7 +612,7 @@ hash2list(hash, self)
VALUE hash; VALUE hash;
VALUE self; 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 hash;
VALUE self; VALUE self;
{ {
return ary2list2(hash2kv_enc(hash, Qnil, self), self); return ary2list2(hash2kv_enc(hash, Qnil, self), Qfalse, self);
} }
static VALUE static VALUE
@ -669,11 +725,7 @@ get_eval_string_core(obj, enc_flag, self)
} }
case T_ARRAY: case T_ARRAY:
if (RTEST(enc_flag)) { return ary2list(obj, enc_flag, self);
return fromDefaultEnc_toUTF8(ary2list(obj, self), self);
} else {
return ary2list(obj, self);
}
case T_FALSE: case T_FALSE:
return rb_str_new2("0"); return rb_str_new2("0");