1 /*
2  * Copyright 2004, 2005 Richard Wilson <info@tinct.net>
3  *
4  * This file is part of NetSurf, http://www.netsurf-browser.org/
5  *
6  * NetSurf is free software; you can redistribute it and/or modify
7  * it under the terms of the GNU General Public License as published by
8  * the Free Software Foundation; version 2 of the License.
9  *
10  * NetSurf 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 General Public License for more details.
14  *
15  * You should have received a copy of the GNU General Public License
16  * along with this program.  If not, see <http://www.gnu.org/licenses/>.
17  */
18 
19 /**
20  * \file
21  * Window themes implementation.
22  */
23 
24 #include <alloca.h>
25 #include <assert.h>
26 #include <stdio.h>
27 #include <stdbool.h>
28 #include <string.h>
29 #include "oslib/dragasprite.h"
30 #include "oslib/os.h"
31 #include "oslib/osgbpb.h"
32 #include "oslib/osfile.h"
33 #include "oslib/osfind.h"
34 #include "oslib/osspriteop.h"
35 #include "oslib/wimpspriteop.h"
36 #include "oslib/squash.h"
37 #include "oslib/wimp.h"
38 #include "oslib/wimpextend.h"
39 #include "oslib/wimpspriteop.h"
40 
41 #include "utils/nsoption.h"
42 #include "utils/log.h"
43 
44 #include "riscos/cookies.h"
45 #include "riscos/dialog.h"
46 #include "riscos/global_history.h"
47 #include "riscos/gui.h"
48 #include "riscos/hotlist.h"
49 #include "riscos/menus.h"
50 #include "riscos/theme.h"
51 #include "riscos/toolbar.h"
52 #include "riscos/wimp.h"
53 #include "riscos/wimp_event.h"
54 #include "riscos/wimputils.h"
55 
56 /** @todo provide a proper interface for these and make them static again! */
57 
58 static struct theme_descriptor *theme_current = NULL;
59 static struct theme_descriptor *theme_descriptors = NULL;
60 
61 static bool ro_gui_theme_add_descriptor(const char *folder, const char *leafname);
62 static void ro_gui_theme_get_available_in_dir(const char *directory);
63 static void ro_gui_theme_free(struct theme_descriptor *descriptor);
64 
65 /**
66  * Initialise the theme handler
67  */
ro_gui_theme_initialise(void)68 void ro_gui_theme_initialise(void)
69 {
70 	struct theme_descriptor *descriptor;
71 
72 	theme_descriptors = ro_gui_theme_get_available();
73 	descriptor = ro_gui_theme_find(nsoption_charp(theme));
74 	if (!descriptor)
75 		descriptor = ro_gui_theme_find("Aletheia");
76 	ro_gui_theme_apply(descriptor);
77 }
78 
79 
80 /**
81  * Finalise the theme handler
82  */
ro_gui_theme_finalise(void)83 void ro_gui_theme_finalise(void)
84 {
85 	ro_gui_theme_close(theme_current, false);
86 	ro_gui_theme_free(theme_descriptors);
87 }
88 
89 
90 /**
91  * Finds a theme from the cached values.
92  *
93  * The returned theme is only guaranteed to be valid until the next call
94  * to ro_gui_theme_get_available() unless it has been opened using
95  * ro_gui_theme_open().
96  *
97  * \param leafname  the filename of the theme_descriptor to return
98  * \return the requested theme_descriptor, or NULL if not found
99  */
ro_gui_theme_find(const char * leafname)100 struct theme_descriptor *ro_gui_theme_find(const char *leafname)
101 {
102 	struct theme_descriptor *descriptor;
103 
104 	if (!leafname)
105 		return NULL;
106 
107 	for (descriptor = theme_descriptors; descriptor;
108 			descriptor = descriptor->next)
109 		if (!strcmp(leafname, descriptor->leafname))
110 			return descriptor;
111 	/* fallback for 10 chars on old filesystems */
112 	for (descriptor = theme_descriptors; descriptor;
113 			descriptor = descriptor->next)
114 		if (!strncmp(leafname, descriptor->leafname, 10))
115 			return descriptor;
116 	return NULL;
117 }
118 
119 
120 /**
121  * Reads and caches the currently available themes.
122  *
123  * \return the requested theme_descriptor, or NULL if not found
124  */
ro_gui_theme_get_available(void)125 struct theme_descriptor *ro_gui_theme_get_available(void)
126 {
127 	struct theme_descriptor *current;
128 	struct theme_descriptor *test;
129 
130 	/* close any unused descriptors */
131 	ro_gui_theme_free(theme_descriptors);
132 
133 	/* add our default 'Aletheia' theme */
134 	ro_gui_theme_add_descriptor("NetSurf:Resources", "Aletheia");
135 
136 	/* scan our choices directory */
137 	ro_gui_theme_get_available_in_dir(nsoption_charp(theme_path));
138 
139 	/* sort alphabetically in a very rubbish way */
140 	if ((theme_descriptors) && (theme_descriptors->next)) {
141 		current = theme_descriptors;
142 		while ((test = current->next)) {
143 			if (strcmp(current->name, test->name) > 0) {
144 				current->next->previous = current->previous;
145 				if (current->previous)
146 					current->previous->next = current->next;
147 				current->next = test->next;
148 				test->next = current;
149 				current->previous = test;
150 				if (current->next)
151 					current->next->previous = current;
152 
153 				current = test->previous;
154 				if (!current) current = test;
155 			} else {
156 				current = current->next;
157 			}
158 		}
159 		while (theme_descriptors->previous)
160 			theme_descriptors = theme_descriptors->previous;
161 	}
162 
163 	return theme_descriptors;
164 }
165 
166 
167 /**
168  * Adds the themes in a directory to the global cache.
169  *
170  * \param directory  the directory to scan
171  */
ro_gui_theme_get_available_in_dir(const char * directory)172 static void ro_gui_theme_get_available_in_dir(const char *directory)
173 {
174 	int context = 0;
175 	int read_count;
176 	osgbpb_INFO(100) info;
177 
178 	while (context != -1) {
179 	  	/* read some directory info */
180 		os_error *error = xosgbpb_dir_entries_info(directory,
181 				(osgbpb_info_list *) &info, 1, context,
182 				sizeof(info), 0, &read_count, &context);
183 		if (error) {
184 			NSLOG(netsurf, INFO,
185 			      "xosgbpb_dir_entries_info: 0x%x: %s",
186 			      error->errnum,
187 			      error->errmess);
188 			if (error->errnum == 0xd6)	/* no such dir */
189 				return;
190 			ro_warn_user("MiscError", error->errmess);
191 			break;
192 		}
193 
194 		/* only process files */
195 		if ((read_count != 0) && (info.obj_type == fileswitch_IS_FILE))
196 			ro_gui_theme_add_descriptor(directory, info.name);
197 	}
198 }
199 
200 
201 /**
202  * Returns the current theme handle, or NULL if none is set.
203  *
204  * \return		The theme descriptor handle, or NULL.
205  */
206 
ro_gui_theme_get_current(void)207 struct theme_descriptor *ro_gui_theme_get_current(void)
208 {
209 	return theme_current;
210 }
211 
212 
213 /**
214  * Returns a sprite area for use with the given theme.  This may return a
215  * pointer to the wimp sprite pool if a theme area isn't available.
216  *
217  * \param *descriptor		The theme to use, or NULL for the current.
218  * \return			A pointer to the theme sprite area.
219  */
220 
ro_gui_theme_get_sprites(struct theme_descriptor * descriptor)221 osspriteop_area *ro_gui_theme_get_sprites(struct theme_descriptor *descriptor)
222 {
223 	osspriteop_area		*area;
224 
225 	if (descriptor == NULL)
226 		descriptor = theme_current;
227 
228 	if (descriptor != NULL && descriptor->theme != NULL)
229 		area = descriptor->theme->sprite_area;
230 	else
231 		area = (osspriteop_area *) 1;
232 
233 	return area;
234 }
235 
236 
237 /**
238  * Returns an interger element from the specified theme, or the current theme
239  * if the descriptor is NULL.
240  *
241  * This is an attempt to abstract the theme data from its clients: it should
242  * simplify the task of expanding the theme system in the future should this
243  * be necessary to include other parts of the RISC OS GUI in the theme system.
244  *
245  * \param *descriptor		The theme to use, or NULL for the current.
246  * \param style			The style to use.
247  * \param element		The style element to return.
248  * \return			The requested value, or 0.
249  */
250 
ro_gui_theme_get_style_element(struct theme_descriptor * descriptor,theme_style style,theme_element element)251 int ro_gui_theme_get_style_element(struct theme_descriptor *descriptor,
252 		theme_style style, theme_element element)
253 {
254 	if (descriptor == NULL)
255 		descriptor = theme_current;
256 
257 	if (descriptor == NULL)
258 		return 0;
259 
260 	switch (style) {
261 	case THEME_STYLE_NONE:
262 		switch(element) {
263 		case THEME_ELEMENT_FOREGROUND:
264 			return wimp_COLOUR_BLACK;
265 		case THEME_ELEMENT_BACKGROUND:
266 			return wimp_COLOUR_VERY_LIGHT_GREY;
267 		default:
268 			return 0;
269 		}
270 		break;
271 
272 	case THEME_STYLE_BROWSER_TOOLBAR:
273 		switch (element) {
274 		case THEME_ELEMENT_FOREGROUND:
275 			return wimp_COLOUR_BLACK;
276 		case THEME_ELEMENT_BACKGROUND:
277 			return descriptor->browser_background;
278 		default:
279 			return 0;
280 		}
281 		break;
282 
283 	case THEME_STYLE_HOTLIST_TOOLBAR:
284 	case THEME_STYLE_COOKIES_TOOLBAR:
285 	case THEME_STYLE_GLOBAL_HISTORY_TOOLBAR:
286 		switch (element) {
287 		case THEME_ELEMENT_FOREGROUND:
288 			return wimp_COLOUR_BLACK;
289 		case THEME_ELEMENT_BACKGROUND:
290 			return descriptor->hotlist_background;
291 		default:
292 			return 0;
293 		}
294 		break;
295 
296 	case THEME_STYLE_STATUS_BAR:
297 		switch (element) {
298 		case THEME_ELEMENT_FOREGROUND:
299 			return descriptor->status_foreground;
300 		case THEME_ELEMENT_BACKGROUND:
301 			return descriptor->status_background;
302 		default:
303 			return 0;
304 		}
305 		break;
306 
307 	default:
308 		return 0;
309 	}
310 }
311 
312 /**
313  * Returns details of the throbber as defined in a theme.
314  *
315  * \param *descriptor		The theme of interest (NULL for current).
316  * \param *frames		Return the number of animation frames.
317  * \param *width		Return the throbber width.
318  * \param *height		Return the throbber height.
319  * \param *right		Return the 'locate on right' flag.
320  * \param *redraw		Return the 'forcible redraw' flag.
321  * \return			true if meaningful data has been returned;
322  *				else false.
323  */
324 
ro_gui_theme_get_throbber_data(struct theme_descriptor * descriptor,int * frames,int * width,int * height,bool * right,bool * redraw)325 bool ro_gui_theme_get_throbber_data(struct theme_descriptor *descriptor,
326 		int *frames, int *width, int *height,
327 		bool *right, bool *redraw)
328 {
329 	if (descriptor == NULL)
330 		descriptor = theme_current;
331 
332 	if (descriptor == NULL || descriptor->theme == NULL)
333 		return false;
334 
335 	if (frames != NULL)
336 		*frames = descriptor->theme->throbber_frames;
337 	if (width != NULL)
338 		*width = descriptor->theme->throbber_width;
339 	if (height != NULL)
340 		*height = descriptor->theme->throbber_height;
341 	if (right != NULL)
342 		*right = descriptor->throbber_right;
343 	if (redraw != NULL)
344 		*redraw = descriptor->throbber_redraw;
345 
346 	return true;
347 }
348 
349 
350 /**
351  * Checks a theme is valid and adds it to the current list
352  *
353  * \param folder	the theme folder
354  * \param leafname	the theme leafname
355  * \return whether the theme was added
356  */
ro_gui_theme_add_descriptor(const char * folder,const char * leafname)357 bool ro_gui_theme_add_descriptor(const char *folder, const char *leafname)
358 {
359 	struct theme_file_header file_header;
360 	struct theme_descriptor *current;
361 	struct theme_descriptor *test;
362 	int output_left;
363 	os_fw file_handle;
364 	os_error *error;
365 	char *filename;
366 
367 	/* create a full filename */
368 	filename = malloc(strlen(folder) + strlen(leafname) + 2);
369 	if (!filename) {
370 	  	NSLOG(netsurf, INFO, "No memory for malloc");
371 	  	ro_warn_user("NoMemory", 0);
372 	  	return false;
373 	}
374 	sprintf(filename, "%s.%s", folder, leafname);
375 
376 	/* get the header */
377 	error = xosfind_openinw(osfind_NO_PATH, filename, 0,
378 			&file_handle);
379 	if (error) {
380 		NSLOG(netsurf, INFO, "xosfind_openinw: 0x%x: %s",
381 		      error->errnum, error->errmess);
382 		ro_warn_user("FileError", error->errmess);
383 		free(filename);
384 		return false;
385 	}
386 	if (file_handle == 0) {
387 		free(filename);
388 		return false;
389 	}
390 	error = xosgbpb_read_atw(file_handle,
391 			(byte *) &file_header,
392 			sizeof (struct theme_file_header),
393 			0, &output_left);
394 	xosfind_closew(file_handle);
395 	if (error) {
396 		NSLOG(netsurf, INFO, "xosbgpb_read_atw: 0x%x: %s",
397 		      error->errnum, error->errmess);
398 		ro_warn_user("FileError", error->errmess);
399 		free(filename);
400 		return false;
401 	}
402 	if (output_left > 0) {	/* should try to read more? */
403 	  	free(filename);
404 	  	return false;
405 	}
406 
407 	/* create a new theme descriptor */
408 	current = (struct theme_descriptor *)calloc(1,
409 			sizeof(struct theme_descriptor));
410 	if (!current) {
411 		NSLOG(netsurf, INFO, "calloc failed");
412 		ro_warn_user("NoMemory", 0);
413 		free(filename);
414 		return false;
415 	}
416 	if (!ro_gui_theme_read_file_header(current, &file_header)) {
417 		free(filename);
418 		free(current);
419 		return false;
420 	}
421 	current->filename = filename;
422 	current->leafname = current->filename + strlen(folder) + 1;
423 
424 	/* don't add duplicates */
425 	for (test = theme_descriptors; test; test = test->next) {
426 		if (!strcmp(current->name, test->name)) {
427 			free(current->filename);
428 			free(current);
429 			return false;
430 		}
431 	}
432 
433 	/* link in our new descriptor at the head*/
434 	if (theme_descriptors) {
435 		current->next = theme_descriptors;
436 		theme_descriptors->previous = current;
437 	}
438 	theme_descriptors = current;
439 	return true;
440 
441 }
442 
443 
444 /**
445  * Fills in the basic details for a descriptor from a file header.
446  * The filename string is not set.
447  *
448  * \param descriptor   the descriptor to set up
449  * \param file_header  the header to read from
450  * \return false for a badly formed theme, true otherwise
451  */
ro_gui_theme_read_file_header(struct theme_descriptor * descriptor,struct theme_file_header * file_header)452 bool ro_gui_theme_read_file_header(struct theme_descriptor *descriptor,
453 		struct theme_file_header *file_header)
454 {
455 	if ((file_header->magic_value != 0x4d54534e) ||
456 			(file_header->parser_version > 2))
457 		return false;
458 
459 	strcpy(descriptor->name, file_header->name);
460 	strcpy(descriptor->author, file_header->author);
461 	descriptor->browser_background = file_header->browser_bg;
462 	descriptor->hotlist_background = file_header->hotlist_bg;
463 	descriptor->status_background = file_header->status_bg;
464 	descriptor->status_foreground = file_header->status_fg;
465 	descriptor->decompressed_size = file_header->decompressed_sprite_size;
466 	descriptor->compressed_size = file_header->compressed_sprite_size;
467 	if (file_header->parser_version >= 2) {
468 		descriptor->throbber_right =
469 				!(file_header->theme_flags & (1 << 0));
470 		descriptor->throbber_redraw =
471 				file_header->theme_flags & (1 << 1);
472 	} else {
473 		descriptor->throbber_right =
474 				(file_header->theme_flags == 0x00);
475 		descriptor->throbber_redraw = true;
476 	}
477 	return true;
478 }
479 
480 
481 /**
482  * Opens a theme ready for use.
483  *
484  * \param descriptor  the theme_descriptor to open
485  * \param list	      whether to open all themes in the list
486  * \return whether the operation was successful
487  */
ro_gui_theme_open(struct theme_descriptor * descriptor,bool list)488 bool ro_gui_theme_open(struct theme_descriptor *descriptor, bool list)
489 {
490 	fileswitch_object_type obj_type;
491 	squash_output_status status;
492 	os_coord dimensions;
493 	os_mode mode;
494 	os_error *error;
495 	struct theme_descriptor *next_descriptor;
496 	char sprite_name[16];
497 	const char *name = sprite_name;
498 	bool result = true;
499 	int i, n;
500 	int workspace_size, file_size;
501 	char *raw_data, *workspace;
502 	osspriteop_area *decompressed;
503 
504 	/*	If we are freeing the whole of the list then we need to
505 		start at the first descriptor.
506 	*/
507 	if (list && descriptor)
508 		while (descriptor->previous) descriptor = descriptor->previous;
509 
510 	/*	Open the themes
511 	*/
512 	for (; descriptor; descriptor = next_descriptor) {
513 		/* see if we should iterate through the entire list */
514 		if (list)
515 			next_descriptor = descriptor->next;
516 		else
517 			next_descriptor = NULL;
518 
519 		/* if we are already loaded, increase the usage count */
520 		if (descriptor->theme) {
521 			descriptor->theme->users = descriptor->theme->users + 1;
522 			continue;
523 		}
524 
525 		/* create a new theme */
526 		descriptor->theme = (struct theme *)calloc(1,
527 				sizeof(struct theme));
528 		if (!descriptor->theme) {
529 			NSLOG(netsurf, INFO, "calloc() failed");
530 			ro_warn_user("NoMemory", 0);
531 			continue;
532 		}
533 		descriptor->theme->users = 1;
534 
535 		/* try to load the associated file */
536 		error = xosfile_read_stamped_no_path(descriptor->filename,
537 				&obj_type, 0, 0, &file_size, 0, 0);
538 		if (error) {
539 			NSLOG(netsurf, INFO,
540 			      "xosfile_read_stamped_no_path: 0x%x: %s",
541 			      error->errnum,
542 			      error->errmess);
543 			ro_warn_user("FileError", error->errmess);
544 			continue;
545 		}
546 		if (obj_type != fileswitch_IS_FILE)
547 			continue;
548 		raw_data = malloc(file_size);
549 		if (!raw_data) {
550 			NSLOG(netsurf, INFO, "malloc() failed");
551 			ro_warn_user("NoMemory", 0);
552 			continue;
553 		}
554 		error = xosfile_load_stamped_no_path(descriptor->filename,
555 				(byte *)raw_data, 0, 0, 0, 0, 0);
556 		if (error) {
557 			free(raw_data);
558 			NSLOG(netsurf, INFO,
559 			      "xosfile_load_stamped_no_path: 0x%x: %s",
560 			      error->errnum,
561 			      error->errmess);
562 			ro_warn_user("FileError", error->errmess);
563 			continue;
564 		}
565 
566 		/* decompress the new data */
567 		error = xsquash_decompress_return_sizes(-1, &workspace_size, 0);
568 		if (error) {
569 			free(raw_data);
570 			NSLOG(netsurf, INFO,
571 			      "xsquash_decompress_return_sizes: 0x%x: %s",
572 			      error->errnum,
573 			      error->errmess);
574 			ro_warn_user("MiscError", error->errmess);
575 			continue;
576 		}
577 		decompressed = (osspriteop_area *)malloc(
578 				descriptor->decompressed_size);
579 		workspace = malloc(workspace_size);
580 		if ((!decompressed) || (!workspace)) {
581 			free(decompressed);
582 			free(raw_data);
583 			NSLOG(netsurf, INFO, "malloc() failed");
584 			ro_warn_user("NoMemory", 0);
585 			continue;
586 		}
587 		error = xsquash_decompress(squash_INPUT_ALL_PRESENT, workspace,
588 				(byte *)(raw_data + sizeof(
589 						struct theme_file_header)),
590 				descriptor->compressed_size,
591 				(byte *)decompressed,
592 				descriptor->decompressed_size,
593 				&status, 0, 0, 0, 0);
594 		free(workspace);
595 		free(raw_data);
596 		if (error) {
597 			free(decompressed);
598 			NSLOG(netsurf, INFO, "xsquash_decompress: 0x%x: %s",
599 			      error->errnum, error->errmess);
600 			ro_warn_user("MiscError", error->errmess);
601 			continue;
602 		}
603 		if (status != 0) {
604 			free(decompressed);
605 			continue;
606 		}
607 		descriptor->theme->sprite_area = decompressed;
608 
609 		/* find the highest sprite called 'throbber%i', and get the
610 		 * maximum dimensions for all 'thobber%i' icons. */
611 		for (i = 1; i <= descriptor->theme->sprite_area->sprite_count;
612 				i++) {
613 			error = xosspriteop_return_name(osspriteop_USER_AREA,
614 					descriptor->theme->sprite_area,
615 					sprite_name, 16, i, 0);
616 			if (error) {
617 				NSLOG(netsurf, INFO,
618 				      "xosspriteop_return_name: 0x%x: %s",
619 				      error->errnum,
620 				      error->errmess);
621 				ro_warn_user("MiscError", error->errmess);
622 				continue;
623 			}
624 			if (strncmp(sprite_name, "throbber", 8))
625 				continue;
626 
627 			/* get the max sprite width/height */
628 			error = xosspriteop_read_sprite_info(
629 					osspriteop_USER_AREA,
630 					descriptor->theme->sprite_area,
631 					(osspriteop_id) name,
632 					&dimensions.x, &dimensions.y,
633 					(osbool *) 0, &mode);
634 			if (error) {
635 				NSLOG(netsurf, INFO,
636 				      "xosspriteop_read_sprite_info: 0x%x: %s",
637 				      error->errnum,
638 				      error->errmess);
639 				ro_warn_user("MiscError", error->errmess);
640 				continue;
641 			}
642 			ro_convert_pixels_to_os_units(&dimensions, mode);
643 			if (descriptor->theme->throbber_width <	dimensions.x)
644 				descriptor->theme->throbber_width =
645 						dimensions.x;
646 			if (descriptor->theme->throbber_height < dimensions.y)
647 				descriptor->theme->throbber_height =
648 						dimensions.y;
649 
650 			/* get the throbber number */
651 			n = atoi(sprite_name + 8);
652 			if (descriptor->theme->throbber_frames < n)
653 				descriptor->theme->throbber_frames = n;
654 		}
655 	}
656 	return result;
657 }
658 
659 
660 /**
661  * Applies the theme to all current windows and subsequent ones.
662  *
663  * \param descriptor  the theme_descriptor to open
664  * \return whether the operation was successful
665  */
ro_gui_theme_apply(struct theme_descriptor * descriptor)666 bool ro_gui_theme_apply(struct theme_descriptor *descriptor)
667 {
668 	struct theme_descriptor *theme_previous;
669 
670 	/* check if the theme is already applied */
671 	if (descriptor == theme_current)
672 		return true;
673 
674 	/* re-open the new-theme and release the current theme */
675 	if (!ro_gui_theme_open(descriptor, false))
676 		return false;
677 	theme_previous = theme_current;
678 	theme_current = descriptor;
679 
680 	/* apply the theme to all the current toolbar-ed windows */
681 	ro_toolbar_theme_update();
682 
683 	ro_gui_theme_close(theme_previous, false);
684 	return true;
685 }
686 
687 
688 /**
689  * Closes a theme after use.
690  *
691  * \param descriptor  the theme_descriptor to close
692  * \param list	      whether to open all themes in the list
693  * \return whether the operation was successful
694  */
ro_gui_theme_close(struct theme_descriptor * descriptor,bool list)695 void ro_gui_theme_close(struct theme_descriptor *descriptor, bool list)
696 {
697 
698 	if (!descriptor)
699 		return;
700 
701 	/* move to the start of the list */
702 	while (list && descriptor->previous)
703 		descriptor = descriptor->previous;
704 
705 	/* close the themes */
706 	while (descriptor) {
707 		if (descriptor->theme) {
708 			descriptor->theme->users = descriptor->theme->users - 1;
709 			if (descriptor->theme->users <= 0) {
710 				free(descriptor->theme->sprite_area);
711 				free(descriptor->theme);
712 				descriptor->theme = NULL;
713 			}
714 		}
715 		if (!list)
716 			return;
717 		descriptor = descriptor->next;
718 	}
719 }
720 
721 
722 /**
723  * Frees any unused theme descriptors.
724  *
725  * \param descriptor  the theme_descriptor to free
726  */
ro_gui_theme_free(struct theme_descriptor * descriptor)727 void ro_gui_theme_free(struct theme_descriptor *descriptor)
728 {
729 	struct theme_descriptor *next_descriptor;
730 
731 	if (!descriptor)
732 		return;
733 
734 	/* move to the start of the list */
735 	while (descriptor->previous)
736 		descriptor = descriptor->previous;
737 
738 	/* free closed themes */
739 	for (; descriptor; descriptor = next_descriptor) {
740 		next_descriptor = descriptor->next;
741 
742 		/* no theme? no descriptor */
743 		if (!descriptor->theme) {
744 			if (descriptor->previous)
745 				descriptor->previous->next = descriptor->next;
746 			if (descriptor->next)
747 				descriptor->next->previous =
748 						descriptor->previous;
749 
750 			/* keep the cached list in sync */
751 			if (theme_descriptors == descriptor)
752 				theme_descriptors = next_descriptor;
753 
754 			/* release any memory */
755 			free(descriptor->filename);
756 			free(descriptor);
757 		}
758 	}
759 }
760 
761 
762