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