1 /* theme.c - functions related to theme handling
2  *
3  * This program is free software; you can redistribute it and/or
4  * modify it under the terms of the GNU General Public
5  * License as published by the Free Software Foundation; either
6  * version 2 of the License, or (at your option) any later version.
7  */
8 #include <gtk/gtk.h>
9 #include <stdlib.h>
10 #include <sys/types.h>
11 #include <dirent.h>
12 #include <string.h>
13 #include <sys/stat.h>
14 #include <unistd.h>
15 #include <stdio.h>
16 
17 #include "theme.h"
18 
19 #include "themerc.h"
20 #include "game.h"
21 #include "gtkutils.h" /* ut_simple_message... */
22 #include "prefs.h"
23 
24 GtkbTheme *gtkbTheme=NULL;
25 
26 /* some file/dir names */
27 gchar *INSTALLPATH=DATADIR "/gtkballs/themes/";
28 gchar *THEMEPREFIX="/.gtkballs/themes/";
29 
30 #define DELTA(d, h) \
31         d = h < 0 ? (d > -h ? d + h : 0) : (d + h < 255 ? d + h : 255)
32 
hilight_pixbuff8(GdkPixbuf * pb,gint dr,gint dg,gint db)33 void hilight_pixbuff8(GdkPixbuf *pb, gint dr, gint dg, gint db) {
34         /* pb created with 8b/rgb without alpha */
35         gint i;
36         gint nc = gdk_pixbuf_get_n_channels(pb);
37         gint delta = nc - 3;
38         gint w = gdk_pixbuf_get_width(pb);
39         gint l = w * gdk_pixbuf_get_height(pb);
40         gint d = gdk_pixbuf_get_rowstride(pb) - w * nc;
41         guchar *data = gdk_pixbuf_get_pixels(pb);
42 
43         for(i = 0; i < l; i++) {
44                 if(i && (i%w == 0)) {
45                         data += d;
46                 }
47                 DELTA(*data, dr);
48                 data++;
49                 DELTA(*data, dg);
50                 data++;
51                 DELTA(*data, db);
52                 data += delta + 1;
53         }
54 }
55 
find_theme_path(gchar * themename)56 gchar *find_theme_path(gchar *themename) {
57   	gchar *homedir;
58   	gchar *themepath;
59   	struct stat buf;
60 
61   	if((homedir = getenv("HOME")) &&
62            (themepath = g_strconcat(homedir, THEMEPREFIX, themename, G_DIR_SEPARATOR_S, NULL))) {
63   		if(!stat(themepath, &buf) && S_ISDIR(buf.st_mode)) {
64 			return themepath;
65         	}
66         	g_free(themepath);
67 	}
68   	if((themepath = g_strconcat(INSTALLPATH, themename, G_DIR_SEPARATOR_S, NULL))) {
69         	if(!stat(themepath, &buf) && S_ISDIR(buf.st_mode)) {
70 			return themepath;
71         	}
72         	g_free(themepath);
73   	}
74   	return NULL;
75 }
76 
gtkb_load_pixmap(GtkbPixmap * pixmap,gchar * path,gchar * pixmapname)77 gint gtkb_load_pixmap(GtkbPixmap *pixmap, gchar *path, gchar *pixmapname) {
78         gchar *fname;
79         GError *error = NULL;
80         gint rv = 1;
81 
82         if(!(fname = g_strconcat(path, pixmapname, NULL))) {
83                 return 0;
84         }
85 	if(pixmap->pixbuf) g_object_unref(pixmap->pixbuf);
86         pixmap->pixbuf = gdk_pixbuf_new_from_file(fname, &error);
87         if(!pixmap->pixbuf) {
88 		ut_simple_message_box(error->message);
89 		g_error_free(error);
90         	rv = 0;
91         } else {
92         	pixmap->xsize = gdk_pixbuf_get_width(pixmap->pixbuf);
93         	pixmap->ysize = gdk_pixbuf_get_height(pixmap->pixbuf);
94         }
95         g_free(fname);
96   	return rv;
97 }
98 
gtkb_pixmap_free(GtkbPixmap pixmap)99 void gtkb_pixmap_free(GtkbPixmap pixmap) {
100 	if(pixmap.pixbuf) {
101                 g_object_unref(pixmap.pixbuf);
102         }
103 }
104 
gtkb_theme_free(GtkbTheme * theme)105 void gtkb_theme_free(GtkbTheme *theme) {
106         gint i, j;
107 
108         if(!theme) {
109                 return;
110         }
111 	gtkb_pixmap_free(theme->emptycell);
112 	gtkb_pixmap_free(theme->hemptycell);
113         for(i = 0; i < 8; i++) {
114 		gtkb_pixmap_free(theme->paws[i]);
115         }
116         if(theme->balls) {
117         	for(i = 0; i < theme->numballs; i++) {
118         		gtkb_pixmap_free(theme->balls[i].ball);
119 	        	gtkb_pixmap_free(theme->balls[i].small);
120         	        if(theme->balls[i].jump) {
121                 		for(j = 0; j < theme->balls[i].jumpphases; j++) {
122                 			gtkb_pixmap_free(theme->balls[i].jump[j]);
123 	                	}
124         	        	g_free(theme->balls[i].jump);
125                 		if(theme->balls[i].jumpdelays) {
126 					g_free(theme->balls[i].jumpdelays);
127 	                        }
128         	        }
129                 	if(theme->balls[i].destroy) {
130                 		for(j = 0; j < theme->balls[i].destroyphases; j++) {
131                 			gtkb_pixmap_free(theme->balls[i].destroy[j]);
132 	                	}
133         	        	g_free(theme->balls[i].destroy);
134                 		if(theme->balls[i].destroydelays) {
135                 			g_free(theme->balls[i].destroydelays);
136 	                        }
137         	        }
138 	        }
139         }
140         g_free(theme->balls);
141         g_free(theme);
142 }
143 
144 /* warning! tmpname will be free()'d! */
gtkb_load_pixmap_from(gchar ** trc,gchar * themepath,gchar * tmpname,GtkbPixmap * pixmap)145 gint gtkb_load_pixmap_from(gchar **trc, gchar *themepath, gchar *tmpname, GtkbPixmap *pixmap) {
146         gchar *val;
147         gint ret;
148 
149         val = trc_get_str(trc, tmpname);
150         g_free(tmpname);
151         if(val == NULL) return 0;
152         ret = gtkb_load_pixmap(pixmap, themepath, val);
153         g_free(val);
154 
155         return ret;
156 }
157 
158 #define CHECKRET(ret, cond) \
159         if(ret == cond) { \
160                 gtkb_theme_free(theme); \
161         	trc_close(trc); \
162 		return NULL; \
163         }
164 
gtkb_make_hl_pixmap(GtkbTheme * theme)165 void gtkb_make_hl_pixmap(GtkbTheme *theme) {
166         if(theme->hemptycell.pixbuf) {
167         	gtkb_pixmap_free(theme->hemptycell);
168         }
169         theme->hemptycell.pixbuf = gdk_pixbuf_copy(theme->emptycell.pixbuf);
170         theme->hemptycell.xsize = theme->emptycell.xsize;
171         theme->hemptycell.ysize = theme->emptycell.ysize;
172         hilight_pixbuff8(theme->hemptycell.pixbuf, prefs_get_hl_dr(), prefs_get_hl_dg(), prefs_get_hl_db());
173 }
174 
gtkb_load_theme(gchar * themepath)175 GtkbTheme *gtkb_load_theme(gchar *themepath) {
176         gchar **trc, *opt;
177         gchar *paws[] = {"down_up", "left_right", "up_down", "right_left",
178                          "down_right", "down_left", "up_right", "up_left", NULL};
179         gint  i, j, ret;
180         GtkbTheme *theme;
181 
182         opt = g_strconcat(themepath, "themerc", NULL);
183         trc = trc_open(opt); /* open theme description file */
184         g_free(opt);
185         if(!trc) {
186 		return NULL;
187         }
188         theme = g_new0(GtkbTheme, 1);
189 
190         /* find and load "empty cell" pixmap. */
191         opt = trc_get_str(trc, "cell");
192 	CHECKRET(opt, NULL);
193         ret = gtkb_load_pixmap(&theme->emptycell, themepath, opt);
194         g_free(opt);
195 	CHECKRET(ret, 0);
196 /*
197         theme->hemptycell.pixbuf = gdk_pixbuf_copy(theme->emptycell.pixbuf);
198         theme->hemptycell.xsize = theme->emptycell.xsize;
199         theme->hemptycell.ysize = theme->emptycell.ysize;
200 	CHECKRET(theme->hemptycell.pixbuf, NULL);
201         hilight_pixbuff8(theme->hemptycell.pixbuf, prefs_get_hl_dr(), prefs_get_hl_dg(), prefs_get_hl_db());
202 */
203         gtkb_make_hl_pixmap(theme);
204 
205         /* find and load "footprints" pixmaps. */
206         for(i = 0; paws[i]; i++) {
207 		CHECKRET(gtkb_load_pixmap_from(trc, themepath, g_strconcat("paw.", paws[i], NULL),
208                                                &theme->paws[i]),
209 			 0);
210         }
211 
212         /* query number of available balls in theme */
213         theme->numballs = trc_get_uint(trc, "ball.numbers");
214 	CHECKRET(theme->numballs, -1);
215         if(theme->numballs < rules_get_colors()) CHECKRET(0, 0); /* yes, i know. its ugly =) */
216         theme->balls = g_new0(GtkbBall, theme->numballs);
217 
218         /* find and load all balls data. */
219         for(i = 0; i < theme->numballs; i++) {
220 		CHECKRET(gtkb_load_pixmap_from(trc, themepath, g_strdup_printf("ball.%d.still", i + 1),
221                                                &theme->balls[i].ball),
222 			 0);
223 		CHECKRET(gtkb_load_pixmap_from(trc, themepath, g_strdup_printf("ball.%d.small", i + 1),
224                                                &theme->balls[i].small),
225 			 0);
226 
227                 opt = g_strdup_printf("ball.%d.jump.numbers", i + 1);
228                 theme->balls[i].jumpphases = trc_get_uint(trc, opt);
229                 g_free(opt);
230 		CHECKRET(theme->balls[i].jumpphases, -1);
231         	if(theme->balls[i].jumpphases < 2) CHECKRET(0, 0); /* yes, i know. its ugly =) */
232                 theme->balls[i].jump = g_new0(GtkbPixmap, theme->balls[i].jumpphases);
233                 theme->balls[i].jumpdelays = g_new0(gint, theme->balls[i].jumpphases);
234 
235                 for(j = 0; j < theme->balls[i].jumpphases; j++) {
236 			CHECKRET(gtkb_load_pixmap_from(trc, themepath,
237                                                        g_strdup_printf("ball.%d.jump.%d", i + 1, j + 1),
238                                                        &theme->balls[i].jump[j]),
239 			         0);
240                 	opt = g_strdup_printf("ball.%d.jump.%d.usec", i + 1, j + 1);
241                         theme->balls[i].jumpdelays[j] = trc_get_uint(trc, opt);
242                 	g_free(opt);
243                 	CHECKRET(theme->balls[i].jumpdelays[j], -1);
244                 }
245 
246                 opt = g_strdup_printf("ball.%d.destroy.numbers", i + 1);
247                 theme->balls[i].destroyphases = trc_get_uint(trc, opt);
248                 g_free(opt);
249 		CHECKRET(theme->balls[i].destroyphases, -1);
250         	if(theme->balls[i].destroyphases < 2) CHECKRET(0, 0); /* yes, i know. its ugly =) */
251 		if(theme->balls[i].destroyphases > theme->maxdestphases) {
252                         theme->maxdestphases = theme->balls[i].destroyphases;
253                 }
254                 theme->balls[i].destroy = g_new0(GtkbPixmap, theme->balls[i].destroyphases);
255                 theme->balls[i].destroydelays = g_new0(gint, theme->balls[i].destroyphases);
256 
257                 for(j = 0; j < theme->balls[i].destroyphases; j++) {
258 			CHECKRET(gtkb_load_pixmap_from(trc, themepath,
259                                                        g_strdup_printf("ball.%d.destroy.%d", i + 1, j + 1),
260                                                        &theme->balls[i].destroy[j]),
261 			         0);
262                 	opt = g_strdup_printf("ball.%d.destroy.%d.usec", i + 1, j + 1);
263                         theme->balls[i].destroydelays[j] = trc_get_uint(trc, opt);
264                 	g_free(opt);
265                 	CHECKRET(theme->balls[i].destroydelays[j], -1);
266                 }
267         }
268         trc_close(trc);
269 
270         return theme;
271 }
272 
load_theme(gchar * themename)273 gint load_theme(gchar *themename) {
274   	gchar *themepath;
275         GtkbTheme *theme;
276 
277   	if(!(themepath = find_theme_path(themename))) return 0;
278 
279         theme = gtkb_load_theme(themepath);
280   	g_free(themepath);
281 
282         if(!theme) return 0;
283 
284         gtkb_theme_free(gtkbTheme);
285         gtkbTheme = theme;
286 
287   	return 1;
288 }
289 
gtkb_theme_free_handler(GtkWidget * widget,gpointer data)290 gint gtkb_theme_free_handler(GtkWidget *widget, gpointer data) {
291         gtkb_theme_free(gtkbTheme);
292         gtkbTheme = NULL;
293         return 0;
294 }
295 
gtkb_theme_get_balls_num(void)296 gint gtkb_theme_get_balls_num(void) {
297         return gtkbTheme ? gtkbTheme->numballs : 0;
298 }
299 
300 /* returns board coordinate of the pointer */
gtkb_theme_get_coord_at_x(gint x)301 gint gtkb_theme_get_coord_at_x(gint x) {
302         if(gtkbTheme) {
303   		return (x - 1) / gtkbTheme->emptycell.xsize;
304         }
305         return -1;
306 }
307 
gtkb_theme_get_coord_at_y(gint y)308 gint gtkb_theme_get_coord_at_y(gint y) {
309         if(gtkbTheme) {
310   		return (y - 1) / gtkbTheme->emptycell.ysize;
311         }
312         return -1;
313 }
314 
theme_get_width(void)315 gint theme_get_width(void) {
316         return gtkbTheme->emptycell.xsize;
317 }
318 
theme_get_height(void)319 gint theme_get_height(void) {
320         return gtkbTheme->emptycell.xsize;
321 }
322 
323 /* find all available themes. */
get_available_themes(void)324 gchar **get_available_themes(void) {
325   	DIR *directory;
326   	struct dirent *dir_entry;
327   	struct stat entry_stat;
328   	gchar *entry, *currdir, *hdir, *rcentry;
329   	gint i, j, num, flag;
330 	gchar  **tlist = NULL;
331 
332         if(getenv("HOME")) {
333  		hdir = g_strconcat(getenv("HOME"), THEMEPREFIX, NULL);
334         } else {
335  		hdir = g_strdup("./"); /* FIXME: does it work on non unix os? */
336         }
337   	for(j = 0, currdir = INSTALLPATH, num = 0; j < 2; j++, currdir = hdir) {
338         	if(!(directory = opendir(currdir))) {
339                         continue;
340                 }
341                 while((dir_entry = readdir(directory))) {
342                 	if(!strncmp(dir_entry->d_name, ".", 2) ||
343                            !strncmp(dir_entry->d_name, "..", 3)) {
344                                 continue;
345                         }
346 			entry = g_strconcat(currdir, dir_entry->d_name, NULL);
347                         if(!stat(entry, &entry_stat) && S_ISDIR(entry_stat.st_mode)) {
348 				rcentry = g_strconcat(entry, G_DIR_SEPARATOR_S, "themerc", NULL);
349                                 if(!stat(rcentry, &entry_stat)) {
350                                 	flag = 0;
351                                         for(i=0; i < num && !flag; i++) {
352                                         	if(!strcmp(tlist[i], dir_entry->d_name)) {
353 							flag++;
354                                                 }
355                                         }
356                                         if(!flag) {
357                                         	num++;
358 	                                        tlist = g_realloc(tlist, num * sizeof(gchar *));
359         	                                tlist[num-1] = g_strdup(dir_entry->d_name);
360                 	                }
361                                 }
362                         	g_free(rcentry);
363                         }
364                         g_free(entry);
365                 }
366                 closedir(directory);
367   	}
368         g_free(hdir);
369 	tlist = g_realloc(tlist, (num + 1) * sizeof(gchar *));
370         tlist[num] = NULL;
371   	return tlist;
372 }
373