1#
2#  tk/validation.rb - validation support module for entry, spinbox, and so on
3#
4require 'tk'
5
6module Tk
7  module ValidateConfigure
8    def self.__def_validcmd(scope, klass, keys=nil)
9      keys = klass._config_keys unless keys
10      keys.each{|key|
11        eval("def #{key}(*args, &b)
12                 __validcmd_call(#{klass.name}, '#{key}', *args, &b)
13              end", scope)
14      }
15    end
16
17    def __validcmd_call(klass, key, *args, &b)
18      return cget(key) if args.empty? && !b
19
20      cmd = (b)? proc(&b) : args.shift
21
22      if cmd.kind_of?(klass)
23        configure(key, cmd)
24      elsif !args.empty?
25        configure(key, [cmd, args])
26      else
27        configure(key, cmd)
28      end
29    end
30
31    def __validation_class_list
32      # maybe need to override
33      []
34    end
35
36    def __get_validate_key2class
37      k2c = {}
38      __validation_class_list.each{|klass|
39        klass._config_keys.each{|key|
40          k2c[key.to_s] = klass
41        }
42      }
43      k2c
44    end
45
46    def __conv_vcmd_on_hash_kv(keys)
47      key2class = __get_validate_key2class
48
49      keys = _symbolkey2str(keys)
50      key2class.each{|key, klass|
51        if keys[key].kind_of?(Array)
52          cmd, *args = keys[key]
53          #keys[key] = klass.new(cmd, args.join(' '))
54          keys[key] = klass.new(cmd, *args)
55        # elsif keys[key].kind_of?(Proc) ||  keys[key].kind_of?(Method)
56        elsif TkComm._callback_entry?(keys[key])
57          keys[key] = klass.new(keys[key])
58        end
59      }
60      keys
61    end
62
63    def create_self(keys)
64      super(__conv_vcmd_on_hash_kv(keys))
65    end
66    private :create_self
67
68    def configure(slot, value=TkComm::None)
69      if slot.kind_of?(Hash)
70        super(__conv_vcmd_on_hash_kv(slot))
71      else
72        super(__conv_vcmd_on_hash_kv(slot=>value))
73      end
74      self
75    end
76=begin
77    def configure(slot, value=TkComm::None)
78      key2class = __get_validate_key2class
79
80      if slot.kind_of?(Hash)
81        slot = _symbolkey2str(slot)
82        key2class.each{|key, klass|
83          if slot[key].kind_of?(Array)
84            cmd, *args = slot[key]
85            slot[key] = klass.new(cmd, args.join(' '))
86          elsif slot[key].kind_of?(Proc) || slot[key].kind_of?(Method)
87            slot[key] = klass.new(slot[key])
88          end
89        }
90        super(slot)
91
92      else
93        slot = slot.to_s
94        if (klass = key2class[slot])
95          if value.kind_of?(Array)
96            cmd, *args = value
97            value = klass.new(cmd, args.join(' '))
98          elsif value.kind_of?(Proc) || value.kind_of?(Method)
99            value = klass.new(value)
100          end
101        end
102        super(slot, value)
103      end
104
105      self
106    end
107=end
108  end
109
110  module ItemValidateConfigure
111    def self.__def_validcmd(scope, klass, keys=nil)
112      keys = klass._config_keys unless keys
113      keys.each{|key|
114        eval("def item_#{key}(id, *args, &b)
115                 __item_validcmd_call(#{klass.name}, '#{key}', id, *args, &b)
116              end", scope)
117      }
118    end
119
120    def __item_validcmd_call(tagOrId, klass, key, *args, &b)
121      return itemcget(tagid(tagOrId), key) if args.empty? && !b
122
123      cmd = (b)? proc(&b) : args.shift
124
125      if cmd.kind_of?(klass)
126        itemconfigure(tagid(tagOrId), key, cmd)
127      elsif !args.empty?
128        itemconfigure(tagid(tagOrId), key, [cmd, args])
129      else
130        itemconfigure(tagid(tagOrId), key, cmd)
131      end
132    end
133
134    def __item_validation_class_list(id)
135      # maybe need to override
136      []
137    end
138
139    def __get_item_validate_key2class(id)
140      k2c = {}
141      __item_validation_class_list(id).each{|klass|
142        klass._config_keys.each{|key|
143          k2c[key.to_s] = klass
144        }
145      }
146    end
147
148    def __conv_item_vcmd_on_hash_kv(keys)
149      key2class = __get_item_validate_key2class(tagid(tagOrId))
150
151      keys = _symbolkey2str(keys)
152      key2class.each{|key, klass|
153        if keys[key].kind_of?(Array)
154          cmd, *args = keys[key]
155          #keys[key] = klass.new(cmd, args.join(' '))
156          keys[key] = klass.new(cmd, *args)
157        # elsif keys[key].kind_of?(Proc) || keys[key].kind_of?(Method)
158        elsif TkComm._callback_entry?(keys[key])
159          keys[key] = klass.new(keys[key])
160        end
161      }
162      keys
163    end
164
165    def itemconfigure(tagOrId, slot, value=TkComm::None)
166      if slot.kind_of?(Hash)
167        super(__conv_item_vcmd_on_hash_kv(slot))
168      else
169        super(__conv_item_vcmd_on_hash_kv(slot=>value))
170      end
171      self
172    end
173=begin
174    def itemconfigure(tagOrId, slot, value=TkComm::None)
175      key2class = __get_item_validate_key2class(tagid(tagOrId))
176
177      if slot.kind_of?(Hash)
178        slot = _symbolkey2str(slot)
179        key2class.each{|key, klass|
180          if slot[key].kind_of?(Array)
181            cmd, *args = slot[key]
182            slot[key] = klass.new(cmd, args.join(' '))
183          elsif slot[key].kind_of?(Proc) ||  slot[key].kind_of?(Method)
184            slot[key] = klass.new(slot[key])
185          end
186        }
187        super(slot)
188
189      else
190        slot = slot.to_s
191        if (klass = key2class[slot])
192          if value.kind_of?(Array)
193            cmd, *args = value
194            value = klass.new(cmd, args.join(' '))
195          elsif value.kind_of?(Proc) || value.kind_of?(Method)
196            value = klass.new(value)
197          end
198        end
199        super(slot, value)
200      end
201
202      self
203    end
204=end
205  end
206end
207
208class TkValidateCommand
209  include TkComm
210  extend  TkComm
211
212  class ValidateArgs < TkUtil::CallbackSubst
213    KEY_TBL = [
214      [ ?d, ?n, :action ],
215      [ ?i, ?x, :index ],
216      [ ?s, ?e, :current ],
217      [ ?v, ?s, :type ],
218      [ ?P, ?e, :value ],
219      [ ?S, ?e, :string ],
220      [ ?V, ?s, :triggered ],
221      [ ?W, ?w, :widget ],
222      nil
223    ]
224
225    PROC_TBL = [
226      [ ?n, TkComm.method(:number) ],
227      [ ?s, TkComm.method(:string) ],
228      [ ?w, TkComm.method(:window) ],
229
230      [ ?e, proc{|val|
231          #enc = Tk.encoding
232          enc = ((Tk.encoding)? Tk.encoding : Tk.encoding_system)
233          if enc
234            Tk.fromUTF8(TkComm::string(val), enc)
235          else
236            TkComm::string(val)
237          end
238        }
239      ],
240
241      [ ?x, proc{|val|
242          idx = TkComm::number(val)
243          if idx < 0
244            nil
245          else
246            idx
247          end
248        }
249      ],
250
251      nil
252    ]
253
254=begin
255    # for Ruby m17n :: ?x --> String --> char-code ( getbyte(0) )
256    KEY_TBL.map!{|inf|
257      if inf.kind_of?(Array)
258        inf[0] = inf[0].getbyte(0) if inf[0].kind_of?(String)
259        inf[1] = inf[1].getbyte(0) if inf[1].kind_of?(String)
260      end
261      inf
262    }
263
264    PROC_TBL.map!{|inf|
265      if inf.kind_of?(Array)
266        inf[0] = inf[0].getbyte(0) if inf[0].kind_of?(String)
267      end
268      inf
269    }
270=end
271
272    _setup_subst_table(KEY_TBL, PROC_TBL);
273
274    #
275    # NOTE: The order of parameters which passed to callback procedure is
276    #        <extra_arg>, <extra_arg>, ... , <subst_arg>, <subst_arg>, ...
277    #
278
279    #def self._get_extra_args_tbl
280    #  # return an array of convert procs
281    #  []
282    #end
283
284    def self.ret_val(val)
285      (val)? '1': '0'
286    end
287  end
288
289  ###############################################
290
291  def self._config_keys
292    # array of config-option key (string or symbol)
293    ['vcmd', 'validatecommand', 'invcmd', 'invalidcommand']
294  end
295
296  def _initialize_for_cb_class(klass, cmd = Proc.new, *args)
297    extra_args_tbl = klass._get_extra_args_tbl
298
299    if args.compact.size > 0
300      args.map!{|arg| klass._sym2subst(arg)}
301      args = args.join(' ')
302      keys = klass._get_subst_key(args)
303      if cmd.kind_of?(String)
304        id = cmd
305      elsif cmd.kind_of?(TkCallbackEntry)
306        @id = install_cmd(cmd)
307      else
308        @id = install_cmd(proc{|*arg|
309             ex_args = []
310             extra_args_tbl.reverse_each{|conv| ex_args << conv.call(arg.pop)}
311             klass.ret_val(cmd.call(
312               *(ex_args.concat(klass.scan_args(keys, arg)))
313             ))
314        }) + ' ' + args
315      end
316    else
317      keys, args = klass._get_all_subst_keys
318      if cmd.kind_of?(String)
319        id = cmd
320      elsif cmd.kind_of?(TkCallbackEntry)
321        @id = install_cmd(cmd)
322      else
323        @id = install_cmd(proc{|*arg|
324             ex_args = []
325             extra_args_tbl.reverse_each{|conv| ex_args << conv.call(arg.pop)}
326             klass.ret_val(cmd.call(
327               *(ex_args << klass.new(*klass.scan_args(keys, arg)))
328             ))
329        }) + ' ' + args
330      end
331    end
332  end
333
334  def initialize(cmd = Proc.new, *args)
335    _initialize_for_cb_class(self.class::ValidateArgs, cmd, *args)
336  end
337
338  def to_eval
339    @id
340  end
341end
342
343module TkValidation
344  include Tk::ValidateConfigure
345
346  class ValidateCmd < TkValidateCommand
347    module Action
348      Insert = 1
349      Delete = 0
350      Others = -1
351      Focus  = -1
352      Forced = -1
353      Textvariable = -1
354      TextVariable = -1
355    end
356  end
357
358  #####################################
359
360  def __validation_class_list
361    super() << ValidateCmd
362  end
363
364  Tk::ValidateConfigure.__def_validcmd(binding, ValidateCmd)
365
366=begin
367  def validatecommand(cmd = Proc.new, args = nil)
368    if cmd.kind_of?(ValidateCmd)
369      configure('validatecommand', cmd)
370    elsif args
371      configure('validatecommand', [cmd, args])
372    else
373      configure('validatecommand', cmd)
374    end
375  end
376=end
377#  def validatecommand(*args, &b)
378#    __validcmd_call(ValidateCmd, 'validatecommand', *args, &b)
379#  end
380#  alias vcmd validatecommand
381
382=begin
383  def invalidcommand(cmd = Proc.new, args = nil)
384    if cmd.kind_of?(ValidateCmd)
385      configure('invalidcommand', cmd)
386    elsif args
387      configure('invalidcommand', [cmd, args])
388    else
389      configure('invalidcommand', cmd)
390    end
391  end
392=end
393#  def invalidcommand(*args, &b)
394#    __validcmd_call(ValidateCmd, 'invalidcommand', *args, &b)
395#  end
396#  alias invcmd invalidcommand
397end
398