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.
- data/.gitignore +5 -0
- data/FAQ.rdoc +39 -0
- data/History.txt +175 -0
- data/README.rdoc +153 -0
- data/TODO +67 -0
- data/bin/tkar +104 -0
- data/examples/dial.rb +172 -0
- data/examples/help.gif +0 -0
- data/examples/home.gif +0 -0
- data/examples/mkgrid.rb +58 -0
- data/examples/ps.rb +47 -0
- data/examples/rotate +26 -0
- data/examples/s +3 -0
- data/examples/sample +14 -0
- data/examples/sample.rb +98 -0
- data/examples/sample2 +48 -0
- data/examples/sample3 +57 -0
- data/examples/server.rb +45 -0
- data/examples/tavis.rb +90 -0
- data/install.rb +1015 -0
- data/lib/tkar.rb +109 -0
- data/lib/tkar/argos.rb +214 -0
- data/lib/tkar/canvas.rb +370 -0
- data/lib/tkar/help-window.rb +168 -0
- data/lib/tkar/primitives.rb +376 -0
- data/lib/tkar/stream.rb +284 -0
- data/lib/tkar/timer.rb +174 -0
- data/lib/tkar/tkaroid.rb +95 -0
- data/lib/tkar/version.rb +5 -0
- data/lib/tkar/window.rb +383 -0
- data/protocol.rdoc +539 -0
- data/rakefile +56 -0
- data/tasks/ann.rake +80 -0
- data/tasks/bones.rake +20 -0
- data/tasks/gem.rake +201 -0
- data/tasks/git.rake +40 -0
- data/tasks/notes.rake +27 -0
- data/tasks/post_load.rake +34 -0
- data/tasks/rdoc.rake +51 -0
- data/tasks/rubyforge.rake +55 -0
- data/tasks/setup.rb +292 -0
- data/tasks/spec.rake +54 -0
- data/tasks/svn.rake +47 -0
- data/tasks/test.rake +40 -0
- data/tasks/zentest.rake +36 -0
- metadata +116 -0
data/lib/tkar.rb
ADDED
@@ -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
|
data/lib/tkar/argos.rb
ADDED
@@ -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
|
data/lib/tkar/canvas.rb
ADDED
@@ -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
|