From 1d82954444cba4017a26ee5feff93e885dba2245 Mon Sep 17 00:00:00 2001 From: nagai Date: Mon, 6 Nov 2006 06:56:37 +0000 Subject: * ext/tk/lib/tk/itemconfig.rb: bug fix on 'itemconfiginfo' method, and modify to make it easy to override 'itemconfiginfo' method. * ext/tk/lib/tkextlib/tile/treeview.rb : support Tile 0.7.8. * ext/tk/lib/tkextlib/version.rb : [new] add Tk::Tkextlib_RELEASE_DATE to get the information from scripts. * ext/tk/lib/tk.rb: load 'tkextlib/version.rb', and update RELEASE_DATE. * ext/tk/lib/tkextlib/SUPPORT_STATUS: update. * ext/tk/sample/editable_listbox.rb: [new] the listbox with editable items. It's one of the example about usage of Place geometry manager. * ext/tk/sample/tktextio.rb: improve the functions of TkTextIO class. Those are required by 'irbtkw.rbw'. * ext/tk/sample/irbtkw.rbw: [new] IRB on Ruby/Tk. It doesn't need any real console. IRB works on a text widget without I/O blocking. That is, thread switching on IRB will work properly, even if on Windows. git-svn-id: svn+ssh://ci.ruby-lang.org/ruby/branches/ruby_1_8@11283 b2dd03c8-39d4-4d8f-98ff-823fe69b080e --- ext/tk/ChangeLog.tkextlib | 8 + ext/tk/lib/tk.rb | 3 +- ext/tk/lib/tk/itemconfig.rb | 15 +- ext/tk/lib/tkextlib/SUPPORT_STATUS | 4 +- ext/tk/lib/tkextlib/tile/treeview.rb | 1070 +++++++++++++++++++++++++++++----- ext/tk/lib/tkextlib/version.rb | 6 + ext/tk/sample/editable_listbox.rb | 69 +++ ext/tk/sample/irbtkw.rbw | 119 ++++ ext/tk/sample/tktextio.rb | 547 +++++++++++++++-- 9 files changed, 1651 insertions(+), 190 deletions(-) create mode 100644 ext/tk/lib/tkextlib/version.rb create mode 100644 ext/tk/sample/editable_listbox.rb create mode 100644 ext/tk/sample/irbtkw.rbw (limited to 'ext') diff --git a/ext/tk/ChangeLog.tkextlib b/ext/tk/ChangeLog.tkextlib index 557abe44a5..88cc70abb9 100644 --- a/ext/tk/ChangeLog.tkextlib +++ b/ext/tk/ChangeLog.tkextlib @@ -1,3 +1,11 @@ +2006-11-06 Hidetoshi NAGAI + + * lib/tkextlib/version.rb: keep release date of tkextlib on + "Tk::Tkextlib_RELEASE_DATE". + + * lib/tkextlib/tile/treeview.rb : support Tile 0.7.8. + Now, you can handle tree items as objects. + 2006-10-04 Hidetoshi NAGAI * lib/tkextlib/tile.rb, lib/tkextlib/tile/* : support Tile 0.7.6. diff --git a/ext/tk/lib/tk.rb b/ext/tk/lib/tk.rb index a7f2a5af26..36a58bafba 100644 --- a/ext/tk/lib/tk.rb +++ b/ext/tk/lib/tk.rb @@ -4597,7 +4597,7 @@ end #Tk.freeze module Tk - RELEASE_DATE = '2006-09-01'.freeze + RELEASE_DATE = '2006-11-06'.freeze autoload :AUTO_PATH, 'tk/variable' autoload :TCL_PACKAGE_PATH, 'tk/variable' @@ -4609,6 +4609,7 @@ end # call setup script for Tk extension libraries (base configuration) begin + require 'tkextlib/version.rb' require 'tkextlib/setup.rb' rescue LoadError # ignore diff --git a/ext/tk/lib/tk/itemconfig.rb b/ext/tk/lib/tk/itemconfig.rb index 0b84be38b8..30d589de82 100644 --- a/ext/tk/lib/tk/itemconfig.rb +++ b/ext/tk/lib/tk/itemconfig.rb @@ -289,7 +289,7 @@ module TkItemConfigMethod self end - def itemconfiginfo(tagOrId, slot = nil) + def __itemconfiginfo_core(tagOrId, slot = nil) if TkComm::GET_CONFIGINFO_AS_ARRAY if (slot && slot.to_s =~ /^(|latin|ascii|kanji)(#{__item_font_optkeys(tagid(tagOrId)).join('|')})$/) fontkey = $2 @@ -594,7 +594,7 @@ module TkItemConfigMethod if v.empty? conf[__item_configinfo_struct(tagid(tagOrId))[:current_value]] = nil else - conf[__item_configinfo_struct(tagid(tagOrId))[:current_value]] = TkVarAccess.new + conf[__item_configinfo_struct(tagid(tagOrId))[:current_value]] = TkVarAccess.new(v) end end @@ -1020,13 +1020,18 @@ module TkItemConfigMethod end end end + private :__itemconfiginfo_core + + def itemconfiginfo(tagOrId, slot = nil) + __itemconfiginfo_core(tagOrId, slot) + end def current_itemconfiginfo(tagOrId, slot = nil) if TkComm::GET_CONFIGINFO_AS_ARRAY if slot org_slot = slot begin - conf = itemconfiginfo(tagOrId, slot) + conf = __itemconfiginfo_core(tagOrId, slot) if ( ! __item_configinfo_struct(tagid(tagOrId))[:alias] \ || conf.size > __item_configinfo_struct(tagid(tagOrId))[:alias] + 1 ) return {conf[0] => conf[-1]} @@ -1037,7 +1042,7 @@ module TkItemConfigMethod "there is a configure alias loop about '#{org_slot}'" else ret = {} - itemconfiginfo(tagOrId).each{|conf| + __itemconfiginfo_core(tagOrId).each{|conf| if ( ! __item_configinfo_struct(tagid(tagOrId))[:alias] \ || conf.size > __item_configinfo_struct(tagid(tagOrId))[:alias] + 1 ) ret[conf[0]] = conf[-1] @@ -1047,7 +1052,7 @@ module TkItemConfigMethod end else # ! TkComm::GET_CONFIGINFO_AS_ARRAY ret = {} - itemconfiginfo(slot).each{|key, conf| + itemconfiginfo(tagOrId, slot).each{|key, conf| ret[key] = conf[-1] if conf.kind_of?(Array) } ret diff --git a/ext/tk/lib/tkextlib/SUPPORT_STATUS b/ext/tk/lib/tkextlib/SUPPORT_STATUS index 3d6b6116ca..15925cba72 100644 --- a/ext/tk/lib/tkextlib/SUPPORT_STATUS +++ b/ext/tk/lib/tkextlib/SUPPORT_STATUS @@ -1,7 +1,7 @@ [ current support status of Tcl/Tk extensions ] - *******<<< RELEASE_DATE of the libraries : 2006/10/04 >>>******* + *** RELEASE_DATE of the libraries => see 'tkextlib/version.rb' *** The following list shows *CURRENT* status when this file was modifyed at last. If you want to add other Tcl/Tk extensions to the planed list @@ -83,7 +83,7 @@ BLT 2.4z http://sourceforge.net/projects/blt TkTreeCtrl CVS/Hd(2005-12-02) http://sourceforge.net/projects/tktreectrl ==> treectrl -Tile CVS/Hd(2006-10-01) +Tile 0.7.8 http://sourceforge.net/projects/tktable ==> tile diff --git a/ext/tk/lib/tkextlib/tile/treeview.rb b/ext/tk/lib/tkextlib/tile/treeview.rb index 6d6074b800..e0539b9660 100644 --- a/ext/tk/lib/tkextlib/tile/treeview.rb +++ b/ext/tk/lib/tkextlib/tile/treeview.rb @@ -9,129 +9,898 @@ module Tk module Tile class Treeview < TkWindow end + end +end - module TreeviewConfig - include TkItemConfigMethod +module Tk::Tile::TreeviewConfig + include TkItemConfigMethod - def __item_cget_cmd(id) - [self.path, id[0], id[1]] - end - private :__item_cget_cmd + def __item_configinfo_struct(id) + # maybe need to override + {:key=>0, :alias=>nil, :db_name=>nil, :db_class=>nil, + :default_value=>nil, :current_value=>1} + end + private :__item_configinfo_struct - def __item_config_cmd(id) - [self.path, id[0], id[1]] - end - private :__item_config_cmd - - def __item_numstrval_optkeys(id) - case id[0] - when :item, 'item' - ['width'] - when :column, 'column' - super(id[1]) - when :heading, 'heading' - super(id[1]) - end - end - private :__item_numstrval_optkeys - - def __item_strval_optkeys(id) - case id[0] - when :item, 'item' - super(id) + ['id'] - when :column, 'column' - super(id[1]) - when :heading, 'heading' - super(id[1]) - end - end - private :__item_strval_optkeys - - def __item_boolval_optkeys(id) - case id[0] - when :item, 'item' - ['open'] - when :column, 'column' - super(id[1]) - when :heading, 'heading' - super(id[1]) + def __itemconfiginfo_core(tagOrId, slot = nil) + if TkComm::GET_CONFIGINFO_AS_ARRAY + if (slot && slot.to_s =~ /^(|latin|ascii|kanji)(#{__item_font_optkeys(tagid(tagOrId)).join('|')})$/) + fontkey = $2 + return [slot.to_s, tagfontobj(tagid(tagOrId), fontkey)] + else + if slot + slot = slot.to_s + case slot + when /^(#{__tile_specific_item_optkeys(tagid(tagOrId)).join('|')})$/ + begin + # On tile-0.7.{2-8}, 'state' options has no '-' at its head. + val = tk_call(*(__item_confinfo_cmd(tagid(tagOrId)) << slot)) + rescue + # Maybe, 'state' option has '-' in future. + val = tk_call(*(__item_confinfo_cmd(tagid(tagOrId)) << "-#{slot}")) + end + return [slot, val] + + when /^(#{__item_val2ruby_optkeys(tagid(tagOrId)).keys.join('|')})$/ + method = _symbolkey2str(__item_val2ruby_optkeys(tagid(tagOrId)))[slot] + optval = tk_call_without_enc(*(__item_confinfo_cmd(tagid(tagOrId)) << "-#{slot}")) + begin + val = method.call(tagOrId, optval) + rescue => e + warn("Warning:: #{e.message} (when #{method}lcall(#{tagOrId.inspect}, #{optval.inspect})") if $DEBUG + val = optval + end + return [slot, val] + + when /^(#{__item_methodcall_optkeys(tagid(tagOrId)).keys.join('|')})$/ + method = _symbolkey2str(__item_methodcall_optkeys(tagid(tagOrId)))[slot] + return [slot, self.__send__(method, tagOrId)] + + when /^(#{__item_numval_optkeys(tagid(tagOrId)).join('|')})$/ + begin + val = number(tk_call_without_enc(*(__item_confinfo_cmd(tagid(tagOrId)) << "-#{slot}"))) + rescue + val = nil + end + return [slot, val] + + when /^(#{__item_numstrval_optkeys(tagid(tagOrId)).join('|')})$/ + val = num_or_str(tk_call_without_enc(*(__item_confinfo_cmd(tagid(tagOrId)) << "-#{slot}"))) + return [slot, val] + + when /^(#{__item_boolval_optkeys(tagid(tagOrId)).join('|')})$/ + begin + val = bool(tk_call_without_enc(*(__item_confinfo_cmd(tagid(tagOrId)) << "-#{slot}"))) + rescue + val = nil + end + return [slot, val] + + when /^(#{__item_listval_optkeys(tagid(tagOrId)).join('|')})$/ + val = simplelist(tk_call_without_enc(*(__item_confinfo_cmd(tagid(tagOrId)) << "-#{slot}"))) + return [slot, val] + + when /^(#{__item_numlistval_optkeys(tagid(tagOrId)).join('|')})$/ + val = tk_call_without_enc(*(__item_confinfo_cmd(tagid(tagOrId)) << "-#{slot}")) + if val =~ /^[0-9]/ + return [slot, list(val)] + else + return [slot, val] + end + + when /^(#{__item_strval_optkeys(tagid(tagOrId)).join('|')})$/ + val = tk_call_without_enc(*(__item_confinfo_cmd(tagid(tagOrId)) << "-#{slot}")) + return [slot, val] + + when /^(#{__item_tkvariable_optkeys(tagid(tagOrId)).join('|')})$/ + val = tk_call_without_enc(*(__item_confinfo_cmd(tagid(tagOrId)) << "-#{slot}")) + if val.empty? + return [slot, nil] + else + return [slot, TkVarAccess.new(val)] + end + + else + val = tk_call_without_enc(*(__item_confinfo_cmd(tagid(tagOrId)) << "-#{slot}")) + if val.index('{') + return [slot, tk_split_list(val)] + else + return [slot, tk_tcl2ruby(val)] + end + end + + else # ! slot + ret = Hash[*(tk_split_simplelist(tk_call_without_enc(*(__item_confinfo_cmd(tagid(tagOrId)))), false, false))].to_a.collect{|conf| + conf[0] = conf[0][1..-1] if conf[0][0] == ?- + case conf[0] + when /^(#{__item_val2ruby_optkeys(tagid(tagOrId)).keys.join('|')})$/ + method = _symbolkey2str(__item_val2ruby_optkeys(tagid(tagOrId)))[conf[0]] + optval = conf[1] + begin + val = method.call(tagOrId, optval) + rescue => e + warn("Warning:: #{e.message} (when #{method}.call(#{tagOrId.inspect}, #{optval.inspect})") if $DEBUG + val = optval + end + conf[1] = val + + when /^(#{__item_strval_optkeys(tagid(tagOrId)).join('|')})$/ + # do nothing + + when /^(#{__item_numval_optkeys(tagid(tagOrId)).join('|')})$/ + begin + conf[1] = number(conf[1]) + rescue + conf[1] = nil + end + + when /^(#{__item_numstrval_optkeys(tagid(tagOrId)).join('|')})$/ + conf[1] = num_or_str(conf[1]) + + when /^(#{__item_boolval_optkeys(tagid(tagOrId)).join('|')})$/ + begin + conf[1] = bool(conf[1]) + rescue + conf[1] = nil + end + + when /^(#{__item_listval_optkeys(tagid(tagOrId)).join('|')})$/ + conf[1] = simplelist(conf[1]) + + when /^(#{__item_numlistval_optkeys(tagid(tagOrId)).join('|')})$/ + if conf[1] =~ /^[0-9]/ + conf[1] = list(conf[1]) + end + + when /^(#{__item_tkvariable_optkeys(tagid(tagOrId)).join('|')})$/ + if conf[1].empty? + conf[1] = nil + else + conf[1] = TkVarAccess.new(conf[1]) + end + + else + if conf[1].index('{') + conf[1] = tk_split_list(conf[1]) + else + conf[1] = tk_tcl2ruby(conf[1]) + end + end + + conf + } + + __item_font_optkeys(tagid(tagOrId)).each{|optkey| + optkey = optkey.to_s + fontconf = ret.assoc(optkey) + if fontconf + ret.delete_if{|inf| inf[0] =~ /^(|latin|ascii|kanji)#{optkey}$/} + fontconf[1] = tagfontobj(tagid(tagOrId), optkey) + ret.push(fontconf) + end + } + + __item_methodcall_optkeys(tagid(tagOrId)).each{|optkey, method| + ret << [optkey.to_s, self.__send__(method, tagOrId)] + } + + ret end end - private :__item_boolval_optkeys - - def __item_listval_optkeys(id) - case id[0] - when :item, 'item' - ['values'] - when :column, 'column' - [] - when :heading, 'heading' - [] + + else # ! TkComm::GET_CONFIGINFO_AS_ARRAY + if (slot && slot.to_s =~ /^(|latin|ascii|kanji)(#{__item_font_optkeys(tagid(tagOrId)).join('|')})$/) + fontkey = $2 + return {slot.to_s => tagfontobj(tagid(tagOrId), fontkey)} + else + if slot + slot = slot.to_s + case slot + when /^(#{__tile_specific_item_optkeys(tagid(tagOrId)).join('|')})$/ + begin + # On tile-0.7.{2-8}, 'state' option has no '-' at its head. + val = tk_call(*(__item_confinfo_cmd(tagid(tagOrId)) << slot)) + rescue + # Maybe, 'state' option has '-' in future. + val = tk_call(*(__item_confinfo_cmd(tagid(tagOrId)) << "-#{slot}")) + end + return {slot => val} + + when /^(#{__item_val2ruby_optkeys(tagid(tagOrId)).keys.join('|')})$/ + method = _symbolkey2str(__item_val2ruby_optkeys(tagid(tagOrId)))[slot] + optval = tk_call_without_enc(*(__item_confinfo_cmd(tagid(tagOrId)) << "-#{slot}")) + begin + val = method.call(tagOrId, optval) + rescue => e + warn("Warning:: #{e.message} (when #{method}lcall(#{tagOrId.inspect}, #{optval.inspect})") if $DEBUG + val = optval + end + return {slot => val} + + when /^(#{__item_methodcall_optkeys(tagid(tagOrId)).keys.join('|')})$/ + method = _symbolkey2str(__item_methodcall_optkeys(tagid(tagOrId)))[slot] + return {slot => self.__send__(method, tagOrId)} + + when /^(#{__item_numval_optkeys(tagid(tagOrId)).join('|')})$/ + begin + val = number(tk_call_without_enc(*(__item_confinfo_cmd(tagid(tagOrId)) << "-#{slot}"))) + rescue + val = nil + end + return {slot => val} + + when /^(#{__item_numstrval_optkeys(tagid(tagOrId)).join('|')})$/ + val = num_or_str(tk_call_without_enc(*(__item_confinfo_cmd(tagid(tagOrId)) << "-#{slot}"))) + return {slot => val} + + when /^(#{__item_boolval_optkeys(tagid(tagOrId)).join('|')})$/ + begin + val = bool(tk_call_without_enc(*(__item_confinfo_cmd(tagid(tagOrId)) << "-#{slot}"))) + rescue + val = nil + end + return {slot => val} + + when /^(#{__item_listval_optkeys(tagid(tagOrId)).join('|')})$/ + val = simplelist(tk_call_without_enc(*(__item_confinfo_cmd(tagid(tagOrId)) << "-#{slot}"))) + return {slot => val} + + when /^(#{__item_numlistval_optkeys(tagid(tagOrId)).join('|')})$/ + val = tk_call_without_enc(*(__item_confinfo_cmd(tagid(tagOrId)) << "-#{slot}")) + if val =~ /^[0-9]/ + return {slot => list(val)} + else + return {slot => val} + end + + when /^(#{__item_strval_optkeys(tagid(tagOrId)).join('|')})$/ + val = tk_call_without_enc(*(__item_confinfo_cmd(tagid(tagOrId)) << "-#{slot}")) + return {slot => val} + + when /^(#{__item_tkvariable_optkeys(tagid(tagOrId)).join('|')})$/ + val = tk_call_without_enc(*(__item_confinfo_cmd(tagid(tagOrId)) << "-#{slot}")) + if val.empty? + return {slot => nil} + else + return {slot => TkVarAccess.new(val)} + end + + else + val = tk_call_without_enc(*(__item_confinfo_cmd(tagid(tagOrId)) << "-#{slot}")) + if val.index('{') + return {slot => tk_split_list(val)} + else + return {slot => tk_tcl2ruby(val)} + end + end + + else # ! slot + ret = {} + ret = Hash[*(tk_split_simplelist(tk_call_without_enc(*(__item_confinfo_cmd(tagid(tagOrId)))), false, false))].to_a.collect{|conf| + conf[0] = conf[0][1..-1] if conf[0][0] == ?- + + optkey = conf[0] + case optkey + when /^(#{__item_val2ruby_optkeys(tagid(tagOrId)).keys.join('|')})$/ + method = _symbolkey2str(__item_val2ruby_optkeys(tagid(tagOrId)))[optkey] + optval = conf[1] + begin + val = method.call(tagOrId, optval) + rescue => e + warn("Warning:: #{e.message} (when #{method}.call(#{tagOrId.inspect}, #{optval.inspect})") if $DEBUG + val = optval + end + conf[1] = val + + when /^(#{__item_strval_optkeys(tagid(tagOrId)).join('|')})$/ + # do nothing + + when /^(#{__item_numval_optkeys(tagid(tagOrId)).join('|')})$/ + begin + conf[1] = number(conf[1]) + rescue + conf[1] = nil + end + + when /^(#{__item_numstrval_optkeys(tagid(tagOrId)).join('|')})$/ + conf[1] = num_or_str(conf[1]) + + when /^(#{__item_boolval_optkeys(tagid(tagOrId)).join('|')})$/ + begin + conf[1] = bool(conf[1]) + rescue + conf[1] = nil + end + + when /^(#{__item_listval_optkeys(tagid(tagOrId)).join('|')})$/ + conf[1] = simplelist(conf[1]) + + when /^(#{__item_numlistval_optkeys(tagid(tagOrId)).join('|')})$/ + if conf[1] =~ /^[0-9]/ + conf[1] = list(conf[1]) + end + + when /^(#{__item_tkvariable_optkeys(tagid(tagOrId)).join('|')})$/ + if conf[1].empty? + conf[1] = nil + else + conf[1] = TkVarAccess.new(conf[1]) + end + + else + if conf[1].index('{') + return [slot, tk_split_list(conf[1])] + else + return [slot, tk_tcl2ruby(conf[1])] + end + end + + ret[conf[0]] = conf[1] + } + + __item_font_optkeys(tagid(tagOrId)).each{|optkey| + optkey = optkey.to_s + fontconf = ret[optkey] + if fontconf.kind_of?(Array) + ret.delete(optkey) + ret.delete('latin' << optkey) + ret.delete('ascii' << optkey) + ret.delete('kanji' << optkey) + fontconf[1] = tagfontobj(tagid(tagOrId), optkey) + ret[optkey] = fontconf + end + } + + __item_methodcall_optkeys(tagid(tagOrId)).each{|optkey, method| + ret[optkey.to_s] = self.__send__(method, tagOrId) + } + + ret end end - private :__item_listval_optkeys + end + end - alias __itemcget itemcget - alias __itemconfigure itemconfigure - alias __itemconfiginfo itemconfiginfo - alias __current_itemconfiginfo current_itemconfiginfo + ################### - private :__itemcget, :__itemconfigure - private :__itemconfiginfo, :__current_itemconfiginfo + def __item_cget_cmd(id) + [self.path, id[0], id[1]] + end + private :__item_cget_cmd - # Treeview Item - def itemcget(tagOrId, option) - __itemcget([:item, tagOrId], option) - end - def itemconfigure(tagOrId, slot, value=None) - __itemconfigure([:item, tagOrId], slot, value) - end - def itemconfiginfo(tagOrId, slot=nil) - __itemconfiginfo([:item, tagOrId], slot) - end - def current_itemconfiginfo(tagOrId, slot=nil) - __current_itemconfiginfo([:item, tagOrId], slot) - end + def __item_config_cmd(id) + [self.path, id[0], id[1]] + end + private :__item_config_cmd - # Treeview Column - def columncget(tagOrId, option) - __itemcget([:column, tagOrId], option) - end - def columnconfigure(tagOrId, slot, value=None) - __itemconfigure([:column, tagOrId], slot, value) - end - def columnconfiginfo(tagOrId, slot=nil) - __itemconfiginfo([:column, tagOrId], slot) - end - def current_columnconfiginfo(tagOrId, slot=nil) - __current_itemconfiginfo([:column, tagOrId], slot) - end - alias column_cget columncget - alias column_configure columnconfigure - alias column_configinfo columnconfiginfo - alias current_column_configinfo current_columnconfiginfo - - # Treeview Heading - def headingcget(tagOrId, option) - __itemcget([:heading, tagOrId], option) - end - def headingconfigure(tagOrId, slot, value=None) - __itemconfigure([:heading, tagOrId], slot, value) + def __item_numstrval_optkeys(id) + case id[0] + when :item, 'item' + ['width'] + when :column, 'column' + super(id[1]) + when :tag, 'tag' + super(id[1]) + when :heading, 'heading' + super(id[1]) + else + super(id[1]) + end + end + private :__item_numstrval_optkeys + + def __item_strval_optkeys(id) + case id[0] + when :item, 'item' + super(id) + ['id'] + when :column, 'column' + super(id[1]) + when :tag, 'tag' + super(id[1]) + when :heading, 'heading' + super(id[1]) + else + super(id[1]) + end + end + private :__item_strval_optkeys + + def __item_boolval_optkeys(id) + case id[0] + when :item, 'item' + ['open'] + when :column, 'column' + super(id[1]) + when :tag, 'tag' + super(id[1]) + when :heading, 'heading' + super(id[1]) + end + end + private :__item_boolval_optkeys + + def __item_listval_optkeys(id) + case id[0] + when :item, 'item' + ['values'] + when :column, 'column' + [] + when :heading, 'heading' + [] + else + [] + end + end + private :__item_listval_optkeys + + def __item_val2ruby_optkeys(id) + case id[0] + when :item, 'item' + { + 'tags'=>proc{|arg_id, val| + simplelist(val).collect{|tag| + Tk::Tile::Treeview::Tag.id2obj(self, tag) + } + } + } + when :column, 'column' + {} + when :heading, 'heading' + {} + else + {} + end + end + private :__item_val2ruby_optkeys + + def __tile_specific_item_optkeys(id) + case id[0] + when :item, 'item' + [] + when :column, 'column' + [] + when :heading, 'heading' + ['state'] # On tile-0.7.{2-8}, 'state' options has no '-' at its head. + else + [] + end + end + private :__item_val2ruby_optkeys + + def itemconfiginfo(tagOrId, slot = nil) + __itemconfiginfo_core(tagOrId, slot) + end + + def current_itemconfiginfo(tagOrId, slot = nil) + if TkComm::GET_CONFIGINFO_AS_ARRAY + if slot + org_slot = slot + begin + conf = __itemconfiginfo_core(tagOrId, slot) + if ( ! __item_configinfo_struct(tagid(tagOrId))[:alias] \ + || conf.size > __item_configinfo_struct(tagid(tagOrId))[:alias] + 1 ) + return {conf[0] => conf[-1]} + end + slot = conf[__item_configinfo_struct(tagid(tagOrId))[:alias]] + end while(org_slot != slot) + fail RuntimeError, + "there is a configure alias loop about '#{org_slot}'" + else + ret = {} + __itemconfiginfo_core(tagOrId).each{|conf| + if ( ! __item_configinfo_struct(tagid(tagOrId))[:alias] \ + || conf.size > __item_configinfo_struct(tagid(tagOrId))[:alias] + 1 ) + ret[conf[0]] = conf[-1] + end + } + ret end - def headingconfiginfo(tagOrId, slot=nil) - __itemconfiginfo([:heading, tagOrId], slot) + else # ! TkComm::GET_CONFIGINFO_AS_ARRAY + ret = {} + itemconfiginfo(tagOrId, slot).each{|key, conf| + ret[key] = conf[-1] if conf.kind_of?(Array) + } + ret + end + end + + alias __itemcget itemcget + alias __itemconfigure itemconfigure + alias __itemconfiginfo itemconfiginfo + alias __current_itemconfiginfo current_itemconfiginfo + + private :__itemcget, :__itemconfigure + private :__itemconfiginfo, :__current_itemconfiginfo + + # Treeview Item + def itemcget(tagOrId, option) + __itemcget([:item, tagOrId], option) + end + def itemconfigure(tagOrId, slot, value=None) + __itemconfigure([:item, tagOrId], slot, value) + end + def itemconfiginfo(tagOrId, slot=nil) + __itemconfiginfo([:item, tagOrId], slot) + end + def current_itemconfiginfo(tagOrId, slot=nil) + __current_itemconfiginfo([:item, tagOrId], slot) + end + + # Treeview Column + def columncget(tagOrId, option) + __itemcget([:column, tagOrId], option) + end + def columnconfigure(tagOrId, slot, value=None) + __itemconfigure([:column, tagOrId], slot, value) + end + def columnconfiginfo(tagOrId, slot=nil) + __itemconfiginfo([:column, tagOrId], slot) + end + def current_columnconfiginfo(tagOrId, slot=nil) + __current_itemconfiginfo([:column, tagOrId], slot) + end + alias column_cget columncget + alias column_configure columnconfigure + alias column_configinfo columnconfiginfo + alias current_column_configinfo current_columnconfiginfo + + # Treeview Heading + def headingcget(tagOrId, option) + if __tile_specific_item_optkeys([:heading, tagOrId]).index(option.to_s) + begin + # On tile-0.7.{2-8}, 'state' options has no '-' at its head. + tk_call(*(__item_cget_cmd([:heading, tagOrId]) << option.to_s)) + rescue + # Maybe, 'state' option has '-' in future. + tk_call(*(__item_cget_cmd([:heading, tagOrId]) << "-#{option}")) end - def current_headingconfiginfo(tagOrId, slot=nil) - __current_itemconfiginfo([:heading, tagOrId], slot) + else + __itemcget([:heading, tagOrId], option) + end + end + def headingconfigure(tagOrId, slot, value=None) + if slot.kind_of?(Hash) + slot = _symbolkey2str(slot) + sp_kv = [] + __tile_specific_item_optkeys([:heading, tagOrId]).each{|k| + sp_kv << k << _get_eval_string(slot.delete(k)) if slot.has_key?(k) + } + tk_call(*(__item_config_cmd([:heading, tagOrId]).concat(sp_kv))) + tk_call(*(__item_config_cmd([:heading, tagOrId]).concat(hash_kv(slot)))) + elsif __tile_specific_item_optkeys([:heading, tagOrId]).index(slot.to_s) + begin + # On tile-0.7.{2-8}, 'state' options has no '-' at its head. + tk_call(*(__item_cget_cmd([:heading, tagOrId]) << slot.to_s << value)) + rescue + # Maybe, 'state' option has '-' in future. + tk_call(*(__item_cget_cmd([:heading, tagOrId]) << "-#{slot}" << value)) end - alias heading_cget headingcget - alias heading_configure headingconfigure - alias heading_configinfo headingconfiginfo - alias current_heading_configinfo current_headingconfiginfo + else + __itemconfigure([:heading, tagOrId], slot, value) + end + self + end + def headingconfiginfo(tagOrId, slot=nil) + __itemconfiginfo([:heading, tagOrId], slot) + end + def current_headingconfiginfo(tagOrId, slot=nil) + __current_itemconfiginfo([:heading, tagOrId], slot) + end + alias heading_cget headingcget + alias heading_configure headingconfigure + alias heading_configinfo headingconfiginfo + alias current_heading_configinfo current_headingconfiginfo + + # Treeview Tag + def tagcget(tagOrId, option) + __itemcget([:tag, tagOrId], option) + end + def tagconfigure(tagOrId, slot, value=None) + __itemconfigure([:tag, tagOrId], slot, value) + end + def tagconfiginfo(tagOrId, slot=nil) + __itemconfiginfo([:tag, tagOrId], slot) + end + def current_tagconfiginfo(tagOrId, slot=nil) + __current_itemconfiginfo([:tag, tagOrId], slot) + end + alias tag_cget tagcget + alias tag_configure tagconfigure + alias tag_configinfo tagconfiginfo + alias current_tag_configinfo current_tagconfiginfo +end + +######################## + +class Tk::Tile::Treeview::Item < TkObject + ItemID_TBL = TkCore::INTERP.create_table + TkCore::INTERP.init_ip_env{ Tk::Tile::Treeview::Item::ItemID_TBL.clear } + + def self.id2obj(tree, id) + tpath = tree.path + return id unless Tk::Tile::Treeview::Item::ItemID_TBL[tpath] + (Tk::Tile::Treeview::Item::ItemID_TBL[tpath][id])? \ + Tk::Tile::Treeview::Item::ItemID_TBL[tpath][id]: id + end + + def self.assign(tree, id) + tpath = tree.path + if Tk::Tile::Treeview::Item::ItemID_TBL[tpath] && + Tk::Tile::Treeview::Item::ItemID_TBL[tpath][id] + return Tk::Tile::Treeview::Item::ItemID_TBL[tpath][id] + end + + obj = self.allocate + obj.instance_eval{ + @parent = @t = tree + @tpath = tpath + @path = @id = id + } + ItemID_TBL[tpath] = {} unless ItemID_TBL[tpath] + Tk::Tile::Treeview::Item::ItemID_TBL[tpath][id] = obj + obj + end + + def _insert_item(tree, parent_item, idx, keys={}) + keys = _symbolkey2str(keys) + id = keys.delete('id') + if id + num_or_str(tk_call(tree, 'insert', + parent_item, idx, '-id', id, *hash_kv(keys))) + else + num_or_str(tk_call(tree, 'insert', parent_item, idx, *hash_kv(keys))) end end + private :_insert_item + + def initialize(tree, parent_item = '', idx = 'end', keys = {}) + if parent_item.kind_of?(Hash) + keys = parent_item + idx = 'end' + parent_item = '' + elsif idx.kind_of?(Hash) + keys = idx + idx = 'end' + end + + @parent = @t = tree + @tpath = tree.path + @path = @id = _insert_item(@t, parent_item, idx, keys) + ItemID_TBL[@tpath] = {} unless ItemID_TBL[@tpath] + ItemID_TBL[@tpath][@id] = self + end + def id + @id + end + + def cget(option) + @t.itemcget(@id, option) + end + + def configure(key, value=None) + @t.itemconfigure(@id, key, value) + self + end + + def configinfo(key=nil) + @t.itemconfiginfo(@id, key) + end + + def current_configinfo(key=nil) + @t.current_itemconfiginfo(@id, key) + end + + def open? + cget('open') + end + def open + configure('open', true) + self + end + def close + configure('open', false) + self + end + + def bbox(column=None) + @t.bbox(@id, column) + end + + def children + @t.children(@id) + end + def set_children(*items) + @t.set_children(@id, *items) + self + end + + def delete + @t.delete(@id) + self + end + + def detach + @t.detach(@id) + self + end + + def exist? + @t.exist?(@id) + end + + def focus + @t.focus_item(@id) + end + + def index + @t.index(@id) + end + + def insert(idx='end', keys={}) + @t.insert(@id, idx, keys) + end + + def move(parent, idx) + @t.move(@id, parent, idx) + self + end + + def next_item + @t.next_item(@id) + end + + def parent_item + @t.parent_item(@id) + end + + def prev_item + @t.prev_item(@id) + end + + def see + @t.see(@id) + self + end + + def selection_add + @t.selection_add(@id) + self + end + + def selection_remove + @t.selection_remove(@id) + self + end + + def selection_set + @t.selection_set(@id) + self + end + + def selection_toggle + @t.selection_toggle(@id) + self + end + + def get_directory + @t.get_directory(@id) + end + alias get_dictionary get_directory + + def get(col) + @t.get(@id, col) + end + + def set(col, value) + @t.set(@id, col, value) + end +end + +######################## + +class Tk::Tile::Treeview::Root < Tk::Tile::Treeview::Item + def self.new(tree, keys = {}) + tpath = tree.path + if Tk::Tile::Treeview::Item::ItemID_TBL[tpath] && + Tk::Tile::Treeview::Item::ItemID_TBL[tpath][''] + Tk::Tile::Treeview::Item::ItemID_TBL[tpath][''] + else + super(tree, keys) + end + end + + def initialize(tree, keys = {}) + @parent = @t = tree + @tpath = tree.path + @path = @id = '' + unless Tk::Tile::Treeview::Item::ItemID_TBL[@tpath] + Tk::Tile::Treeview::Item::ItemID_TBL[@tpath] = {} + end + Tk::Tile::Treeview::Item::ItemID_TBL[@tpath][@id] = self + end end +######################## + +class Tk::Tile::Treeview::Tag < TkObject + include TkTreatTagFont + + TagID_TBL = TkCore::INTERP.create_table + Tag_ID = ['tile_treeview_tag'.freeze, '00000'.taint].freeze + + TkCore::INTERP.init_ip_env{ Tk::Tile::Treeview::Tag::TagID_TBL.clear } + + def self.id2obj(tree, id) + tpath = tree.path + return id unless Tk::Tile::Treeview::Tag::TagID_TBL[tpath] + (Tk::Tile::Treeview::Tag::TagID_TBL[tpath][id])? \ + Tk::Tile::Treeview::Tag::TagID_TBL[tpath][id]: id + end + + def initialize(tree, keys=nil) + @parent = @t = tree + @tpath = tree.path + @path = @id = Tag_ID.join(TkCore::INTERP._ip_id_) + TagID_TBL[@tpath] = {} unless TagID_TBL[@tpath] + TagID_TBL[@tpath][@id] = self + Tag_ID[1].succ! + if keys && keys != None + tk_call_without_enc(@tpath, 'tag', 'configure', *hash_kv(keys, true)) + end + end + def id + @id + end + + def bind(seq, *args) + if TkComm._callback_entry?(args[0]) || !block_given? + cmd = args.shift + else + cmd = Proc.new + end + @t.tag_bind(@id, seq, cmd, *args) + self + end + + def bind_append(seq, *args) + if TkComm._callback_entry?(args[0]) || !block_given? + cmd = args.shift + else + cmd = Proc.new + end + @t.tag_bind_append(@id, seq, cmd, *args) + self + end + + def bind_remove(seq) + @t.tag_bind_remove(@id, seq) + self + end + + def bindinfo(seq=nil) + @t.tag_bindinfo(@id, seq) + end + + def cget(option) + @t.tagcget(@id, option) + end + + def configure(key, value=None) + @t.tagconfigure(@id, key, value) + self + end + + def configinfo(key=nil) + @t.tagconfiginfo(@id, key) + end + + def current_configinfo(key=nil) + @t.current_tagconfiginfo(@id, key) + end +end + +######################## + class Tk::Tile::Treeview < TkWindow include Tk::Tile::TileWidget include Scrollable @@ -146,24 +915,38 @@ class Tk::Tile::Treeview < TkWindow WidgetClassName = 'Treeview'.freeze WidgetClassNames[WidgetClassName] = self + def __destroy_hook__ + Tk::Tile::Treeview::Item::ItemID_TBL.delete(@path) + Tk::Tile::Treeview::Tag::ItemID_TBL.delete(@path) + end + def self.style(*args) [self::WidgetClassName, *(args.map!{|a| _get_eval_string(a)})].join('.') end def tagid(id) - if id.kind_of?(Array) + if id.kind_of?(Tk::Tile::Treeview::Item) || + id.kind_of?(Tk::Tile::Treeview::Tag) + id.id + elsif id.kind_of?(Array) [id[0], _get_eval_string(id[1])] else _get_eval_string(id) end end + def root + Tk::Tile::Treeview::Root.new(self) + end + def bbox(item, column=None) list(tk_send('item', 'bbox', item, column)) end def children(item) - simplelist(tk_send_without_enc('children', item)) + simplelist(tk_send_without_enc('children', item)).collect{|id| + Tk::Tile::Treeview::Item.id2obj(self, id) + } end def set_children(item, *items) tk_send_without_enc('children', item, @@ -185,21 +968,33 @@ class Tk::Tile::Treeview < TkWindow bool(tk_send_without_enc('exists', _get_eval_enc_str(item))) end - def focus_item(item = None) - tk_send('focus', item) + def focus_item(item = nil) + if item + tk_send('focus', item) + item + else + id = tk_send('focus') + (id.empty?)? nil: Tk::Tile::Treeview::Item.id2obj(self, id) + end end def identify(x, y) # tile-0.7.2 or previous ret = simplelist(tk_send('identify', x, y)) case ret[0] - when 'heading', 'separator', 'cell' + when 'heading', 'separator' ret[-1] = num_or_str(ret[-1]) + when 'cell' + ret[1] = Tk::Tile::Treeview::Item.id2obj(self, ret[1]) + ret[-1] = num_or_str(ret[-1]) + when 'item', 'row' + ret[1] = Tk::Tile::Treeview::Item.id2obj(self, ret[1]) end end def row_identify(x, y) - tk_send('identify', 'row', x, y) + id = tk_send('identify', 'row', x, y) + (id.empty?)? nil: Tk::Tile::Treeview::Item.id2obj(self, id) end def column_identify(x, y) @@ -210,38 +1005,47 @@ class Tk::Tile::Treeview < TkWindow number(tk_send('index', item)) end - def insert(parent, idx, keys={}) - keys = _symbolkey2str(keys) - id = keys.delete('id') - if id - num_or_str(tk_send('insert', parent, idx, '-id', id, *hash_kv(keys))) - else - num_or_str(tk_send('insert', parent, idx, *hash_kv(keys))) - end + # def insert(parent, idx='end', keys={}) + # keys = _symbolkey2str(keys) + # id = keys.delete('id') + # if id + # num_or_str(tk_send('insert', parent, idx, '-id', id, *hash_kv(keys))) + # else + # num_or_str(tk_send('insert', parent, idx, *hash_kv(keys))) + # end + # end + def insert(parent, idx='end', keys={}) + Tk::Tile::Treeview::Item.new(self, parent, idx, keys) end - def instate(spec, cmd=Proc.new) - tk_send('instate', spec, cmd) - end - def state(spec=None) - tk_send('state', spec) - end + # def instate(spec, cmd=Proc.new) + # tk_send('instate', spec, cmd) + # end + # def state(spec=None) + # tk_send('state', spec) + # end def move(item, parent, idx) tk_send('move', item, parent, idx) self end - def next(item) - tk_send('next', item) + def next_item(item) + id = tk_send('next', item) + (id.empty?)? nil: Tk::Tile::Treeview::Item.id2obj(self, id) end - def parent(item) - tk_send('parent', item) + def parent_item(item) + if (id = tk_send('parent', item)).empty? + Tk::Tile::Treeview::Root.new(self) + else + Tk::Tile::Treeview::Item.id2obj(self, id) + end end - def prev(item) - tk_send('prev', item) + def prev_item(item) + id = tk_send('prev', item) + (id.empty?)? nil: Tk::Tile::Treeview::Item.id2obj(self, id) end def see(item) @@ -250,7 +1054,9 @@ class Tk::Tile::Treeview < TkWindow end def selection - simplelist(tk_send('selection')) + simplelist(tk_send('selection')).collect{|id| + Tk::Tile::Treeview::Item.id2obj(self, id) + } end alias selection_get selection diff --git a/ext/tk/lib/tkextlib/version.rb b/ext/tk/lib/tkextlib/version.rb new file mode 100644 index 0000000000..83dfbd0e61 --- /dev/null +++ b/ext/tk/lib/tkextlib/version.rb @@ -0,0 +1,6 @@ +# +# release date of tkextlib +# +module Tk + Tkextlib_RELEASE_DATE = '2006-11-06'.freeze +end diff --git a/ext/tk/sample/editable_listbox.rb b/ext/tk/sample/editable_listbox.rb new file mode 100644 index 0000000000..99345da380 --- /dev/null +++ b/ext/tk/sample/editable_listbox.rb @@ -0,0 +1,69 @@ +# +# Editable_TkListbox class +# +# When "DoubleClick-1" on a listbox item, the entry box is opend on the +# item. And when hit "Return" key on the entry box after modifying the +# text, the entry box is closed and the item is changed. Or when hit +# "Escape" key, the entry box is closed without modification. +# +# by Hidetoshi NAGAI (nagai@ai.kyutech.ac.jp) +# +require 'tk' + +class Editable_TkListbox < TkListbox + def _ebox_placer(coord_y) + idx = self.nearest(coord_y) + x, y, w, h = self.bbox(idx) + @ebox.place(:x => 0, :relwidth => 1.0, + :y => y - self.selectborderwidth, + :height => h + 2 * self.selectborderwidth) + @ebox.pos = idx + @ebox.value = self.listvariable.list[idx] + @ebox.focus + end + private :_ebox_placer + + + def create_self(keys) + super(keys) + + unless self.listvariable + self.listvariable = TkVariable.new(self.get(0, :end)) + end + + @ebox = TkEntry.new(self){ + @pos = -1 + def self.pos; @pos; end + def self.pos=(idx); @pos = idx; end + } + + @ebox.bind('Return'){ + list = self.listvariable.list + list[@ebox.pos] = @ebox.value + self.listvariable.value = list + @ebox.place_forget + @ebox.pos = -1 + } + + @ebox.bind('Escape'){ + @ebox.place_forget + @ebox.pos = -1 + } + + self.bind('Double-1', '%y'){|y| _ebox_placer(y) } + end +end + +if $0 == __FILE__ + scr = TkScrollbar.new.pack(:side=>:right, :fill=>:y) + + lbox1 = Editable_TkListbox.new.pack(:side=>:left) + lbox2 = Editable_TkListbox.new.pack(:side=>:left) + + scr.assign(lbox1, lbox2) + + lbox1.insert(:end, *%w(a b c d e f g h i j k l m n)) + lbox2.insert(:end, 0,1,2,3,4,5,6,7,8,9,0,1,2,3) + + Tk.mainloop +end diff --git a/ext/tk/sample/irbtkw.rbw b/ext/tk/sample/irbtkw.rbw new file mode 100644 index 0000000000..92fa5692f2 --- /dev/null +++ b/ext/tk/sample/irbtkw.rbw @@ -0,0 +1,119 @@ +#!/usr/bin/env ruby +# +# irbtkw.rb : IRB console with Ruby/Tk +# +# by Hidetoshi NAGAI (nagai@ai.kyutech.ac.jp) +# +release = '2006/11/06' + +require 'tk' +begin + require 'tktextio' +rescue LoadError + require File.join(File.dirname(File.expand_path(__FILE__)), 'tktextio.rb') +end + +require 'irb' + +# console setup +top = TkToplevel.new(:title=>'IRB console') +top.protocol(:WM_DELETE_WINDOW){ Tk.exit } + +console = TkTextIO.new(top, :mode=>:console, + :width=>80).pack(:side=>:left, + :expand=>true, :fill=>:both) +console.yscrollbar(TkScrollbar.new(top, :width=>10).pack(:before=>console, + :side=>:right, + :expand=>false, + :fill=>:y)) +ev_loop = Thread.new{Tk.mainloop} + +# window position control +root = Tk.root + +r_x = root.winfo_rootx +r_y = root.winfo_rooty +r_w = root.winfo_width + +t_x = top.winfo_rootx +t_y = top.winfo_rooty +t_w = top.winfo_width + +delta = 10 + +ratio = 0.8 +s_w = (ratio * root.winfo_screenwidth).to_i + +if r_x < t_x + r_x, t_x = t_x, r_x +end +if t_x + t_w + r_w + delta < s_w + r_x = t_x + t_w + delta +elsif t_w + r_w + delta < s_w + r_x = s_w - r_w + t_x = r_x - t_w +else + r_x = s_w - r_w + t_x = 0 +end + +root.geometry("+#{r_x}+#{r_y}") +top.geometry("+#{t_x}+#{t_y}") + +root.raise +console.focus + +# I/O setup +$stdin = console +$stdout = console +$stderr = console + +# dummy for rubyw.exe on Windows +def STDIN.tty? + true +end + +# IRB setup +IRB.init_config(nil) +IRB.conf[:USE_READLINE] = false +IRB.init_error +irb = IRB::Irb.new +IRB.conf[:MAIN_CONTEXT] = irb.context + +class IRB::StdioInputMethod + def gets + prompt = "\n" << @prompt + $stdin.instance_eval{ + flush + @prompt = prompt + _set_console_line + @prompt = nil + _see_pos + } + + @line[@line_no += 1] = $stdin.gets + end +end + +# IRB start +$stdout.print("*** IRB console on Ruby/Tk (#{release}) ") +irb_thread = Thread.new{ + catch(:IRB_EXIT){ + loop { + begin + irb.eval_input + rescue Exception + end + } + } +} + +console.bind('Control-c'){ + console.insert('end', "^C\n") + irb_thread.raise RubyLex::TerminateLineInput +} + +irb_thread.join + +# exit +Tk.exit diff --git a/ext/tk/sample/tktextio.rb b/ext/tk/sample/tktextio.rb index cb59c2d9d6..0b78f45b10 100644 --- a/ext/tk/sample/tktextio.rb +++ b/ext/tk/sample/tktextio.rb @@ -1,7 +1,7 @@ #!/usr/bin/env ruby # -# sample class of handling I/O stream on a TkText widget -# by Hidetoshi NAGAI +# TkTextIO class :: handling I/O stream on a TkText widget +# by Hidetoshi NAGAI (nagai@ai.kyutech.ac.jp) # # NOTE: TkTextIO supports 'character' (not 'byte') access only. # So, for example, TkTextIO#getc returns a character, TkTextIO#pos @@ -14,69 +14,376 @@ # TkTextIO. # require 'tk' +require 'tk/text' +require 'tk/textmark' +require 'thread' class TkTextIO < TkText + # keep safe level + @@create_queues = proc{ [Queue.new, Mutex.new, Queue.new, Mutex.new] } + + OPT_DEFAULTS = { + 'mode' => nil, + 'overwrite' => false, + 'text' => nil, + 'show' => :pos, + 'wrap' => 'char', + 'sync' => true, + 'prompt' => nil, + 'prompt_cmd' => nil, + 'hist_size' => 1000, + } + def create_self(keys) - mode = nil - ovwt = false - text = nil - wrap = 'char' - show = :pos - - if keys.kind_of?(Hash) - mode = keys.delete('mode') - ovwt = keys.delete('overwrite') - text = keys.delete('text') - show = keys.delete('show') if keys.has_key?('show') - wrap = keys.delete('wrap') || 'char' - end + opts = _get_io_params((keys.kind_of?(Hash))? keys: {}) super(keys) - self['wrap'] = wrap - insert('1.0', text) + @count_var = TkVariable.new - @txtpos = TkTextMark.new(self, '1.0') - @txtpos.gravity = :left + @write_buffer = '' + @read_buffer = '' + @buf_size = 0 + @buf_max = 1024 - self.show_mode = show + @write_buf_queue, @write_buf_mutex, + @read_buf_queue, @read_buf_mutex = @@create_queues.call - @sync = true - @overwrite = (ovwt)? true: false + @idle_flush = TkTimer.new(:idle, 1, proc{ @flusher.run rescue nil }) + @timer_flush = TkTimer.new(250, -1, proc{ @flusher.run rescue nil }) + + @flusher = Thread.new{ loop { Thread.stop; flush() } } + + @receiver = Thread.new{ + begin + loop { + str = @write_buf_queue.deq + @write_buf_mutex.synchronize { @write_buffer << str } + @idle_flush.start + } + ensure + @flusher.kill + end + } + + @timer_flush.start + + _setup_io(opts) + end + private :create_self + + def destroy + @flusher.kill rescue nil + + @idle_flush.stop rescue nil + @timer_flush.stop rescue nil + + @receiver.kill rescue nil + + super() + end + + #################################### + + def _get_io_params(keys) + opts = {} + self.class.const_get(:OPT_DEFAULTS).each{|k, v| + if keys.has_key?(k) + opts[k] = keys.delete(k) + else + opts[k] = v + end + } + opts + end + + def _setup_io(opts) + unless defined? @txtpos + @txtpos = TkTextMark.new(self, '1.0') + else + @txtpos.set('1.0') + end + @txtpos.gravity = :left @lineno = 0 @line_offset = 0 - @count_var = TkVariable.new + + @hist_max = opts['hist_size'] + @hist_index = 0 + @history = Array.new(@hist_max) + @history[0] = '' + + self['wrap'] = wrap + + self.show_mode = opts['show'] + + self.value = opts['text'] if opts['text'] + + @overwrite = (opts['overwrite'])? true: false + + @sync = opts['sync'] + + @prompt = opts['prompt'] + @prompt_cmd = opts['prompt_cmd'] @open = {:r => true, :w => true} # default is 'r+' - case mode - when 'r' + @console_mode = false + @end_of_stream = false + @console_buffer = nil + + case opts['mode'] + when nil + # do nothing + + when :console, 'console' + @console_mode = true + # @console_buffer = TkTextIO.new(:mode=>'r') + @console_buffer = self.class.new(:mode=>'r') + self.show_mode = :insert + + when 'r', 'rb' @open[:r] = true; @open[:w] = nil - when 'r+' + when 'r+', 'rb+', 'r+b' @open[:r] = true; @open[:w] = true - when 'w' + when 'w', 'wb' @open[:r] = nil; @open[:w] = true self.value='' - when 'w+' + when 'w+', 'wb+', 'w+b' @open[:r] = true; @open[:w] = true self.value='' - when 'a' + when 'a', 'ab' @open[:r] = nil; @open[:w] = true - @txtpos = TkTextMark.new(self, 'end - 1 char') + @txtpos.set('end - 1 char') @txtpos.gravity = :right - when 'a+' + when 'a+', 'ab+', 'a+b' @open[:r] = true; @open[:w] = true - @txtpos = TkTextMark.new(self, 'end - 1 char') + @txtpos.set('end - 1 char') @txtpos.gravity = :right + + else + fail ArgumentError, "unknown mode `#{opts['mode']}'" + end + + unless defined? @ins_head + @ins_head = TkTextMark.new(self, 'insert') + @ins_head.gravity = :left + end + + unless defined? @ins_tail + @ins_tail = TkTextMark.new(self, 'insert') + @ins_tail.gravity = :right + end + + unless defined? @tmp_mark + @tmp_mark = TkTextMark.new(self, 'insert') + @tmp_mark.gravity = :left + end + + if @console_mode + _set_console_line + _setup_console_bindings + end + end + private :_get_io_params, :_setup_io + + def _set_console_line + @tmp_mark.set(@ins_tail) + + mark_set('insert', 'end') + + prompt = '' + prompt << @prompt_cmd.call if @prompt_cmd + prompt << @prompt if @prompt + insert(@tmp_mark, prompt) + + @ins_head.set(@ins_tail) + @ins_tail.set('insert') + + @txtpos.set(@tmp_mark) + + _see_pos + end + + def _replace_console_line(str) + self.delete(@ins_head, @ins_tail) + self.insert(@ins_head, str) + end + + def _get_console_line + @tmp_mark.set(@ins_tail) + s = self.get(@ins_head, @tmp_mark) + _set_console_line + s + end + private :_set_console_line, :_replace_console_line, :_get_console_line + + def _cb_up + @history[@hist_index].replace(self.get(@ins_head, @ins_tail)) + @hist_index += 1 + @hist_index -= 1 if @hist_index >= @hist_max || !@history[@hist_index] + _replace_console_line(@history[@hist_index]) if @history[@hist_index] + Tk.callback_break + end + def _cb_down + @history[@hist_index].replace(self.get(@ins_head, @ins_tail)) + @hist_index -= 1 + @hist_index = 0 if @hist_index < 0 + _replace_console_line(@history[@hist_index]) if @history[@hist_index] + Tk.callback_break + end + def _cb_left + if @console_mode && compare('insert', '<=', @ins_head) + mark_set('insert', @ins_head) + Tk.callback_break + end + end + def _cb_backspace + if @console_mode && compare('insert', '<=', @ins_head) + Tk.callback_break + end + end + def _cb_ctrl_a + if @console_mode + mark_set('insert', @ins_head) + Tk.callback_break + end + end + private :_cb_up, :_cb_down, :_cb_left, :_cb_backspace, :_cb_ctrl_a + + def _setup_console_bindings + @bindtag = TkBindTag.new + + tags = self.bindtags + tags[tags.index(self)+1, 0] = @bindtag + self.bindtags = tags + + @bindtag.bind('Return'){ + insert('end - 1 char', "\n") + if (str = _get_console_line) + @read_buf_queue.push(str) + + @history[0].replace(str.chomp) + @history.pop + @history.unshift('') + @hist_index = 0 + end + + Tk.update + Tk.callback_break + } + @bindtag.bind('Alt-Return'){ + Tk.callback_continue + } + + @bindtag.bind('FocusIn'){ + if @console_mode + mark_set('insert', @ins_tail) + Tk.callback_break + end + } + + ins_mark = TkTextMark.new(self, 'insert') + + @bindtag.bind('ButtonPress'){ + if @console_mode + ins_mark.set('insert') + end + } + + @bindtag.bind('ButtonRelease-1'){ + if @console_mode && compare('insert', '<=', @ins_head) + mark_set('insert', ins_mark) + Tk.callback_break + end + } + + @bindtag.bind('ButtonRelease-2', '%x %y'){|x, y| + if @console_mode + # paste a text at 'insert' only + x1, y1, x2, y2 = bbox(ins_mark) + unless x == x1 && y == y1 + Tk.event_generate(self, 'ButtonRelease-2', :x=>x1, :y=>y1) + Tk.callback_break + end + end + } + + @bindtag.bind('Up'){ _cb_up } + @bindtag.bind('Control-p'){ _cb_up } + + @bindtag.bind('Down'){ _cb_down } + @bindtag.bind('Control-n'){ _cb_down } + + @bindtag.bind('Left'){ _cb_left } + @bindtag.bind('Control-b'){ _cb_left } + + @bindtag.bind('BackSpace'){ _cb_backspace } + @bindtag.bind('Control-h'){ _cb_backspace } + + @bindtag.bind('Home'){ _cb_ctrl_a } + @bindtag.bind('Control-a'){ _cb_ctrl_a } + end + private :_setup_console_bindings + + def _block_read(size = nil, ret = '', block_mode = true) + return '' if size == 0 + return nil if ! @read_buf_queue && @read_buffer.empty? + ret = '' unless ret.kind_of?(String) + ret.replace('') unless ret.empty? + + if block_mode == nil # partial + if @read_buffer.empty? + ret << @read_buffer.slice!(0..-1) + return ret + end + end + + if size.kind_of?(Numeric) + loop{ + @read_buf_mutex.synchronize { + buf_len = @read_buffer.length + if buf_len >= size + ret << @read_buffer.slice!(0, size) + return ret + else + ret << @read_buffer.slice!(0..-1) + size -= buf_len + return ret unless @read_buf_queue + end + } + @read_buffer << @read_buf_queue.pop + } + else # readline + rs = (size)? size: $/ + rs = rs.to_s if rs.kind_of?(Regexp) + loop{ + @read_buf_mutex.synchronize { + if (str = @read_buffer.slice!(/\A(.*)(#{rs})/m)) + ret << str + return ret + else + ret << @read_buffer.slice!(0..-1) + return ret unless @read_buf_queue + end + } + @read_buffer << @read_buf_queue.pop + } end end + def _block_write + ###### currently, not support + end + private :_block_read, :_block_write + + #################################### + def <<(obj) _write(obj) self @@ -107,14 +414,15 @@ class TkTextIO < TkText nil end - def closed? - close_read? && close_write? - end - def closed_read? - !@open[:r] - end - def closed_write? - !@open[:w] + def closed?(dir=nil) + case dir + when :r, 'r' + !@open[:r] + when :w, 'w' + !@open[:w] + else + !@open[:r] && !@open[:w] + end end def _check_readable @@ -129,7 +437,7 @@ class TkTextIO < TkText def each_line(rs = $/) _check_readable - while(s = gets) + while(s = self.gets(rs)) yield(s) end self @@ -138,7 +446,7 @@ class TkTextIO < TkText def each_char _check_readable - while(c = getc) + while(c = self.getc) yield(c) end self @@ -151,7 +459,7 @@ class TkTextIO < TkText alias eof eof? def fcntl(*args) - fail NotImplementedError, 'fcntl is not implemented on TkTextIO' + fail NotImplementedError, "fcntl is not implemented on #{self.class}" end def fsync @@ -163,11 +471,19 @@ class TkTextIO < TkText end def flush - Tk.update if @open[:w] && @sync + Thread.pass + if @open[:w] || ! @write_buffer.empty? + @write_buf_mutex.synchronize { + _sync_write_buf(@write_buffer) + @write_buffer[0..-1] = '' + } + end self end def getc + return _block_read(1) if @console_mode + _check_readable return nil if eof? c = get(@txtpos) @@ -177,8 +493,10 @@ class TkTextIO < TkText end def gets(rs = $/) + return _block_read(rs) if @console_mode + _check_readable - return nil if eof? + return nil if eof? _readline(rs) end @@ -233,7 +551,6 @@ class TkTextIO < TkText alias tell pos def pos=(idx) - # @txtpos.set((idx.kind_of?(Numeric))? "1.0 + #{idx} char": idx) seek(idx, IO::SEEK_SET) idx end @@ -306,6 +623,8 @@ class TkTextIO < TkText private :_read def read(len=nil, buf=nil) + return _block_read(len, buf) if @console_mode + _check_readable if len return "" if len == 0 @@ -321,6 +640,8 @@ class TkTextIO < TkText end def readchar + return _block_read(1) if @console_mode + _check_readable fail EOFError if eof? c = get(@txtpos) @@ -334,6 +655,7 @@ class TkTextIO < TkText s = get(@txtpos, 'end - 1 char') @txtpos.set('end - 1 char') elsif rs == '' + @count_var.value # make it global idx = tksearch_with_count([:regexp], @count_var, "\n(\n)+", @txtpos, 'end - 1 char') if idx @@ -345,6 +667,7 @@ class TkTextIO < TkText @txtpos.set('end - 1 char') end else + @count_var.value # make it global idx = tksearch_with_count(@count_var, rs, @txtpos, 'end - 1 char') if idx s = get(@txtpos, "#{idx} + #{@count_var.value} char") @@ -363,12 +686,22 @@ class TkTextIO < TkText private :_readline def readline(rs = $/) + return _block_readline(rs) if @console_mode + _check_readable fail EOFError if eof? _readline(rs) end def readlines(rs = $/) + if @console_mode + lines = [] + while (line = _block_readline(rs)) + lines << line + end + return lines + end + _check_readable lines = [] until(eof?) @@ -379,7 +712,11 @@ class TkTextIO < TkText end def readpartial(maxlen, buf=nil) + #return @console_buffer.readpartial(maxlen, buf) if @console_mode + return _block_read(maxlen, buf, nil) if @console_mode + _check_readable + fail EOFError if eof? s = _read(maxlen) buf.replace(s) if buf.kind_of?(String) s @@ -471,6 +808,8 @@ class TkTextIO < TkText end def sysread(len, buf=nil) + return _block_read(len, buf) if @console_mode + _check_readable fail EOFError if eof? s = _read(len) @@ -492,6 +831,13 @@ class TkTextIO < TkText end def ungetc(c) + if @console_mode + @read_buf_mutex.synchronize { + @read_buffer[0,0] = c.chr + } + return nil + end + _check_readable c = c.chr if c.kind_of?(Fixnum) if compare(@txtpos, '>', '1.0') @@ -506,8 +852,10 @@ class TkTextIO < TkText nil end +=begin def _write(obj) - s = _get_eval_string(obj) + #s = _get_eval_string(obj) + s = (obj.kind_of?(String))? obj: obj.to_s n = number(tk_call('string', 'length', s)) delete(@txtpos, @txtpos + "#{n} char") if @overwrite self.insert(@txtpos, s) @@ -518,6 +866,37 @@ class TkTextIO < TkText n end private :_write +=end +#=begin + def _sync_write_buf(s) + if (n = number(tk_call('string', 'length', s))) > 0 + delete(@txtpos, @txtpos + "#{n} char") if @overwrite + self.insert(@txtpos, s) + #Tk.update + + @txtpos.set(@txtpos + "#{n} char") + @txtpos.set('end - 1 char') if compare(@txtpos, '>=', :end) + + @ins_head.set(@txtpos) if compare(@txtpos, '>', @ins_head) + + _see_pos + end + self + end + private :_sync_write_buf + + def _write(obj) + s = (obj.kind_of?(String))? obj: obj.to_s + n = number(tk_call('string', 'length', s)) + @write_buf_queue.enq(s) + if @sync + Thread.pass + Tk.update + end + n + end + private :_write +#=end def write(obj) _check_writable @@ -529,13 +908,19 @@ end # TEST #################### if __FILE__ == $0 + ev_loop = Thread.new{Tk.mainloop} + f = TkFrame.new.pack - tio = TkTextIO.new(f, :show=>:pos, + #tio = TkTextIO.new(f, :show=>:nil, + #tio = TkTextIO.new(f, :show=>:pos, + tio = TkTextIO.new(f, :show=>:insert, :text=>">>> This is an initial text line. <<<\n\n"){ - yscrollbar(TkScrollbar.new(f).pack(:side=>:right, :fill=>:y)) +# yscrollbar(TkScrollbar.new(f).pack(:side=>:right, :fill=>:y)) pack(:side=>:left, :fill=>:both, :expand=>true) } + Tk.update + $stdin = tio $stdout = tio $stderr = tio @@ -599,5 +984,67 @@ if __FILE__ == $0 tio.seek(0, IO::SEEK_END) - Tk.mainloop + STDOUT.print("tio.sync == #{tio.sync}\n") +# tio.sync = false +# STDOUT.print("tio.sync == #{tio.sync}\n") + + (0..10).each{|i| + STDOUT.print("#{i}\n") + s = '' + (0..1000).each{ s << '*' } + print(s) + } + print("\n") + print("\n=========================================================\n\n") + + s = '' + timer = TkTimer.new(:idle, -1, proc{ + #STDOUT.print("idle call\n") + unless s.empty? + print(s) + s = '' + end + }).start + (0..10).each{|i| + STDOUT.print("#{i}\n") + (0..1000).each{ s << '*' } + } +# timer.stop + until s.empty? + sleep 0.1 + end + timer.stop + +=begin + tio.sync = false + print("\n") + #(0..10000).each{ putc('*') } + (0..10).each{|i| + STDOUT.print("#{i}\n") + (0..1000).each{ putc('*') } + } + + (0..10).each{|i| + STDOUT.print("#{i}\n") + s = '' + (0..1000).each{ s << '*' } + print(s) + } +=end + + num = 0 +# io = TkTextIO.new(:mode=>:console, :prompt=>'').pack +#=begin + io = TkTextIO.new(:mode=>:console, + :prompt_cmd=>proc{ + s = "[#{num}]" + num += 1 + s + }, + :prompt=>'-> ').pack +#=end + Thread.new{loop{sleep 2; io.puts 'hoge'}} + Thread.new{loop{p io.gets}} + + ev_loop.join end -- cgit v1.2.3