DALi C# binding
[platform/core/uifw/dali-toolkit.git] / plugins / dali-swig / property-wrapper.rb
1 #!/usr/bin/env ruby
2 require 'pathname'
3 require 'scanf'
4
5 # Script does the following:
6 # - greps dali-core for DALI_PROPERTY macro which holds all the information about a property ( type, read only etc)
7 # - uses the filename of the macro to detect the class the properties belong to. E.g. actor-impl.cpp  = Actor
8 # - Scans each property macro and builds a list of DALi classes with an array of properties
9 # - Generates the csharp get/set  code for each property
10 # - Pastes the property get / set code into the DALi csharp files
11
12 # Given a DALi C++ property type this table stores the
13 # information needed to produce a csharp getter / setter
14 $typeTable =  [
15         ["BOOLEAN",     "bool",             "ref",  "bool temp = false;"],
16         ["FLOAT",       "float",            "ref",  "float temp = 0.0f;"],
17         ["INTEGER",     "int",              "ref",  "int temp = 0;"],
18         ["VECTOR2",     "Vector2",          "",     "Vector2 temp = new Vector2(0.0f,0.0f);"],
19         ["VECTOR3",     "Vector3",          "",     "Vector3 temp = new Vector3(0.0f,0.0f,0.0f);"],
20         ["VECTOR4",     "Vector4",          "",     "Vector4 temp = new Vector4(0.0f,0.0f,0.0f,0.0f);"],
21         ["MATRIX3",     "Matrix3",          "",     "Matrix3 temp = new Matrix3();"],
22         ["MATRIX",      "Matrix",           "",     "Matrix temp = new Matrix();"  ],
23         ["RECTANGLE",   "RectInteger",      "",     "RectInteger temp = new RectInteger(0,0,0,0);"],
24         ["ROTATION",    "Quaternion",       "",    "Quaternion temp = new Quaternion();"],
25         ["STRING",      "string",           "out",  "string temp;"],
26         ["ARRAY",       "Dali.Property.Array",   "",     "Dali.Property.Array temp = new Dali.Property.Array();"],
27         ["MAP",         "Dali.Property.Map",     "",     "Dali.Property.Map temp = new Dali.Property.Map();"],
28     ]
29 $daliSwigPath = String.new;
30
31 # use the table above to get information for a specific type
32 def getCSharpType( type )
33
34     entry = $typeTable.select{ |a| a.first == type }
35     if( entry == nil )
36       return nil
37     end
38     return entry[0]
39 end
40
41
42 # Property struct stores the information about a property after parsing the C++ DALI_PROPERTY macro
43 $propertyStruct = Struct.new("Property", :name, :type, :writable, :animatable,:constrainInput, :enum, :shortenum,  :csharpGetter, :csharpSetter, :childProperty,)
44
45 # daliClass struct stores a class name and an array of properties
46 $daliClassStruct = Struct.new("DaliClass", :name, :properties )
47
48 # class array stores all the dali classes ( actor, image etc)
49 $daliClassArray = Array.new
50
51 # list of files not generated by swig that we have tried to inject properties into
52 $filesNotWrapped= Array.new
53
54 # stats
55 $totalProperties = 0
56 $totalDaliClasses = 0
57
58 # global paths
59 $rootPath = ""
60 $daliCorePath = ""
61 $daliSwigPath = ""
62
63 # Extracts data  DALI__PROPERTY( "points", ARRAY,true,false,false,Dali::Path::Property::POINTS )
64 def extractPropertyInfo( propertyMacro )
65
66     # want to extract the property name, type + read only status
67     # split the DALI_PROPERTY macro definition by comma and quotes, and delete any empty segments
68     data = propertyMacro.split(/[\s,"]/).reject { |s| s.empty? }
69
70     propertyName = data[1]
71
72     # e.g. turn viewMatrix into ViewMatrix
73     propertyName[0] = propertyName[0].capitalize
74
75     # extract the property enum name Dali::Path::Property::POINTS -> POINTS
76     shortenum =  data[6].split(":").last
77
78     # store the :name, :type, :writable, :animatable, :constrainInput, :enum
79     property = $propertyStruct.new;
80
81     property.name = propertyName
82     property.type = data[2]
83     property.writable = (data[3]=="true")
84     property.animatable = (data[4] == "true")
85     property.constrainInput = (data[5]=="true")
86     property.enum = shortenum
87
88     return property;
89 end
90
91 # Extracts data from Toolkit property definition
92 def extractToolkitPropertyInfo( propertyMacro )
93
94     # Extract the property name, type
95     property = $propertyStruct.new;
96
97     # Split the macro definition by comma and quotes, close bracket and delete any empty segments
98     data = propertyMacro.split(/[\s,")]/).reject { |s| s.empty? }
99
100     if(data[1] == "PropertyRegistration")
101
102       # Properties defined in Control using PropertyRegistration
103       # const PropertyRegistration Control::Impl::PROPERTY_1(typeRegistration, "styleName", Toolkit::Control::Property::STYLE_NAME, Property::STRING, &Control::Impl::SetProperty, &Control::Impl::GetProperty);
104
105       # Creates an array of strings that looks like this:
106       # const 0
107       # PropertyRegistration 1
108       # Control::Impl::PROPERTY_1 2
109       # typeRegistration 3
110       # styleName 4
111       # Toolkit::Control::Property::STYLE_NAME 5
112       # Property::STRING 6
113       # &Control::Impl::SetProperty 7
114       # &Control::Impl::GetProperty 8
115       #
116
117       property.name = data[4]
118
119       propertyType = data[6].rpartition("::")
120       property.type = propertyType[2]
121
122       propertyEnum = data[5].rpartition("::")
123       property.enum = propertyEnum[2]
124
125     else
126
127       # Properties defined in macro DALI_PROPERTY_REGISTRATION or DALI_ANIMATABLE_PROPERTY_REGISTRATION or DALI_CHILD_PROPERTY_REGISTRATION
128       # or DALI_ANIMATABLE_PROPERTY_REGISTRATION_WITH_DEFAULT:
129       # DALI_PROPERTY_REGISTRATION(Toolkit, TextLabel, "multiLine", BOOLEAN, MULTI_LINE)
130       # DALI_ANIMATABLE_PROPERTY_REGISTRATION_WITH_DEFAULT(Toolkit, ImageView, "pixelArea", Vector4(0.f, 0.f, 1.f, 1.f), PIXEL_AREA)
131
132       # Creates an array of strings that looks like this:
133       # DALI_PROPERTY_REGISTRATION( 0
134       # Toolkit 1
135       # PageTurnView 2
136       # pageSize 3
137       # VECTOR2 4
138       # PAGE_SIZE 5
139       #
140
141       property.name = data[3]
142
143       #puts property.name
144       if property.name == "image"
145         property.name = "imageMap"
146       end
147
148       if( data[0] == "DALI_ANIMATABLE_PROPERTY_REGISTRATION_WITH_DEFAULT(" )
149         # TODO: Need to work out the property type from the value
150         property.type = "VECTOR4"
151       else
152         property.type = data[4]
153       end
154
155       property.enum = data[data.length-1]
156
157     end
158
159     # e.g. turn styleName into StyleName
160     property.name[0] = property.name[0].capitalize
161
162     property.writable = true
163     property.animatable = false
164     property.constrainInput = false
165     property.childProperty = false;
166
167     # check to see if it's a child property
168     if( data[0] == "DALI_CHILD_PROPERTY_REGISTRATION(" )
169       #puts(" #{property.name} is child property ")
170       property.childProperty = true;
171     end
172     if( data[0] == "DALI_ANIMATABLE_PROPERTY_REGISTRATION(" )
173       #puts("  #{property.name} is animatable")
174       property.animatable = true;
175     end
176
177     return property;
178 end
179
180 def writePropertiesToCSharpFile( daliClass )
181
182    # open the CSharp file autogenerated by SWIG
183   swigFiles =  $daliSwigPath + "/csharp/"
184
185   fileName =(swigFiles+daliClass.name) + ".cs"
186
187   # it's possible some classes in dali-core aren't being wrapped by swig, so if the swig generated file
188   # doesn't exist just return
189   if( ! File.exist?(fileName) )
190     $filesNotWrapped.push("#{daliClass.name}.cs ")
191     return
192   end
193
194   File.open(fileName, 'r+') do |file|
195
196   last_line  =0
197   file.each { last_line = file.pos unless file.eof? }
198
199   # we seek to the end of the file... minus 3 characters which lets us overwrite the 2 closing brackets
200   # so we can insert the getter/setter stuff into the file.
201   file.seek( last_line-3, IO::SEEK_SET)
202
203   for property in daliClass.properties
204
205     if (!property.childProperty)
206       file.write( property.csharpGetter );
207       file.write( property.csharpSetter );
208     end
209
210   end
211
212   file.write("\n}\n\n}");  # re-insert the closing brackets we over-wrote
213   end
214
215   puts("Injected #{daliClass.properties.length} C# Properties into #{daliClass.name}.cs".blueBackground)
216
217 end
218
219 def writeChildPropertiesToCSharpFile( daliClass )
220
221   # open the CSharp file autogenerated by SWIG
222   swigFiles =  $daliSwigPath + "/csharp/"
223
224   # Add all the child properties to Control
225   fileName = (swigFiles+"Control") + ".cs"
226
227   if( ! File.exist?(fileName) )
228     return
229   end
230
231   File.open(fileName, 'r+') do |file|
232
233   last_line  =0
234   file.each { last_line = file.pos unless file.eof? }
235
236   # we seek to the end of the file... minus 3 characters which lets us overwrite the 2 closing brackets
237   # so we can insert the getter/setter stuff into the file.
238   file.seek( last_line-3, IO::SEEK_SET)
239
240   $childPropertyCount = 0
241
242   for property in daliClass.properties
243
244     if (property.childProperty)
245       file.write( property.csharpGetter );
246       file.write( property.csharpSetter );
247       $childPropertyCount += 1
248     end
249
250   end
251
252   file.write("\n}\n\n}");  # re-insert the closing brackets we over-wrote
253   end
254
255   puts("Injected #{$childPropertyCount} C# Child Properties into #{"Control"}.cs".blueBackground)
256
257 end
258
259 # Write the CSharp data to the generated .cs file
260 def writeCSharpData
261
262     for daliClass in  $daliClassArray
263
264         #puts ( daliClass.name )
265
266         hasChildProperties = false
267
268         for property in daliClass.properties
269             propertyInfo = getCSharpType( property.type )
270
271             if( propertyInfo.length() < 2 )
272                 # some types aren't supported yet like Rotation
273                 next
274             end
275
276             $totalProperties+=1  #  keep track of total
277
278             propertyType = propertyInfo[1]    # e.g. bool or int
279             propertyArg =  propertyInfo[2]  # e.g. ref or out
280             tempDeclaration = propertyInfo[3] # e.g. bool temp;
281
282             propertyName = "#{daliClass.name}.Property.#{property.enum}"
283
284             if property.childProperty
285               propertyName = "#{daliClass.name}.ChildProperty.#{property.enum}"
286               hasChildProperties = true
287             end
288
289             property.csharpGetter ="  public #{propertyType} #{property.name} \n"\
290                      "  { \n"\
291                      "    get \n" \
292                      "    {\n"\
293                      "      #{tempDeclaration}\n"\
294                      "      GetProperty( #{propertyName}).Get( #{propertyArg} temp );\n"\
295                      "      return temp;\n"\
296                      "    }\n"
297
298             if property.writable
299                   #text.SetProperty(TextLabel.Property.HORIZONTAL_ALIGNMENT, new Property.Value("CENTER"));
300                   property.csharpSetter = "    set \n" \
301                          "    { \n"\
302                          "      SetProperty( #{propertyName}, new Dali.Property.Value( value ) );\n" \
303                          "    }\n"\
304                          "  }\n"
305             else
306                    property.csharpSetter = "}"  # close the opening property declaration
307             end
308         end
309         # write normal properties to the class's own csharp file
310         writePropertiesToCSharpFile( daliClass )
311         # write child properties to Control.cs
312         if (hasChildProperties)
313           writeChildPropertiesToCSharpFile( daliClass )
314         end
315     end
316
317 end
318
319 def getDaliClassItem( className )
320
321    # puts( "getDaliClassItem  "+ className )
322     index = $daliClassArray.index{ |a| a.name == className }
323
324     if( index == nil)
325         # create a new item along with a array for it's properites
326         classItem = $daliClassStruct.new( className, Array.new )
327         $daliClassArray.push( classItem )
328         $totalDaliClasses+=1  # for stats
329     else
330         # puts("class found " + className )
331         classItem = $daliClassArray[ index ]
332     end
333
334     return classItem;
335
336 end
337
338
339
340 def init
341
342     pn = Pathname.new(Dir.pwd)
343     fullPath = pn.to_s
344
345     $rootPath = fullPath.slice(0..( fullPath.index('/dali-toolkit')))
346     $daliCorePath = $rootPath + "dali-core/dali"   # source code path
347     $daliSwigPath = $rootPath + "dali-toolkit/plugins/dali-swig"
348     $daliToolkitPath = $rootPath + "dali-toolkit/dali-toolkit"  # source code path
349
350     puts("--------------------------------------------")
351     puts("Injecting DALi properties into SWIG generated C# files ")
352     puts("")
353
354
355 end
356
357 def writeDaliCoreProperties
358
359     puts("Scanning for DALI_PROPERTY macro in dali-core");
360     puts("Scanning folder: #{$daliCorePath}\n\n");
361
362     # Executed a recursive grep over dali-core for the DALI_PROPERTY macro
363     result =`grep --include *.cpp  -r "DALI_PROPERTY( \" #{$daliCorePath}`
364
365
366     # We now have a list of lines that look like this:
367     # dali/internal/event/animation/path-impl.cpp:DALI__PROPERTY( "points", ARRAY,true,false,false,Dali::Path::Property::POINTS )
368
369     lines = result.split(/\n+/);
370     for line in lines
371
372
373       # Split the line into file name and property macro, splt 2 means just create two strings as we don't want to split
374       # property Dali::Path::Property::POINTS string as well
375
376       data  = line.split(":",2)
377       fileName = data[0];
378       macro = data[1];
379
380       # Get the class name from the filename  ( e.g. image-actor-impl.cpp => image-actor)
381       className = File.basename(fileName,"-impl.cpp").capitalize
382
383       # convert it from image-actor to ImageActor
384       className = className.split(/ |\_|\-/).map(&:capitalize).join
385
386       # Get the property information ( name, type, read/writeable)
387       propertyInfo = extractPropertyInfo( macro );
388
389       # get or create a new DALi class item which stores the property information
390       classItem  = getDaliClassItem( className )
391
392       classItem.properties.push( propertyInfo )
393
394     end
395
396     writeCSharpData()
397 end
398
399 def writeDaliToolkitProperties
400
401
402     puts("\nScanning for PROPERTY_REGISTRATION macros in dali-toolkit");
403     puts("Scanning folder: #{$daliToolkitPath}\n\n");
404
405     $daliClassArray.clear;
406
407     # Executed a recursive grep over dali-toolkit for following macros
408     # DALI_PROPERTY_REGISTRATION
409     # DALI_ANIMATABLE_PROPERTY_REGISTRATION
410     # DALI_CHILD_PROPERTY_REGISTRATION
411     result =`grep --include *.cpp  -w 'Control::Impl::SetProperty\\|DALI_ANIMATABLE_PROPERTY_REGISTRATION_WITH_DEFAULT(\\|DALI_CHILD_PROPERTY_REGISTRATION(\\|DALI_ANIMATABLE_PROPERTY_REGISTRATION(\\|DALI_PROPERTY_REGISTRATION' -r #{$daliToolkitPath}`
412
413     if( result == "" )
414       puts("Error parsing #{$daliToolkitPath} no properties found")
415       return
416     end
417     # create an array to store each DALi class and it's assoc
418     classArray = Array.new
419
420     # We now have a list of lines that look like this:
421     # text-controls/text-label-impl.cpp:DALI_PROPERTY_REGISTRATION( Toolkit, TextLabel, "multiLine", BOOLEAN, MULTI_LINE )
422     lines = result.split(/\n+/);
423     for line in lines
424
425
426       # Split the line into file name and property macro, split 2 means just create two strings
427       data  = line.split(":",2)
428       fileName = data[0];
429       macro = data[1];
430
431       # Get the class name from the filename  ( e.g. image-actor-impl.cpp => image-actor)
432       className = File.basename(fileName,"-impl.cpp").capitalize
433
434       # convert it from image-actor to ImageActor
435       className = className.split(/ |\_|\-/).map(&:capitalize).join
436
437       #puts(className);
438       #puts(fileName);
439
440         # Get the property information ( name, type, read/writeable)
441       propertyInfo = extractToolkitPropertyInfo( macro );
442
443       # get or create a new DALi class item which stores the property information
444       classItem  = getDaliClassItem( className )
445
446       classItem.properties.push( propertyInfo )
447
448     end
449
450     writeCSharpData()
451
452 end
453
454 # helper class to color the background
455 class String
456 def blueBackground;   "\e[44m#{self}\e[0m" end
457 end
458
459 def writeStats
460
461   puts("\nFiles that have not been wrapped file by SWIG ( not included in dali.i file):")
462   for i in $filesNotWrapped
463     puts(i)
464   end
465
466   puts("Done. Injected #{$totalProperties} properties into #{$totalDaliClasses} DALi C# classes".blueBackground)
467
468 end
469
470 init()
471
472 writeDaliCoreProperties()
473
474 writeDaliToolkitProperties()
475
476 writeStats()
477