/* * Copyright 2004, 2005 Richard Wilson * * This file is part of NetSurf, http://www.netsurf-browser.org/ * * NetSurf is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; version 2 of the License. * * NetSurf is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ /** * \file * Window themes implementation. */ #include #include #include #include #include #include "oslib/dragasprite.h" #include "oslib/os.h" #include "oslib/osgbpb.h" #include "oslib/osfile.h" #include "oslib/osfind.h" #include "oslib/osspriteop.h" #include "oslib/wimpspriteop.h" #include "oslib/squash.h" #include "oslib/wimp.h" #include "oslib/wimpextend.h" #include "oslib/wimpspriteop.h" #include "utils/nsoption.h" #include "utils/log.h" #include "riscos/cookies.h" #include "riscos/dialog.h" #include "riscos/global_history.h" #include "riscos/gui.h" #include "riscos/hotlist.h" #include "riscos/menus.h" #include "riscos/theme.h" #include "riscos/toolbar.h" #include "riscos/wimp.h" #include "riscos/wimp_event.h" #include "riscos/wimputils.h" /** @todo provide a proper interface for these and make them static again! */ static struct theme_descriptor *theme_current = NULL; static struct theme_descriptor *theme_descriptors = NULL; static bool ro_gui_theme_add_descriptor(const char *folder, const char *leafname); static void ro_gui_theme_get_available_in_dir(const char *directory); static void ro_gui_theme_free(struct theme_descriptor *descriptor); /** * Initialise the theme handler */ void ro_gui_theme_initialise(void) { struct theme_descriptor *descriptor; theme_descriptors = ro_gui_theme_get_available(); descriptor = ro_gui_theme_find(nsoption_charp(theme)); if (!descriptor) descriptor = ro_gui_theme_find("Aletheia"); ro_gui_theme_apply(descriptor); } /** * Finalise the theme handler */ void ro_gui_theme_finalise(void) { ro_gui_theme_close(theme_current, false); ro_gui_theme_free(theme_descriptors); } /** * Finds a theme from the cached values. * * The returned theme is only guaranteed to be valid until the next call * to ro_gui_theme_get_available() unless it has been opened using * ro_gui_theme_open(). * * \param leafname the filename of the theme_descriptor to return * \return the requested theme_descriptor, or NULL if not found */ struct theme_descriptor *ro_gui_theme_find(const char *leafname) { struct theme_descriptor *descriptor; if (!leafname) return NULL; for (descriptor = theme_descriptors; descriptor; descriptor = descriptor->next) if (!strcmp(leafname, descriptor->leafname)) return descriptor; /* fallback for 10 chars on old filesystems */ for (descriptor = theme_descriptors; descriptor; descriptor = descriptor->next) if (!strncmp(leafname, descriptor->leafname, 10)) return descriptor; return NULL; } /** * Reads and caches the currently available themes. * * \return the requested theme_descriptor, or NULL if not found */ struct theme_descriptor *ro_gui_theme_get_available(void) { struct theme_descriptor *current; struct theme_descriptor *test; /* close any unused descriptors */ ro_gui_theme_free(theme_descriptors); /* add our default 'Aletheia' theme */ ro_gui_theme_add_descriptor("NetSurf:Resources", "Aletheia"); /* scan our choices directory */ ro_gui_theme_get_available_in_dir(nsoption_charp(theme_path)); /* sort alphabetically in a very rubbish way */ if ((theme_descriptors) && (theme_descriptors->next)) { current = theme_descriptors; while ((test = current->next)) { if (strcmp(current->name, test->name) > 0) { current->next->previous = current->previous; if (current->previous) current->previous->next = current->next; current->next = test->next; test->next = current; current->previous = test; if (current->next) current->next->previous = current; current = test->previous; if (!current) current = test; } else { current = current->next; } } while (theme_descriptors->previous) theme_descriptors = theme_descriptors->previous; } return theme_descriptors; } /** * Adds the themes in a directory to the global cache. * * \param directory the directory to scan */ static void ro_gui_theme_get_available_in_dir(const char *directory) { int context = 0; int read_count; osgbpb_INFO(100) info; while (context != -1) { /* read some directory info */ os_error *error = xosgbpb_dir_entries_info(directory, (osgbpb_info_list *) &info, 1, context, sizeof(info), 0, &read_count, &context); if (error) { NSLOG(netsurf, INFO, "xosgbpb_dir_entries_info: 0x%x: %s", error->errnum, error->errmess); if (error->errnum == 0xd6) /* no such dir */ return; ro_warn_user("MiscError", error->errmess); break; } /* only process files */ if ((read_count != 0) && (info.obj_type == fileswitch_IS_FILE)) ro_gui_theme_add_descriptor(directory, info.name); } } /** * Returns the current theme handle, or NULL if none is set. * * \return The theme descriptor handle, or NULL. */ struct theme_descriptor *ro_gui_theme_get_current(void) { return theme_current; } /** * Returns a sprite area for use with the given theme. This may return a * pointer to the wimp sprite pool if a theme area isn't available. * * \param *descriptor The theme to use, or NULL for the current. * \return A pointer to the theme sprite area. */ osspriteop_area *ro_gui_theme_get_sprites(struct theme_descriptor *descriptor) { osspriteop_area *area; if (descriptor == NULL) descriptor = theme_current; if (descriptor != NULL && descriptor->theme != NULL) area = descriptor->theme->sprite_area; else area = (osspriteop_area *) 1; return area; } /** * Returns an interger element from the specified theme, or the current theme * if the descriptor is NULL. * * This is an attempt to abstract the theme data from its clients: it should * simplify the task of expanding the theme system in the future should this * be necessary to include other parts of the RISC OS GUI in the theme system. * * \param *descriptor The theme to use, or NULL for the current. * \param style The style to use. * \param element The style element to return. * \return The requested value, or 0. */ int ro_gui_theme_get_style_element(struct theme_descriptor *descriptor, theme_style style, theme_element element) { if (descriptor == NULL) descriptor = theme_current; if (descriptor == NULL) return 0; switch (style) { case THEME_STYLE_NONE: switch(element) { case THEME_ELEMENT_FOREGROUND: return wimp_COLOUR_BLACK; case THEME_ELEMENT_BACKGROUND: return wimp_COLOUR_VERY_LIGHT_GREY; default: return 0; } break; case THEME_STYLE_BROWSER_TOOLBAR: switch (element) { case THEME_ELEMENT_FOREGROUND: return wimp_COLOUR_BLACK; case THEME_ELEMENT_BACKGROUND: return descriptor->browser_background; default: return 0; } break; case THEME_STYLE_HOTLIST_TOOLBAR: case THEME_STYLE_COOKIES_TOOLBAR: case THEME_STYLE_GLOBAL_HISTORY_TOOLBAR: switch (element) { case THEME_ELEMENT_FOREGROUND: return wimp_COLOUR_BLACK; case THEME_ELEMENT_BACKGROUND: return descriptor->hotlist_background; default: return 0; } break; case THEME_STYLE_STATUS_BAR: switch (element) { case THEME_ELEMENT_FOREGROUND: return descriptor->status_foreground; case THEME_ELEMENT_BACKGROUND: return descriptor->status_background; default: return 0; } break; default: return 0; } } /** * Returns details of the throbber as defined in a theme. * * \param *descriptor The theme of interest (NULL for current). * \param *frames Return the number of animation frames. * \param *width Return the throbber width. * \param *height Return the throbber height. * \param *right Return the 'locate on right' flag. * \param *redraw Return the 'forcible redraw' flag. * \return true if meaningful data has been returned; * else false. */ bool ro_gui_theme_get_throbber_data(struct theme_descriptor *descriptor, int *frames, int *width, int *height, bool *right, bool *redraw) { if (descriptor == NULL) descriptor = theme_current; if (descriptor == NULL || descriptor->theme == NULL) return false; if (frames != NULL) *frames = descriptor->theme->throbber_frames; if (width != NULL) *width = descriptor->theme->throbber_width; if (height != NULL) *height = descriptor->theme->throbber_height; if (right != NULL) *right = descriptor->throbber_right; if (redraw != NULL) *redraw = descriptor->throbber_redraw; return true; } /** * Checks a theme is valid and adds it to the current list * * \param folder the theme folder * \param leafname the theme leafname * \return whether the theme was added */ bool ro_gui_theme_add_descriptor(const char *folder, const char *leafname) { struct theme_file_header file_header; struct theme_descriptor *current; struct theme_descriptor *test; int output_left; os_fw file_handle; os_error *error; char *filename; /* create a full filename */ filename = malloc(strlen(folder) + strlen(leafname) + 2); if (!filename) { NSLOG(netsurf, INFO, "No memory for malloc"); ro_warn_user("NoMemory", 0); return false; } sprintf(filename, "%s.%s", folder, leafname); /* get the header */ error = xosfind_openinw(osfind_NO_PATH, filename, 0, &file_handle); if (error) { NSLOG(netsurf, INFO, "xosfind_openinw: 0x%x: %s", error->errnum, error->errmess); ro_warn_user("FileError", error->errmess); free(filename); return false; } if (file_handle == 0) { free(filename); return false; } error = xosgbpb_read_atw(file_handle, (byte *) &file_header, sizeof (struct theme_file_header), 0, &output_left); xosfind_closew(file_handle); if (error) { NSLOG(netsurf, INFO, "xosbgpb_read_atw: 0x%x: %s", error->errnum, error->errmess); ro_warn_user("FileError", error->errmess); free(filename); return false; } if (output_left > 0) { /* should try to read more? */ free(filename); return false; } /* create a new theme descriptor */ current = (struct theme_descriptor *)calloc(1, sizeof(struct theme_descriptor)); if (!current) { NSLOG(netsurf, INFO, "calloc failed"); ro_warn_user("NoMemory", 0); free(filename); return false; } if (!ro_gui_theme_read_file_header(current, &file_header)) { free(filename); free(current); return false; } current->filename = filename; current->leafname = current->filename + strlen(folder) + 1; /* don't add duplicates */ for (test = theme_descriptors; test; test = test->next) { if (!strcmp(current->name, test->name)) { free(current->filename); free(current); return false; } } /* link in our new descriptor at the head*/ if (theme_descriptors) { current->next = theme_descriptors; theme_descriptors->previous = current; } theme_descriptors = current; return true; } /** * Fills in the basic details for a descriptor from a file header. * The filename string is not set. * * \param descriptor the descriptor to set up * \param file_header the header to read from * \return false for a badly formed theme, true otherwise */ bool ro_gui_theme_read_file_header(struct theme_descriptor *descriptor, struct theme_file_header *file_header) { if ((file_header->magic_value != 0x4d54534e) || (file_header->parser_version > 2)) return false; strcpy(descriptor->name, file_header->name); strcpy(descriptor->author, file_header->author); descriptor->browser_background = file_header->browser_bg; descriptor->hotlist_background = file_header->hotlist_bg; descriptor->status_background = file_header->status_bg; descriptor->status_foreground = file_header->status_fg; descriptor->decompressed_size = file_header->decompressed_sprite_size; descriptor->compressed_size = file_header->compressed_sprite_size; if (file_header->parser_version >= 2) { descriptor->throbber_right = !(file_header->theme_flags & (1 << 0)); descriptor->throbber_redraw = file_header->theme_flags & (1 << 1); } else { descriptor->throbber_right = (file_header->theme_flags == 0x00); descriptor->throbber_redraw = true; } return true; } /** * Opens a theme ready for use. * * \param descriptor the theme_descriptor to open * \param list whether to open all themes in the list * \return whether the operation was successful */ bool ro_gui_theme_open(struct theme_descriptor *descriptor, bool list) { fileswitch_object_type obj_type; squash_output_status status; os_coord dimensions; os_mode mode; os_error *error; struct theme_descriptor *next_descriptor; char sprite_name[16]; const char *name = sprite_name; bool result = true; int i, n; int workspace_size, file_size; char *raw_data, *workspace; osspriteop_area *decompressed; /* If we are freeing the whole of the list then we need to start at the first descriptor. */ if (list && descriptor) while (descriptor->previous) descriptor = descriptor->previous; /* Open the themes */ for (; descriptor; descriptor = next_descriptor) { /* see if we should iterate through the entire list */ if (list) next_descriptor = descriptor->next; else next_descriptor = NULL; /* if we are already loaded, increase the usage count */ if (descriptor->theme) { descriptor->theme->users = descriptor->theme->users + 1; continue; } /* create a new theme */ descriptor->theme = (struct theme *)calloc(1, sizeof(struct theme)); if (!descriptor->theme) { NSLOG(netsurf, INFO, "calloc() failed"); ro_warn_user("NoMemory", 0); continue; } descriptor->theme->users = 1; /* try to load the associated file */ error = xosfile_read_stamped_no_path(descriptor->filename, &obj_type, 0, 0, &file_size, 0, 0); if (error) { NSLOG(netsurf, INFO, "xosfile_read_stamped_no_path: 0x%x: %s", error->errnum, error->errmess); ro_warn_user("FileError", error->errmess); continue; } if (obj_type != fileswitch_IS_FILE) continue; raw_data = malloc(file_size); if (!raw_data) { NSLOG(netsurf, INFO, "malloc() failed"); ro_warn_user("NoMemory", 0); continue; } error = xosfile_load_stamped_no_path(descriptor->filename, (byte *)raw_data, 0, 0, 0, 0, 0); if (error) { free(raw_data); NSLOG(netsurf, INFO, "xosfile_load_stamped_no_path: 0x%x: %s", error->errnum, error->errmess); ro_warn_user("FileError", error->errmess); continue; } /* decompress the new data */ error = xsquash_decompress_return_sizes(-1, &workspace_size, 0); if (error) { free(raw_data); NSLOG(netsurf, INFO, "xsquash_decompress_return_sizes: 0x%x: %s", error->errnum, error->errmess); ro_warn_user("MiscError", error->errmess); continue; } decompressed = (osspriteop_area *)malloc( descriptor->decompressed_size); workspace = malloc(workspace_size); if ((!decompressed) || (!workspace)) { free(decompressed); free(raw_data); NSLOG(netsurf, INFO, "malloc() failed"); ro_warn_user("NoMemory", 0); continue; } error = xsquash_decompress(squash_INPUT_ALL_PRESENT, workspace, (byte *)(raw_data + sizeof( struct theme_file_header)), descriptor->compressed_size, (byte *)decompressed, descriptor->decompressed_size, &status, 0, 0, 0, 0); free(workspace); free(raw_data); if (error) { free(decompressed); NSLOG(netsurf, INFO, "xsquash_decompress: 0x%x: %s", error->errnum, error->errmess); ro_warn_user("MiscError", error->errmess); continue; } if (status != 0) { free(decompressed); continue; } descriptor->theme->sprite_area = decompressed; /* find the highest sprite called 'throbber%i', and get the * maximum dimensions for all 'thobber%i' icons. */ for (i = 1; i <= descriptor->theme->sprite_area->sprite_count; i++) { error = xosspriteop_return_name(osspriteop_USER_AREA, descriptor->theme->sprite_area, sprite_name, 16, i, 0); if (error) { NSLOG(netsurf, INFO, "xosspriteop_return_name: 0x%x: %s", error->errnum, error->errmess); ro_warn_user("MiscError", error->errmess); continue; } if (strncmp(sprite_name, "throbber", 8)) continue; /* get the max sprite width/height */ error = xosspriteop_read_sprite_info( osspriteop_USER_AREA, descriptor->theme->sprite_area, (osspriteop_id) name, &dimensions.x, &dimensions.y, (osbool *) 0, &mode); if (error) { NSLOG(netsurf, INFO, "xosspriteop_read_sprite_info: 0x%x: %s", error->errnum, error->errmess); ro_warn_user("MiscError", error->errmess); continue; } ro_convert_pixels_to_os_units(&dimensions, mode); if (descriptor->theme->throbber_width < dimensions.x) descriptor->theme->throbber_width = dimensions.x; if (descriptor->theme->throbber_height < dimensions.y) descriptor->theme->throbber_height = dimensions.y; /* get the throbber number */ n = atoi(sprite_name + 8); if (descriptor->theme->throbber_frames < n) descriptor->theme->throbber_frames = n; } } return result; } /** * Applies the theme to all current windows and subsequent ones. * * \param descriptor the theme_descriptor to open * \return whether the operation was successful */ bool ro_gui_theme_apply(struct theme_descriptor *descriptor) { struct theme_descriptor *theme_previous; /* check if the theme is already applied */ if (descriptor == theme_current) return true; /* re-open the new-theme and release the current theme */ if (!ro_gui_theme_open(descriptor, false)) return false; theme_previous = theme_current; theme_current = descriptor; /* apply the theme to all the current toolbar-ed windows */ ro_toolbar_theme_update(); ro_gui_theme_close(theme_previous, false); return true; } /** * Closes a theme after use. * * \param descriptor the theme_descriptor to close * \param list whether to open all themes in the list * \return whether the operation was successful */ void ro_gui_theme_close(struct theme_descriptor *descriptor, bool list) { if (!descriptor) return; /* move to the start of the list */ while (list && descriptor->previous) descriptor = descriptor->previous; /* close the themes */ while (descriptor) { if (descriptor->theme) { descriptor->theme->users = descriptor->theme->users - 1; if (descriptor->theme->users <= 0) { free(descriptor->theme->sprite_area); free(descriptor->theme); descriptor->theme = NULL; } } if (!list) return; descriptor = descriptor->next; } } /** * Frees any unused theme descriptors. * * \param descriptor the theme_descriptor to free */ void ro_gui_theme_free(struct theme_descriptor *descriptor) { struct theme_descriptor *next_descriptor; if (!descriptor) return; /* move to the start of the list */ while (descriptor->previous) descriptor = descriptor->previous; /* free closed themes */ for (; descriptor; descriptor = next_descriptor) { next_descriptor = descriptor->next; /* no theme? no descriptor */ if (!descriptor->theme) { if (descriptor->previous) descriptor->previous->next = descriptor->next; if (descriptor->next) descriptor->next->previous = descriptor->previous; /* keep the cached list in sync */ if (theme_descriptors == descriptor) theme_descriptors = next_descriptor; /* release any memory */ free(descriptor->filename); free(descriptor); } } }