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 //pragma(msg, ImplementClassImpl!(Class, typeof(this))()); 63 mixin(ImplementClassImpl!(Class, typeof(this))()); 64 } 65 66 /** 67 * This template generates the boilerplate needed to implement a 68 * GTK interface in D. 69 * 70 * Base is the Gtk struct for the base class, and Iface is the 71 * Gtk Iface struct for the interface. 72 * 73 * In your constructor you will need to instantiate the Gtk class 74 * by calling the ObjectG costructor: `super(getType(), null);` 75 * 76 * If you are using ImplementInterface in conjunction with ImplementClass 77 * you will need to mixin ImplementClass before mixing in any interfaces. 78 */ 79 mixin template ImplementInterface(Base, Iface) 80 { 81 mixin(ImplementInterfaceImpl!(Base, Iface, typeof(this))()); 82 } 83 84 template ImplementClassImpl(Klass, Impl) 85 { 86 string ImplementClassImpl() 87 { 88 string result; 89 90 result ~= "import glib.Str;\n"~ 91 "import gobject.ObjectG;\n"~ 92 "import gobject.Type : Type;\n"~ 93 "import gobject.c.functions : g_type_class_peek_parent, g_object_get_data;\n"; 94 95 if ( !is(Klass == gobject.c.types.GObject) ) 96 result ~= "import "~ getTypeImport!Klass() ~": "~ getTypeFunction!Klass()[0..$-2] ~";\n"; 97 98 if ( !hasMember!(Impl, toCamelCase!Impl()) ) 99 { 100 result ~= "\nstruct "~ toPascalCase!Impl() ~"\n"~ 101 "{\n"~ 102 "\t"~ Klass.stringof ~" parentInstance;\n"~ 103 "}\n\n"; 104 105 result ~= "struct "~ toPascalCase!Impl() ~"Class\n"~ 106 "{\n"~ 107 "\t"~ Klass.stringof ~"Class parentClass;\n"~ 108 "}\n\n"; 109 110 result ~= "protected "~ toPascalCase!Impl() ~"* "~ toCamelCase!Impl() ~";\n\n"; 111 112 result ~= "protected override void* getStruct()\n"~ 113 "{\n"~ 114 "\treturn cast(void*)gObject;\n"~ 115 "}\n\n"; 116 } 117 118 if ( !implements!Impl("getType") ) 119 { 120 result ~= "public static GType getType()\n"~ 121 "{\n"~ 122 "\timport std.algorithm : startsWith;\n\n"~ 123 "\tGType "~ toCamelCase!Impl() ~"Type = Type.fromName(\""~ toPascalCase!Impl() ~"\");\n\n"~ 124 "\tif ("~ toCamelCase!Impl() ~"Type == GType.INVALID)\n"~ 125 "\t{\n"~ 126 "\t\t"~ toCamelCase!Impl() ~"Type = Type.registerStaticSimple(\n"~ 127 "\t\t\t"~ getTypeFunction!Klass() ~",\n"~ 128 "\t\t\t\""~ toPascalCase!Impl() ~"\",\n"~ 129 "\t\t\tcast(uint)"~ toPascalCase!Impl() ~"Class.sizeof,\n"~ 130 "\t\t\tcast(GClassInitFunc) &"~ toCamelCase!Impl() ~"ClassInit,\n"~ 131 "\t\t\tcast(uint)"~ toPascalCase!Impl() ~".sizeof, null, cast(GTypeFlags)0);\n\n"~ 132 "\t\tforeach ( member; __traits(derivedMembers, "~ Impl.stringof ~") )\n"~ 133 "\t\t{\n"~ 134 "\t\t\tstatic if ( member.startsWith(\"_implementInterface\") )\n"~ 135 "\t\t\t\t__traits(getMember, "~ Impl.stringof ~", member)("~ toCamelCase!Impl() ~"Type);\n"~ 136 "\t\t}\n"~ 137 "\t}\n\n"~ 138 "\treturn "~ toCamelCase!Impl() ~"Type;\n"~ 139 "}\n\n"; 140 } 141 142 result ~= "extern(C)\n{\n"; 143 144 if ( !implements!Impl(toCamelCase!Impl() ~"ClassInit") ) 145 { 146 result ~= "static void "~ toCamelCase!Impl() ~"ClassInit (void* klass)\n"~ 147 "{\n"~ 148 "\t"~ fullyQualifiedName!(getClass!Klass) ~"* "~ toCamelCase!(getClass!Klass)() ~" = cast("~ fullyQualifiedName!(getClass!Klass) ~"*)klass;\n"; 149 150 result ~= setFunctionPointers!(getClass!Klass)(); 151 152 result ~= "}\n\n"; 153 } 154 155 result ~= getWrapFunctions!(getClass!Klass)(); 156 result ~= "}"; 157 158 return result; 159 } 160 161 string setFunctionPointers(GtkClass)() 162 { 163 string result; 164 165 alias names = FieldNameTuple!GtkClass; 166 foreach ( i, member; Fields!GtkClass ) 167 { 168 static if ( names[i] == "parentClass" ) 169 { 170 result ~= "\t"~ fullyQualifiedName!member ~"* "~ toCamelCase!member() ~" = cast("~ fullyQualifiedName!member ~"*)klass;\n"; 171 result ~= setFunctionPointers!member(); 172 } 173 else if ( isCallable!member && 174 implements!Impl(names[i]) && 175 !implements!Impl(toCamelCase!Impl() ~ names[i].capitalizeFirst) ) 176 //TODO: __traits(isOverrideFunction, Foo.foo) ? 177 { 178 result ~= "\t"~ toCamelCase!GtkClass() ~"."~ names[i] ~" = &"~ toCamelCase!Impl() ~ names[i].capitalizeFirst ~";\n"; 179 } 180 } 181 182 result ~= "\n"; 183 184 return result; 185 } 186 187 string getWrapFunctions(GtkClass)() 188 { 189 string result; 190 191 alias names = FieldNameTuple!GtkClass; 192 foreach ( i, member; Fields!GtkClass ) 193 { 194 static if ( names[i] == "parentClass" ) 195 { 196 result ~= getWrapFunctions!member(); 197 } 198 else static if ( isCallable!member && 199 implements!Impl(names[i]) && 200 !implements!Impl(toCamelCase!Impl() ~ names[i].capitalizeFirst) ) 201 //TODO: __traits(isOverrideFunction, Foo.foo) ? 202 { 203 result ~= getWrapFunction!(Impl, member, names[i]); 204 } 205 } 206 207 return result; 208 } 209 } 210 211 template ImplementInterfaceImpl(Base, Klass, Impl) 212 { 213 string ImplementInterfaceImpl() 214 { 215 string result; 216 217 result ~= "import glib.Str;\n"~ 218 "import gobject.Type : Type;\n"~ 219 "import gobject.c.functions : g_type_class_peek_parent, g_object_get_data;\n"; 220 221 if ( !is(Base == gobject.c.types.GObject) ) 222 result ~= "import "~ getTypeImport!Base() ~": "~ getTypeFunction!Base()[0..$-2] ~";\n"; 223 224 result ~= "import "~ getTypeImport!Klass() ~" : "~ getTypeFunction!Klass()[0..$-2] ~";\n\n"; 225 226 if ( !hasMember!(Impl, toCamelCase!Impl()) ) 227 { 228 result ~= "\nstruct "~ toPascalCase!Impl() ~"\n"~ 229 "{\n"~ 230 "\t"~ Base.stringof ~" parentInstance;\n"~ 231 "}\n\n"; 232 233 result ~= "struct "~ toPascalCase!Impl() ~"Class\n"~ 234 "{\n"~ 235 "\t"~ Base.stringof ~"Class parentClass;\n"~ 236 "}\n\n"; 237 238 result ~= "protected "~ toPascalCase!Impl() ~"* "~ toCamelCase!Impl() ~";\n\n"; 239 240 result ~= "protected override void* getStruct()\n"~ 241 "{\n"~ 242 "\treturn cast(void*)gObject;\n"~ 243 "}\n\n"; 244 245 if ( is(Base == gobject.c.types.GObject) ) 246 { 247 result ~= "public this()\n"~ 248 "{\n"~ 249 "\tauto p = super(getType(), null);\n"~ 250 "\t"~ toCamelCase!Impl() ~" = cast("~ toPascalCase!Impl() ~"*) p.getObjectGStruct();\n"~ 251 "}\n\n"; 252 } 253 } 254 255 if ( !implements!Impl("getType") ) 256 { 257 result ~= "public static GType getType()\n"~ 258 "{\n"~ 259 "\tGType "~ toCamelCase!Impl() ~"Type = Type.fromName(\""~ toPascalCase!Impl() ~"\");\n\n"~ 260 "\tif ("~ toCamelCase!Impl() ~"Type == GType.INVALID)\n"~ 261 "\t{\n"~ 262 "\t\t"~ toCamelCase!Impl() ~"Type = Type.registerStaticSimple(\n"~ 263 "\t\t\t"~ getTypeFunction!Base() ~",\n"~ 264 "\t\t\t\""~ toPascalCase!Impl() ~"\",\n"~ 265 "\t\t\tcast(uint)"~ toPascalCase!Impl() ~"Class.sizeof,\n"~ 266 "\t\t\tcast(GClassInitFunc) &"~ toCamelCase!Impl() ~"ClassInit,\n"~ 267 "\t\t\tcast(uint)"~ toPascalCase!Impl() ~".sizeof, null, cast(GTypeFlags)0);\n\n"~ 268 "\t\tforeach ( member; __traits(derivedMembers, "~ Impl.stringof ~") )\n"~ 269 "\t\t{\n"~ 270 "\t\t\tstatic if ( member.startsWith(\"_implementInterface\") )\n"~ 271 "\t\t\t\t__traits(getMember, "~ Impl.stringof ~", member)("~ toCamelCase!Impl() ~"Type);\n"~ 272 "\t\t}\n"~ 273 "\t}\n\n"~ 274 "\treturn "~ toCamelCase!Impl() ~"Type;\n"~ 275 "}\n\n"; 276 } 277 278 result ~= "static void _implementInterface"~ Klass.stringof ~"(GType type)\n"~ 279 "{\n"~ 280 "\tGInterfaceInfo "~ Klass.stringof ~"Info =\n"~ 281 "\t{\n"~ 282 "\t\tcast(GInterfaceInitFunc) &"~ toCamelCase!Klass() ~"Init,\n"~ 283 "\t\tnull,\n"~ 284 "\t\tnull\n"~ 285 "\t};\n"~ 286 "\tType.addInterfaceStatic(type, "~ getTypeFunction!Klass() ~", &"~ Klass.stringof ~"Info);\n"~ 287 "};\n\n"; 288 289 result ~= "extern(C)\n{\n"; 290 291 if ( !implements!Impl(toCamelCase!Impl() ~"ClassInit") ) 292 { 293 result ~= "static void "~ toCamelCase!Impl() ~"ClassInit (void* klass)\n"~ 294 "{\n"~ 295 "\t"~ fullyQualifiedName!(getClass!Base) ~"* "~ toCamelCase!(getClass!Base)() ~" = cast("~ fullyQualifiedName!(getClass!Base) ~"*)klass;\n"~ 296 "}\n\n"; 297 } 298 299 if ( !implements!Impl(toCamelCase!Klass() ~"Init") ) 300 { 301 result ~= "static void "~ toCamelCase!Klass() ~"Init ("~ Klass.stringof ~" *iface)\n"~ 302 "{\n"; 303 304 auto names = FieldNameTuple!Klass; 305 foreach ( i, member; Fields!Klass ) 306 { 307 if ( isCallable!member && implements!Impl(names[i]) && (!implements!Impl("addOn"~ names[i].capitalizeFirst) || implements!Impl(toCamelCase!Impl() ~ names[i].capitalizeFirst) ) ) 308 { 309 result ~= "\tiface."~ names[i] ~" = &"~ toCamelCase!Impl() ~ names[i].capitalizeFirst ~";\n"; 310 } 311 } 312 313 result ~= "}\n\n"; 314 } 315 316 alias names = FieldNameTuple!Klass; 317 foreach ( i, member; Fields!Klass ) 318 { 319 if ( isCallable!member && 320 implements!Impl(names[i]) && 321 !implements!Impl(toCamelCase!Impl() ~ names[i].capitalizeFirst) && 322 !implements!Impl("addOn"~ names[i].capitalizeFirst) ) 323 { 324 result ~= getWrapFunction!(Impl, member, names[i]); 325 } 326 } 327 328 result ~= "}"; 329 330 return result; 331 } 332 } 333 334 private string getTypeFunction(Iface)() 335 { 336 string result; 337 338 if ( is(Iface == gobject.c.types.GObject) ) 339 return "GType.OBJECT"; 340 else 341 { 342 foreach ( i, char c; Iface.stringof ) 343 { 344 if ( c.isUpper && i > 0 ) 345 result ~= "_"~c; 346 else 347 result ~= c; 348 } 349 350 return result.toLower.replace("_iface", "")~ "_get_type()"; 351 } 352 } 353 354 private string getTypeImport(Iface)() 355 { 356 return fullyQualifiedName!Iface.replace("types."~ Iface.stringof, "functions"); 357 } 358 359 template getClass(Instance) 360 { 361 mixin("import "~ getClassImport!Instance() ~"; alias getClass = "~ Instance.stringof ~"Class;"); 362 } 363 364 private string getClassImport(Klass)() 365 { 366 return fullyQualifiedName!Klass.replace("."~ Klass.stringof, ""); 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 if ( ParamStorage[i] == STC.out_ || ParamStorage[i] == STC.ref_ ) 425 result ~= "*"~ParamNames[i]; 426 else 427 result ~= ParamNames[i]; 428 } 429 } 430 431 result ~= ");\n\n"; 432 433 foreach ( i, param; Params ) 434 { 435 if ( (ParamStorage[i] == STC.out_ || ParamStorage[i] == STC.ref_) && isGtkdType!(DParamTypes[i]) ) 436 { 437 result ~= "\tif ( d_"~ ParamNames[i] ~" !is null )\n"~ 438 "\t\t*"~ ParamNames[i] ~" = *d_"~ ParamNames[i] ~".get"~ DParamTypes[i].stringof ~"Struct();\n"; 439 } 440 } 441 442 if ( isGtkdType!(ReturnType!(__traits(getMember, Impl, name))) && isPointer!(ReturnType!Member) ) 443 result ~= "\treturn ret ? ret.get"~ (ReturnType!(__traits(getMember, Impl, name))).stringof ~"Struct() : null;\n"; 444 else if ( !is(ReturnType!Member == void) ) 445 result ~= "\treturn ret;\n"; 446 447 result ~= "}\n\n"; 448 } 449 450 return result; 451 } 452 453 private string toCamelCase(Type)() 454 { 455 string result; 456 457 foreach (i, word; to!string(fullyQualifiedName!Type).split(".")) 458 { 459 if ( i == 0 ) 460 word = word[0 .. 1].toLower ~ word[1 .. $]; 461 else 462 word = word.capitalizeFirst; 463 464 result ~= word; 465 } 466 467 return result; 468 } 469 470 private string toPascalCase(Type)() 471 { 472 string result; 473 474 foreach (word; to!string(fullyQualifiedName!Type).split(".")) 475 { 476 result ~= word.capitalizeFirst; 477 } 478 479 return result; 480 } 481 482 private template isGtkdType(T) 483 { 484 static if ( __traits(compiles, new T(cast(typeof(T.tupleof[0]))null, true)) ) 485 enum bool isGtkdType = hasMember!(T, "get"~ T.stringof ~"Struct"); 486 else 487 enum bool isGtkdType = false; 488 } 489 490 private bool implements(Impl)(string member) 491 { 492 return (cast(string[])[__traits(derivedMembers, Impl)]).canFind(member); 493 } 494 495 private string capitalizeFirst(string str) 496 { 497 if ( str.empty ) 498 return str; 499 else if ( str.length == 1 ) 500 return str.toUpper; 501 else 502 return str[0 .. 1].toUpper ~ str[1 .. $]; 503 }