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 }