537 lines
15 KiB
C
537 lines
15 KiB
C
/* blib - Library of useful things to hack the Blinkenlights
|
|
*
|
|
* Copyright (C) 2002 The Blinkenlights Crew
|
|
* Sven Neumann <sven@gimp.org>
|
|
*
|
|
* This program 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; either version 2 of the License, or
|
|
* (at your option) any later version.
|
|
*
|
|
* This program 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, write to the Free Software
|
|
* Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
|
|
*/
|
|
|
|
#include "config.h"
|
|
|
|
#include <string.h>
|
|
|
|
#include <glib-object.h>
|
|
|
|
#include "btypes.h"
|
|
#include "bobject.h"
|
|
#include "bparser.h"
|
|
#include "btheme.h"
|
|
#include "btheme-parser.h"
|
|
#include "butils.h"
|
|
|
|
|
|
enum
|
|
{
|
|
THEME = B_PARSER_STATE_USER,
|
|
BACKGROUND,
|
|
OVERLAY,
|
|
GRID,
|
|
GRID_WINDOW,
|
|
SPAN,
|
|
SPAN_WINDOW,
|
|
WINDOW,
|
|
FINISH
|
|
};
|
|
|
|
typedef struct
|
|
{
|
|
BTheme *theme;
|
|
gchar *dirname;
|
|
gboolean lazy;
|
|
BOverlay *overlay;
|
|
gint dx, dy;
|
|
gint sx, sy;
|
|
GList *windows;
|
|
} ParserData;
|
|
|
|
|
|
static BParserState b_theme_start_element (BParserState state,
|
|
const gchar *element_name,
|
|
const gchar **attribute_names,
|
|
const gchar **attribute_values,
|
|
gpointer user_data,
|
|
GError **error);
|
|
static BParserState b_theme_end_element (BParserState state,
|
|
const gchar *element_name,
|
|
const gchar *cdata,
|
|
gsize cdata_len,
|
|
gpointer user_data,
|
|
GError **error);
|
|
static void b_theme_parse_header (BTheme *theme,
|
|
const gchar **attribute_names,
|
|
const gchar **attribute_values);
|
|
static void b_theme_parse_image (ParserData *data,
|
|
BOverlay *overlay,
|
|
const gchar **attribute_names,
|
|
const gchar **attribute_values);
|
|
static void b_theme_parse_grid (ParserData *data,
|
|
const gchar **attribute_names,
|
|
const gchar **attribute_values);
|
|
static void b_theme_parse_window (BWindow *window,
|
|
const gchar **attribute_names,
|
|
const gchar **attribute_values);
|
|
static void b_theme_overlay_add_window (BTheme *theme,
|
|
BOverlay *overlay,
|
|
const BWindow *window);
|
|
static gboolean b_theme_window_validate (BTheme *theme,
|
|
const BWindow *window);
|
|
|
|
|
|
gboolean
|
|
b_theme_parser_parse (BTheme *theme,
|
|
gboolean lazy,
|
|
GError **error)
|
|
{
|
|
BParser *parser;
|
|
ParserData data = { NULL };
|
|
GIOChannel *io;
|
|
const gchar *filename;
|
|
gboolean retval;
|
|
|
|
g_return_val_if_fail (theme != NULL, FALSE);
|
|
g_return_val_if_fail (error == NULL || *error == NULL, FALSE);
|
|
|
|
filename = b_object_get_filename (B_OBJECT (theme));
|
|
g_return_val_if_fail (filename != NULL, FALSE);
|
|
|
|
io = g_io_channel_new_file (filename, "r", error);
|
|
if (!io)
|
|
return FALSE;
|
|
|
|
data.theme = theme;
|
|
data.dirname = g_path_get_dirname (filename);
|
|
data.lazy = lazy;
|
|
|
|
parser = b_parser_new (b_theme_start_element, b_theme_end_element, &data);
|
|
|
|
retval = b_parser_parse_io_channel (parser, io, TRUE, error);
|
|
|
|
if (retval && b_parser_get_state (parser) != FINISH)
|
|
{
|
|
g_set_error (error, G_MARKUP_ERROR, G_MARKUP_ERROR_INVALID_CONTENT,
|
|
"This does not look like a Blinkenlights Theme");
|
|
retval = FALSE;
|
|
}
|
|
|
|
g_io_channel_unref (io);
|
|
|
|
b_parser_free (parser);
|
|
|
|
g_free (data.dirname);
|
|
|
|
if (retval &&
|
|
(theme->rows < 1 || theme->columns < 1 ||
|
|
theme->channels < 1 ||
|
|
theme->maxval < 1 || theme->maxval > 256 ||
|
|
theme->width < 1 || theme->height < 1))
|
|
{
|
|
g_set_error (error, G_MARKUP_ERROR, G_MARKUP_ERROR_INVALID_CONTENT,
|
|
"Incorrect theme header");
|
|
retval = FALSE;
|
|
}
|
|
|
|
return retval;
|
|
}
|
|
|
|
static BParserState
|
|
b_theme_start_element (BParserState state,
|
|
const gchar *element_name,
|
|
const gchar **attribute_names,
|
|
const gchar **attribute_values,
|
|
gpointer user_data,
|
|
GError **error)
|
|
{
|
|
ParserData *data = (ParserData *) user_data;
|
|
|
|
switch (state)
|
|
{
|
|
case B_PARSER_STATE_TOPLEVEL:
|
|
if (strcmp (element_name, "blinkentheme") == 0)
|
|
{
|
|
b_theme_parse_header (data->theme,
|
|
attribute_names, attribute_values);
|
|
return THEME;
|
|
}
|
|
break;
|
|
|
|
case THEME:
|
|
if (data->lazy)
|
|
return B_PARSER_STATE_UNKNOWN;
|
|
|
|
if (strcmp (element_name, "background") == 0)
|
|
{
|
|
b_theme_parse_image (data, NULL,
|
|
attribute_names, attribute_values);
|
|
b_parse_coordinates (attribute_names, attribute_values,
|
|
&data->theme->bg_image_x,
|
|
&data->theme->bg_image_y);
|
|
return BACKGROUND;
|
|
}
|
|
else if (strcmp (element_name, "overlay") == 0)
|
|
{
|
|
BOverlay *overlay = g_new0 (BOverlay, 1);
|
|
|
|
b_theme_parse_image (data, overlay,
|
|
attribute_names, attribute_values);
|
|
|
|
data->overlay = overlay;
|
|
return OVERLAY;
|
|
}
|
|
break;
|
|
|
|
case OVERLAY:
|
|
if (strcmp (element_name, "grid") == 0)
|
|
{
|
|
b_theme_parse_grid (data, attribute_names, attribute_values);
|
|
return GRID;
|
|
}
|
|
else if (strcmp (element_name, "span") == 0)
|
|
{
|
|
b_theme_parse_grid (data, attribute_names, attribute_values);
|
|
return SPAN;
|
|
}
|
|
else if (strcmp (element_name, "window") == 0)
|
|
{
|
|
BWindow window;
|
|
|
|
b_theme_parse_window (&window, attribute_names, attribute_values);
|
|
|
|
if (b_theme_window_validate (data->theme, &window))
|
|
b_theme_overlay_add_window (data->theme, data->overlay, &window);
|
|
|
|
return WINDOW;
|
|
}
|
|
|
|
break;
|
|
|
|
case GRID:
|
|
case SPAN:
|
|
if (strcmp (element_name, "window") == 0)
|
|
{
|
|
BWindow window;
|
|
|
|
b_theme_parse_window (&window, attribute_names, attribute_values);
|
|
if (b_theme_window_validate (data->theme, &window))
|
|
data->windows = g_list_append (data->windows,
|
|
g_memdup (&window,
|
|
sizeof (BWindow)));
|
|
|
|
return state == GRID ? GRID_WINDOW : SPAN_WINDOW;
|
|
}
|
|
break;
|
|
|
|
case GRID_WINDOW:
|
|
case SPAN_WINDOW:
|
|
case WINDOW:
|
|
g_printerr ("window element should be empty");
|
|
break;
|
|
|
|
default:
|
|
break;
|
|
}
|
|
|
|
return B_PARSER_STATE_UNKNOWN;
|
|
}
|
|
|
|
static BParserState
|
|
b_theme_end_element (BParserState state,
|
|
const gchar *element_name,
|
|
const gchar *cdata,
|
|
gsize cdata_len,
|
|
gpointer user_data,
|
|
GError **error)
|
|
{
|
|
ParserData *data = (ParserData *) user_data;
|
|
GList *list;
|
|
|
|
switch (state)
|
|
{
|
|
case THEME:
|
|
data->theme->overlays = g_list_reverse (data->theme->overlays);
|
|
return FINISH;
|
|
|
|
case BACKGROUND:
|
|
return THEME;
|
|
|
|
case OVERLAY:
|
|
g_return_val_if_fail (data->overlay != NULL, THEME);
|
|
|
|
if (data->overlay->windows)
|
|
{
|
|
data->overlay->windows = g_list_reverse (data->overlay->windows);
|
|
data->theme->overlays = g_list_prepend (data->theme->overlays,
|
|
data->overlay);
|
|
}
|
|
else
|
|
{
|
|
g_printerr ("no windows defined in overlay, skipping");
|
|
|
|
g_free (data->overlay->image);
|
|
g_free (data->overlay);
|
|
}
|
|
data->overlay = NULL;
|
|
return THEME;
|
|
|
|
case GRID:
|
|
case SPAN:
|
|
for (list = data->windows; list; list = list->next)
|
|
{
|
|
BWindow *template = list->data;
|
|
gint row, col;
|
|
|
|
template->column = 0;
|
|
if (state == GRID)
|
|
template->row = 0;
|
|
|
|
for (row = template->row; row < data->theme->rows; row++)
|
|
{
|
|
for (col = 0; col < data->theme->columns; col++)
|
|
{
|
|
BWindow window = *template;
|
|
|
|
window.row = row;
|
|
window.column = col;
|
|
window.src_x += col * data->sx;
|
|
window.rect.x += col * data->dx;
|
|
|
|
#if 0
|
|
g_print ("window %d,%d (%d) is %dx%d @ %d,%d src=%d,%d\n",
|
|
window.column, window.row, window.value,
|
|
window.rect.w, window.rect.h,
|
|
window.rect.x, window.rect.y,
|
|
window.src_x, window.src_y);
|
|
#endif
|
|
|
|
b_theme_overlay_add_window (data->theme, data->overlay,
|
|
&window);
|
|
}
|
|
|
|
if (state == SPAN)
|
|
break;
|
|
|
|
template->src_y += data->sy;
|
|
template->rect.y += data->dy;
|
|
}
|
|
|
|
g_free (template);
|
|
}
|
|
|
|
g_list_free (data->windows);
|
|
data->windows = NULL;
|
|
return OVERLAY;
|
|
|
|
case GRID_WINDOW:
|
|
return GRID;
|
|
|
|
case SPAN_WINDOW:
|
|
return SPAN;
|
|
|
|
case WINDOW:
|
|
return OVERLAY;
|
|
|
|
default:
|
|
break;
|
|
}
|
|
|
|
return B_PARSER_STATE_UNKNOWN;
|
|
}
|
|
|
|
static void
|
|
b_theme_parse_header (BTheme *theme,
|
|
const gchar **attribute_names,
|
|
const gchar **attribute_values)
|
|
{
|
|
const gchar **name;
|
|
const gchar **value;
|
|
|
|
if (theme->type)
|
|
{
|
|
g_free (theme->type);
|
|
theme->type = NULL;
|
|
}
|
|
|
|
theme->rows = 0;
|
|
theme->columns = 0;
|
|
theme->channels = 1;
|
|
theme->maxval = 1;
|
|
theme->width = 0;
|
|
theme->height = 0;
|
|
|
|
for (name = attribute_names, value = attribute_values;
|
|
*name && *value;
|
|
name++, value++)
|
|
{
|
|
if (strcmp (*name, "title") == 0)
|
|
b_object_set_name (B_OBJECT (theme), *value);
|
|
if (strcmp (*name, "type") == 0)
|
|
theme->type = g_strdup (*value);
|
|
if (strcmp (*name, "rows") == 0)
|
|
b_parse_int (*value, &theme->rows);
|
|
if (strcmp (*name, "columns") == 0)
|
|
b_parse_int (*value, &theme->columns);
|
|
if (strcmp (*name, "channels") == 0)
|
|
b_parse_int (*value, &theme->channels);
|
|
if (strcmp (*name, "maxval") == 0)
|
|
b_parse_int (*value, &theme->maxval);
|
|
if (strcmp (*name, "width") == 0)
|
|
b_parse_int (*value, &theme->width);
|
|
if (strcmp (*name, "height") == 0)
|
|
b_parse_int (*value, &theme->height);
|
|
}
|
|
}
|
|
|
|
static void
|
|
b_theme_parse_image (ParserData *data,
|
|
BOverlay *overlay,
|
|
const gchar **attribute_names,
|
|
const gchar **attribute_values)
|
|
{
|
|
const gchar **name;
|
|
const gchar **value;
|
|
gchar **image;
|
|
BColor *color;
|
|
|
|
image = overlay ? &overlay->image : &data->theme->bg_image;
|
|
|
|
for (name = attribute_names, value = attribute_values;
|
|
*name && *value;
|
|
name++, value++)
|
|
{
|
|
if (!*image && strcmp (*name, "image") == 0)
|
|
*image = g_build_filename (data->dirname, *value, NULL);
|
|
}
|
|
|
|
color = overlay ? &overlay->color : &data->theme->bg_color;
|
|
|
|
if (overlay)
|
|
color->r = color->g = color->b = 0xFF;
|
|
else
|
|
color->r = color->g = color->b = 0x00;
|
|
|
|
color->a = 0xFF;
|
|
|
|
b_parse_color (attribute_names, attribute_values, color);
|
|
}
|
|
|
|
static void
|
|
b_theme_parse_grid (ParserData *data,
|
|
const gchar **attribute_names,
|
|
const gchar **attribute_values)
|
|
{
|
|
const gchar **name;
|
|
const gchar **value;
|
|
|
|
data->dx = data->dy = data->sx = data->sy = 0;
|
|
|
|
for (name = attribute_names, value = attribute_values;
|
|
*name && *value;
|
|
name++, value++)
|
|
{
|
|
if (strcmp (*name, "dx") == 0)
|
|
b_parse_int (*value, &data->dx);
|
|
if (strcmp (*name, "dy") == 0)
|
|
b_parse_int (*value, &data->dy);
|
|
if (strcmp (*name, "sx") == 0)
|
|
b_parse_int (*value, &data->sx);
|
|
if (strcmp (*name, "sy") == 0)
|
|
b_parse_int (*value, &data->sy);
|
|
}
|
|
}
|
|
|
|
static void
|
|
b_theme_parse_window (BWindow *window,
|
|
const gchar **attribute_names,
|
|
const gchar **attribute_values)
|
|
{
|
|
const gchar **name;
|
|
const gchar **value;
|
|
|
|
window->value = B_WINDOW_VALUE_ALL;
|
|
window->row = window->column = 0;
|
|
window->src_x = window->src_y = 0;
|
|
window->rect.x = window->rect.y = window->rect.w = window->rect.h = 0;
|
|
|
|
for (name = attribute_names, value = attribute_values;
|
|
*name && *value;
|
|
name++, value++)
|
|
{
|
|
if (strcmp (*name, "value") == 0 && strcmp (*value, "all"))
|
|
b_parse_int (*value, &window->value);
|
|
if (strcmp (*name, "row") == 0)
|
|
b_parse_int (*value, &window->row);
|
|
if (strcmp (*name, "column") == 0)
|
|
b_parse_int (*value, &window->column);
|
|
if (strcmp (*name, "src-x") == 0)
|
|
b_parse_int (*value, &window->src_x);
|
|
if (strcmp (*name, "src-y") == 0)
|
|
b_parse_int (*value, &window->src_y);
|
|
}
|
|
|
|
b_parse_rectangle (attribute_names, attribute_values, &window->rect);
|
|
}
|
|
|
|
static void
|
|
b_theme_overlay_add_window (BTheme *theme,
|
|
BOverlay *overlay,
|
|
const BWindow *window)
|
|
{
|
|
BWindow *windows;
|
|
GList *list;
|
|
gint i;
|
|
|
|
for (list = overlay->windows; list; list = list->next)
|
|
{
|
|
windows = list->data;
|
|
|
|
if (windows->row == window->row && windows->column == window->column)
|
|
break;
|
|
}
|
|
|
|
if (!list)
|
|
{
|
|
windows = g_new (BWindow, theme->maxval);
|
|
for (i = 0; i < theme->maxval; i++)
|
|
{
|
|
windows[i] = *window;
|
|
windows[i].value = B_WINDOW_VALUE_ALL;
|
|
}
|
|
overlay->windows = g_list_prepend (overlay->windows, windows);
|
|
}
|
|
|
|
if (window->value != B_WINDOW_VALUE_ALL)
|
|
{
|
|
windows[window->value - 1] = *window;
|
|
}
|
|
}
|
|
|
|
static gboolean
|
|
b_theme_window_validate (BTheme *theme,
|
|
const BWindow *window)
|
|
{
|
|
if (window
|
|
&&
|
|
(window->value != B_WINDOW_VALUE_ALL &&
|
|
(window->value < 1 || window->value > theme->maxval))
|
|
&&
|
|
(window->row < 0 || window->row >= theme->rows ||
|
|
window->column < 0 || window->column >= theme->columns))
|
|
{
|
|
g_printerr ("Invalid window, skipping");
|
|
return FALSE;
|
|
}
|
|
|
|
return TRUE;
|
|
}
|