tkar 0.63

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,109 @@
1
+ require 'enumerator'
2
+
3
+ module Tkar
4
+ class Error < StandardError; end
5
+ module Recoverable; end
6
+
7
+ RADIANS_TO_DEGREES = 180.0 / Math::PI
8
+ DEGREES_TO_RADIANS = Math::PI / 180.0
9
+
10
+ require 'tkar/window'
11
+ require 'tkar/stream'
12
+
13
+ def self.run(argv, opts)
14
+ verbose = opts["v"]
15
+ title = argv.empty? ? "stdin" : argv.join(":")
16
+ root = TkRoot.new { title "Tkar @ #{title}"; iconname "Tkar" }
17
+ root.resizable true, true
18
+ window = Window.new(root, opts)
19
+ canvas = window.canvas
20
+
21
+ thread = Thread.new do
22
+ movie = false
23
+ if movie
24
+ win_id = root.winfo_id
25
+ pid = Process.pid
26
+ end
27
+
28
+ persist = opts["persist"]
29
+
30
+ f = MessageStream.for(argv, opts)
31
+ window.def_message_out do |msg|
32
+ begin
33
+ f.put_msg msg if f
34
+ $stderr.puts msg if verbose
35
+ rescue MessageStream::StreamClosed
36
+ rescue Errno::ECONNABORTED, Errno::ECONNRESET
37
+ raise unless persist
38
+ end
39
+ end
40
+
41
+ cmd = args = nil
42
+
43
+ update_count = 0
44
+ start_time = Time.now
45
+
46
+ j = 0
47
+ begin
48
+ loop do
49
+ cmd, *args = f.get_cmd
50
+ break if cmd == "done"
51
+ exit if cmd == "exit"
52
+ if cmd == "update"
53
+ f.put_msg "update" ## optional?
54
+ update_count += 1
55
+ if verbose and update_count % 100 == 0
56
+ rate = update_count / (Time.now - start_time)
57
+ $stderr.puts "update rate: %10.2f updates per second" % rate
58
+ update_count = 0
59
+ start_time = Time.now
60
+ end
61
+ end
62
+ r = canvas.send(cmd, *args)
63
+ # f.put_msg return_value if return_value ?
64
+
65
+ if movie and cmd == "update"
66
+ # add a frame to the movie
67
+ mcmd =
68
+ "import -window #{win_id} \"tmp/#{pid}_frame_%05d.miff\"" % j
69
+ j += 1
70
+ rslt = system(mcmd)
71
+ unless rslt
72
+ puts $?
73
+ exit
74
+ end
75
+ end
76
+ end
77
+ rescue MessageStream::StreamClosed
78
+ f.put_msg "stream closed"
79
+ exit unless persist
80
+ canvas.update
81
+ rescue Recoverable => ex
82
+ f.put_msg ex
83
+ retry
84
+ rescue Errno::ECONNABORTED, Errno::ECONNRESET
85
+ raise unless persist
86
+ rescue => ex
87
+ begin
88
+ f.put_msg ex
89
+ f.put_msg "exiting"
90
+ rescue Errno::ECONNABORTED, Errno::ECONNRESET
91
+ end
92
+ $stderr.puts "#{ex.class}: #{ex.message}", ex.backtrace.join("\n ")
93
+ raise unless persist
94
+ end
95
+ end
96
+
97
+ # if /mswin32/ =~ RUBY_PLATFORM
98
+ # 3.times do
99
+ # timer = TkTimer.new(1) do
100
+ # 3.times {thread.run}
101
+ # end
102
+ #
103
+ # timer.start
104
+ # end
105
+ # end
106
+
107
+ Tk.mainloop
108
+ end
109
+ end
@@ -0,0 +1,214 @@
1
+ # A slim command-line parser that does one thing well: turn an array of
2
+ # strings, such as ARGV, into a hash of recognized options and their
3
+ # arguments, leaving unrecognized strings in the original array.
4
+ #
5
+ # Argos was Odysseus' faithful dog, who was good at recognizing ;)
6
+ #
7
+ # Synopsis:
8
+ #
9
+ # require 'argos'
10
+ #
11
+ # optdef = {
12
+ # "v" => true,
13
+ # "n" => proc {|arg| Integer(arg)}
14
+ # }
15
+ #
16
+ # argv = %w{-v -n10 filename}
17
+ # opts = Argos.parse_options(argv, optdef)
18
+ # p opts # ==> {"v"=>true, "n"=>10}
19
+ # p argv # ==> ["filename"]
20
+ #
21
+ # Features:
22
+ #
23
+ # - Operates on ARGV or any given array of strings.
24
+ #
25
+ # - Output is a hash of {option => value, ...}.
26
+ #
27
+ # - You can merge this hash on top of a hash of defaults if you want.
28
+ #
29
+ # - Supports both long ("--foo") and short ("-f") options.
30
+ #
31
+ # - A long option with an argument is --foo=bar or --foo bar.
32
+ #
33
+ # - A short option with an argument is -fbar or -f bar.
34
+ #
35
+ # - The options -x and --x are synonymous.
36
+ #
37
+ # - Short options with no args can be combined as -xyz in place of -x -y -z.
38
+ #
39
+ # - If -z takes an argument, then -xyz foo is same as -x -y -z foo.
40
+ #
41
+ # - The string "--" terminates option parsing, leaving the rest untouched.
42
+ #
43
+ # - The string "-" is not considered an option.
44
+ #
45
+ # - ARGV (or other given array) is modified: it has all parsed options
46
+ # and arguments removed, so you can use ARGF to treat the rest as input files.
47
+ #
48
+ # - Unrecognized arguments are left in the argument array. You can catch them
49
+ # with grep(/^-./), in case you want to pass them on to another program or
50
+ # warn the user.
51
+ #
52
+ # - Argument validation and conversion are in terms of an option definition
53
+ # hash, which specifies which options are allowed, the number of arguments
54
+ # for each (0 or 1), and how to generate the value from the argument, if any.
55
+ #
56
+ # - Repetition of args ("-v -v", or "-vv") can be handled by closures. See
57
+ # the example below.
58
+ #
59
+ # - Everything is ducky. For example, handlers only need an #arity method
60
+ # and a #[] method to be recognized as callable. Otherwise they are treated
61
+ # as static objects.
62
+ #
63
+ # Limitations:
64
+ #
65
+ # - A particular option takes either 0 args or 1 arg. There are no optional
66
+ # arguments, in the sense of both "-x" and "-x3" being accepted.
67
+ #
68
+ # - Options lose their ordering in the output hash (but they are parsed in
69
+ # order and you can keep track using state in the handler closures).
70
+ #
71
+ # - There is no usage/help output.
72
+ #
73
+ # Copyright (C) 2006-2009 Joel VanderWerf, mailto:vjoel@users.sourceforge.net.
74
+ #
75
+ # License is the Ruby license. See http://www.ruby-lang.org.
76
+ #
77
+ module Argos
78
+ module_function
79
+
80
+ # Raised (a) when an option that takes an argument occurs at the end of the
81
+ # argv list, with no argument following it, or (b) when a handler barfs.
82
+ class OptionError < ArgumentError; end
83
+
84
+ # Called when an option that takes an argument occurs at the end of the
85
+ # argv list, with no argument following it.
86
+ def argument_missing opt
87
+ raise OptionError, "#{opt}: no argument provided."
88
+ end
89
+
90
+ def handle opt, handler, *args # :nodoc
91
+ args.empty? ? handler[] : handler[args[0]]
92
+ rescue => ex
93
+ raise OptionError, "#{opt}: #{ex}"
94
+ end
95
+
96
+ # Returns the hash of parsed options and argument values. The +argv+ array
97
+ # is modified: every recognized option and argument is deleted.
98
+ #
99
+ # The +optdef+ hash defines the options and their arguments.
100
+ #
101
+ # Each key is an option name (without "-" chars).
102
+ #
103
+ # The value for a key in +optdef+
104
+ # is used to generate the value for the same key in the options hash
105
+ # returned by this method.
106
+ #
107
+ # If the value has an #arity method and arity > 0, the value is considered to
108
+ # be a handler; it is called with the argument string to return the value
109
+ # associated with the option in the hash returned by the method.
110
+ #
111
+ # If the arity <= 0, the value is considered to be a handler for an option
112
+ # without arguments; it is called with no arguments to return the value of
113
+ # the option.
114
+ #
115
+ # If there is no arity method, the object itself is used as the value of
116
+ # the option.
117
+ #
118
+ # Only one kind of input will cause an exception (not counting exceptions
119
+ # raised by handler code or by bugs):
120
+ #
121
+ # - An option is found at the end of the list, and it requires an argument.
122
+ # This results in a call to #argument_missing, which by default raises
123
+ # OptionError.
124
+ #
125
+ def parse_options argv, optdef
126
+ orig = argv.dup; argv.clear
127
+ opts = {}
128
+
129
+ loop do
130
+ case (argstr=orig.shift)
131
+ when nil, "--"
132
+ argv.concat orig
133
+ break
134
+
135
+ when /^(--)([^=]+)=(.*)/, /^(-)([^-])(.+)/
136
+ short = ($1 == "-"); opt = $2; arg = $3
137
+ unless optdef.key?(opt)
138
+ argv << argstr
139
+ next
140
+ end
141
+ handler = optdef[opt]
142
+ arity = (handler.arity rescue nil)
143
+ opts[opt] =
144
+ case arity
145
+ when nil; orig.unshift("-#{arg}") if short; handler
146
+ when 0,-1; orig.unshift("-#{arg}") if short; handle(opt, handler)
147
+ else handle(opt, handler, arg)
148
+ end
149
+
150
+ when /^--(.+)/, /^-(.)$/
151
+ opt = $1
152
+ unless optdef.key?(opt)
153
+ argv << argstr
154
+ next
155
+ end
156
+ handler = optdef[opt]
157
+ arity = (handler.arity rescue nil)
158
+ opts[opt] =
159
+ case arity
160
+ when nil; handler
161
+ when 0,-1; handle(opt, handler)
162
+ else handle(opt, handler, orig.shift || argument_missing(opt))
163
+ end
164
+
165
+ else
166
+ argv << argstr
167
+ end
168
+ end
169
+
170
+ opts
171
+ end
172
+ end
173
+
174
+ if __FILE__ == $0
175
+
176
+ v = 0
177
+ defaults = {
178
+ "v" => v,
179
+ "port" => 4000,
180
+ "host" => "localhost"
181
+ }
182
+
183
+ optdef = {
184
+ "x" => true,
185
+ "y" => "y",
186
+ "z" => 3,
187
+ "v" => proc {v+=1}, # no argument, but call the proc to get the value
188
+ "port" => proc {|arg| Integer(arg)},
189
+ "n" => proc {|arg| Integer(arg)},
190
+ "t" => proc {|arg| Float(arg)},
191
+ "cmd" => proc {|arg| arg.split(",")}
192
+ }
193
+
194
+ ARGV.replace %w{
195
+ -xyzn5 somefile --port 5000 -t -1.23 -vv -v --unrecognized-option
196
+ --cmd=ls,-l otherfile -- --port
197
+ }
198
+
199
+ begin
200
+ cli_opts = Argos.parse_options(ARGV, optdef)
201
+ rescue Argos::OptionError => ex
202
+ $stderr.puts ex.message
203
+ exit
204
+ end
205
+
206
+ opts = defaults.merge cli_opts
207
+
208
+ p opts
209
+ p ARGV
210
+ unless ARGV.empty?
211
+ puts "Some arg-looking strings were not handled:", *ARGV.grep(/^-./)
212
+ end
213
+
214
+ end
@@ -0,0 +1,370 @@
1
+ require 'tk'
2
+ require 'tkar/tkaroid'
3
+ require 'tkar/primitives'
4
+ require 'tkar/timer'
5
+
6
+ module Tkar
7
+ class Canvas < TkCanvas
8
+ class Error < Tkar::Error; end
9
+ class MissingObject < Error; end
10
+
11
+ attr_reader :zoom
12
+ attr_accessor :follow_id
13
+
14
+ def initialize(*)
15
+ super
16
+
17
+ @shapes = {}
18
+ @shape_def = {}
19
+ @zoom = 1.0
20
+ init_objects
21
+ end
22
+
23
+ def init_objects
24
+ @objects = {}
25
+ @changed = {}
26
+ @layers = [] # sorted array of layer numbers
27
+ @objects_by_layer = {} # layer => [obj, ...]
28
+ follow nil
29
+ end
30
+
31
+ def zoom_by zf
32
+ zf = Float(zf)
33
+ @zoom *= zf
34
+
35
+ vf = (1 - 1/zf) / 2
36
+
37
+ x0, x1 = xview
38
+ xf = x0 + vf * (x1-x0)
39
+
40
+ y0, y1 = yview
41
+ yf = y0 + vf * (y1-y0)
42
+
43
+ scale 'all', 0, 0, zf, zf
44
+ adjust_scrollregion
45
+
46
+ xview "moveto", xf
47
+ yview "moveto", yf
48
+ end
49
+
50
+ def adjust_scrollregion
51
+ configure :scrollregion => @bounds.map {|u|u*@zoom}
52
+ ## if all of canvas can be shown, hide the scroll bars
53
+ end
54
+
55
+ def xview(mode=nil, *args)
56
+ if mode and mode == "scroll" and @follow_xdelta
57
+ number, what = args
58
+ x_pre, = xview
59
+ r = super(mode, *args)
60
+ x_post, = xview
61
+ x0,y0,x1,y1 = @bounds
62
+ @follow_xdelta += (x_post - x_pre) * (x1-x0)
63
+ r
64
+ elsif not mode
65
+ super()
66
+ else
67
+ super(mode, *args)
68
+ end
69
+ end
70
+
71
+ def yview(mode=nil, *args)
72
+ if mode and mode == "scroll" and @follow_ydelta
73
+ number, what = args
74
+ y_pre, = yview
75
+ r = super(mode, *args)
76
+ y_post, = yview
77
+ x0,y0,x1,y1 = @bounds
78
+ @follow_ydelta += (y_post - y_pre) * (y1-y0)
79
+ r
80
+ elsif not mode
81
+ super()
82
+ else
83
+ super(mode, *args)
84
+ end
85
+ end
86
+
87
+ # fixes a bug in RubyTk
88
+ def scan_dragto(x, y, gain = 10)
89
+ tk_send_without_enc('scan', 'dragto', x, y, gain)
90
+ self
91
+ end
92
+
93
+ def view_followed_obj
94
+ tkaroid = get_object(@follow_id)
95
+ if tkaroid
96
+ view_at(tkaroid.x + @follow_xdelta, tkaroid.y + @follow_ydelta)
97
+ end
98
+ end
99
+
100
+ def current_width
101
+ Integer(TkWinfo.geometry(self)[/\d+/])
102
+ end
103
+
104
+ def current_height
105
+ Integer(TkWinfo.geometry(self)[/\d+x(\d+)/, 1])
106
+ end
107
+
108
+ # ------------------------
109
+ # :section: Tkaroid manipulation commands
110
+ #
111
+ # Methods which operate on the population of objects shown in the canvas.
112
+ # ------------------------
113
+
114
+ def get_shape name
115
+ @shapes[name] || (fail MissingObject, "No such shape, #{name}")
116
+ end
117
+
118
+ def get_object tkar_id
119
+ @objects[tkar_id] || (fail MissingObject, "No such object, #{tkar_id}")
120
+ end
121
+
122
+ def get_objects_by_layer layer
123
+ ary = @objects_by_layer[layer]
124
+ unless ary
125
+ ary = @objects_by_layer[layer] = []
126
+ @layers << layer
127
+ @layers.sort!
128
+ end
129
+ ary
130
+ end
131
+
132
+ def insert_at_layer tkaroid
133
+ layer = tkaroid.layer
134
+ peers = get_objects_by_layer(layer)
135
+ if peers.empty?
136
+ high = @layers.find {|l| l > layer}
137
+ if high
138
+ high_objects = get_objects_by_layer(high)
139
+ unless high_objects.empty?
140
+ lower tkaroid.tag, high_objects.first.tag
141
+ end
142
+ else
143
+ low = @layers.reverse.find {|l| l < layer}
144
+ if low
145
+ low_objects = get_objects_by_layer(low)
146
+ unless low_objects.empty?
147
+ raise tkaroid.tag, low_objects.last.tag
148
+ end
149
+ #else must be the only object!
150
+ end
151
+ end
152
+ else
153
+ raise tkaroid.tag, peers.last.tag
154
+ end
155
+ peers << tkaroid
156
+ end
157
+
158
+ def current_tkaroid
159
+ object = find_withtag('current').first
160
+ if object
161
+ tags = object.tags
162
+ tkar_id = tags.grep(/^tkar\d+$/).first[/\d+/].to_i
163
+ @objects[tkar_id]
164
+ end
165
+ end
166
+
167
+ # ------------------------
168
+ # :section: Commands
169
+ #
170
+ # Methods which handle incoming commands.
171
+ # ------------------------
172
+
173
+ def add shape_name, tkar_id, flags, layer, x, y, r, *params
174
+ del(tkar_id)
175
+
176
+ tkaroid = Tkaroid.new do |t|
177
+ t.shape = get_shape(shape_name)
178
+ t.id = tkar_id
179
+ t.flags = flags
180
+ t.layer = layer
181
+ t.x = x
182
+ t.y = y
183
+ t.r = r
184
+ t.params = params
185
+ t.newly_added = true
186
+ end
187
+
188
+ @objects[tkar_id] = tkaroid
189
+ @changed[tkar_id] = tkaroid
190
+ end
191
+
192
+ # Not "delete"! That already exists in tk.
193
+ def del tkar_id
194
+ tkaroid = @objects[tkar_id]
195
+ if tkaroid
196
+ if @follow_id == tkar_id
197
+ follow nil
198
+ end
199
+ delete tkaroid.tag
200
+ @objects.delete tkar_id
201
+ @changed.delete tkar_id
202
+ get_objects_by_layer(tkaroid.layer).delete tkaroid
203
+ end
204
+ end
205
+
206
+ def moveto tkar_id, x, y
207
+ tkaroid = get_object(tkar_id)
208
+ unless tkaroid.x == x and tkaroid.y == y
209
+ tkaroid.x = x
210
+ tkaroid.y = y
211
+ @changed[tkar_id] = tkaroid
212
+ end
213
+ end
214
+
215
+ def rot tkar_id, r
216
+ tkaroid = get_object(tkar_id)
217
+ unless tkaroid.r == r
218
+ tkaroid.r = r
219
+ @changed[tkar_id] = tkaroid
220
+ end
221
+ end
222
+
223
+ def param tkar_id, idx, val
224
+ tkaroid = get_object(tkar_id)
225
+ params = tkaroid.params
226
+ unless params[idx] == val
227
+ tkaroid.params[idx] = val
228
+ @changed[tkar_id] = tkaroid
229
+ end
230
+ end
231
+
232
+ def check_param param
233
+ if param.nil?
234
+ nil
235
+ elsif param.is_a? String and param[0] == ?*
236
+ param_idx = Integer(param[1..-1])
237
+ proc {|param_array| param_array[param_idx]}
238
+ else
239
+ (Integer(param) rescue Float(param)) rescue param
240
+ end
241
+ end
242
+
243
+ def compile_shape defn
244
+ part_spec_defs = defn.scan(/([_A-Za-z]+)([^;]*);?/)
245
+
246
+ part_spec_defs.map do |prim_name, args|
247
+ args = args.split(",")
248
+ key_args, args = args.partition {|arg| /:/ =~ arg}
249
+
250
+ macro = @shape_def[prim_name]
251
+ if macro
252
+ macro2 = macro.gsub(/\*\d+/) {|s| i=Integer(s[/\d+/]); args[i]}
253
+ macro2 = [macro2, *key_args].join(",")
254
+ compile_shape(macro2)
255
+ else
256
+ args.map! {|arg| check_param(arg)}
257
+ key_args.map! {|key_arg| key_arg.split(":")}
258
+ key_args.map! do |k, v|
259
+ [Primitives.handle_shortcuts(k), v]
260
+ end
261
+ key_args = key_args.inject({}) {|h,(k,v)| h[k] = check_param(v); h}
262
+ Primitives.send(prim_name, args, key_args)
263
+ end
264
+ end
265
+ end
266
+
267
+ def shape name, *defn
268
+ defn = defn.join(";")
269
+ part_spec_makers = compile_shape(defn)
270
+ part_spec_makers.flatten!
271
+ @shape_def[name] = defn # should prevent inf recursion
272
+
273
+ @shapes[name] = proc do |tkaroid|
274
+ cos_r = Math::cos(tkaroid.r)
275
+ sin_r = Math::sin(tkaroid.r)
276
+
277
+ part_spec_makers.map do |maker|
278
+ maker[tkaroid, cos_r, sin_r]
279
+ end
280
+ end
281
+ end
282
+
283
+ def update
284
+ z = (zoom - 1).abs > 0.01 && zoom
285
+
286
+ thread = Thread.current
287
+ pri = thread.priority
288
+ thread.priority += 10
289
+
290
+ @changed.each do |tkar_id, tkaroid|
291
+ newly_added = tkaroid.update(self, z)
292
+ if newly_added
293
+ insert_at_layer(tkaroid)
294
+ end
295
+ end
296
+ @changed.clear
297
+
298
+ view_followed_obj if @follow_id
299
+ ensure
300
+ thread.priority = pri if thread
301
+ end
302
+
303
+ def title str
304
+ @root.title str
305
+ end
306
+
307
+ #background, height, width # already defined!
308
+
309
+ def window_xy x,y
310
+ s = ""
311
+ s << "+" if x > 0
312
+ s << x.to_s
313
+ s << "+" if y > 0
314
+ s << y.to_s
315
+ @root.geometry s
316
+ end
317
+
318
+ def zoom_to z
319
+ zoom_by(z/@zoom)
320
+ end
321
+
322
+ def view_at x, y
323
+ x0,y0,x1,y1 = @bounds
324
+ width = TkWinfo.width(self)
325
+ height = TkWinfo.height(self)
326
+ xview "moveto", (x-x0-(width/(@zoom*2.0)))/(x1-x0).to_f
327
+ yview "moveto", (y-y0-(height/(@zoom*2.0)))/(y1-y0).to_f
328
+ end
329
+
330
+ def view_at_screen x,y
331
+ view_at(canvasx(x)/@zoom, canvasy(y)/@zoom)
332
+ end
333
+
334
+ def view_id id
335
+ tkaroid = get_object(id)
336
+ view_at tkaroid.x, tkaroid.y if tkaroid
337
+ end
338
+
339
+ def wait t
340
+ unless @timer
341
+ @timer = Timer.new(t)
342
+ end
343
+ @timer.wait(t)
344
+ end
345
+
346
+ def follow id
347
+ @follow_id = id
348
+ @follow_xdelta = id && 0
349
+ @follow_ydelta = id && 0
350
+ end
351
+
352
+ def bounds x0,y0,x1,y1
353
+ @bounds = [x0,y0,x1,y1]
354
+ adjust_scrollregion
355
+ end
356
+
357
+ def delete_all
358
+ @objects.values.each do |tkaroid|
359
+ delete tkaroid.tag
360
+ end
361
+ init_objects
362
+ end
363
+
364
+ def scale_obj tkar_id, xf, yf
365
+ tkaroid = get_object(tkar_id)
366
+ z = @zoom
367
+ scale tkaroid.tag, tkaroid.x*z, tkaroid.y*z, xf, yf
368
+ end
369
+ end
370
+ end