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.Type : Type;\n"~ 91 "import gobject.c.functions : g_type_class_peek_parent, g_object_get_data;\n"; 92 93 if ( !is(Klass == gobject.c.types.GObject) ) 94 result ~= "import "~ getTypeImport!Klass() ~": "~ getTypeFunction!Klass()[0..$-2] ~";\n"; 95 96 if ( !hasMember!(Impl, toCamelCase!Impl()) ) 97 { 98 result ~= "\nstruct "~ toPascalCase!Impl() ~"\n"~ 99 "{\n"~ 100 "\t"~ Klass.stringof ~" parentInstance;\n"~ 101 "}\n\n"; 102 103 result ~= "struct "~ toPascalCase!Impl() ~"Class\n"~ 104 "{\n"~ 105 "\t"~ Klass.stringof ~"Class parentClass;\n"~ 106 "}\n\n"; 107 108 result ~= "protected "~ toPascalCase!Impl() ~"* "~ toCamelCase!Impl() ~";\n"~ 109 "protected static "~ Klass.stringof ~"Class* parentClass = null;\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 "\tparentClass = cast("~ Klass.stringof ~"Class*) g_type_class_peek_parent(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 template getClass(Instance) 210 { 211 mixin("import "~ getClassImport!Instance() ~"; alias getClass = "~ Instance.stringof ~"Class;"); 212 } 213 214 private string getClassImport(Klass)() 215 { 216 return fullyQualifiedName!Klass.replace("."~ Klass.stringof, ""); 217 } 218 } 219 220 template ImplementInterfaceImpl(Base, Klass, Impl) 221 { 222 string ImplementInterfaceImpl() 223 { 224 string result; 225 226 result ~= "import glib.Str;\n"~ 227 "import gobject.Type : Type;\n"~ 228 "import gobject.c.functions : g_type_class_peek_parent, g_object_get_data;\n"; 229 230 if ( !is(Base == gobject.c.types.GObject) ) 231 result ~= "import "~ getTypeImport!Base() ~": "~ getTypeFunction!Base()[0..$-2] ~";\n"; 232 233 result ~= "import "~ getTypeImport!Klass() ~" : "~ getTypeFunction!Klass()[0..$-2] ~";\n\n"; 234 235 if ( !hasMember!(Impl, toCamelCase!Impl()) ) 236 { 237 result ~= "\nstruct "~ toPascalCase!Impl() ~"\n"~ 238 "{\n"~ 239 "\t"~ Base.stringof ~" parentInstance;\n"~ 240 "}\n\n"; 241 242 result ~= "struct "~ toPascalCase!Impl() ~"Class\n"~ 243 "{\n"~ 244 "\t"~ Base.stringof ~"Class parentClass;\n"~ 245 "}\n\n"; 246 247 result ~= "protected "~ toPascalCase!Impl() ~"* "~ toCamelCase!Impl() ~";\n"~ 248 "protected static "~ Base.stringof ~"Class* parentClass = null;\n\n"; 249 250 result ~= "protected override void* getStruct()\n"~ 251 "{\n"~ 252 "\treturn cast(void*)gObject;\n"~ 253 "}\n\n"; 254 255 if ( is(Base == gobject.c.types.GObject) ) 256 { 257 result ~= "public this()\n"~ 258 "{\n"~ 259 "\tauto p = super(getType(), null);\n"~ 260 "\t"~ toCamelCase!Impl() ~" = cast("~ toPascalCase!Impl() ~"*) p.getObjectGStruct();\n"~ 261 "}\n\n"; 262 } 263 } 264 265 if ( !implements!Impl("getType") ) 266 { 267 result ~= "public static GType getType()\n"~ 268 "{\n"~ 269 "\tGType "~ toCamelCase!Impl() ~"Type = Type.fromName(\""~ toPascalCase!Impl() ~"\");\n\n"~ 270 "\tif ("~ toCamelCase!Impl() ~"Type == GType.INVALID)\n"~ 271 "\t{\n"~ 272 "\t\t"~ toCamelCase!Impl() ~"Type = Type.registerStaticSimple(\n"~ 273 "\t\t\t"~ getTypeFunction!Base() ~",\n"~ 274 "\t\t\t\""~ toPascalCase!Impl() ~"\",\n"~ 275 "\t\t\tcast(uint)"~ toPascalCase!Impl() ~"Class.sizeof,\n"~ 276 "\t\t\tcast(GClassInitFunc) &"~ toCamelCase!Impl() ~"ClassInit,\n"~ 277 "\t\t\tcast(uint)"~ toPascalCase!Impl() ~".sizeof, null, cast(GTypeFlags)0);\n\n"~ 278 "\t\tforeach ( member; __traits(derivedMembers, "~ Impl.stringof ~") )\n"~ 279 "\t\t{\n"~ 280 "\t\t\tstatic if ( member.startsWith(\"_implementInterface\") )\n"~ 281 "\t\t\t\t__traits(getMember, "~ Impl.stringof ~", member)("~ toCamelCase!Impl() ~"Type);\n"~ 282 "\t\t}\n"~ 283 "\t}\n\n"~ 284 "\treturn "~ toCamelCase!Impl() ~"Type;\n"~ 285 "}\n\n"; 286 } 287 288 result ~= "static void _implementInterface"~ Klass.stringof ~"(GType type)\n"~ 289 "{\n"~ 290 "\tGInterfaceInfo "~ Klass.stringof ~"Info =\n"~ 291 "\t{\n"~ 292 "\t\tcast(GInterfaceInitFunc) &"~ toCamelCase!Klass() ~"Init,\n"~ 293 "\t\tnull,\n"~ 294 "\t\tnull\n"~ 295 "\t};\n"~ 296 "\tType.addInterfaceStatic(type, "~ getTypeFunction!Klass() ~", &"~ Klass.stringof ~"Info);\n"~ 297 "};\n\n"; 298 299 result ~= "extern(C)\n{\n"; 300 301 if ( !implements!Impl(toCamelCase!Impl() ~"ClassInit") ) 302 { 303 result ~= "static void "~ toCamelCase!Impl() ~"ClassInit (void* klass)\n"~ 304 "{\n"~ 305 "\tparentClass = cast("~ Base.stringof ~"Class*) g_type_class_peek_parent(klass);\n"~ 306 "}\n\n"; 307 } 308 309 if ( !implements!Impl(toCamelCase!Klass() ~"Init") ) 310 { 311 result ~= "static void "~ toCamelCase!Klass() ~"Init ("~ Klass.stringof ~" *iface)\n"~ 312 "{\n"; 313 314 auto names = FieldNameTuple!Klass; 315 foreach ( i, member; Fields!Klass ) 316 { 317 if ( isCallable!member && implements!Impl(names[i]) && (!implements!Impl("addOn"~ names[i].capitalizeFirst) || implements!Impl(toCamelCase!Impl() ~ names[i].capitalizeFirst) ) ) 318 { 319 result ~= "\tiface."~ names[i] ~" = &"~ toCamelCase!Impl() ~ names[i].capitalizeFirst ~";\n"; 320 } 321 } 322 323 result ~= "}\n\n"; 324 } 325 326 alias names = FieldNameTuple!Klass; 327 foreach ( i, member; Fields!Klass ) 328 { 329 if ( isCallable!member && 330 implements!Impl(names[i]) && 331 !implements!Impl(toCamelCase!Impl() ~ names[i].capitalizeFirst) && 332 !implements!Impl("addOn"~ names[i].capitalizeFirst) ) 333 { 334 result ~= getWrapFunction!(Impl, member, names[i]); 335 } 336 } 337 338 result ~= "}"; 339 340 return result; 341 } 342 } 343 344 private string getTypeFunction(Iface)() 345 { 346 string result; 347 348 if ( is(Iface == gobject.c.types.GObject) ) 349 return "GType.OBJECT"; 350 else 351 { 352 foreach ( i, char c; Iface.stringof ) 353 { 354 if ( c.isUpper && i > 0 ) 355 result ~= "_"~c; 356 else 357 result ~= c; 358 } 359 360 return result.toLower.replace("_iface", "")~ "_get_type()"; 361 } 362 } 363 364 private string getTypeImport(Iface)() 365 { 366 return fullyQualifiedName!Iface.replace("types."~ Iface.stringof, "functions"); 367 } 368 369 private string getWrapFunction(Impl, Member, string name)() 370 { 371 string result; 372 373 static if ( isCallable!Member ) 374 { 375 alias Params = Parameters!Member; 376 alias STC = ParameterStorageClass; 377 auto ParamStorage = [STC.none, ParameterStorageClassTuple!(__traits(getMember, Impl, name))]; 378 auto ParamNames = ["iface", ParameterIdentifierTuple!(__traits(getMember, Impl, name))]; 379 alias DParamTypes = AliasSeq!(void, Parameters!(__traits(getMember, Impl, name))); 380 381 result ~= "static "~ ReturnType!Member.stringof ~" "~ toCamelCase!Impl() ~ name.capitalizeFirst ~"("; 382 383 foreach ( i, param; Params ) 384 { 385 if ( i > 0 ) 386 result ~= ", "; 387 result ~= param.stringof ~" "~ ParamNames[i]; 388 } 389 390 result ~= ")\n"~ 391 "{\n"; 392 393 if ( implements!Impl("get"~ Impl.stringof ~"Struct") && implements!Impl("getStruct") ) 394 result ~= "\tauto impl = ObjectG.getDObject!("~ Impl.stringof ~")(cast("~ toPascalCase!Impl() ~"*)iface);\n"; 395 else 396 result ~= "\tauto impl = cast("~ Impl.stringof ~")g_object_get_data(cast(GObject*)iface, \"GObject\".ptr);\n"; 397 398 foreach ( i, param; Params ) 399 { 400 if ( ParamStorage[i] == STC.out_ && isGtkdType!(DParamTypes[i]) ) 401 result ~= "\t"~ DParamTypes[i].stringof ~" d_"~ ParamNames[i] ~";\n"; 402 else if ( ParamStorage[i] == STC.ref_ && isGtkdType!(DParamTypes[i]) ) 403 result ~= "\t"~ DParamTypes[i].stringof ~" d_"~ ParamNames[i] ~" = "~ ParamNames[i] ~".get"~ DParamTypes[i].stringof ~"Struct();\n"; 404 } 405 406 if ( is(ReturnType!Member == void) ) 407 result ~= "\n\timpl."~ name ~"("; 408 else 409 result ~= "\n\tauto ret = impl."~ name ~"("; 410 411 foreach ( i, param; Params ) 412 { 413 if ( i == 0 ) 414 continue; 415 else 416 { 417 if ( i > 1 ) 418 result ~= ", "; 419 420 if ( (ParamStorage[i] == STC.out_ || ParamStorage[i] == STC.ref_) && isGtkdType!(DParamTypes[i]) ) 421 result ~= "d_"~ ParamNames[i]; 422 else if ( isGtkdType!(DParamTypes[i]) ) 423 result ~= "ObjectG.getDObject!("~ DParamTypes[i].stringof ~")("~ ParamNames[i] ~")"; 424 else 425 result ~= ParamNames[i]; 426 } 427 } 428 429 result ~= ");\n\n"; 430 431 foreach ( i, param; Params ) 432 { 433 if ( (ParamStorage[i] == STC.out_ || ParamStorage[i] == STC.ref_) && isGtkdType!(DParamTypes[i]) ) 434 { 435 result ~= "\tif ( d_"~ ParamNames[i] ~" !is null )\n"~ 436 "\t\t*"~ ParamNames[i] ~" = *d_"~ ParamNames[i] ~".get"~ DParamTypes[i].stringof ~"Struct();\n"; 437 } 438 } 439 440 if ( isGtkdType!(ReturnType!(__traits(getMember, Impl, name))) && isPointer!(ReturnType!Member) ) 441 result ~= "\treturn ret ? ret.get"~ (ReturnType!(__traits(getMember, Impl, name))).stringof ~"Struct() : null;\n"; 442 else if ( !is(ReturnType!Member == void) ) 443 result ~= "\treturn ret;\n"; 444 445 result ~= "}\n\n"; 446 } 447 448 return result; 449 } 450 451 private string toCamelCase(Type)() 452 { 453 string result; 454 455 foreach (i, word; to!string(fullyQualifiedName!Type).split(".")) 456 { 457 if ( i == 0 ) 458 word = word[0 .. 1].toLower ~ word[1 .. $]; 459 else 460 word = word.capitalizeFirst; 461 462 result ~= word; 463 } 464 465 return result; 466 } 467 468 private string toPascalCase(Type)() 469 { 470 string result; 471 472 foreach (word; to!string(fullyQualifiedName!Type).split(".")) 473 { 474 result ~= word.capitalizeFirst; 475 } 476 477 return result; 478 } 479 480 private template isGtkdType(T) 481 { 482 static if ( __traits(compiles, new T(cast(typeof(T.tupleof[0]))null, true)) ) 483 enum bool isGtkdType = hasMember!(T, "get"~ T.stringof ~"Struct"); 484 else 485 enum bool isGtkdType = false; 486 } 487 488 private bool implements(Impl)(string member) 489 { 490 return (cast(string[])[__traits(derivedMembers, Impl)]).canFind(member); 491 } 492 493 private string capitalizeFirst(string str) 494 { 495 if ( str.empty ) 496 return str; 497 else if ( str.length == 1 ) 498 return str.toUpper; 499 else 500 return str[0 .. 1].toUpper ~ str[1 .. $]; 501 }