/* gcompris - gameutil.c
*
* Copyright (C) 2000, 2008 Bruno Coudoin
*
* 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 3 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, see .
*/
#include
#include
#include
/* g_mkdir */
#define G_STDIO_NO_WRAP_ON_UNIX
#include
/* libxml includes */
#include
#include "gcompris.h"
/* GdkPixbuf RGBA C-Source image dump for a NULL image*/
#ifdef __SUNPRO_C
#pragma align 4 (null_img)
#endif
#ifdef __GNUC__
static const guint8 null_img[] __attribute__ ((__aligned__ (4))) =
#else
static const guint8 null_img[] =
#endif
{ ""
/* Pixbuf magic (0x47646b50) */
"GdkP"
/* length: header (24) + pixel_data (4) */
"\0\0\0\34"
/* pixdata_type (0x1010002) */
"\1\1\0\2"
/* rowstride (4) */
"\0\0\0\4"
/* width (1) */
"\0\0\0\1"
/* height (1) */
"\0\0\0\1"
/* pixel_data: */
"%%%\0"};
/** load a pixmap from the filesystem
*
* \param format: If format contains $LOCALE, it will be first replaced by the current long locale
* and if not found the short locale name. It support printf formating.
* \param ...: additional params for the format (printf like)
*
* \return a new allocated pixbuf or NULL
*/
GdkPixbuf *gc_pixmap_load(const gchar *format, ...)
{
va_list args;
gchar *filename;
gchar *pixmapfile;
GdkPixbuf *pixmap=NULL;
if (!format)
return NULL;
va_start (args, format);
pixmapfile = g_strdup_vprintf (format, args);
va_end (args);
/* Search */
filename = gc_file_find_absolute(pixmapfile);
if(filename)
pixmap = gdk_pixbuf_new_from_file(filename,NULL);
if (!filename || !pixmap)
{
char *str;
if(!pixmap)
g_warning("Loading image '%s' returned a null pointer", pixmapfile);
else
g_warning ("Couldn't find file %s !", pixmapfile);
str = g_strdup_printf("%s\n%s\n%s\n%s",
_("Couldn't find or load the file"),
pixmapfile,
_("This activity is incomplete."),
_("Exit it and report\nthe problem to the authors."));
gc_dialog (str, NULL);
g_free(pixmapfile);
g_free(str);
/* Create an empty pixmap because activities does not manage loading error */
return gdk_pixbuf_new_from_inline(-1, null_img, FALSE, NULL);
}
g_free(pixmapfile);
g_free(filename);
return(pixmap);
}
/*************************************************************
* colorshift a pixbuf
* code taken from the gnome-panel of gnome-core
*/
static void
do_colorshift (GdkPixbuf *dest, GdkPixbuf *src, int shift)
{
gint i, j;
gint width, height, has_alpha, srcrowstride, destrowstride;
guchar *target_pixels;
guchar *original_pixels;
guchar *pixsrc;
guchar *pixdest;
int val;
guchar r,g,b;
has_alpha = gdk_pixbuf_get_has_alpha (src);
width = gdk_pixbuf_get_width (src);
height = gdk_pixbuf_get_height (src);
srcrowstride = gdk_pixbuf_get_rowstride (src);
destrowstride = gdk_pixbuf_get_rowstride (dest);
target_pixels = gdk_pixbuf_get_pixels (dest);
original_pixels = gdk_pixbuf_get_pixels (src);
for (i = 0; i < height; i++) {
pixdest = target_pixels + i*destrowstride;
pixsrc = original_pixels + i*srcrowstride;
for (j = 0; j < width; j++) {
r = *(pixsrc++);
g = *(pixsrc++);
b = *(pixsrc++);
val = r + shift;
*(pixdest++) = CLAMP(val, 0, 255);
val = g + shift;
*(pixdest++) = CLAMP(val, 0, 255);
val = b + shift;
*(pixdest++) = CLAMP(val, 0, 255);
if (has_alpha)
*(pixdest++) = *(pixsrc++);
}
}
}
GdkPixbuf *
make_hc_pixbuf(GdkPixbuf *pb, gint val)
{
GdkPixbuf *new;
if(!pb)
return NULL;
new = gdk_pixbuf_new(gdk_pixbuf_get_colorspace(pb),
gdk_pixbuf_get_has_alpha(pb),
gdk_pixbuf_get_bits_per_sample(pb),
gdk_pixbuf_get_width(pb),
gdk_pixbuf_get_height(pb));
do_colorshift(new, pb, val);
/*do_saturate_darken (new, pb, (int)(1.00*255), (int)(1.15*255));*/
return new;
}
/**
* Free the highlight image from our image_focus system
*
* It must be called before assigning a new image to an item that
* already has a focus enabled with gc_item_focus_event().
*/
void
gc_item_focus_free(GnomeCanvasItem *item, void *none)
{
GdkPixbuf *pixbuf;
pixbuf = (GdkPixbuf *)g_object_get_data (G_OBJECT (item), "pixbuf_ref");
if(pixbuf)
{
g_object_set_data (G_OBJECT (item), "pixbuf_ref", NULL);
gdk_pixbuf_unref(pixbuf);
}
}
/**
* Set the focus of the given image (highlight or not)
*
*/
void gc_item_focus_set(GnomeCanvasItem *item, gboolean focus)
{
GdkPixbuf *dest = NULL;
GdkPixbuf *pixbuf;
GdkPixbuf *pixbuf_ref;
gtk_object_get (GTK_OBJECT (item), "pixbuf", &pixbuf, NULL);
g_return_if_fail (pixbuf != NULL);
gdk_pixbuf_unref(pixbuf);
/* Store the first pixbuf */
pixbuf_ref = (GdkPixbuf *)g_object_get_data (G_OBJECT (item), "pixbuf_ref");
if(!pixbuf_ref)
{
g_object_set_data (G_OBJECT (item), "pixbuf_ref", pixbuf);
pixbuf_ref = pixbuf;
gdk_pixbuf_ref(pixbuf);
g_signal_connect (item, "destroy",
G_CALLBACK (gc_item_focus_free),
NULL);
}
switch (focus)
{
case TRUE:
dest = make_hc_pixbuf(pixbuf, 30);
gnome_canvas_item_set (item,
"pixbuf", dest,
NULL);
break;
case FALSE:
gnome_canvas_item_set (item,
"pixbuf", pixbuf_ref,
NULL);
break;
default:
break;
}
if(dest!=NULL)
gdk_pixbuf_unref (dest);
}
/**
* Callback over a canvas item, this function will highlight the focussed item
* or the given one
*
*/
gint
gc_item_focus_event(GnomeCanvasItem *item, GdkEvent *event,
GnomeCanvasItem *dest_item)
{
if(dest_item!=NULL)
item = dest_item;
switch (event->type)
{
case GDK_ENTER_NOTIFY:
gc_item_focus_set(item, TRUE);
break;
case GDK_LEAVE_NOTIFY:
gc_item_focus_set(item, FALSE);
break;
default:
break;
}
return FALSE;
}
/*
* Return a new copy of the given string in which it has
* changes '\''n' to '\n'.
* The recognized sequences are \b
* \f \n \r \t \\ \" and the octal format.
*
*/
gchar *reactivate_newline(char *str)
{
gchar *newstr;
if(str==NULL)
return NULL;
xmlParserCtxtPtr ctxt = xmlNewParserCtxt();
newstr = (gchar *)xmlStringDecodeEntities(ctxt,
BAD_CAST str,
XML_SUBSTITUTE_REF,
0,
0,
0);
xmlFreeParserCtxt(ctxt);
return newstr;
}
/* ======================================= */
void gc_item_absolute_move(GnomeCanvasItem *item, int x, int y) {
double dx1, dy1, dx2, dy2;
gnome_canvas_item_get_bounds(item, &dx1, &dy1, &dx2, &dy2);
gnome_canvas_item_move(item, ((double)x)-dx1, ((double)y)-dy1);
}
/* ======================================= */
/** As gnome does not implement its own API : gc_item_rotate
we have to do it ourselves ....
IMPORTANT NOTE : This is designed for an item with "anchor" = GTK_ANCHOR_CENTER
rotation is clockwise if angle > 0
*/
void
gc_item_rotate(GnomeCanvasItem *item, double angle) {
double r[6],t[6], x1, x2, y1, y2;
gnome_canvas_item_get_bounds( item, &x1, &y1, &x2, &y2 );
art_affine_translate( t , -(x2+x1)/2, -(y2+y1)/2 );
art_affine_rotate( r, angle );
art_affine_multiply( r, t, r);
art_affine_translate( t , (x2+x1)/2, (y2+y1)/2 );
art_affine_multiply( r, r, t);
gnome_canvas_item_affine_absolute(item, r );
}
/* As gnome does not implement its own API : gc_item_rotate_relative
we have to do it ourselves ....
IMPORTANT NOTE : This is designed for an item with "anchor" = GTK_ANCHOR_CENTER
rotation is clockwise if angle > 0
*/
void
gc_item_rotate_relative(GnomeCanvasItem *item, double angle) {
double x1, x2, y1, y2;
double tx1, tx2, ty1, ty2;
double cx, cy;
double t;
double r[6];
// gnome_canvas_item_get_bounds( item, &x1, &y1, &x2, &y2 );
/* WARNING: Do not use gnome_canvas_item_get_bounds which gives unpredictable results */
if(GNOME_IS_CANVAS_LINE(item)) {
GnomeCanvasPoints *points;
gtk_object_get (GTK_OBJECT (item), "points", &points, NULL);
x1 = points->coords[0];
y1 = points->coords[1];
x2 = points->coords[2];
y2 = points->coords[3];
} else if(GNOME_IS_CANVAS_PIXBUF(item)){
gtk_object_get (GTK_OBJECT (item), "x", &x1, NULL);
gtk_object_get (GTK_OBJECT (item), "y", &y1, NULL);
gtk_object_get (GTK_OBJECT (item), "width", &x2, NULL);
gtk_object_get (GTK_OBJECT (item), "height", &y2, NULL);
x2 += x1;
y2 += y1;
} else if(GNOME_IS_CANVAS_GROUP(item)){
gtk_object_get (GTK_OBJECT (item), "x", &x1, NULL);
gtk_object_get (GTK_OBJECT (item), "y", &y1, NULL);
x2 = x1;
y2 = y1;
} else {
gtk_object_get (GTK_OBJECT (item), "x1", &x1, NULL);
gtk_object_get (GTK_OBJECT (item), "y1", &y1, NULL);
gtk_object_get (GTK_OBJECT (item), "x2", &x2, NULL);
gtk_object_get (GTK_OBJECT (item), "y2", &y2, NULL);
}
tx1 = x1;
ty1 = y1;
tx2 = x2;
ty2 = y2;
x1 = MIN(tx1,tx2);
y1 = MIN(ty1,ty2);
x2 = MAX(tx1,tx2);
y2 = MAX(ty1,ty2);
cx = (x2+x1)/2;
cy = (y2+y1)/2;
/* Taken from anim by Yves Combe
* This matrix rotate around ( cx, cy )
* This is the result of the product:
* T_{-c} Rot (t) T_c
*
* 1 0 cx cos(t) -sin(t) 0 1 0 -cx
* 0 1 cy by sin(t) cos(t) 0 by 0 1 -cy
* 0 0 1 0 0 1 0 0 1
*/
t = M_PI*angle/180.0;
r[0] = cos(t);
r[1] = sin(t);
r[2] = -sin(t);
r[3] = cos(t);
r[4] = (1-cos(t))*cx + sin(t)*cy;
r[5] = -sin(t)*cx + (1 - cos(t))*cy;
gnome_canvas_item_affine_relative(item, r );
}
/** rotates an item around the center (x,y), relative to the widget's coordinates
*/
void
gc_item_rotate_with_center(GnomeCanvasItem *item, double angle, int x, int y) {
double r[6],t[6], x1, x2, y1, y2, tx, ty;
gnome_canvas_item_get_bounds( item, &x1, &y1, &x2, &y2 );
tx = x1 + x;
ty = y1 + y;
art_affine_translate( t , -tx, -ty );
art_affine_rotate( r, angle );
art_affine_multiply( r, t, r);
art_affine_translate( t , tx, ty );
art_affine_multiply( r, r, t);
gnome_canvas_item_affine_absolute(item, r );
}
/** rotates an item around the center (x,y), relative to the widget's coordinates
* The rotation is relative to the previous rotation
*/
void
gc_item_rotate_relative_with_center(GnomeCanvasItem *item, double angle, int x, int y) {
double r[6],t[6], x1, x2, y1, y2, tx, ty;
gnome_canvas_item_get_bounds( item, &x1, &y1, &x2, &y2 );
tx = x1 + x;
ty = y1 + y;
art_affine_translate( t , -tx, -ty );
art_affine_rotate( r, angle );
art_affine_multiply( r, t, r);
art_affine_translate( t , tx, ty );
art_affine_multiply( r, r, t);
gnome_canvas_item_affine_relative(item, r );
}
/**
* Display the number of stars representing the difficulty level at the x,y location
* The stars are created in a group 'parent'
* The new group in which the stars are created is returned.
* This is only usefull for the menu plugin and the configuration dialog box.
*/
GnomeCanvasGroup *gc_difficulty_display(GnomeCanvasGroup *parent,
double x, double y,
double ratio,
gint difficulty)
{
GdkPixbuf *pixmap = NULL;
GnomeCanvasGroup *stars_group = NULL;
GnomeCanvasPixbuf *item = NULL;
gchar *filename = NULL;
if(difficulty==0 || difficulty>6)
return NULL;
filename = g_strdup_printf("difficulty_star%d.png", difficulty);
pixmap = gc_skin_pixmap_load(filename);
g_free(filename);
if(!pixmap)
return NULL;
stars_group = GNOME_CANVAS_GROUP(
gnome_canvas_item_new (parent,
gnome_canvas_group_get_type (),
"x", (double) 0,
"y", (double) 0,
NULL));
item = GNOME_CANVAS_PIXBUF(gnome_canvas_item_new (stars_group,
gnome_canvas_pixbuf_get_type (),
"pixbuf", pixmap,
"x", x,
"y", y,
"width", (double) gdk_pixbuf_get_width(pixmap) * ratio,
"height", (double) gdk_pixbuf_get_height(pixmap) * ratio,
"width_set", TRUE,
"height_set", TRUE,
NULL));
gtk_signal_connect(GTK_OBJECT(item), "event",
(GtkSignalFunc) gc_item_focus_event,
NULL);
gdk_pixbuf_unref(pixmap);
return(stars_group);
}
gchar *g_utf8_strndup(gchar* utf8text, gint n)
{
gchar* result;
gint len = g_utf8_strlen(utf8text, -1);
if( n < len && n > 0 )
len = n;
result = g_strndup(utf8text, g_utf8_offset_to_pointer(utf8text, len) - utf8text);
return result;
}
/** \brief search a given relative file in all gcompris dir it could be found
*
* \param format: If format contains $LOCALE, it will be first replaced by the current long locale
* and if not found the short locale name. It support printf formating.
* \param ...: additional params for the format (printf like)
*
* \return NULL or a new gchar* with the absolute_filename of the given filename or a full url to it.
*
*/
gchar*
gc_file_find_absolute(const gchar *format, ...)
{
va_list args;
int i = 0;
gchar *filename;
gchar *absolute_filename;
gchar *dir_to_search[4];
GcomprisProperties *properties = gc_prop_get();
if (!format)
return NULL;
va_start (args, format);
filename = g_strdup_vprintf (format, args);
va_end (args);
/* Check it's already found */
if( g_file_test (filename, G_FILE_TEST_EXISTS))
{
return filename;
}
/*
* Search it on the file system
*/
if(properties->server)
dir_to_search[i++] = "";
dir_to_search[i++] = properties->user_dir;
dir_to_search[i++] = properties->package_data_dir;
dir_to_search[i++] = NULL;
absolute_filename = g_strdup(filename);
i = 0;
while (dir_to_search[i])
{
gchar **tmp;
g_free(absolute_filename);
/* Check there is a $LOCALE to replace */
if((tmp = g_strsplit(filename, "$LOCALE", -1)))
{
gchar locale[6];
gchar *filename2;
/* First try with the long locale */
g_strlcpy(locale, gc_locale_get(), sizeof(locale));
filename2 = g_strjoinv(locale, tmp);
absolute_filename = g_strdup_printf("%s/%s", dir_to_search[i], filename2);
if(g_file_test (absolute_filename, G_FILE_TEST_EXISTS))
{
g_strfreev(tmp);
g_free(filename2);
goto FOUND;
}
g_free(absolute_filename);
/* Now check if this file is on the net */
if((absolute_filename = gc_net_get_url_from_file(filename2, NULL)))
{
g_strfreev(tmp);
g_free(filename2);
goto FOUND;
}
g_free(filename2);
g_free(absolute_filename);
/* Try the short locale */
if(g_strv_length(tmp)>1)
{
locale[2] = '\0';
filename2 = g_strjoinv(locale, tmp);
g_strfreev(tmp);
absolute_filename = g_strdup_printf("%s/%s", dir_to_search[i], filename2);
if(g_file_test (absolute_filename, G_FILE_TEST_EXISTS))
{
g_free(filename2);
goto FOUND;
}
g_free(absolute_filename);
/* Now check if this file is on the net */
if((absolute_filename = gc_net_get_url_from_file(filename2, NULL)))
{
g_free(filename2);
goto FOUND;
}
g_free(filename2);
}
else
g_strfreev(tmp);
}
else
{
absolute_filename = g_strdup_printf("%s/%s", dir_to_search[i], filename);
if(g_file_test (absolute_filename, G_FILE_TEST_EXISTS))
goto FOUND;
g_free(absolute_filename);
/* Now check if this file is on the net */
if((absolute_filename = gc_net_get_url_from_file(filename, NULL)))
goto FOUND;
g_free(absolute_filename);
}
i++;
}
g_free(filename);
g_free(absolute_filename);
return NULL;
FOUND:
g_free(filename);
return absolute_filename;
}
gchar*
gc_file_find_absolute_writeable(const gchar *format, ...)
{
gchar *filename, *absolute_filename, *dirname;
GcomprisProperties *prop;
va_list args;
va_start (args, format);
filename = g_strdup_vprintf (format, args);
va_end (args);
prop = gc_prop_get();
absolute_filename = g_build_filename(prop->user_dir, filename,NULL);
g_free(filename);
dirname = g_path_get_dirname(absolute_filename);
if(!g_file_test(dirname, G_FILE_TEST_IS_DIR))
{
if(g_mkdir_with_parents(dirname, 0755))
{
g_free(absolute_filename);
absolute_filename=NULL;
}
}
g_free(dirname);
if(absolute_filename)
gc_cache_add(absolute_filename);
return absolute_filename;
}
/** Create a directory if needed.
*
* \param rootdir: the directory to create
*
* return 0 if OK, -1 if ERROR
*/
int
gc_util_create_rootdir (gchar *rootdir)
{
if(g_file_test(rootdir, G_FILE_TEST_IS_DIR)) {
return 0;
}
return(g_mkdir(rootdir, 0755));
}
/** Play the activity intro voice
*
* \param gcomprisboard
*
* return void
*/
void
gc_activity_intro_play (GcomprisBoard *gcomprisBoard)
{
gchar *str;
str = gc_file_find_absolute("voices/$LOCALE/intro/%s.ogg",
gcomprisBoard->name, NULL);
gc_sound_play_ogg(str, NULL);
g_free(str);
}