# # tkcanvas.rb - Tk canvas classes # $Date$ # by Yukihiro Matsumoto <matz@caelum.co.jp> # $Date$ # by Hidetoshi Nagai <nagai@ai.kyutech.ac.jp> require "tk" require 'tkfont' module TkTreatCItemFont include TkTreatItemFont ItemCMD = ['itemconfigure'.freeze, TkComm::None].freeze def __conf_cmd(idx) ItemCMD[idx] end def __item_pathname(tagOrId) if tagOrId.kind_of?(TkcItem) || tagOrId.kind_of?(TkcTag) self.path + ';' + tagOrId.id.to_s else self.path + ';' + tagOrId.to_s end end private :__conf_cmd, :__item_pathname end class TkCanvas<TkWindow include TkTreatCItemFont include Scrollable TkCommandNames = ['canvas'.freeze].freeze WidgetClassName = 'Canvas'.freeze WidgetClassNames[WidgetClassName] = self def __destroy_hook__ TkcItem::CItemID_TBL.delete(@path) end def create_self(keys) if keys and keys != None tk_call 'canvas', @path, *hash_kv(keys) else tk_call 'canvas', @path end end private :create_self def tagid(tag) if tag.kind_of?(TkcItem) || tag.kind_of?(TkcTag) tag.id else tag end end private :tagid def addtag(tag, mode, *args) tk_send 'addtag', tagid(tag), mode, *args self end def addtag_above(tagOrId, target) addtag(tagOrId, 'above', tagid(target)) end def addtag_all(tagOrId) addtag(tagOrId, 'all') end def addtag_below(tagOrId, target) addtag(tagOrId, 'below', tagid(target)) end def addtag_closest(tagOrId, x, y, halo=None, start=None) addtag(tagOrId, 'closest', x, y, halo, start) end def addtag_enclosed(tagOrId, x1, y1, x2, y2) addtag(tagOrId, 'enclosed', x1, y1, x2, y2) end def addtag_overlapping(tagOrId, x1, y1, x2, y2) addtag(tagOrId, 'overlapping', x1, y1, x2, y2) end def addtag_withtag(tagOrId, tag) addtag(tagOrId, 'withtag', tagid(tag)) end def bbox(tagOrId, *tags) list(tk_send('bbox', tagid(tagOrId), *tags.collect{|t| tagid(t)})) end def itembind(tag, context, cmd=Proc.new, args=nil) _bind([path, "bind", tagid(tag)], context, cmd, args) self end def itembind_append(tag, context, cmd=Proc.new, args=nil) _bind_append([path, "bind", tagid(tag)], context, cmd, args) self end def itembind_remove(tag, context) _bind_remove([path, "bind", tagid(tag)], context) self end def itembindinfo(tag, context=nil) _bindinfo([path, "bind", tagid(tag)], context) end def canvasx(x, *args) tk_tcl2ruby(tk_send('canvasx', x, *args)) end def canvasy(y, *args) tk_tcl2ruby(tk_send('canvasy', y, *args)) end def coords(tag, *args) if args == [] tk_split_list(tk_send('coords', tagid(tag))) else tk_send('coords', tagid(tag), *(args.flatten)) end end def dchars(tag, first, last=None) tk_send 'dchars', tagid(tag), first, last self end def delete(*args) if TkcItem::CItemID_TBL[self.path] find('withtag', *args).each{|item| TkcItem::CItemID_TBL[self.path].delete(item.id) } end tk_send 'delete', *args.collect{|t| tagid(t)} self end alias remove delete def dtag(tag, tag_to_del=None) tk_send 'dtag', tagid(tag), tag_to_del self end def find(mode, *args) list(tk_send('find', mode, *args)).collect!{|id| TkcItem.id2obj(self, id) } end def find_above(target) find('above', tagid(target)) end def find_all find('all') end def find_below(target) find('below', tagid(target)) end def find_closest(x, y, halo=None, start=None) find('closest', x, y, halo, start) end def find_enclosed(x1, y1, x2, y2) find('enclosed', x1, y1, x2, y2) end def find_overlapping(x1, y1, x2, y2) find('overlapping', x1, y1, x2, y2) end def find_withtag(tag) find('withtag', tag) end def itemfocus(tagOrId=nil) if tagOrId tk_send 'focus', tagid(tagOrId) self else ret = tk_send('focus') if ret == "" nil else TkcItem.id2obj(self, ret) end end end def gettags(tagOrId) list(tk_send('gettags', tagid(tagOrId))).collect{|tag| TkcTag.id2obj(self, tag) } end def icursor(tagOrId, index) tk_send 'icursor', tagid(tagOrId), index self end def index(tagOrId, index) number(tk_send('index', tagid(tagOrId), index)) end def insert(tagOrId, index, string) tk_send 'insert', tagid(tagOrId), index, string self end def itemcget(tagOrId, option) case option.to_s when 'dash', 'activedash', 'disableddash' conf = tk_send('itemcget', tagid(tagOrId), "-#{option}") if conf =~ /^[0-9]/ list(conf) else conf end when 'text', 'label', 'show', 'data', 'file', 'maskdata', 'maskfile' tk_send 'itemcget', tagid(tagOrId), "-#{option}" when 'font', 'kanjifont' #fnt = tk_tcl2ruby(tk_send('itemcget', tagid(tagOrId), "-#{option}")) fnt = tk_tcl2ruby(tk_send('itemcget', tagid(tagOrId), '-font')) unless fnt.kind_of?(TkFont) fnt = tagfontobj(tagid(tagOrId), fnt) end if option.to_s == 'kanjifont' && JAPANIZED_TK && TK_VERSION =~ /^4\.*/ # obsolete; just for compatibility fnt.kanji_font else fnt end else tk_tcl2ruby tk_send('itemcget', tagid(tagOrId), "-#{option}") end end def itemconfigure(tagOrId, key, value=None) if key.kind_of? Hash key = _symbolkey2str(key) if ( key['font'] || key['kanjifont'] \ || key['latinfont'] || key['asciifont'] ) tagfont_configure(tagid(tagOrId), key.dup) else tk_send 'itemconfigure', tagid(tagOrId), *hash_kv(key) end else if ( key == 'font' || key == :font || key == 'kanjifont' || key == :kanjifont || key == 'latinfont' || key == :latinfont || key == 'asciifont' || key == :asciifont ) if value == None tagfontobj(tagid(tagOrId)) else tagfont_configure(tagid(tagOrId), {key=>value}) end else tk_send 'itemconfigure', tagid(tagOrId), "-#{key}", value end end self end # def itemconfigure(tagOrId, key, value=None) # if key.kind_of? Hash # tk_send 'itemconfigure', tagid(tagOrId), *hash_kv(key) # else # tk_send 'itemconfigure', tagid(tagOrId), "-#{key}", value # end # end # def itemconfigure(tagOrId, keys) # tk_send 'itemconfigure', tagid(tagOrId), *hash_kv(keys) # end def itemconfiginfo(tagOrId, key=nil) if key case key.to_s when 'dash', 'activedash', 'disableddash' conf = tk_split_simplelist(tk_send('itemconfigure', tagid(tagOrId), "-#{key}")) if conf[3] && conf[3] =~ /^[0-9]/ conf[3] = list(conf[3]) end if conf[4] && conf[4] =~ /^[0-9]/ conf[4] = list(conf[4]) end when 'text', 'label', 'show', 'data', 'file', 'maskdata', 'maskfile' conf = tk_split_simplelist(tk_send('itemconfigure', tagid(tagOrId), "-#{key}")) when 'font', 'kanjifont' conf = tk_split_simplelist(tk_send('itemconfigure', tagid(tagOrId),"-#{key}") ) conf[4] = tagfont_configinfo(tagid(tagOrId), conf[4]) else conf = tk_split_list(tk_send('itemconfigure', tagid(tagOrId), "-#{key}")) end conf[0] = conf[0][1..-1] conf else ret = tk_split_simplelist(tk_send('itemconfigure', tagid(tagOrId))).collect{|conflist| conf = tk_split_simplelist(conflist) conf[0] = conf[0][1..-1] case conf[0] when 'text', 'label', 'show', 'data', 'file', 'maskdata', 'maskfile' when 'dash', 'activedash', 'disableddash' if conf[3] && conf[3] =~ /^[0-9]/ conf[3] = list(conf[3]) end if conf[4] && conf[4] =~ /^[0-9]/ conf[4] = list(conf[4]) end else if conf[3] if conf[3].index('{') conf[3] = tk_split_list(conf[3]) else conf[3] = tk_tcl2ruby(conf[3]) end end if conf[4] if conf[4].index('{') conf[4] = tk_split_list(conf[4]) else conf[4] = tk_tcl2ruby(conf[4]) end end end conf } fontconf = ret.assoc('font') if fontconf ret.delete_if{|item| item[0] == 'font' || item[0] == 'kanjifont'} fontconf[4] = tagfont_configinfo(tagid(tagOrId), fontconf[4]) ret.push(fontconf) else ret end end end def lower(tag, below=None) tk_send 'lower', tagid(tag), tagid(below) self end def move(tag, x, y) tk_send 'move', tagid(tag), x, y self end def postscript(keys) tk_send "postscript", *hash_kv(keys) end def raise(tag, above=None) tk_send 'raise', tagid(tag), tagid(above) self end def scale(tag, x, y, xs, ys) tk_send 'scale', tagid(tag), x, y, xs, ys self end def scan_mark(x, y) tk_send 'scan', 'mark', x, y self end def scan_dragto(x, y) tk_send 'scan', 'dragto', x, y self end def select(mode, *args) r = tk_send('select', mode, *args) (mode == 'item')? TkcItem.id2obj(self, r): self end def select_adjust(tagOrId, index) select('adjust', tagid(tagOrId), index) end def select_clear select('clear') end def select_from(tagOrId, index) select('from', tagid(tagOrId), index) end def select_item select('item') end def select_to(tagOrId, index) select('to', tagid(tagOrId), index) end def itemtype(tag) TkcItem.type2class(tk_send('type', tagid(tag))) end end module TkcTagAccess include TkComm include TkTreatTagFont def addtag(tag) @c.addtag(tag, 'with', @id) self end def bbox @c.bbox(@id) end def bind(seq, cmd=Proc.new, args=nil) @c.itembind @id, seq, cmd, args self end def bind_append(seq, cmd=Proc.new, args=nil) @c.itembind_append @id, seq, cmd, args self end def bind_remove(seq) @c.itembind_remove @id, seq self end def bindinfo(seq=nil) @c.itembindinfo @id, seq end def cget(option) @c.itemcget @id, option end def configure(key, value=None) @c.itemconfigure @id, key, value self end # def configure(keys) # @c.itemconfigure @id, keys # end def configinfo(key=nil) @c.itemconfiginfo @id, key end def coords(*args) @c.coords @id, *args end def dchars(first, last=None) @c.dchars @id, first, last self end def dtag(tag_to_del=None) @c.dtag @id, tag_to_del self end def find @c.find 'withtag', @id end alias list find def focus @c.itemfocus @id end def gettags @c.gettags @id end def icursor(index) @c.icursor @id, index self end def index(index) @c.index @id, index end def insert(beforethis, string) @c.insert @id, beforethis, string self end def lower(belowthis=None) @c.lower @id, belowthis self end def move(xamount, yamount) @c.move @id, xamount, yamount self end def raise(abovethis=None) @c.raise @id, abovethis self end def scale(xorigin, yorigin, xscale, yscale) @c.scale @id, xorigin, yorigin, xscale, yscale self end def select_adjust(index) @c.select('adjust', @id, index) self end def select_from(index) @c.select('from', @id, index) self end def select_to(index) @c.select('to', @id, index) self end def itemtype @c.itemtype @id end # Following operators support logical expressions of canvas tags # (for Tk8.3+). # If tag1.path is 't1' and tag2.path is 't2', then # ltag = tag1 & tag2; ltag.path => "(t1)&&(t2)" # ltag = tag1 | tag2; ltag.path => "(t1)||(t2)" # ltag = tag1 ^ tag2; ltag.path => "(t1)^(t2)" # ltag = - tag1; ltag.path => "!(t1)" def & (tag) if tag.kind_of? TkObject TkcTagString.new(@c, '(' + @id + ')&&(' + tag.path + ')') else TkcTagString.new(@c, '(' + @id + ')&&(' + tag.to_s + ')') end end def | (tag) if tag.kind_of? TkObject TkcTagString.new(@c, '(' + @id + ')||(' + tag.path + ')') else TkcTagString.new(@c, '(' + @id + ')||(' + tag.to_s + ')') end end def ^ (tag) if tag.kind_of? TkObject TkcTagString.new(@c, '(' + @id + ')^(' + tag.path + ')') else TkcTagString.new(@c, '(' + @id + ')^(' + tag.to_s + ')') end end def -@ TkcTagString.new(@c, '!(' + @id + ')') end end class TkcTag<TkObject include TkcTagAccess CTagID_TBL = TkCore::INTERP.create_table Tk_CanvasTag_ID = ['ctag'.freeze, '00000'.taint].freeze TkCore::INTERP.init_ip_env{ CTagID_TBL.clear } def TkcTag.id2obj(canvas, id) cpath = canvas.path return id unless CTagID_TBL[cpath] CTagID_TBL[cpath][id]? CTagID_TBL[cpath][id]: id end def initialize(parent, mode=nil, *args) if not parent.kind_of?(TkCanvas) fail Kernel.format("%s need to be TkCanvas", parent.inspect) end @c = parent @cpath = parent.path @path = @id = Tk_CanvasTag_ID.join CTagID_TBL[@cpath] = {} unless CTagID_TBL[@cpath] CTagID_TBL[@cpath][@id] = self Tk_CanvasTag_ID[1].succ! if mode tk_call @c.path, "addtag", @id, mode, *args end end def id @id end def delete @c.delete @id CTagID_TBL[@cpath].delete(@id) if CTagID_TBL[@cpath] self end alias remove delete alias destroy delete def set_to_above(target) @c.addtag_above(@id, target) self end alias above set_to_above def set_to_all @c.addtag_all(@id) self end alias all set_to_all def set_to_below(target) @c.addtag_below(@id, target) self end alias below set_to_below def set_to_closest(x, y, halo=None, start=None) @c.addtag_closest(@id, x, y, halo, start) self end alias closest set_to_closest def set_to_enclosed(x1, y1, x2, y2) @c.addtag_enclosed(@id, x1, y1, x2, y2) self end alias enclosed set_to_enclosed def set_to_overlapping(x1, y1, x2, y2) @c.addtag_overlapping(@id, x1, y1, x2, y2) self end alias overlapping set_to_overlapping def set_to_withtag(target) @c.addtag_withtag(@id, target) self end alias withtag set_to_withtag end class TkcTagString<TkcTag def self.new(parent, name, *args) if CTagID_TBL[parent.path] && CTagID_TBL[parent.path][name] return CTagID_TBL[parent.path][name] else super(parent, name, *args) end end def initialize(parent, name, mode=nil, *args) if not parent.kind_of?(TkCanvas) fail Kernel.format("%s need to be TkCanvas", parent.inspect) end @c = parent @cpath = parent.path @path = @id = name CTagID_TBL[@cpath] = {} unless CTagID_TBL[@cpath] CTagID_TBL[@cpath][@id] = self if mode tk_call @c.path, "addtag", @id, mode, *args end end end TkcNamedTag = TkcTagString class TkcTagAll<TkcTag def initialize(parent) if not parent.kind_of?(TkCanvas) fail Kernel.format("%s need to be TkCanvas", parent.inspect) end @c = parent @cpath = parent.path @path = @id = 'all' CTagID_TBL[@cpath] = {} unless CTagID_TBL[@cpath] CTagID_TBL[@cpath][@id] = self end end class TkcTagCurrent<TkcTag def initialize(parent) if not parent.kind_of?(TkCanvas) fail Kernel.format("%s need to be TkCanvas", parent.inspect) end @c = parent @cpath = parent.path @path = @id = 'current' CTagID_TBL[@cpath] = {} unless CTagID_TBL[@cpath] CTagID_TBL[@cpath][@id] = self end end class TkcGroup<TkcTag Tk_cGroup_ID = ['tkcg'.freeze, '00000'.taint].freeze def create_self(parent, *args) if not parent.kind_of?(TkCanvas) fail Kernel.format("%s need to be TkCanvas", parent.inspect) end @c = parent @cpath = parent.path @path = @id = Tk_cGroup_ID.join CTagID_TBL[@cpath] = {} unless CTagID_TBL[@cpath] CTagID_TBL[@cpath][@id] = self Tk_cGroup_ID[1].succ! add(*args) if args != [] end private :create_self def include(*tags) for i in tags i.addtag @id end self end def exclude(*tags) for i in tags i.delete @id end self end end class TkcItem<TkObject include TkcTagAccess CItemTypeToClass = {} CItemID_TBL = TkCore::INTERP.create_table TkCore::INTERP.init_ip_env{ CItemID_TBL.clear } def TkcItem.type2class(type) CItemTypeToClass[type] end def TkcItem.id2obj(canvas, id) cpath = canvas.path return id unless CItemID_TBL[cpath] CItemID_TBL[cpath][id]? CItemID_TBL[cpath][id]: id end def initialize(parent, *args) if not parent.kind_of?(TkCanvas) fail Kernel.format("%s need to be TkCanvas", parent.inspect) end @parent = @c = parent @path = parent.path fontkeys = {} if args.size == 1 && args[0].kind_of?(Hash) args[0] = _symbolkey2str(args[0]) coords = args[0].delete('coords') if not coords.kind_of?(Array) fail "coords parameter must be given by an Array" end args[0,0] = coords.flatten end if args[-1].kind_of? Hash keys = _symbolkey2str(args.pop) ['font', 'kanjifont', 'latinfont', 'asciifont'].each{|key| fontkeys[key] = keys.delete(key) if keys.key?(key) } args += hash_kv(keys) end @id = create_self(*args).to_i ;# 'canvas item id' is integer number CItemID_TBL[@path] = {} unless CItemID_TBL[@path] CItemID_TBL[@path][@id] = self configure(fontkeys) unless fontkeys.empty? ######## old version # if args[-1].kind_of? Hash # keys = args.pop # end # @id = create_self(*args).to_i ;# 'canvas item id' is integer number # CItemID_TBL[@path] = {} unless CItemID_TBL[@path] # CItemID_TBL[@path][@id] = self # if keys # # tk_call @path, 'itemconfigure', @id, *hash_kv(keys) # configure(keys) if keys # end ######## end def create_self(*args); end private :create_self def id @id end def delete @c.delete @id CItemID_TBL[@path].delete(@id) if CItemID_TBL[@path] self end alias remove delete alias destroy delete end class TkcArc<TkcItem CItemTypeToClass['arc'] = self def create_self(*args) tk_call(@path, 'create', 'arc', *args) end private :create_self end class TkcBitmap<TkcItem CItemTypeToClass['bitmap'] = self def create_self(*args) tk_call(@path, 'create', 'bitmap', *args) end private :create_self end class TkcImage<TkcItem CItemTypeToClass['image'] = self def create_self(*args) tk_call(@path, 'create', 'image', *args) end private :create_self end class TkcLine<TkcItem CItemTypeToClass['line'] = self def create_self(*args) tk_call(@path, 'create', 'line', *args) end private :create_self end class TkcOval<TkcItem CItemTypeToClass['oval'] = self def create_self(*args) tk_call(@path, 'create', 'oval', *args) end private :create_self end class TkcPolygon<TkcItem CItemTypeToClass['polygon'] = self def create_self(*args) tk_call(@path, 'create', 'polygon', *args) end private :create_self end class TkcRectangle<TkcItem CItemTypeToClass['rectangle'] = self def create_self(*args) tk_call(@path, 'create', 'rectangle', *args) end private :create_self end class TkcText<TkcItem CItemTypeToClass['text'] = self def create_self(*args) tk_call(@path, 'create', 'text', *args) end private :create_self end class TkcWindow<TkcItem CItemTypeToClass['window'] = self def create_self(*args) tk_call(@path, 'create', 'window', *args) end private :create_self end class TkImage<TkObject include Tk TkCommandNames = ['image'.freeze].freeze Tk_IMGTBL = TkCore::INTERP.create_table Tk_Image_ID = ['i'.freeze, '00000'.taint].freeze TkCore::INTERP.init_ip_env{ Tk_IMGTBL.clear } def initialize(keys=nil) @path = Tk_Image_ID.join Tk_Image_ID[1].succ! tk_call 'image', 'create', @type, @path, *hash_kv(keys) Tk_IMGTBL[@path] = self end def delete Tk_IMGTBL.delete(@id) if @id tk_call('image', 'delete', @path) self end def height number(tk_call('image', 'height', @path)) end def inuse bool(tk_call('image', 'inuse', @path)) end def itemtype tk_call('image', 'type', @path) end def width number(tk_call('image', 'width', @path)) end def TkImage.names Tk.tk_call('image', 'names').split.collect!{|id| (Tk_IMGTBL[id])? Tk_IMGTBL[id] : id } end def TkImage.types Tk.tk_call('image', 'types').split end end class TkBitmapImage<TkImage def initialize(*args) @type = 'bitmap' super end end class TkPhotoImage<TkImage def initialize(*args) @type = 'photo' super end def blank tk_send 'blank' self end def cget(option) case option.to_s when 'data', 'file' tk_send 'cget', option else tk_tcl2ruby tk_send('cget', option) end end def copy(source, *opts) args = opts.collect{|term| if term.kind_of?(String) && term.include?(?\s) term.split else term end }.flatten tk_send 'copy', source, *args self end def data(keys=nil) tk_send('data', *hash_kv(keys)) end def get(x, y) tk_send('get', x, y).split.collect{|n| n.to_i} end def put(data, *to) if to == [] tk_send 'put', data else tk_send 'put', data, '-to', *to end self end def read(file, *opts) args = opts.collect{|term| if term.kind_of?(String) && term.include?(?\s) term.split else term end }.flatten tk_send 'read', file, *args self end def redither tk_send 'redither' self end def get_transparency(x, y) bool(tk_send('transparency', 'get', x, y)) end def set_transparency(x, y, st) tk_send('transparency', 'set', x, y, st) self end def write(file, *opts) args = opts.collect{|term| if term.kind_of?(String) && term.include?(?\s) term.split else term end }.flatten tk_send 'write', file, *args self end end