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.
- 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
|