RUIC 0.0.1

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