1 /* 2 * This file is part of gtkD. 3 * 4 * gtkD is free software; you can redistribute it and/or modify 5 * it under the terms of the GNU Lesser General Public License 6 * as published by the Free Software Foundation; either version 3 7 * of the License, or (at your option) any later version, with 8 * some exceptions, please read the COPYING file. 9 * 10 * gtkD is distributed in the hope that it will be useful, 11 * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 * GNU Lesser General Public License for more details. 14 * 15 * You should have received a copy of the GNU Lesser General Public License 16 * along with gtkD; if not, write to the Free Software 17 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110, USA 18 */ 19 20 module gtkd.Implement; 21 22 import std.algorithm; 23 import std.traits; 24 import std.meta; 25 import std.range; 26 import std.string; 27 import std.uni; 28 import std.conv; 29 import gobject.c.types; 30 31 /** 32 * This template generates the boilerplate needed to override 33 * GTK functions from D. 34 * 35 * Example: 36 * -------------------------- 37 * class MyApplication : Application 38 * { 39 * import gtkd.Implement; 40 * import gobject.c.functions : g_object_newv; 41 * 42 * mixin ImplementClass!GtkApplication; 43 * 44 * this() 45 * { 46 * //TODO: sort out the constructor. 47 * super(cast(GtkApplication*)g_object_newv(getType(), 0, null), true); 48 * 49 * setApplicationId("org.gtkd.demo.popupmenu"); 50 * setFlags(GApplicationFlags.FLAGS_NONE); 51 * } 52 * 53 * override void activate() 54 * { 55 * new PopupMenuDemo(this); 56 * } 57 * } 58 * -------------------------- 59 */ 60 mixin template ImplementClass(Class) 61 { 62 mixin(ImplementClassImpl!(Class, typeof(this))()); 63 } 64 65 /** 66 * This template generates the boilerplate needed to implement a 67 * GTK interface in D. 68 * 69 * Base is the Gtk struct for the base class, and Iface is the 70 * Gtk Iface struct for the interface. 71 * 72 * In your constructor you will need to instantiate the Gtk class 73 * by calling the ObjectG costructor: `super(getType(), null);` 74 * 75 * If you are using ImplementInterface in conjunction with ImplementClass 76 * you will need to mixin ImplementClass before mixing in any interfaces. 77 */ 78 mixin template ImplementInterface(Base, Iface) 79 { 80 mixin(ImplementInterfaceImpl!(Base, Iface, typeof(this))()); 81 } 82 83 template ImplementClassImpl(Klass, Impl) 84 { 85 string ImplementClassImpl() 86 { 87 string result; 88 89 result ~= "import glib.Str;\n"~ 90 "import gobject.ObjectG;\n"~ 91 "import gobject.Type : Type;\n"~ 92 "import gobject.c.functions : g_type_class_peek_parent, g_object_get_data;\n"; 93 94 if ( !is(Klass == gobject.c.types.GObject) ) 95 result ~= "import "~ getTypeImport!Klass() ~": "~ getTypeFunction!Klass()[0..$-2] ~";\n"; 96 97 if ( !hasMember!(Impl, toCamelCase!Impl()) ) 98 { 99 result ~= "\nstruct "~ toPascalCase!Impl() ~"\n"~ 100 "{\n"~ 101 "\t"~ Klass.stringof ~" parentInstance;\n"~ 102 "}\n\n"; 103 104 result ~= "struct "~ toPascalCase!Impl() ~"Class\n"~ 105 "{\n"~ 106 "\t"~ Klass.stringof ~"Class parentClass;\n"~ 107 "}\n\n"; 108 109 result ~= "protected "~ toPascalCase!Impl() ~"* "~ toCamelCase!Impl() ~";\n\n"; 110 111 result ~= "protected override void* getStruct()\n"~ 112 "{\n"~ 113 "\treturn cast(void*)gObject;\n"~ 114 "}\n\n"; 115 } 116 117 if ( !implements!Impl("getType") ) 118 { 119 result ~= "public static GType getType()\n"~ 120 "{\n"~ 121 "\timport std.algorithm : startsWith;\n\n"~ 122 "\tGType "~ toCamelCase!Impl() ~"Type = Type.fromName(\""~ toPascalCase!Impl() ~"\");\n\n"~ 123 "\tif ("~ toCamelCase!Impl() ~"Type == GType.INVALID)\n"~ 124 "\t{\n"~ 125 "\t\t"~ toCamelCase!Impl() ~"Type = Type.registerStaticSimple(\n"~ 126 "\t\t\t"~ getTypeFunction!Klass() ~",\n"~ 127 "\t\t\t\""~ toPascalCase!Impl() ~"\",\n"~ 128 "\t\t\tcast(uint)"~ toPascalCase!Impl() ~"Class.sizeof,\n"~ 129 "\t\t\tcast(GClassInitFunc) &"~ toCamelCase!Impl() ~"ClassInit,\n"~ 130 "\t\t\tcast(uint)"~ toPascalCase!Impl() ~".sizeof, null, cast(GTypeFlags)0);\n\n"~ 131 "\t\tforeach ( member; __traits(derivedMembers, "~ Impl.stringof ~") )\n"~ 132 "\t\t{\n"~ 133 "\t\t\tstatic if ( member.startsWith(\"_implementInterface\") )\n"~ 134 "\t\t\t\t__traits(getMember, "~ Impl.stringof ~", member)("~ toCamelCase!Impl() ~"Type);\n"~ 135 "\t\t}\n"~ 136 "\t}\n\n"~ 137 "\treturn "~ toCamelCase!Impl() ~"Type;\n"~ 138 "}\n\n"; 139 } 140 141 result ~= "extern(C)\n{\n"; 142 143 if ( !implements!Impl(toCamelCase!Impl() ~"ClassInit") ) 144 { 145 result ~= "static void "~ toCamelCase!Impl() ~"ClassInit (void* klass)\n"~ 146 "{\n"~ 147 "\t"~ fullyQualifiedName!(getClass!Klass) ~"* "~ toCamelCase!(getClass!Klass)() ~" = cast("~ fullyQualifiedName!(getClass!Klass) ~"*)klass;\n"; 148 149 result ~= setFunctionPointers!(getClass!Klass)(); 150 151 result ~= "}\n\n"; 152 } 153 154 result ~= getWrapFunctions!(getClass!Klass)(); 155 result ~= "}"; 156 157 return result; 158 } 159 160 string setFunctionPointers(GtkClass)() 161 { 162 string result; 163 164 alias names = FieldNameTuple!GtkClass; 165 foreach ( i, member; Fields!GtkClass ) 166 { 167 static if ( names[i] == "parentClass" ) 168 { 169 result ~= "\t"~ fullyQualifiedName!member ~"* "~ toCamelCase!member() ~" = cast("~ fullyQualifiedName!member ~"*)klass;\n"; 170 result ~= setFunctionPointers!member(); 171 } 172 else if ( isCallable!member && 173 implements!Impl(names[i]) && 174 !implements!Impl(toCamelCase!Impl() ~ names[i].capitalizeFirst) ) 175 //TODO: __traits(isOverrideFunction, Foo.foo) ? 176 { 177 result ~= "\t"~ toCamelCase!GtkClass() ~"."~ names[i] ~" = &"~ toCamelCase!Impl() ~ names[i].capitalizeFirst ~";\n"; 178 } 179 } 180 181 result ~= "\n"; 182 183 return result; 184 } 185 186 string getWrapFunctions(GtkClass)() 187 { 188 string result; 189 190 alias names = FieldNameTuple!GtkClass; 191 foreach ( i, member; Fields!GtkClass ) 192 { 193 static if ( names[i] == "parentClass" ) 194 { 195 result ~= getWrapFunctions!member(); 196 } 197 else static if ( isCallable!member && 198 implements!Impl(names[i]) && 199 !implements!Impl(toCamelCase!Impl() ~ names[i].capitalizeFirst) ) 200 //TODO: __traits(isOverrideFunction, Foo.foo) ? 201 { 202 result ~= getWrapFunction!(Impl, member, names[i]); 203 } 204 } 205 206 return result; 207 } 208 } 209 210 template ImplementInterfaceImpl(Base, Klass, Impl) 211 { 212 string ImplementInterfaceImpl() 213 { 214 string result; 215 216 result ~= "import glib.Str;\n"~ 217 "import gobject.Type : Type;\n"~ 218 "import gobject.c.functions : g_type_class_peek_parent, g_object_get_data;\n"; 219 220 if ( !is(Base == gobject.c.types.GObject) ) 221 result ~= "import "~ getTypeImport!Base() ~": "~ getTypeFunction!Base()[0..$-2] ~";\n"; 222 223 result ~= "import "~ getTypeImport!Klass() ~" : "~ getTypeFunction!Klass()[0..$-2] ~";\n\n"; 224 225 if ( !hasMember!(Impl, toCamelCase!Impl()) ) 226 { 227 result ~= "\nstruct "~ toPascalCase!Impl() ~"\n"~ 228 "{\n"~ 229 "\t"~ Base.stringof ~" parentInstance;\n"~ 230 "}\n\n"; 231 232 result ~= "struct "~ toPascalCase!Impl() ~"Class\n"~ 233 "{\n"~ 234 "\t"~ Base.stringof ~"Class parentClass;\n"~ 235 "}\n\n"; 236 237 result ~= "protected "~ toPascalCase!Impl() ~"* "~ toCamelCase!Impl() ~";\n\n"; 238 239 result ~= "protected override void* getStruct()\n"~ 240 "{\n"~ 241 "\treturn cast(void*)gObject;\n"~ 242 "}\n\n"; 243 244 if ( is(Base == gobject.c.types.GObject) ) 245 { 246 result ~= "public this()\n"~ 247 "{\n"~ 248 "\tauto p = super(getType(), null);\n"~ 249 "\t"~ toCamelCase!Impl() ~" = cast("~ toPascalCase!Impl() ~"*) p.getObjectGStruct();\n"~ 250 "}\n\n"; 251 } 252 } 253 254 if ( !implements!Impl("getType") ) 255 { 256 result ~= "public static GType getType()\n"~ 257 "{\n"~ 258 "\tGType "~ toCamelCase!Impl() ~"Type = Type.fromName(\""~ toPascalCase!Impl() ~"\");\n\n"~ 259 "\tif ("~ toCamelCase!Impl() ~"Type == GType.INVALID)\n"~ 260 "\t{\n"~ 261 "\t\t"~ toCamelCase!Impl() ~"Type = Type.registerStaticSimple(\n"~ 262 "\t\t\t"~ getTypeFunction!Base() ~",\n"~ 263 "\t\t\t\""~ toPascalCase!Impl() ~"\",\n"~ 264 "\t\t\tcast(uint)"~ toPascalCase!Impl() ~"Class.sizeof,\n"~ 265 "\t\t\tcast(GClassInitFunc) &"~ toCamelCase!Impl() ~"ClassInit,\n"~ 266 "\t\t\tcast(uint)"~ toPascalCase!Impl() ~".sizeof, null, cast(GTypeFlags)0);\n\n"~ 267 "\t\tforeach ( member; __traits(derivedMembers, "~ Impl.stringof ~") )\n"~ 268 "\t\t{\n"~ 269 "\t\t\tstatic if ( member.startsWith(\"_implementInterface\") )\n"~ 270 "\t\t\t\t__traits(getMember, "~ Impl.stringof ~", member)("~ toCamelCase!Impl() ~"Type);\n"~ 271 "\t\t}\n"~ 272 "\t}\n\n"~ 273 "\treturn "~ toCamelCase!Impl() ~"Type;\n"~ 274 "}\n\n"; 275 } 276 277 result ~= "static void _implementInterface"~ Klass.stringof ~"(GType type)\n"~ 278 "{\n"~ 279 "\tGInterfaceInfo "~ Klass.stringof ~"Info =\n"~ 280 "\t{\n"~ 281 "\t\tcast(GInterfaceInitFunc) &"~ toCamelCase!Klass() ~"Init,\n"~ 282 "\t\tnull,\n"~ 283 "\t\tnull\n"~ 284 "\t};\n"~ 285 "\tType.addInterfaceStatic(type, "~ getTypeFunction!Klass() ~", &"~ Klass.stringof ~"Info);\n"~ 286 "};\n\n"; 287 288 result ~= "extern(C)\n{\n"; 289 290 if ( !implements!Impl(toCamelCase!Impl() ~"ClassInit") ) 291 { 292 result ~= "static void "~ toCamelCase!Impl() ~"ClassInit (void* klass)\n"~ 293 "{\n"~ 294 "\t"~ fullyQualifiedName!(getClass!Base) ~"* "~ toCamelCase!(getClass!Base)() ~" = cast("~ fullyQualifiedName!(getClass!Base) ~"*)klass;\n"~ 295 "}\n\n"; 296 } 297 298 if ( !implements!Impl(toCamelCase!Klass() ~"Init") ) 299 { 300 result ~= "static void "~ toCamelCase!Klass() ~"Init ("~ Klass.stringof ~" *iface)\n"~ 301 "{\n"; 302 303 auto names = FieldNameTuple!Klass; 304 foreach ( i, member; Fields!Klass ) 305 { 306 if ( isCallable!member && implements!Impl(names[i]) && (!implements!Impl("addOn"~ names[i].capitalizeFirst) || implements!Impl(toCamelCase!Impl() ~ names[i].capitalizeFirst) ) ) 307 { 308 result ~= "\tiface."~ names[i] ~" = &"~ toCamelCase!Impl() ~ names[i].capitalizeFirst ~";\n"; 309 } 310 } 311 312 result ~= "}\n\n"; 313 } 314 315 alias names = FieldNameTuple!Klass; 316 foreach ( i, member; Fields!Klass ) 317 { 318 if ( isCallable!member && 319 implements!Impl(names[i]) && 320 !implements!Impl(toCamelCase!Impl() ~ names[i].capitalizeFirst) && 321 !implements!Impl("addOn"~ names[i].capitalizeFirst) ) 322 { 323 result ~= getWrapFunction!(Impl, member, names[i]); 324 } 325 } 326 327 result ~= "}"; 328 329 return result; 330 } 331 } 332 333 private string getTypeFunction(Iface)() 334 { 335 string result; 336 337 if ( is(Iface == gobject.c.types.GObject) ) 338 return "GType.OBJECT"; 339 else 340 { 341 foreach ( i, char c; Iface.stringof ) 342 { 343 if ( c.isUpper && i > 0 ) 344 result ~= "_"~c; 345 else 346 result ~= c; 347 } 348 349 return result.toLower.replace("_iface", "")~ "_get_type()"; 350 } 351 } 352 353 private string getTypeImport(Iface)() 354 { 355 return fullyQualifiedName!Iface.replace("types."~ Iface.stringof, "functions"); 356 } 357 358 template getClass(Instance) 359 { 360 mixin("import "~ getClassImport!Instance() ~"; alias getClass = "~ Instance.stringof ~"Class;"); 361 } 362 363 private string getClassImport(Klass)() 364 { 365 return fullyQualifiedName!Klass.replace("."~ Klass.stringof, ""); 366 } 367 368 private string getWrapFunction(Impl, Member, string name)() 369 { 370 string result; 371 372 static if ( isCallable!Member ) 373 { 374 alias Params = Parameters!Member; 375 alias STC = ParameterStorageClass; 376 auto ParamStorage = [STC.none, ParameterStorageClassTuple!(__traits(getMember, Impl, name))]; 377 auto ParamNames = ["iface", ParameterIdentifierTuple!(__traits(getMember, Impl, name))]; 378 alias DParamTypes = AliasSeq!(void, Parameters!(__traits(getMember, Impl, name))); 379 380 result ~= "static "~ ReturnType!Member.stringof ~" "~ toCamelCase!Impl() ~ name.capitalizeFirst ~"("; 381 382 foreach ( i, param; Params ) 383 { 384 if ( i > 0 ) 385 result ~= ", "; 386 result ~= param.stringof ~" "~ ParamNames[i]; 387 } 388 389 result ~= ")\n"~ 390 "{\n"; 391 392 if ( implements!Impl("get"~ Impl.stringof ~"Struct") && implements!Impl("getStruct") ) 393 result ~= "\tauto impl = ObjectG.getDObject!("~ Impl.stringof ~")(cast("~ toPascalCase!Impl() ~"*)iface);\n"; 394 else 395 result ~= "\tauto impl = cast("~ Impl.stringof ~")g_object_get_data(cast(GObject*)iface, \"GObject\".ptr);\n"; 396 397 foreach ( i, param; Params ) 398 { 399 if ( ParamStorage[i] == STC.out_ && isGtkdType!(DParamTypes[i]) ) 400 result ~= "\t"~ DParamTypes[i].stringof ~" d_"~ ParamNames[i] ~";\n"; 401 else if ( ParamStorage[i] == STC.ref_ && isGtkdType!(DParamTypes[i]) ) 402 result ~= "\t"~ DParamTypes[i].stringof ~" d_"~ ParamNames[i] ~" = "~ ParamNames[i] ~".get"~ DParamTypes[i].stringof ~"Struct();\n"; 403 } 404 405 if ( is(ReturnType!Member == void) ) 406 result ~= "\n\timpl."~ name ~"("; 407 else 408 result ~= "\n\tauto ret = impl."~ name ~"("; 409 410 foreach ( i, param; Params ) 411 { 412 if ( i == 0 ) 413 continue; 414 else 415 { 416 if ( i > 1 ) 417 result ~= ", "; 418 419 if ( (ParamStorage[i] == STC.out_ || ParamStorage[i] == STC.ref_) && isGtkdType!(DParamTypes[i]) ) 420 result ~= "d_"~ ParamNames[i]; 421 else if ( isGtkdType!(DParamTypes[i]) ) 422 result ~= "ObjectG.getDObject!("~ DParamTypes[i].stringof ~")("~ ParamNames[i] ~")"; 423 else 424 result ~= ParamNames[i]; 425 } 426 } 427 428 result ~= ");\n\n"; 429 430 foreach ( i, param; Params ) 431 { 432 if ( (ParamStorage[i] == STC.out_ || ParamStorage[i] == STC.ref_) && isGtkdType!(DParamTypes[i]) ) 433 { 434 result ~= "\tif ( d_"~ ParamNames[i] ~" !is null )\n"~ 435 "\t\t*"~ ParamNames[i] ~" = *d_"~ ParamNames[i] ~".get"~ DParamTypes[i].stringof ~"Struct();\n"; 436 } 437 } 438 439 if ( isGtkdType!(ReturnType!(__traits(getMember, Impl, name))) && isPointer!(ReturnType!Member) ) 440 result ~= "\treturn ret ? ret.get"~ (ReturnType!(__traits(getMember, Impl, name))).stringof ~"Struct() : null;\n"; 441 else if ( !is(ReturnType!Member == void) ) 442 result ~= "\treturn ret;\n"; 443 444 result ~= "}\n\n"; 445 } 446 447 return result; 448 } 449 450 private string toCamelCase(Type)() 451 { 452 string result; 453 454 foreach (i, word; to!string(fullyQualifiedName!Type).split(".")) 455 { 456 if ( i == 0 ) 457 word = word[0 .. 1].toLower ~ word[1 .. $]; 458 else 459 word = word.capitalizeFirst; 460 461 result ~= word; 462 } 463 464 return result; 465 } 466 467 private string toPascalCase(Type)() 468 { 469 string result; 470 471 foreach (word; to!string(fullyQualifiedName!Type).split(".")) 472 { 473 result ~= word.capitalizeFirst; 474 } 475 476 return result; 477 } 478 479 private template isGtkdType(T) 480 { 481 static if ( __traits(compiles, new T(cast(typeof(T.tupleof[0]))null, true)) ) 482 enum bool isGtkdType = hasMember!(T, "get"~ T.stringof ~"Struct"); 483 else 484 enum bool isGtkdType = false; 485 } 486 487 private bool implements(Impl)(string member) 488 { 489 return (cast(string[])[__traits(derivedMembers, Impl)]).canFind(member); 490 } 491 492 private string capitalizeFirst(string str) 493 { 494 if ( str.empty ) 495 return str; 496 else if ( str.length == 1 ) 497 return str.toUpper; 498 else 499 return str[0 .. 1].toUpper ~ str[1 .. $]; 500 }