netsurf/frontends/riscos/theme.c
2017-09-07 18:12:09 +01:00

762 lines
20 KiB
C

/*
* 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 "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);
}
}
}