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 }