RUIC 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/LICENSE +21 -0
- data/README.md +201 -0
- data/bin/ruic +52 -0
- data/gui/MetaData.xml +387 -0
- data/gui/TODO +2 -0
- data/gui/appattributesmodel.rb +51 -0
- data/gui/appelementsmodel.rb +126 -0
- data/gui/launch.rb +20 -0
- data/gui/makefile +14 -0
- data/gui/resources.qrc +37 -0
- data/gui/resources/images/Objects-Behavior-Normal.png +0 -0
- data/gui/resources/images/Objects-Camera-Normal.png +0 -0
- data/gui/resources/images/Objects-Component-Normal.png +0 -0
- data/gui/resources/images/Objects-Effect-Normal.png +0 -0
- data/gui/resources/images/Objects-Group-Normal.png +0 -0
- data/gui/resources/images/Objects-Image-Normal.png +0 -0
- data/gui/resources/images/Objects-Layer-Normal.png +0 -0
- data/gui/resources/images/Objects-Light-Normal.png +0 -0
- data/gui/resources/images/Objects-Material-Normal.png +0 -0
- data/gui/resources/images/Objects-Model-Normal.png +0 -0
- data/gui/resources/images/Objects-Music-Normal.png +0 -0
- data/gui/resources/images/Objects-Property-Normal.png +0 -0
- data/gui/resources/images/Objects-References-Normal.png +0 -0
- data/gui/resources/images/Objects-Scene-Normal.png +0 -0
- data/gui/resources/images/Objects-Sound-Normal.png +0 -0
- data/gui/resources/images/Objects-Text-Normal.png +0 -0
- data/gui/resources/images/Objects-Unknown-Normal.png +0 -0
- data/gui/resources/images/Objects-Video-Normal.png +0 -0
- data/gui/resources/images/SCXML.ico +0 -0
- data/gui/resources/images/TSCXML.ico +0 -0
- data/gui/resources/images/UIA.ico +0 -0
- data/gui/resources/images/clipboard.png +0 -0
- data/gui/resources/images/console_arrow.png +0 -0
- data/gui/resources/images/cross.png +0 -0
- data/gui/resources/images/currentline.png +0 -0
- data/gui/resources/images/disk-black.png +0 -0
- data/gui/resources/images/disks-black.png +0 -0
- data/gui/resources/images/document--plus.png +0 -0
- data/gui/resources/images/document-copy.png +0 -0
- data/gui/resources/images/executable-actions-delete-active.png +0 -0
- data/gui/resources/images/executable-actions-delete-idle.png +0 -0
- data/gui/resources/images/executable-actions-enter-badge.png +0 -0
- data/gui/resources/images/executable-actions-exit-badge.png +0 -0
- data/gui/resources/images/folder-horizontal--plus.png +0 -0
- data/gui/resources/images/folder-horizontal-open.png +0 -0
- data/gui/resources/images/gear.png +0 -0
- data/gui/resources/images/invalid_breakpoint.png +0 -0
- data/gui/resources/images/scissors-blue.png +0 -0
- data/gui/resources/images/slide-16-active.png +0 -0
- data/gui/resources/images/slide-16-master.png +0 -0
- data/gui/resources/images/slide-16-normal.png +0 -0
- data/gui/resources/images/slide-32-active.png +0 -0
- data/gui/resources/images/slide-32-master.png +0 -0
- data/gui/resources/images/slide-32-normal.png +0 -0
- data/gui/resources/images/studio_architect32.ico +0 -0
- data/gui/resources/images/studio_architect32.png +0 -0
- data/gui/resources/images/textfile_icon.png +0 -0
- data/gui/resources/style/checkbox.png +0 -0
- data/gui/resources/style/dark.qss +459 -0
- data/gui/resources/style/down_arrow.png +0 -0
- data/gui/resources/style/handle.png +0 -0
- data/gui/window.rb +90 -0
- data/gui/window.ui +753 -0
- data/lib/ruic.rb +59 -0
- data/lib/ruic/application.rb +129 -0
- data/lib/ruic/asset_classes.rb +448 -0
- data/lib/ruic/behaviors.rb +31 -0
- data/lib/ruic/interfaces.rb +36 -0
- data/lib/ruic/presentation.rb +354 -0
- data/lib/ruic/statemachine.rb +111 -0
- data/lib/ruic/version.rb +3 -0
- data/ruic.gemspec +23 -0
- data/test/MetaData.xml +387 -0
- data/test/customclasses.ruic +21 -0
- data/test/filtering.ruic +39 -0
- data/test/nonmaster.ruic +21 -0
- data/test/projects/CustomClasses/Brush Strokes.effect +38 -0
- data/test/projects/CustomClasses/CustomClasses.uia +6 -0
- data/test/projects/CustomClasses/CustomClasses.uip +34 -0
- data/test/projects/CustomClasses/copper.material +194 -0
- data/test/projects/CustomClasses/maps/UV-Checker.png +0 -0
- data/test/projects/CustomClasses/maps/effects/brushnoise.dds +0 -0
- data/test/projects/CustomClasses/maps/materials/spherical_checker.png +0 -0
- data/test/projects/ReferencedMaterials/ReferencedMaterials.uia +6 -0
- data/test/projects/ReferencedMaterials/ReferencedMaterials.uip +39 -0
- data/test/properties.ruic +81 -0
- data/test/referencematerials.ruic +53 -0
- data/test/usage.ruic +20 -0
- metadata +166 -0
@@ -0,0 +1,31 @@
|
|
1
|
+
class UIC::Behavior
|
2
|
+
include UIC::FileBacked
|
3
|
+
attr_reader :lua
|
4
|
+
def initialize( lua_path )
|
5
|
+
self.file = lua_path
|
6
|
+
load_from_file if file_found?
|
7
|
+
end
|
8
|
+
def load_from_file
|
9
|
+
@lua = File.read(file,encoding:'utf-8')
|
10
|
+
end
|
11
|
+
|
12
|
+
def errors?
|
13
|
+
!errors.empty?
|
14
|
+
end
|
15
|
+
|
16
|
+
def errors
|
17
|
+
file_found? ? [] : ["File not found: '#{file}'"]
|
18
|
+
end
|
19
|
+
|
20
|
+
end
|
21
|
+
|
22
|
+
class UIC::Application::Behavior < UIC::Behavior
|
23
|
+
include UIC::ElementBacked
|
24
|
+
xmlattribute :id
|
25
|
+
xmlattribute :src
|
26
|
+
def initialize(application,el)
|
27
|
+
self.owner = application
|
28
|
+
self.el = el
|
29
|
+
super( application.path_to(src) )
|
30
|
+
end
|
31
|
+
end
|
@@ -0,0 +1,36 @@
|
|
1
|
+
module UIC::FileBacked
|
2
|
+
attr_accessor :doc, :file
|
3
|
+
def path_to( relative )
|
4
|
+
File.expand_path( relative.gsub('\\','/'), File.dirname(file) )
|
5
|
+
end
|
6
|
+
def filename
|
7
|
+
File.basename(file)
|
8
|
+
end
|
9
|
+
def file_found?
|
10
|
+
!@file_not_found
|
11
|
+
end
|
12
|
+
def file=( new_path )
|
13
|
+
@file = File.expand_path(new_path)
|
14
|
+
@file_not_found = !File.exist?(new_path)
|
15
|
+
# warn "Could not find file '#{new_path}'" unless file_found?
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
module UIC::ElementBacked
|
20
|
+
attr_accessor :owner, :el
|
21
|
+
def self.included(base)
|
22
|
+
base.extend(ClassMethods)
|
23
|
+
end
|
24
|
+
module ClassMethods
|
25
|
+
def xmlattribute(name,&block)
|
26
|
+
define_method(name){ @el[name] }
|
27
|
+
define_method("#{name}=", &(block || ->(new_value){ @el[name]=new_value.to_s }))
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
module UIC::PresentableHash
|
33
|
+
def to_s
|
34
|
+
flat_map{ |k,v| [ k, *(v.is_a?(Array) ? v.map{|v2| "\t#{v2.to_s}" } : v) ] }
|
35
|
+
end
|
36
|
+
end
|
@@ -0,0 +1,354 @@
|
|
1
|
+
class UIC::Presentation
|
2
|
+
include UIC::FileBacked
|
3
|
+
def initialize( uip_path )
|
4
|
+
self.file = uip_path
|
5
|
+
load_from_file if file_found?
|
6
|
+
end
|
7
|
+
|
8
|
+
def load_from_file
|
9
|
+
@doc = Nokogiri.XML( File.read( file, encoding:'utf-8' ), &:noblanks )
|
10
|
+
@graph = @doc.at('Graph')
|
11
|
+
@scene = @graph.at('Scene')
|
12
|
+
@logic = @doc.at('Logic')
|
13
|
+
|
14
|
+
@class_by_ref = {}
|
15
|
+
@doc.xpath('/UIP/Project/Classes/*').each do |reference|
|
16
|
+
path = app.path_to(reference['sourcepath'])
|
17
|
+
raise "Cannot find file '#{path}' referenced by #{self.inspect}" unless File.exist?( path )
|
18
|
+
metaklass = case reference.name
|
19
|
+
when 'CustomMaterial'
|
20
|
+
meta = Nokogiri.XML(File.read(path,encoding:'utf-8')).at('/*/MetaData')
|
21
|
+
from = app.metadata.by_name[ 'MaterialBase' ]
|
22
|
+
app.metadata.create_class( meta, from, reference.name )
|
23
|
+
when 'Effect'
|
24
|
+
meta = Nokogiri.XML(File.read(path,encoding:'utf-8')).at('/*/MetaData')
|
25
|
+
from = app.metadata.by_name[ 'Effect' ]
|
26
|
+
app.metadata.create_class( meta, from, reference.name )
|
27
|
+
when 'Behavior'
|
28
|
+
lua = File.read(path,encoding:'utf-8')
|
29
|
+
meta = lua[ /--\[\[(.+?)(?:--)?\]\]/m, 1 ]
|
30
|
+
meta = Nokogiri.XML("<MetaData>#{meta}</MetaData>").root
|
31
|
+
from = app.metadata.by_name[ 'Behavior' ]
|
32
|
+
app.metadata.create_class( meta, from, reference.name )
|
33
|
+
end
|
34
|
+
@class_by_ref[ "##{reference['id']}" ] = metaklass
|
35
|
+
end
|
36
|
+
|
37
|
+
rebuild_caches_from_document
|
38
|
+
|
39
|
+
@asset_by_el = {} # indexed by asset graph element
|
40
|
+
@slides_for = {} # indexed by asset graph element
|
41
|
+
@slides_by_el = {} # indexed by slide state element
|
42
|
+
end
|
43
|
+
|
44
|
+
def save!
|
45
|
+
File.open(file,'w:utf-8'){ |f|
|
46
|
+
f << @doc.to_xml( indent:1, indent_text:"\t" )
|
47
|
+
.gsub( %r{(<\w+(?: [\w:]+="[^"]*")*)(/?>)}i, '\1 \2' )
|
48
|
+
.sub('"?>','" ?>')
|
49
|
+
}
|
50
|
+
end
|
51
|
+
|
52
|
+
def rebuild_caches_from_document
|
53
|
+
@graph_by_id = {}
|
54
|
+
@scene.traverse{ |x| @graph_by_id[x['id']]=x if x.is_a?(Nokogiri::XML::Element) }
|
55
|
+
|
56
|
+
@graph_by_addset = {}
|
57
|
+
@addsets_by_graph = {}
|
58
|
+
slideindex = {}
|
59
|
+
@logic.xpath('.//Add|.//Set').each do |addset|
|
60
|
+
graph = @graph_by_id[addset['ref'][1..-1]]
|
61
|
+
@graph_by_addset[addset] = graph
|
62
|
+
@addsets_by_graph[graph] ||= {}
|
63
|
+
slide = addset.parent
|
64
|
+
name = slide['name']
|
65
|
+
index = name == 'Master Slide' ? 0 : (slideindex[slide] ||= (slide.index('State') + 1))
|
66
|
+
@addsets_by_graph[graph][name] = addset
|
67
|
+
@addsets_by_graph[graph][index] = addset
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
def asset_by_id( id )
|
72
|
+
(@graph_by_id[id] && asset_for_el( @graph_by_id[id] ))
|
73
|
+
end
|
74
|
+
|
75
|
+
# Find the index of the slide where an element is added
|
76
|
+
def slide_index(graph_element)
|
77
|
+
# TODO: probably faster to .find the first @addsets_by_graph
|
78
|
+
slide = @logic.at(".//Add[@ref='##{graph_element['id']}']/..")
|
79
|
+
(slide ? slide.xpath('count(ancestor::State) + count(preceding-sibling::State[ancestor::State])').to_i : 0) # the Scene is never added
|
80
|
+
end
|
81
|
+
|
82
|
+
def parent_asset( child_graph_el )
|
83
|
+
unless child_graph_el==@scene || child_graph_el.parent.nil?
|
84
|
+
asset_for_el( child_graph_el.parent )
|
85
|
+
end
|
86
|
+
end
|
87
|
+
|
88
|
+
def child_assets( parent_graph_el )
|
89
|
+
parent_graph_el.element_children.map{ |child| asset_for_el(child) }
|
90
|
+
end
|
91
|
+
|
92
|
+
# Get an array of all assets in the scene graph, in document order
|
93
|
+
def assets
|
94
|
+
@graph_by_id.map{ |id,graph_element| asset_for_el(graph_element) }
|
95
|
+
end
|
96
|
+
|
97
|
+
# Returns a hash mapping image paths to arrays of the assets referencing them
|
98
|
+
def image_usage
|
99
|
+
asset_types = app.metadata.by_name.values + @class_by_ref.values
|
100
|
+
|
101
|
+
image_properties_by_type = asset_types.flat_map do |type|
|
102
|
+
type.properties.values
|
103
|
+
.select{ |property| property.type=='Image' || property.type == 'Texture' }
|
104
|
+
.map{ |property| [type,property] }
|
105
|
+
end.group_by(&:first).tap{ |x| x.each{ |t,a| a.map!(&:last) } }
|
106
|
+
|
107
|
+
Hash[ assets.each_with_object({}) do |asset,usage|
|
108
|
+
if properties = image_properties_by_type[asset.class]
|
109
|
+
properties.each do |property|
|
110
|
+
asset[property.name].values.compact.each do |value|
|
111
|
+
value = value['sourcepath'] if property.type=='Image'
|
112
|
+
unless value.nil? || value.empty?
|
113
|
+
value = value.gsub('\\','/').sub(/^.\//,'')
|
114
|
+
usage[value] ||= []
|
115
|
+
usage[value] << asset
|
116
|
+
end
|
117
|
+
end
|
118
|
+
end
|
119
|
+
end
|
120
|
+
end.sort_by do |path,assets|
|
121
|
+
parts = path.downcase.split '/'
|
122
|
+
[ parts.length, parts ]
|
123
|
+
end ].tap{ |h| h.extend(UIC::PresentableHash) }
|
124
|
+
end
|
125
|
+
|
126
|
+
def image_paths
|
127
|
+
image_usage.keys
|
128
|
+
end
|
129
|
+
|
130
|
+
def asset_for_el(el)
|
131
|
+
(@asset_by_el[el] ||= el['class'] ? @class_by_ref[el['class']].new(self,el) : app.metadata.new_instance(self,el))
|
132
|
+
end
|
133
|
+
|
134
|
+
attr_reader :addsets_by_graph
|
135
|
+
protected :addsets_by_graph
|
136
|
+
|
137
|
+
def referenced_files
|
138
|
+
(
|
139
|
+
(images + behaviors + effects + meshes + materials ).map(&:file)
|
140
|
+
+ effects.flat_map(&:images)
|
141
|
+
+ fonts
|
142
|
+
).sort_by{ |f| parts = f.split(/[\/\\]/); [parts.length,parts] }
|
143
|
+
end
|
144
|
+
|
145
|
+
def scene
|
146
|
+
asset_for_el( @scene )
|
147
|
+
end
|
148
|
+
|
149
|
+
def path_to( el, from_el=nil )
|
150
|
+
to_parts = if el.ancestors('Graph')
|
151
|
+
[].tap{ |parts|
|
152
|
+
until el==@graph
|
153
|
+
parts.unshift asset_for_el(el).name
|
154
|
+
el = el.parent
|
155
|
+
end
|
156
|
+
}
|
157
|
+
end
|
158
|
+
if from_el && from_el.ancestors('Graph')
|
159
|
+
from_parts = [].tap{ |parts|
|
160
|
+
until from_el==@graph
|
161
|
+
parts.unshift asset_for_el(from_el).name
|
162
|
+
from_el = from_el.parent
|
163
|
+
end
|
164
|
+
}
|
165
|
+
until to_parts.empty? || from_parts.empty? || to_parts.first!=from_parts.first
|
166
|
+
to_parts.shift
|
167
|
+
from_parts.shift
|
168
|
+
end
|
169
|
+
to_parts.unshift *(['parent']*from_parts.length)
|
170
|
+
end
|
171
|
+
to_parts.join('.')
|
172
|
+
end
|
173
|
+
|
174
|
+
def errors?
|
175
|
+
(!errors.empty?)
|
176
|
+
end
|
177
|
+
|
178
|
+
def errors
|
179
|
+
(file_found? ? [] : ["File not found: '#{file}'"])
|
180
|
+
end
|
181
|
+
|
182
|
+
def at(path,root=@graph)
|
183
|
+
name,path = path.split('.',2)
|
184
|
+
el = case name
|
185
|
+
when 'parent' then root==@scene ? nil : root.parent
|
186
|
+
when 'Scene' then @scene
|
187
|
+
else root.element_children.find{ |el| asset_for_el(el).name==name }
|
188
|
+
end
|
189
|
+
path ? at(path,el) : asset_for_el(el) if el
|
190
|
+
end
|
191
|
+
alias_method :/, :at
|
192
|
+
|
193
|
+
def get_attribute( graph_element, property_name, slide_name_or_index )
|
194
|
+
((addsets=@addsets_by_graph[graph_element]) && ( # State (slide) don't have any addsets
|
195
|
+
( addsets[slide_name_or_index] && addsets[slide_name_or_index][property_name] ) || # Try for a Set on the specific slide
|
196
|
+
( addsets[0] && addsets[0][property_name] ) # …else try the master slide
|
197
|
+
) || graph_element[property_name]) # …else try the graph
|
198
|
+
# TODO: handle animation (child of addset)
|
199
|
+
end
|
200
|
+
|
201
|
+
def set_attribute( graph_element, property_name, slide_name_or_index, str )
|
202
|
+
if attribute_linked?( graph_element, property_name )
|
203
|
+
if @addsets_by_graph[graph_element]
|
204
|
+
@addsets_by_graph[graph_element][0][property_name] = str
|
205
|
+
else
|
206
|
+
raise "TODO"
|
207
|
+
end
|
208
|
+
else
|
209
|
+
if @addsets_by_graph[graph_element]
|
210
|
+
if slide_name_or_index
|
211
|
+
@addsets_by_graph[graph_element][slide_name_or_index][property_name] = str
|
212
|
+
else
|
213
|
+
master = master_slide_for( graph_element )
|
214
|
+
slide_count = master.xpath('count(./State)').to_i
|
215
|
+
0.upto(slide_count).each{ |idx| set_attribute(graph_element,property_name,idx,str) }
|
216
|
+
end
|
217
|
+
else
|
218
|
+
raise "TODO"
|
219
|
+
end
|
220
|
+
end
|
221
|
+
end
|
222
|
+
|
223
|
+
def owning_component( graph_element )
|
224
|
+
asset_for_el( owning_component_element( graph_element ) )
|
225
|
+
end
|
226
|
+
|
227
|
+
def owning_component_element( graph_element )
|
228
|
+
graph_element.at_xpath('(ancestor::Component[1] | ancestor::Scene[1])[last()]')
|
229
|
+
end
|
230
|
+
|
231
|
+
def owning_or_self_component_element( graph_element )
|
232
|
+
graph_element.at_xpath('(ancestor-or-self::Component[1] | ancestor-or-self::Scene[1])[last()]')
|
233
|
+
end
|
234
|
+
|
235
|
+
def master_slide_for( graph_element )
|
236
|
+
comp = owning_or_self_component_element( graph_element )
|
237
|
+
@logic.at("./State[@component='##{comp['id']}']")
|
238
|
+
end
|
239
|
+
|
240
|
+
def slides_for( graph_element )
|
241
|
+
@slides_for[graph_element] ||= begin
|
242
|
+
slides = []
|
243
|
+
master = master_slide_for( graph_element )
|
244
|
+
slides << [master,0] if graph_element==@scene || (@addsets_by_graph[graph_element] && @addsets_by_graph[graph_element][0])
|
245
|
+
slides.concat( master.xpath('./State').map.with_index{ |el,i| [el,i+1] } )
|
246
|
+
slides.map!{ |el,idx| @slides_by_el[el] ||= app.metadata.new_instance(self,el).tap{ |s| s.index=idx; s.name=el['name'] } }
|
247
|
+
UIC::SlideCollection.new( slides )
|
248
|
+
end
|
249
|
+
end
|
250
|
+
|
251
|
+
def has_slide?( graph_element, slide_name_or_index )
|
252
|
+
if graph_element == @scene
|
253
|
+
# The scene is never actually added, so we'll treat it just like the first add, which is on the master slide of the scene
|
254
|
+
has_slide?( @addsets_by_graph.first.first, slide_name_or_index )
|
255
|
+
else
|
256
|
+
@addsets_by_graph[graph_element][slide_name_or_index] || @addsets_by_graph[graph_element][0]
|
257
|
+
end
|
258
|
+
end
|
259
|
+
|
260
|
+
def attribute_linked?(graph_element,attribute_name)
|
261
|
+
!(@addsets_by_graph[graph_element] && @addsets_by_graph[graph_element][1] && @addsets_by_graph[graph_element][1].key?(attribute_name))
|
262
|
+
end
|
263
|
+
|
264
|
+
def unlink_attribute(graph_element,attribute_name)
|
265
|
+
if master?(graph_element) && attribute_linked?(graph_element,attribute_name)
|
266
|
+
master_value = get_attribute( graph_element, attribute_name, 0 )
|
267
|
+
slides_for( graph_element ).to_ary[1..-1].each do |slide|
|
268
|
+
addset = slide.el.at_xpath( ".//*[@ref='##{graph_element['id']}']" ) || slide.el.add_child("<Set ref='##{graph_element['id']}'/>").first
|
269
|
+
addset[attribute_name] = master_value
|
270
|
+
end
|
271
|
+
rebuild_caches_from_document
|
272
|
+
true
|
273
|
+
end
|
274
|
+
end
|
275
|
+
|
276
|
+
def replace_asset( existing_asset, new_type, attributes={} )
|
277
|
+
old_el = existing_asset.el
|
278
|
+
new_el = old_el.replace( "<#{new_type}/>" ).first
|
279
|
+
attributes['id'] = old_el['id']
|
280
|
+
attributes.each{ |att,val| new_el[att.to_s] = val }
|
281
|
+
asset_for_el( new_el ).tap do |new_asset|
|
282
|
+
unsupported_attributes = ".//*[name()='Add' or name()='Set'][@ref='##{old_el['id']}']/@*[name()!='ref' and #{new_asset.properties.keys.map{|p| "name()!='#{p}'"}.join(' and ')}]"
|
283
|
+
@logic.xpath(unsupported_attributes).remove
|
284
|
+
rebuild_caches_from_document
|
285
|
+
end
|
286
|
+
end
|
287
|
+
|
288
|
+
# Is this element added on the master slide?
|
289
|
+
def master?(graph_element)
|
290
|
+
(graph_element == @scene) || !!(@addsets_by_graph[graph_element] && @addsets_by_graph[graph_element][0])
|
291
|
+
end
|
292
|
+
|
293
|
+
def find(options={})
|
294
|
+
index = -1
|
295
|
+
start = options[:under] ? options[:under].el : @graph
|
296
|
+
(options[:attributes]||={})[:name]=options[:name] if options[:name]
|
297
|
+
[].tap do |result|
|
298
|
+
start.xpath('./descendant::*').each do |el|
|
299
|
+
next if options.key?(:type) && el.name != options[:type]
|
300
|
+
next if options.key?(:slide) && !has_slide?(el,options[:slide])
|
301
|
+
next if options.key?(:master) && master?(el)!= options[:master]
|
302
|
+
asset = asset_for_el(el)
|
303
|
+
next if options.key?(:attributes) && options[:attributes].any?{ |att,val|
|
304
|
+
value = asset[att.to_s].value
|
305
|
+
case val
|
306
|
+
when Regexp then val !~ value.to_s
|
307
|
+
when Numeric then (val-value).abs >= 0.001
|
308
|
+
when Array then value.to_a.zip(val).map{ |a,b| b && (a-b).abs>=0.001 }.any?
|
309
|
+
else value != val
|
310
|
+
end if asset.properties[att.to_s]
|
311
|
+
}
|
312
|
+
yield asset, index+=1 if block_given?
|
313
|
+
result << asset
|
314
|
+
end
|
315
|
+
end
|
316
|
+
end
|
317
|
+
end
|
318
|
+
|
319
|
+
def UIC.Presentation( uip_path )
|
320
|
+
UIC::Presentation.new( uip_path )
|
321
|
+
end
|
322
|
+
|
323
|
+
class UIC::Application::Presentation < UIC::Presentation
|
324
|
+
include UIC::ElementBacked
|
325
|
+
xmlattribute :id
|
326
|
+
xmlattribute :src
|
327
|
+
xmlattribute :id do |new_id|
|
328
|
+
main_preso = app.main_presentation
|
329
|
+
super(new_id)
|
330
|
+
app.main_presentation=self if main_preso==self
|
331
|
+
end
|
332
|
+
xmlattribute :active
|
333
|
+
|
334
|
+
def initialize(application,el)
|
335
|
+
self.owner = application
|
336
|
+
self.el = el
|
337
|
+
super( application.path_to(src) )
|
338
|
+
end
|
339
|
+
alias_method :app, :owner
|
340
|
+
|
341
|
+
def path_to( el, from=nil )
|
342
|
+
"#{id}:#{super}"
|
343
|
+
end
|
344
|
+
|
345
|
+
def inspect
|
346
|
+
"<presentation #{file}>"
|
347
|
+
end
|
348
|
+
end
|
349
|
+
|
350
|
+
class Nokogiri::XML::Element
|
351
|
+
def index(kind='*') # Find the index of this element amongs its siblings
|
352
|
+
xpath("count(./preceding-sibling::#{kind})").to_i
|
353
|
+
end
|
354
|
+
end
|
@@ -0,0 +1,111 @@
|
|
1
|
+
class UIC::StateMachine
|
2
|
+
include UIC::FileBacked
|
3
|
+
def initialize( xml )
|
4
|
+
@doc = Nokogiri.XML( xml )
|
5
|
+
end
|
6
|
+
|
7
|
+
def errors?
|
8
|
+
!errors.empty?
|
9
|
+
end
|
10
|
+
|
11
|
+
def errors
|
12
|
+
file_found? ? [] : ["File not found: '#{file}'"]
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
def UIC.StateMachine( scxml_path )
|
17
|
+
UIC::StateMachine.new(File.read(scxml_path,encoding:'utf-8'))
|
18
|
+
.tap{ |o| o.file = scxml_path }
|
19
|
+
end
|
20
|
+
|
21
|
+
class UIC::Application::StateMachine < UIC::StateMachine
|
22
|
+
include UIC::ElementBacked
|
23
|
+
xmlattribute :id
|
24
|
+
xmlattribute :src
|
25
|
+
xmlattribute :datamodel
|
26
|
+
attr_reader :visual_states
|
27
|
+
attr_reader :visual_transitions
|
28
|
+
def initialize(application,el)
|
29
|
+
self.owner = application
|
30
|
+
self.el = el
|
31
|
+
self.file = application.path_to(src)
|
32
|
+
super( File.read( file, encoding:'utf-8' ) )
|
33
|
+
@visuals = @doc.at( "/application/statemachine[@ref='##{id}']/visual-states" )
|
34
|
+
@visuals ||= @doc.root.add_child("<statemachine ref='##{id}'><visual-states/></statemachine>")
|
35
|
+
@visual_states = VisualStates.new( self, @visuals )
|
36
|
+
@visual_transitions = VisualTransitions.new( self, @visuals )
|
37
|
+
end
|
38
|
+
alias_method :app, :owner
|
39
|
+
|
40
|
+
def image_usage
|
41
|
+
(
|
42
|
+
visual_states.flat_map{ |vs| vs.enter_actions.flat_map{ |a| [a,vs] } } +
|
43
|
+
visual_states.flat_map{ |vs| vs.exit_actions.flat_map{ |a| [a,vs] } } +
|
44
|
+
visual_transitions.flat_map{ |vt| vt.actions.flat_map{ |a| [a,vt] } }
|
45
|
+
).select do |visual_action,owner|
|
46
|
+
visual_action.is_a?(UIC::Application::StateMachine::VisualAction::SetAttribute) &&
|
47
|
+
visual_action.value[/\A(['"])[^'"]+\1\Z/] && # ensure that it's a simple string value
|
48
|
+
visual_action.element.properties[ visual_action.attribute ].is_a?( UIC::Property::Image )
|
49
|
+
end.group_by do |visual_action,owner|
|
50
|
+
visual_action.value[/\A(['"])([^'"]+)\1\Z/,2]
|
51
|
+
end.each do |image_path,array|
|
52
|
+
array.map!(&:last)
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
class UIC::Application::StateMachine::VisualStates
|
57
|
+
include Enumerable
|
58
|
+
def initialize(app_machine,visuals_el)
|
59
|
+
@machine = app_machine
|
60
|
+
@wrap = visuals_el
|
61
|
+
@by_el = {}
|
62
|
+
end
|
63
|
+
def each
|
64
|
+
@wrap.xpath('state').each{ |el| yield @by_el[el] ||= VisualState.new(el) }
|
65
|
+
end
|
66
|
+
def [](id)
|
67
|
+
if el=@wrap.at("state[@ref='#{id}']")
|
68
|
+
@by_el[el] ||= VisualState.new(el)
|
69
|
+
end
|
70
|
+
end
|
71
|
+
def length
|
72
|
+
@wrap.xpath('count(state)').to_i
|
73
|
+
end
|
74
|
+
alias_method :count, :length
|
75
|
+
end
|
76
|
+
|
77
|
+
class UIC::Application::StateMachine::VisualTransitions
|
78
|
+
include Enumerable
|
79
|
+
def initialize(app_machine,visuals_el)
|
80
|
+
@machine = app_machine
|
81
|
+
@wrap = visuals_el
|
82
|
+
@by_el = {}
|
83
|
+
end
|
84
|
+
def each
|
85
|
+
@wrap.xpath('transition').each{ |el| yield @by_el[el] ||= VisualState.new(el) }
|
86
|
+
end
|
87
|
+
def [](id)
|
88
|
+
if el=@wrap.at("transition[@ref='#{id}']")
|
89
|
+
@by_el[el] ||= VisualTransition.new(el)
|
90
|
+
end
|
91
|
+
end
|
92
|
+
def length
|
93
|
+
@wrap.xpath('count(transition)').to_i
|
94
|
+
end
|
95
|
+
alias_method :count, :length
|
96
|
+
end
|
97
|
+
|
98
|
+
class UIC::Application::StateMachine::VisualState
|
99
|
+
def initialize(el)
|
100
|
+
@el = el
|
101
|
+
end
|
102
|
+
end
|
103
|
+
|
104
|
+
class UIC::Application::StateMachine::VisualTransition
|
105
|
+
def initialize(el)
|
106
|
+
@el = el
|
107
|
+
end
|
108
|
+
end
|
109
|
+
|
110
|
+
|
111
|
+
end
|