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 utils.GtkPackage;
21 
22 import std.algorithm;
23 import std.array: empty;
24 import std.file;
25 import std.path;
26 import std.string : splitLines, strip, split;
27 import std.uni;
28 import std.stdio;
29 
30 import utils.GtkAlias;
31 import utils.GtkEnum;
32 import utils.GtkFunction;
33 import utils.GtkStruct;
34 import utils.GtkWrapper;
35 import utils.IndentedStringBuilder;
36 import utils.XML;
37 import utils.LinkedHasMap: Map = LinkedHashMap;
38 
39 class GtkPackage
40 {
41 	string name;
42 	string cTypePrefix;
43 	string srcDir;
44 	string bindDir;
45 	GtkWrapper wrapper;
46 
47 	string[] publicImports;
48 	string[] lookupAliases;     /// Aliases defined in the lookupfile.
49 	string[] lookupEnums;       /// Enums defined in the lookupfile.
50 	string[] lookupStructs;     /// Structs defined in the lookupfile.
51 	string[] lookupFuncts;      /// Functions defined in the lookupfile.
52 	string[] lookupConstants;   /// Constants defined in the lookupfile.
53 	
54 	static GtkPackage[string] namespaces;
55 
56 	Map!(string, GtkAlias)    collectedAliases; /// Aliases defined in the gir file.
57 	Map!(string, GtkEnum)     collectedEnums;   /// Enums defined in the gir file.
58 	Map!(string, GtkStruct)   collectedStructs;
59 	Map!(string, GtkFunction) collectedCallbacks;
60 	Map!(string, GtkFunction) collectedFunctions;
61 	GtkEnum stockIDs;           /// The StockID enum (Deprecated).
62 	GtkEnum GdkKeys;            /// The GdkKey enum.
63 
64 	public this(string pack, GtkWrapper wrapper, string srcDir, string bindDir)
65 	{
66 		this.name = pack;
67 		this.wrapper = wrapper;
68 		this.srcDir = srcDir;
69 		this.bindDir = bindDir;
70 		this.stockIDs = new GtkEnum(wrapper, this);
71 		this.GdkKeys  = new GtkEnum(wrapper, this);
72 
73 		try
74 		{
75 			if ( !exists(buildPath(wrapper.outputRoot, srcDir, bindDir)) )
76 				mkdirRecurse(buildPath(wrapper.outputRoot, srcDir, bindDir));
77 		}
78 		catch (Exception)
79 		{
80 			throw new Exception("Failed to create directory: "~ buildPath(wrapper.outputRoot, srcDir, bindDir));
81 		}
82 
83 		try
84 		{
85 			if ( !exists(buildPath(wrapper.outputRoot, srcDir, pack)) )
86 				mkdirRecurse(buildPath(wrapper.outputRoot, srcDir, pack));
87 		}
88 		catch (Exception)
89 		{
90 			throw new Exception("Failed to create directory: "~ buildPath(wrapper.outputRoot, srcDir, pack));
91 		}
92 
93 		publicImports ~= bindDir ~"."~ pack;
94 	}
95 
96 	void parseGIR(string girFile)
97 	{
98 		if ( !isAbsolute(girFile) )
99 			girFile = buildNormalizedPath("/usr/share/gir-1.0", girFile);
100 
101 		auto reader = new XMLReader!string(readText(girFile), girFile);
102 
103 		while ( !reader.empty && reader.front.value != "repository" )
104 			reader.popFront();
105 
106 		reader.popFront();
107 
108 		while ( !reader.empty && reader.front.value == "include" )
109 		{
110 			//TODO: parse imports.
111 
112 			reader.popFront();
113 		}
114 
115 		while ( !reader.empty && reader.front.value != "namespace" )
116 			reader.popFront();
117 
118 		namespaces[reader.front.attributes["name"]] = this;
119 		cTypePrefix = reader.front.attributes["c:identifier-prefixes"];
120 
121 		reader.popFront();
122 
123 		while ( !reader.empty && !reader.endTag("namespace") )
124 		{
125 			if ( reader.front.type == XMLNodeType.EndTag )
126 			{
127 				reader.popFront();
128 				continue;
129 			}
130 
131 			switch (reader.front.value)
132 			{
133 				case "alias":
134 					GtkAlias gtkAlias = new GtkAlias(wrapper);
135 					gtkAlias.parse(reader);
136 
137 					if ( gtkAlias.cType == "GType" )
138 						break;
139 
140 					collectedAliases[gtkAlias.name] = gtkAlias;
141 					break;
142 				case "glib:boxed":
143 					reader.skipTag();
144 					break;
145 				case "bitfield":
146 				case "enumeration":
147 					GtkEnum gtkEnum = new GtkEnum(wrapper, this);
148 					gtkEnum.parse(reader);
149 					collectedEnums[gtkEnum.name] = gtkEnum;
150 					break;
151 				case "class":
152 				case "interface":
153 				case "record":
154 				case "union":
155 					GtkStruct gtkStruct = new GtkStruct(wrapper, this);
156 					gtkStruct.parse(reader);
157 
158 					//Workaround: Dont overwrite the regular pango classes.
159 					if ( gtkStruct.cType.among("PangoCairoFont", "PangoCairoFontMap") )
160 					{
161 						collectedStructs["FcFontMap"].merge(gtkStruct);
162 						break;
163 					}
164 
165 					collectedStructs[gtkStruct.name] = gtkStruct;
166 
167 					if ( name == "pango" )
168 						gtkStruct.name = "Pg"~gtkStruct.name;
169 					break;
170 				case "callback":
171 					GtkFunction callback = new GtkFunction(wrapper, null);
172 					callback.parse(reader);
173 					collectedCallbacks[callback.name] = callback;
174 					break;
175 				case "constant":
176 					parseConstant(reader);
177 					break;
178 				case "function":
179 					parseFunction(reader);
180 					break;
181 				default:
182 					throw new XMLException(reader, "Unexpected tag: "~ reader.front.value ~" in GtkPackage: "~ name);
183 			}
184 			reader.popFront();
185 		}
186 	}
187 
188 	void parseConstant(T)(XMLReader!T reader)
189 	{
190 		if ( reader.front.attributes["name"].startsWith("STOCK_") )
191 		{
192 			GtkEnumMember member = GtkEnumMember(wrapper);
193 			member.parse(reader);
194 			member.name = member.name[6..$];
195 
196 			stockIDs.members ~= member;
197 			return;
198 		}
199 		else if ( reader.front.attributes["c:type"].startsWith("GDK_KEY_") )
200 		{
201 			GtkEnumMember member = GtkEnumMember(wrapper);
202 			member.parse(reader);
203 			member.name = "GDK_"~ member.name[4..$];
204 
205 			GdkKeys.members ~= member;
206 			return;
207 		}
208 
209 		//TODO: other constants.
210 		reader.skipTag();
211 	}
212 
213 	void parseFunction(T)(XMLReader!T reader)
214 	{
215 		GtkFunction funct = new GtkFunction(wrapper, null);
216 		funct.parse(reader);
217 		collectedFunctions[funct.name] = funct;
218 	}
219 
220 	GtkStruct getStruct(string name)
221 	{
222 		GtkPackage pack = this;
223 
224 		if ( name.canFind(".") )
225 		{
226 			string[] vals = name.split(".");
227 
228 			if ( vals[0] !in namespaces )
229 				return null;
230 
231 			pack = namespaces[vals[0]];
232 			name = vals[1];
233 		}
234 		return pack.collectedStructs.get(name, pack.collectedStructs.get("lookup"~name, null));
235 	}
236 
237 	GtkEnum getEnum(string name)
238 	{
239 		GtkPackage pack = this;
240 
241 		if ( name.canFind(".") )
242 		{
243 			string[] vals = name.split(".");
244 
245 			if ( vals[0] !in namespaces )
246 				return null;
247 
248 			pack = namespaces[vals[0]];
249 			name = vals[1];
250 		}
251 		return pack.collectedEnums.get(name, null);
252 	}
253 
254 	void writeClasses()
255 	{
256 		foreach ( strct; collectedStructs )
257 			strct.writeClass();
258 	}
259 
260 	void writeTypes()
261 	{
262 		string buff = wrapper.licence;
263 		auto indenter = new IndentedStringBuilder();
264 
265 		buff ~= "module "~ bindDir ~"."~ name ~"types;\n\n";
266 
267 		buff ~= indenter.format(lookupAliases);
268 		foreach ( a; collectedAliases )
269 		{
270 			buff ~= "\n";
271 			buff ~= indenter.format(a.getAliasDeclaration());
272 		}
273 
274 		buff ~= indenter.format(lookupEnums);
275 		foreach ( e; collectedEnums )
276 		{
277 			buff ~= "\n";
278 			buff ~= indenter.format(e.getEnumDeclaration());
279 		}
280 
281 		buff ~= indenter.format(lookupStructs);
282 		foreach ( s; collectedStructs )
283 		{
284 			if ( s.noExternal || s.noDecleration )
285 				continue;
286 
287 			buff ~= "\n";
288 			buff ~= indenter.format(s.getStructDeclaration());
289 		}
290 
291 		buff ~= indenter.format(lookupFuncts);
292 		foreach ( f; collectedCallbacks )
293 		{
294 			buff ~= "\n";
295 			buff ~= indenter.format(f.getCallbackDeclaration());
296 		}
297 
298 		buff ~= indenter.format(lookupConstants);
299 		if ( stockIDs.members !is null )
300 		{
301 			stockIDs.cName = "StockID";
302 			stockIDs.doc = "StockIds";
303 			buff ~= "\n";
304 			buff ~= indenter.format(stockIDs.getEnumDeclaration());
305 		}
306 
307 		if ( GdkKeys.members !is null )
308 			writeGdkKeys();
309 
310 		std.file.write(buildPath(wrapper.outputRoot, srcDir, bindDir, name ~"types.d"), buff);
311 	}
312 
313 	void writeGdkKeys()
314 	{
315 		string buff = wrapper.licence;
316 
317 		buff ~= "module "~ name ~".Keysyms;\n\n";
318 
319 		buff ~= "/**\n";
320 		buff ~= " * GdkKeysyms.\n";
321 		buff ~= " */\n";
322 		buff ~= "public enum GdkKeysyms\n";
323 		buff ~= "{\n";
324 
325 		foreach ( member; GdkKeys.members )
326 		{
327 			buff ~= "\t"~ tokenToGtkD(member.name, wrapper.aliasses, false) ~" = "~ member.value ~",\n";
328 		}
329 
330 		buff ~= "}\n";
331 
332 		std.file.write(buildPath(wrapper.outputRoot, srcDir, name, "Keysyms.d"), buff);
333 	}
334 
335 	void writeLoaderTable()
336 	{
337 		string buff = wrapper.licence;
338 
339 		buff ~= "module "~ bindDir ~"."~ name ~";\n\n";
340 		buff ~= "import std.stdio;\n";
341 		buff ~= "import "~ bindDir ~"."~ name ~"types;\n";
342 
343 		if ( name == "glib" )
344 			buff ~= "import gtkc.gobjecttypes;\n";
345 		if ( name == "gdk" || name == "pango" )
346 			buff ~= "import gtkc.cairotypes;\n";
347 
348 		buff ~= "import gtkc.Loader;\n"
349 			~ "import gtkc.paths;\n\n"
350 			~ "shared static this()\n"
351 			~ "{";
352 
353 		foreach ( strct; collectedStructs )
354 		{
355 			if ( strct.functions.empty || strct.noExternal )
356 				continue;
357 
358 			buff ~= "\n\t// "~ name ~"."~ strct.name ~"\n\n";
359 
360 			foreach ( funct; strct.functions )
361 			{
362 				if ( funct.type == GtkFunctionType.Callback || funct.type == GtkFunctionType.Signal || funct.name.empty )
363 					continue;
364 
365 				buff ~= "\tLinker.link("~ funct.cType ~", \""~ funct.cType ~"\", "~ getLibrary(funct.cType) ~");\n";
366 			}
367 		}
368 
369 		buff ~= "}\n\n"
370 			~ "__gshared extern(C)\n"
371 			~ "{\n";
372 
373 		foreach ( strct; collectedStructs )
374 		{
375 			if ( strct.functions.empty || strct.noExternal )
376 				continue;
377 
378 			buff ~= "\n\t// "~ name ~"."~ strct.name ~"\n\n";
379 
380 			foreach ( funct; strct.functions )
381 			{
382 				if ( funct.type == GtkFunctionType.Callback || funct.type == GtkFunctionType.Signal || funct.name.empty )
383 					continue;
384 
385 				buff ~= "\t"~ funct.getExternal() ~"\n";
386 			}
387 		}
388 
389 		buff ~= "}\n\n";
390 
391 		foreach ( strct; collectedStructs )
392 		{
393 			if ( strct.functions.empty || strct.noExternal )
394 				continue;
395 
396 			buff ~= "\n// "~ name ~"."~ strct.name ~"\n\n";
397 
398 			foreach ( funct; strct.functions )
399 			{
400 				if ( funct.type == GtkFunctionType.Callback || funct.type == GtkFunctionType.Signal || funct.name.empty )
401 					continue;
402 
403 				if (name == "glgdk")
404 					buff ~= "alias glc_"~ funct.cType ~" "~ funct.cType ~";\n";
405 				else
406 					buff ~= "alias c_"~ funct.cType ~" "~ funct.cType ~";\n";
407 			}
408 		}
409 
410 		std.file.write(buildPath(wrapper.outputRoot, srcDir, bindDir, name ~".d"), buff);
411 	}
412 
413 	private string getLibrary(string funct)
414 	{
415 		string library = "LIBRARY."~ name.toUpper();
416 
417 		if ( startsWith(funct, "gdk") && !startsWith(funct, "gdk_gl") )
418 			return library ~ ", LIBRARY.GDKPIXBUF";
419 		else if	( startsWith(funct, "pango_cairo") )
420 			return library ~ ", LIBRARY.PANGOCAIRO";
421 		else if	( startsWith(funct, "g_module") )
422 			return library ~ ", LIBRARY.GMODULE";
423 		else
424 			return library;
425 	}
426 }