1 /*
2 Copyright (c) 2009-2010 Tero Lindeman (kometbomb)
3 
4 Permission is hereby granted, free of charge, to any person
5 obtaining a copy of this software and associated documentation
6 files (the "Software"), to deal in the Software without
7 restriction, including without limitation the rights to use,
8 copy, modify, merge, publish, distribute, sublicense, and/or sell
9 copies of the Software, and to permit persons to whom the
10 Software is furnished to do so, subject to the following
11 conditions:
12 
13 The above copyright notice and this permission notice shall be
14 included in all copies or substantial portions of the Software.
15 
16 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17 EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
18 OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19 NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
20 HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
21 WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
22 FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
23 OTHER DEALINGS IN THE SOFTWARE.
24 */
25 
26 #include "macros.h"
27 #include "theme.h"
28 #include <string.h>
29 #include "util/bundle.h"
30 #include "gfx/gfx.h"
31 #include "mused.h"
32 #include "gui/menu.h"
33 #include <unistd.h>
34 #include <dirent.h>
35 #include <sys/stat.h>
36 #include <strings.h>
37 #include "action.h"
38 #include "console.h"
39 
40 extern Mused mused;
41 
42 #define MAX_THEMES 64
43 
44 Menu thememenu[MAX_THEMES + 1];
45 extern const Menu prefsmenu[];
46 
47 Uint32 colors[NUM_COLORS];
48 
load_colors(const char * cfg)49 static void load_colors(const char *cfg)
50 {
51 	char *temp = strdup(cfg);
52 
53 	char *token = strtok(temp, "\n");
54 
55 	while (token)
56 	{
57 		char name[51], from[51];
58 		Uint32 color;
59 
60 		static const char *names[NUM_COLORS] =
61 		{
62 			"sequence_counter",
63 			"sequence_normal",
64 			"pattern_selected",
65 			"pattern_bar",
66 			"pattern_beat",
67 			"pattern_instrument",
68 			"pattern_instrument_bar",
69 			"pattern_instrument_beat",
70 			"pattern_volume",
71 			"pattern_volume_bar",
72 			"pattern_volume_beat",
73 			"pattern_ctrl",
74 			"pattern_ctrl_bar",
75 			"pattern_ctrl_beat",
76 			"pattern_command",
77 			"pattern_command_bar",
78 			"pattern_command_beat",
79 			"pattern_normal",
80 			"pattern_disabled",
81 			"program_selected",
82 			"program_even",
83 			"program_odd",
84 			"instrument_selected",
85 			"instrument_normal",
86 			"menu",
87 			"menu_selected",
88 			"menu_header",
89 			"menu_header_selected",
90 			"menu_shortcut",
91 			"menu_shortcut_selected",
92 			"main_text",
93 			"small_text",
94 			"background",
95 			"button_text",
96 			"text_shadow",
97 			"pattern_empty_data",
98 			"wavetable_sample",
99 			"wavetable_background",
100 			"progress_bar",
101 			"pattern_seq_number",
102 			"catometer_eyes",
103 			"statusbar_text"
104 		};
105 
106 		if (sscanf(token, "%50[^ =]%*[ =]%x", name, &color) == 2)
107 		{
108 			int i;
109 			for (i = 0 ; i < NUM_COLORS ; ++i)
110 			{
111 				if (strcasecmp(names[i], name) == 0)
112 				{
113 					colors[i] = color;
114 					FIX_ENDIAN(colors[i]);
115 #if SDL_BYTEORDER == SDL_BIG_ENDIAN
116 // fix so that the color is in the 24 bits of RGB8
117 					colors[i] = (colors[i] >> 8) | ((colors[i] & 0xff) << 24);
118 #endif
119 					break;
120 				}
121 			}
122 
123 			if (i >= NUM_COLORS) warning("Unknown color name '%s'", name);
124 		}
125 		else if (sscanf(token, "%50[^ =]%*[ =]%50s", name, from) == 2)
126 		{
127 			int from_i, to_i;
128 
129 			for (from_i = 0 ; from_i < NUM_COLORS ; ++from_i)
130 			{
131 				if (strcasecmp(names[from_i], from) == 0)
132 				{
133 					break;
134 				}
135 			}
136 
137 			if (from_i >= NUM_COLORS)
138 				warning("Unknown color name '%s'", name);
139 			else
140 			{
141 				for (to_i = 0 ; to_i < NUM_COLORS ; ++to_i)
142 				{
143 					if (strcasecmp(names[to_i], name) == 0)
144 					{
145 						colors[to_i] = color;
146 						break;
147 					}
148 				}
149 
150 				if (to_i >= NUM_COLORS) warning("Unknown color name '%s'", name);
151 				else
152 				{
153 					debug("%s=%s", name, from);
154 					colors[to_i] = colors[from_i];
155 				}
156 			}
157 
158 		}
159 
160 		token = strtok(NULL, "\n");
161 	}
162 
163 	free(temp);
164 }
165 
166 
167 
font_load_and_set_color(Font * font,Bundle * b,char * name,Uint32 color)168 int font_load_and_set_color(Font *font, Bundle *b, char *name, Uint32 color)
169 {
170 	if (font_load(domain, font, b, name))
171 	{
172 		font_set_color(font, color);
173 		return 1;
174 	}
175 	else
176 	{
177 		return 0;
178 	}
179 }
180 
181 
load_img_if_exists(Bundle * res,const char * base_name)182 static SDL_RWops *load_img_if_exists(Bundle *res, const char *base_name)
183 {
184 	/* load base_name.bmp or .png */
185 
186 	char name[100];
187 	const char *ext[] = {"bmp", "png", NULL}, **e;
188 
189 	for (e = ext ; *e ; ++e)
190 	{
191 		snprintf(name, sizeof(name), "%s.%s", base_name, *e);
192 
193 		if (bnd_exists(res, name))
194 		{
195 			SDL_RWops *rw = SDL_RWFromBundle(res, name);
196 			if (rw)
197 				return rw;
198 		}
199 	}
200 
201 	return NULL;
202 }
203 
204 
205 static char cwd[1000] = "";
206 
init_resources_dir(void)207 void init_resources_dir(void)
208 {
209 	if (SDL_getenv("KLYSTRACK") == NULL)
210 	{
211 #if RESOURCES_IN_BINARY_DIR
212 		strncpy(cwd, SDL_GetBasePath(), sizeof(cwd));
213 #else
214 		strncpy(cwd, TOSTRING(RES_PATH), sizeof(cwd));
215 #endif
216 	}
217 	else
218 	{
219 		strncpy(cwd, SDL_getenv("KLYSTRACK"), sizeof(cwd));
220 	}
221 }
222 
query_resource_directory(void)223 char * query_resource_directory(void)
224 {
225 	return cwd;
226 }
227 
228 
set_scaled_cursor()229 void set_scaled_cursor()
230 {
231 	if (mused.mouse_cursor_surface == NULL)
232 		return;
233 
234 	if (mused.mouse_cursor) SDL_FreeCursor(mused.mouse_cursor);
235 
236 	if (mused.flags & USE_SYSTEM_CURSOR)
237 	{
238 		mused.mouse_cursor = SDL_CreateSystemCursor(SDL_SYSTEM_CURSOR_ARROW);
239 	}
240 	else
241 	{
242 		// We'll use SDL_Renderer here because SDL_BlitScaled seems to have an issue with the alpha channel
243 		// Additionally, transparency on a zoomed cursor seems to make the cursor an "XOR" cursor so we need
244 		// to set the transparent color separately after SDL_Renderer has done its thing. SDL bug maybe?
245 
246 		SDL_Surface *temp = SDL_CreateRGBSurface(0, mused.mouse_cursor_surface->surface->w * mused.pixel_scale, mused.mouse_cursor_surface->surface->h * mused.pixel_scale, 32, 0, 0, 0, 0);
247 
248 		SDL_Renderer *renderer = SDL_CreateSoftwareRenderer(temp);
249 		SDL_Texture *tex = SDL_CreateTextureFromSurface(renderer, mused.mouse_cursor_surface->surface);
250 
251 		// Draw the texture on a magic pink background
252 		SDL_SetRenderDrawColor(renderer, 255, 0, 255, 255);
253 		SDL_RenderFillRect(renderer, NULL);
254 		SDL_RenderCopy(renderer, tex, NULL, NULL);
255 
256 		SDL_DestroyTexture(tex);
257 		SDL_DestroyRenderer(renderer);
258 
259 		// Make magic pink transparent
260 		SDL_SetColorKey(temp, SDL_TRUE, SDL_MapRGB(temp->format, 255, 0, 255));
261 
262 		mused.mouse_cursor = SDL_CreateColorCursor(temp, 0, 0);
263 
264 		SDL_FreeSurface(temp);
265 	}
266 
267 	if (mused.mouse_cursor)
268 	{
269 		SDL_SetCursor(mused.mouse_cursor);
270 	}
271 	else
272 	{
273 		warning("SDL_SetCursor failed: %s", SDL_GetError());
274 	}
275 }
276 
277 
set_app_icon()278 void set_app_icon()
279 {
280 	SDL_SetWindowIcon(domain->window, mused.icon_surface->surface);
281 }
282 
283 
load_theme(const char * name)284 void load_theme(const char *name)
285 {
286 	char tmpname[100] = {0};
287 	strncpy(tmpname, name, sizeof(tmpname) - 1);
288 
289 	if (strcmp(name, "Default") != 0)
290 		load_theme("Default"); // for default stuff not in selected theme
291 
292 	Bundle res;
293 	char fullpath[3000] = {0};
294 
295 	snprintf(fullpath, sizeof(fullpath) - 1, "%s/res/%s", query_resource_directory(), tmpname);
296 
297 	debug("Loading theme '%s'", fullpath);
298 
299 	if (bnd_open(&res, fullpath))
300 	{
301 		SDL_RWops *rw;
302 
303 		rw = load_img_if_exists(&res, "bevel");
304 		if (rw)
305 		{
306 			if (mused.slider_bevel) gfx_free_surface(mused.slider_bevel);
307 			mused.slider_bevel = gfx_load_surface_RW(domain, rw, GFX_KEYED);
308 
309 			/* TODO: do we need to store the surface in the params? */
310 
311 			mused.sequence_slider_param.gfx = mused.slider_bevel;
312 			mused.pattern_slider_param.gfx = mused.slider_bevel;
313 			mused.program_slider_param.gfx = mused.slider_bevel;
314 			mused.instrument_list_slider_param.gfx = mused.slider_bevel;
315 			mused.pattern_horiz_slider_param.gfx = mused.slider_bevel;
316 			mused.sequence_horiz_slider_param.gfx = mused.slider_bevel;
317 		}
318 
319 		rw = load_img_if_exists(&res, "vu");
320 		if (rw)
321 		{
322 			if (mused.vu_meter) gfx_free_surface(mused.vu_meter);
323 			mused.vu_meter = gfx_load_surface_RW(domain, rw, GFX_KEYED);
324 		}
325 
326 		rw = load_img_if_exists(&res, "analyzor");
327 		if (rw)
328 		{
329 			if (mused.analyzer) gfx_free_surface(mused.analyzer);
330 			mused.analyzer = gfx_load_surface_RW(domain, rw, GFX_KEYED);
331 		}
332 
333 		rw = load_img_if_exists(&res, "catometer");
334 		if (rw)
335 		{
336 			if (mused.catometer) gfx_free_surface(mused.catometer);
337 			mused.catometer = gfx_load_surface_RW(domain, rw, GFX_KEYED);
338 		}
339 
340 		rw = load_img_if_exists(&res, "cursor");
341 		if (rw)
342 		{
343 			if (mused.mouse_cursor_surface) gfx_free_surface(mused.mouse_cursor_surface);
344 			if (mused.mouse_cursor) SDL_FreeCursor(mused.mouse_cursor);
345 			mused.mouse_cursor_surface = gfx_load_surface_RW(domain, rw, GFX_KEYED);
346 
347 			set_scaled_cursor();
348 		}
349 
350 		rw = load_img_if_exists(&res, "icon");
351 		if (rw)
352 		{
353 			if (mused.icon_surface) gfx_free_surface(mused.icon_surface);
354 			mused.icon_surface = gfx_load_surface_RW(domain, rw, 0);
355 
356 			set_app_icon();
357 		}
358 
359 		rw = load_img_if_exists(&res, "logo");
360 		if (rw)
361 		{
362 			if (mused.logo) gfx_free_surface(mused.logo);
363 			mused.logo = gfx_load_surface_RW(domain, rw, GFX_KEYED);
364 		}
365 
366 		if (bnd_exists(&res, "colors.txt"))
367 		{
368 			SDL_RWops *colors = SDL_RWFromBundle(&res, "colors.txt");
369 			if (colors)
370 			{
371 				SDL_RWseek(colors, 0, SEEK_END);
372 				size_t s = SDL_RWtell(colors);
373 				char *temp = calloc(1, s + 2);
374 				SDL_RWseek(colors, 0, SEEK_SET);
375 				SDL_RWread(colors, temp, 1, s);
376 
377 				strcat(temp, "\n");
378 
379 				SDL_RWclose(colors);
380 
381 				load_colors(temp);
382 				free(temp);
383 			}
384 		}
385 
386 		if (bnd_exists(&res, "7x6.fnt"))
387 		{
388 			font_destroy(&mused.smallfont);
389 			font_load_and_set_color(&mused.smallfont, &res, "7x6.fnt", colors[COLOR_SMALL_TEXT]);
390 			font_destroy(&mused.shortcutfont);
391 			font_load_and_set_color(&mused.shortcutfont, &res, "7x6.fnt", colors[COLOR_MENU_SHORTCUT]);
392 			font_destroy(&mused.shortcutfont_selected);
393 			font_load_and_set_color(&mused.shortcutfont_selected, &res, "7x6.fnt", colors[COLOR_MENU_SHORTCUT_SELECTED]);
394 			font_destroy(&mused.headerfont);
395 			font_load_and_set_color(&mused.headerfont, &res, "7x6.fnt", colors[COLOR_MENU_HEADER]);
396 			font_destroy(&mused.headerfont_selected);
397 			font_load_and_set_color(&mused.headerfont_selected, &res, "7x6.fnt", colors[COLOR_MENU_HEADER_SELECTED]);
398 			font_destroy(&mused.buttonfont);
399 			font_load_and_set_color(&mused.buttonfont, &res, "7x6.fnt", colors[COLOR_BUTTON_TEXT]);
400 		}
401 		else
402 		{
403 			font_set_color(&mused.smallfont, colors[COLOR_SMALL_TEXT]);
404 			font_set_color(&mused.shortcutfont, colors[COLOR_MENU_SHORTCUT]);
405 			font_set_color(&mused.shortcutfont_selected, colors[COLOR_MENU_SHORTCUT_SELECTED]);
406 			font_set_color(&mused.headerfont, colors[COLOR_MENU_HEADER]);
407 			font_set_color(&mused.headerfont_selected, colors[COLOR_MENU_HEADER_SELECTED]);
408 			font_set_color(&mused.buttonfont, colors[COLOR_BUTTON_TEXT]);
409 		}
410 
411 		if (bnd_exists(&res, "8x8.fnt"))
412 		{
413 			if (mused.console) console_destroy(mused.console);
414 			mused.console = console_create(&res);
415 
416 			font_destroy(&mused.largefont);
417 			font_load_and_set_color(&mused.largefont, &res, "8x8.fnt", colors[COLOR_MAIN_TEXT]);
418 			font_destroy(&mused.menufont);
419 			font_load_and_set_color(&mused.menufont, &res, "8x8.fnt", colors[COLOR_MENU_NORMAL]);
420 			font_destroy(&mused.menufont_selected);
421 			font_load_and_set_color(&mused.menufont_selected, &res, "8x8.fnt", colors[COLOR_MENU_SELECTED]);
422 		}
423 		else
424 		{
425 			font_set_color(&mused.largefont, colors[COLOR_MAIN_TEXT]);
426 			font_set_color(&mused.menufont, colors[COLOR_MENU_NORMAL]);
427 			font_set_color(&mused.menufont_selected, colors[COLOR_MENU_SELECTED]);
428 		}
429 
430 		if (bnd_exists(&res, "4x6.fnt"))
431 		{
432 			font_destroy(&mused.tinyfont);
433 			font_load_and_set_color(&mused.tinyfont, &res, "4x6.fnt", colors[COLOR_MAIN_TEXT]);
434 			font_destroy(&mused.tinyfont_sequence_counter);
435 			font_load_and_set_color(&mused.tinyfont_sequence_counter, &res, "4x6.fnt", colors[COLOR_SEQUENCE_COUNTER]);
436 			font_destroy(&mused.tinyfont_sequence_normal);
437 			font_load_and_set_color(&mused.tinyfont_sequence_normal, &res, "4x6.fnt", colors[COLOR_SEQUENCE_NORMAL]);
438 		}
439 		else
440 		{
441 			font_set_color(&mused.tinyfont, colors[COLOR_MAIN_TEXT]);
442 			font_set_color(&mused.tinyfont_sequence_counter, colors[COLOR_SEQUENCE_COUNTER]);
443 			font_set_color(&mused.tinyfont_sequence_normal, colors[COLOR_SEQUENCE_NORMAL]);
444 		}
445 
446 		bnd_free(&res);
447 		strncpy(mused.themename, tmpname, sizeof(mused.themename));
448 		update_theme_menu();
449 
450 		debug("Theme opened ok");
451 	}
452 	else
453 	{
454 		warning("Theme loading failed");
455 
456 		if (strcmp(name, "Default") != 0)
457 		{
458 			load_theme("Default");
459 		}
460 		else
461 		{
462 			char message[4000] = {0};
463 
464 			snprintf(message, sizeof(message) - 1, "Default theme at '%s' could not be loaded.", fullpath);
465 
466 			SDL_ShowSimpleMessageBox(SDL_MESSAGEBOX_ERROR, "Theme files missing", message, domain->window);
467 
468 			fatal("Default theme at '%s' could not be loaded.", fullpath);
469 
470 			exit(1);
471 		}
472 	}
473 }
474 
475 
enum_themes()476 void enum_themes()
477 {
478 	memset(thememenu, 0, sizeof(thememenu));
479 
480 	char path[2000] = {0};
481 	snprintf(path, sizeof(path) - 1, "%s/res", query_resource_directory());
482 	DIR *dir = opendir(path);
483 	debug("Enumerating themes at %s", path);
484 
485 	if (!dir)
486 	{
487 		warning("Could not enumerate themes at %s", path);
488 		return;
489 	}
490 
491 	struct dirent *de = NULL;
492 	int themes = 0;
493 
494 	while ((de = readdir(dir)) != NULL)
495 	{
496 		char fullpath[4000] = {0};
497 
498 		snprintf(fullpath, sizeof(fullpath) - 1, "%s/res/%s", query_resource_directory(), de->d_name);
499 
500 		struct stat attribute;
501 
502 		if (stat(fullpath, &attribute) != -1 && !(attribute.st_mode & S_IFDIR))
503 		{
504 			if (themes >= MAX_THEMES)
505 			{
506 				warning("Maximum themes exceeded");
507 				break;
508 			}
509 
510 			thememenu[themes].parent = prefsmenu;
511 			thememenu[themes].text = strdup(de->d_name);
512 			thememenu[themes].action = load_theme_action;
513 			thememenu[themes].p1 = (void*)thememenu[themes].text;
514 			++themes;
515 		}
516 	}
517 
518 	debug("Got %d themes", themes);
519 
520 	closedir(dir);
521 }
522 
523 
update_theme_menu()524 void update_theme_menu()
525 {
526 	for (int i = 0 ; thememenu[i].text ; ++i)
527 	{
528 		if (strcmp(mused.themename, (char*)thememenu[i].p1) == 0)
529 		{
530 			thememenu[i].flags |= MENU_BULLET;
531 		}
532 		else
533 			thememenu[i].flags &= ~MENU_BULLET;
534 	}
535 }
536 
537 
free_themes()538 void free_themes()
539 {
540 	for (int i = 0 ; i < MAX_THEMES ; ++i)
541 	{
542 		if (thememenu[i].text != NULL) free((void*)thememenu[i].text);
543 	}
544 
545 	memset(thememenu, 0, sizeof(thememenu));
546 }
547 
548 
mix_colors(Uint32 a,Uint32 b)549 Uint32 mix_colors(Uint32 a, Uint32 b)
550 {
551 	Sint32 ba = 255 - ((b >> 24) & 0xff);
552 	Sint32 ar = a & 0xff;
553 	Sint32 ag = (a >> 8) & 0xff;
554 	Sint32 ab = (a >> 16) & 0xff;
555 	Sint32 br = (b & 0xff) - ar;
556 	Sint32 bg = ((b >> 8) & 0xff) - ag;
557 	Sint32 bb = ((b >> 16) & 0xff) - ab;
558 
559 	Uint32 fr = ar + br * ba / 256;
560 	Uint32 fg = ag + bg * ba / 256;
561 	Uint32 fb = ab + bb * ba / 256;
562 
563 	return fr | (fg << 8) | (fb << 16);
564 }
565