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 }