tkar 0.63

Sign up to get free protection for your applications and to get access to all the features.
@@ -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