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