RUIC 0.0.1

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.
Files changed (90) hide show
  1. checksums.yaml +7 -0
  2. data/LICENSE +21 -0
  3. data/README.md +201 -0
  4. data/bin/ruic +52 -0
  5. data/gui/MetaData.xml +387 -0
  6. data/gui/TODO +2 -0
  7. data/gui/appattributesmodel.rb +51 -0
  8. data/gui/appelementsmodel.rb +126 -0
  9. data/gui/launch.rb +20 -0
  10. data/gui/makefile +14 -0
  11. data/gui/resources.qrc +37 -0
  12. data/gui/resources/images/Objects-Behavior-Normal.png +0 -0
  13. data/gui/resources/images/Objects-Camera-Normal.png +0 -0
  14. data/gui/resources/images/Objects-Component-Normal.png +0 -0
  15. data/gui/resources/images/Objects-Effect-Normal.png +0 -0
  16. data/gui/resources/images/Objects-Group-Normal.png +0 -0
  17. data/gui/resources/images/Objects-Image-Normal.png +0 -0
  18. data/gui/resources/images/Objects-Layer-Normal.png +0 -0
  19. data/gui/resources/images/Objects-Light-Normal.png +0 -0
  20. data/gui/resources/images/Objects-Material-Normal.png +0 -0
  21. data/gui/resources/images/Objects-Model-Normal.png +0 -0
  22. data/gui/resources/images/Objects-Music-Normal.png +0 -0
  23. data/gui/resources/images/Objects-Property-Normal.png +0 -0
  24. data/gui/resources/images/Objects-References-Normal.png +0 -0
  25. data/gui/resources/images/Objects-Scene-Normal.png +0 -0
  26. data/gui/resources/images/Objects-Sound-Normal.png +0 -0
  27. data/gui/resources/images/Objects-Text-Normal.png +0 -0
  28. data/gui/resources/images/Objects-Unknown-Normal.png +0 -0
  29. data/gui/resources/images/Objects-Video-Normal.png +0 -0
  30. data/gui/resources/images/SCXML.ico +0 -0
  31. data/gui/resources/images/TSCXML.ico +0 -0
  32. data/gui/resources/images/UIA.ico +0 -0
  33. data/gui/resources/images/clipboard.png +0 -0
  34. data/gui/resources/images/console_arrow.png +0 -0
  35. data/gui/resources/images/cross.png +0 -0
  36. data/gui/resources/images/currentline.png +0 -0
  37. data/gui/resources/images/disk-black.png +0 -0
  38. data/gui/resources/images/disks-black.png +0 -0
  39. data/gui/resources/images/document--plus.png +0 -0
  40. data/gui/resources/images/document-copy.png +0 -0
  41. data/gui/resources/images/executable-actions-delete-active.png +0 -0
  42. data/gui/resources/images/executable-actions-delete-idle.png +0 -0
  43. data/gui/resources/images/executable-actions-enter-badge.png +0 -0
  44. data/gui/resources/images/executable-actions-exit-badge.png +0 -0
  45. data/gui/resources/images/folder-horizontal--plus.png +0 -0
  46. data/gui/resources/images/folder-horizontal-open.png +0 -0
  47. data/gui/resources/images/gear.png +0 -0
  48. data/gui/resources/images/invalid_breakpoint.png +0 -0
  49. data/gui/resources/images/scissors-blue.png +0 -0
  50. data/gui/resources/images/slide-16-active.png +0 -0
  51. data/gui/resources/images/slide-16-master.png +0 -0
  52. data/gui/resources/images/slide-16-normal.png +0 -0
  53. data/gui/resources/images/slide-32-active.png +0 -0
  54. data/gui/resources/images/slide-32-master.png +0 -0
  55. data/gui/resources/images/slide-32-normal.png +0 -0
  56. data/gui/resources/images/studio_architect32.ico +0 -0
  57. data/gui/resources/images/studio_architect32.png +0 -0
  58. data/gui/resources/images/textfile_icon.png +0 -0
  59. data/gui/resources/style/checkbox.png +0 -0
  60. data/gui/resources/style/dark.qss +459 -0
  61. data/gui/resources/style/down_arrow.png +0 -0
  62. data/gui/resources/style/handle.png +0 -0
  63. data/gui/window.rb +90 -0
  64. data/gui/window.ui +753 -0
  65. data/lib/ruic.rb +59 -0
  66. data/lib/ruic/application.rb +129 -0
  67. data/lib/ruic/asset_classes.rb +448 -0
  68. data/lib/ruic/behaviors.rb +31 -0
  69. data/lib/ruic/interfaces.rb +36 -0
  70. data/lib/ruic/presentation.rb +354 -0
  71. data/lib/ruic/statemachine.rb +111 -0
  72. data/lib/ruic/version.rb +3 -0
  73. data/ruic.gemspec +23 -0
  74. data/test/MetaData.xml +387 -0
  75. data/test/customclasses.ruic +21 -0
  76. data/test/filtering.ruic +39 -0
  77. data/test/nonmaster.ruic +21 -0
  78. data/test/projects/CustomClasses/Brush Strokes.effect +38 -0
  79. data/test/projects/CustomClasses/CustomClasses.uia +6 -0
  80. data/test/projects/CustomClasses/CustomClasses.uip +34 -0
  81. data/test/projects/CustomClasses/copper.material +194 -0
  82. data/test/projects/CustomClasses/maps/UV-Checker.png +0 -0
  83. data/test/projects/CustomClasses/maps/effects/brushnoise.dds +0 -0
  84. data/test/projects/CustomClasses/maps/materials/spherical_checker.png +0 -0
  85. data/test/projects/ReferencedMaterials/ReferencedMaterials.uia +6 -0
  86. data/test/projects/ReferencedMaterials/ReferencedMaterials.uip +39 -0
  87. data/test/properties.ruic +81 -0
  88. data/test/referencematerials.ruic +53 -0
  89. data/test/usage.ruic +20 -0
  90. 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