summaryrefslogtreecommitdiff
path: root/frontends/riscos/theme.c
diff options
context:
space:
mode:
Diffstat (limited to 'frontends/riscos/theme.c')
-rw-r--r--frontends/riscos/theme.c741
1 files changed, 741 insertions, 0 deletions
diff --git a/frontends/riscos/theme.c b/frontends/riscos/theme.c
new file mode 100644
index 000000000..714b9e5a1
--- /dev/null
+++ b/frontends/riscos/theme.c
@@ -0,0 +1,741 @@
+/*
+ * Copyright 2004, 2005 Richard Wilson <info@tinct.net>
+ *
+ * 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 <http://www.gnu.org/licenses/>.
+ */
+
+/** \file
+ * Window themes (implementation).
+ */
+
+#include <alloca.h>
+#include <assert.h>
+#include <stdio.h>
+#include <stdbool.h>
+#include <string.h>
+#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 "content/content.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/treeview.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) {
+ LOG("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) {
+ LOG("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) {
+ LOG("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) {
+ LOG("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) {
+ LOG("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) {
+ LOG("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) {
+ LOG("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) {
+ LOG("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);
+ LOG("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);
+ LOG("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);
+ LOG("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);
+ LOG("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) {
+ LOG("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) {
+ LOG("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);
+ }
+ }
+}
+
+