diff options
author | Vincent Untz <vuntz@gnome.org> | 2007-11-12 22:08:41 +0000 |
---|---|---|
committer | Vincent Untz <vuntz@src.gnome.org> | 2007-11-12 22:08:41 +0000 |
commit | 338bccce61a3550a6168f557ccb5abfb8699db74 (patch) | |
tree | c4b20c5761397fd3efbf16321bd4304430f6aca9 /gnome-about | |
parent | 660193fd7fcc065a6ae58e96a97f4e0fe092b52b (diff) | |
download | gnome-desktop-338bccce61a3550a6168f557ccb5abfb8699db74.tar.gz |
Rewrite of gnome-about in python. It's now much better, and it's
2007-11-12 Vincent Untz <vuntz@gnome.org>
Rewrite of gnome-about in python. It's now much better, and it's
accessible. Closes a lot of bugs, see bug #481585.
Based on work by Guillaume Seguin <guillaume@segu.in>
* configure.in: remove gnomecanvas dependency, and check for python
* gnome-about/Makefile.am: updated
* gnome-about/contributors.[ch]: killed
* gnome-about/gnome-about.c: killed
* gnome-about/gnome-about.in: new file
svn path=/trunk/; revision=4921
Diffstat (limited to 'gnome-about')
-rw-r--r-- | gnome-about/Makefile.am | 34 | ||||
-rw-r--r-- | gnome-about/contributors.c | 222 | ||||
-rw-r--r-- | gnome-about/contributors.h | 12 | ||||
-rw-r--r-- | gnome-about/gnome-about.c | 1264 | ||||
-rw-r--r-- | gnome-about/gnome-about.in | 1007 |
5 files changed, 1022 insertions, 1517 deletions
diff --git a/gnome-about/Makefile.am b/gnome-about/Makefile.am index 9cc305ca..e7031edb 100644 --- a/gnome-about/Makefile.am +++ b/gnome-about/Makefile.am @@ -1,20 +1,16 @@ -INCLUDES = \ - $(WARN_CFLAGS) \ - $(DISABLE_DEPRECATED_CFLAGS) \ - $(GNOME_ABOUT_CFLAGS) \ - $(DISABLE_DEPRECATED) \ - -DGNOMELOCALEDIR=\""$(prefix)/$(DATADIRNAME)/locale\"" \ - -DGNOME_ICONDIR=\""$(datadir)/pixmaps\"" \ - -DDATADIR=\"$(datadir)/gnome-about\" - -LDADD = $(GNOME_ABOUT_LIBS) - -bin_PROGRAMS = gnome-about - -gnome_about_SOURCES = \ - gnome-about.c \ - contributors.c \ - contributors.h +bin_SCRIPTS = gnome-about + +gnome-about: gnome-about.in Makefile + sed \ + -e s!\@PYTHON\@!@PYTHON@! \ + -e s!\@GETTEXT_PACKAGE\@!@GETTEXT_PACKAGE@! \ + -e s!\@LOCALEDIR\@!$(datadir)/locale! \ + -e s!\@DATADIR\@!$(datadir)/gnome-about! \ + -e s!\@ICONDIR\@!$(datadir)/pixmaps! \ + -e s!\@PACKAGE_NAME\@!$(PACKAGE_NAME)! \ + -e s!\@PACKAGE_VERSION\@!$(PACKAGE_VERSION)! \ + < $< > $@ + chmod a+x $@ @INTLTOOL_DESKTOP_RULE@ @@ -25,11 +21,11 @@ desktop_DATA = $(desktop_in_files:.desktop.in=.desktop) aboutdir = $(datadir)/gnome-about about_DATA = gnome-64.png contributors.list foundation-members.list -EXTRA_DIST = $(about_DATA) +EXTRA_DIST = $(about_DATA) gnome-about.in SUBDIRS = headers -CLEANFILES = gnome-about.desktop +CLEANFILES = gnome-about.desktop gnome-about check: test -s $(top_srcdir)/gnome-about/foundation-members.list diff --git a/gnome-about/contributors.c b/gnome-about/contributors.c deleted file mode 100644 index b8a91375..00000000 --- a/gnome-about/contributors.c +++ /dev/null @@ -1,222 +0,0 @@ -/* - * contributors.c: - * - * Copyright (C) 2007 Vincent Untz - * - * 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; see the file COPYING. If not, write to the Free - * Software Foundation, Inc., - * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. - * - * Authors: - * Vincent Untz <vuntz@gnome.org> - * - * generate_randomness() comes from gnome-about.c - */ - - -#include <config.h> - -#include <string.h> - -#include <glib.h> -#include <glib/gi18n.h> - -#include <libgnome/gnome-program.h> - -#include "contributors.h" - -static int num_old_contributors; -static int num_foundation_contributors; -static int num_total; -static int *contrib_order = NULL; - -#define OLD_CONTRIBUTORS_FILE "contributors.list" -#define FOUNDATION_CONTRIBUTORS_FILE "foundation-members.list" -static char **old_contributors = NULL; -static char **foundation_contributors = NULL; -static char *translated_contributors[] = { - N_("The Mysterious GEGL"), - N_("The Squeaky Rubber GNOME"), - N_("Wanda The GNOME Fish") -}; - -static void -generate_randomness (void) -{ - int i; - int random_number; - int tmp; - GRand *generator; - - if (contrib_order != NULL) - g_free (contrib_order); - - generator = g_rand_new (); - - contrib_order = g_malloc (num_total * sizeof (int)); - - for (i = 0; i < num_total; i++) - contrib_order[i]=i; - - for (i = 0; i < num_total; i++) { - random_number = g_rand_int_range (generator, i, num_total); - tmp = contrib_order[i]; - contrib_order[i] = contrib_order[random_number]; - contrib_order[random_number] = tmp; - } - - g_rand_free (generator); -} - -const char * -contributors_get (int i) -{ - int real; - - g_assert (contrib_order != NULL); - - real = i % (num_total); - - if (contrib_order[real] < G_N_ELEMENTS (translated_contributors)) - return _(translated_contributors[contrib_order[real]]); - else if (contrib_order[real] < G_N_ELEMENTS (translated_contributors) - + num_old_contributors) - return old_contributors[contrib_order[real] - - G_N_ELEMENTS (translated_contributors)]; - else if (contrib_order[real] < G_N_ELEMENTS (translated_contributors) - + num_old_contributors - + num_foundation_contributors) - return foundation_contributors[contrib_order[real] - - G_N_ELEMENTS (translated_contributors) - - num_old_contributors]; - else - g_assert_not_reached (); -} - - -static void -contributors_read_from_file (const char *file, - char ***contributors, - int *contributors_nb) -{ - char *contents; - GError *error; - int i; - char **contributors_array; - GList *contributors_l; - GList *l; - - g_assert (contributors != NULL); - g_assert (contributors_nb != NULL); - g_assert (*contributors == NULL); - - error = NULL; - if (!g_file_get_contents (file, &contents, NULL, &error)) { - g_printerr ("Cannot read list of contributors: %s\n", - error->message); - g_error_free (error); - return; - } - - *contributors_nb = 0; - *contributors = NULL; - - contributors_l = NULL; - contributors_array = g_strsplit (contents, "\n", -1); - /* we could stop here, and directly use contributors_array, but we need - * to check that the strings are valid UTF-8 */ - - g_free (contents); - - if (contributors_array[0] == NULL || - strcmp (contributors_array[0], - "# gnome-about contributors - format 1") != 0) { - g_strfreev (contributors_array); - return; - } - - for (i = 0; contributors_array[i] != NULL; i++) { - if (!g_utf8_validate (contributors_array[i], -1, NULL) || - contributors_array[i][0] == '\0' || - contributors_array[i][0] == '#') - continue; - - contributors_l = g_list_prepend (contributors_l, - g_strdup (contributors_array[i])); - (*contributors_nb)++; - } - g_strfreev (contributors_array); - - *contributors = g_malloc ((*contributors_nb + 1) * sizeof (char *)); - - for (i = 0, l = contributors_l; l != NULL; l = l->next, i++) - (*contributors)[i] = l->data; - g_list_free (contributors_l); - - /* we want to use g_strfreev */ - (*contributors)[i] = NULL; -} - -static void -contributors_read (const char *filename, - char ***contributors, - int *contributors_nb) -{ - char *file; - - file = gnome_program_locate_file (NULL, - GNOME_FILE_DOMAIN_APP_DATADIR, - filename, - TRUE, NULL); - if (!file) - return; - - contributors_read_from_file (file, contributors, contributors_nb); - num_total += *contributors_nb; - - g_free (file); -} - -void -contributors_free (void) -{ - if (contrib_order) - g_free (contrib_order); - contrib_order = NULL; - - if (old_contributors) - g_strfreev (old_contributors); - old_contributors = NULL; - - if (foundation_contributors) - g_strfreev (foundation_contributors); - foundation_contributors = NULL; -} - -void -contributors_init (void) -{ - num_old_contributors = 0; - num_foundation_contributors = 0; - num_total = G_N_ELEMENTS (translated_contributors); - - contributors_read (OLD_CONTRIBUTORS_FILE, - &old_contributors, - &num_old_contributors); - contributors_read (FOUNDATION_CONTRIBUTORS_FILE, - &foundation_contributors, - &num_foundation_contributors); - - generate_randomness (); -} diff --git a/gnome-about/contributors.h b/gnome-about/contributors.h deleted file mode 100644 index dfa6e85c..00000000 --- a/gnome-about/contributors.h +++ /dev/null @@ -1,12 +0,0 @@ -#ifndef CONTRIBUTORS_H -#define CONTRIBUTORS_H - -G_BEGIN_DECLS - -const char *contributors_get (int i); -void contributors_free (void); -void contributors_init (void); - -G_END_DECLS - -#endif /* CONTRIBUTORS_H */ diff --git a/gnome-about/gnome-about.c b/gnome-about/gnome-about.c deleted file mode 100644 index 3739cfc5..00000000 --- a/gnome-about/gnome-about.c +++ /dev/null @@ -1,1264 +0,0 @@ -/* gnome-about.c: Super Funky Dope gnome about window. - * - * Copyright (C) 2002, Sun Microsystems, Inc. - * Copyright (C) 2003, Kristian Rietveld - * - * 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. - * - * Authors: - * Glynn Foster <glynn.foster@sun.com> - * Mark McLoughlin <mark@skynet.ie> - * Kristian Rietveld <kris@gtk.org> - * Jeff Waugh <jdub@perkypants.org> - * Malcolm Tredinnick <malcolm@commsecure.com.au> - */ - -#include <config.h> - -#include <glib/gi18n.h> - -#include <libgnome/libgnome.h> -#include <libgnomeui/libgnomeui.h> -#include <libgnomecanvas/libgnomecanvas.h> - -/* for parsing gnome-version.xml */ -#include <libxml/tree.h> -#include <libxml/parser.h> - -/* for readdir */ -#include <sys/types.h> -#include <string.h> - -#include <dirent.h> -#include <errno.h> -#include <time.h> - -#include "contributors.h" - -/* pick some good defaults */ -static gdouble canvas_width = 550.0; -static gdouble canvas_height = 350.0; - - -static char **introduction_messages = NULL; -static GnomeCanvasItem *subheader = NULL; -static gdouble version_info_height = 0.0; -static gint contrib_i = 0; - -/* funky animations */ -typedef struct { - GnomeCanvas *canvas; - GnomeCanvasItem *item; -} AnimationData; - -/* contributors */ -static gboolean display_contributors (gpointer data); - -static gboolean -animate_contributor (gpointer data) -{ - AnimationData *ani_data = (AnimationData *)data; - gboolean before_middle; - gdouble tmp, tmp2, y; - guint color, tmpcolor; - gdouble size; - - g_object_get (ani_data->item, - "text_width", &tmp, - "text_height", &tmp2, - "fill_color_rgba", &color, - "size_points", &size, - NULL); - - y = ani_data->item->parent->y1; - - /* ugh */ - g_object_set (GNOME_CANVAS_GROUP (ani_data->item->parent)->item_list->data, - "x1", -1.0 * (tmp / 2.0 + 3.0), - "y1", -1.0 * (tmp2 / 2.0 + 3.0), - "x2", tmp / 2.0 + 3.0, - "y2", tmp2 / 2.0 + 3.0, - NULL); - - before_middle = 130.0 + ((canvas_height - 130.0 - version_info_height - tmp2) / 2.0) < y; - - /* update the color */ - tmpcolor = color & 0xff; - tmpcolor += before_middle ? -6 : 6; - size += before_middle ? 1.0 : -1.0; - size = MAX (0.0, size); - color = GNOME_CANVAS_COLOR_A (tmpcolor, tmpcolor, tmpcolor, tmpcolor); - - g_object_set (ani_data->item, - "fill_color_rgba", color, - "size_points", size, - NULL); - - /* move damnit!! */ - gnome_canvas_item_move (ani_data->item->parent, 0.0, -2.5); - - y -= 2.5; - - gnome_canvas_update_now (ani_data->canvas); - - /* time for a new one ??? */ - if (y <= 130.0) { - display_contributors (ani_data->canvas); - g_free (ani_data); - return FALSE; - } - - return TRUE; -} - -static gboolean -canvas_button_press_event (GtkWidget *widget, - GdkEventButton *event, - gpointer user_data) -{ - gchar *text; - - /* the links should still be clickable */ - if (event->y <= 80.0) - return FALSE; - - text = g_strdup_printf ("<b>%s</b>", contributors_get (contrib_i)); - contrib_i++; - - gnome_canvas_item_set (GNOME_CANVAS_ITEM (user_data), - "markup", text, - NULL); - g_free (text); - - return TRUE; -} - -static gboolean -display_contributors (gpointer data) -{ - GnomeCanvas *canvas = GNOME_CANVAS (data); - AnimationData *ani_data; - - static GnomeCanvasItem *contributor = NULL; - /* FIXME: this is a workaround for a libgnomecanvas bug. See bug #329165. - * When removing it, we should not forget to remove the "ugh" part of - * animate_contributor() */ - static GnomeCanvasItem *contributor_rect = NULL; - static GnomeCanvasItem *contributor_text = NULL; - - if (!contributor) { - gchar *text; - - contributor = - gnome_canvas_item_new (GNOME_CANVAS_GROUP (canvas->root), - gnome_canvas_group_get_type (), - NULL); - - contributor_rect = - gnome_canvas_item_new (GNOME_CANVAS_GROUP (contributor), - gnome_canvas_rect_get_type (), - "fill_color", "White", - NULL); - - text = g_strdup_printf ("<b>%s</b>", contributors_get (contrib_i)); - contributor_text = - gnome_canvas_item_new (GNOME_CANVAS_GROUP (contributor), - gnome_canvas_text_get_type (), - "markup", text, - "anchor", GTK_ANCHOR_CENTER, - "fill_color_rgba", 0xffffffff, - NULL); - g_free (text); - - g_signal_connect (canvas, "button_press_event", - G_CALLBACK (canvas_button_press_event), - contributor_text); - - gnome_canvas_item_move (contributor, - canvas_width / 2.0, - canvas_height - version_info_height); - } else { - gchar *text; - - text = g_strdup_printf ("<b>%s</b>", contributors_get (contrib_i)); - gnome_canvas_item_set (contributor_text, - "markup", text, - "fill_color_rgba", 0xffffffff, - "size_points", 0.0, - NULL); - g_free (text); - - gnome_canvas_item_move (contributor, 0.0, - canvas_height - version_info_height - contributor->y1); - } - - ani_data = g_new0 (AnimationData, 1); - - ani_data->canvas = canvas; - ani_data->item = contributor_text; - - g_timeout_add (75, animate_contributor, ani_data); - contrib_i++; - - return FALSE; -} - -/* subheader */ -static gboolean -display_subheader (gpointer data) -{ - GnomeCanvas *canvas = GNOME_CANVAS (data); - static gboolean first = TRUE; - - if (first) { - guint color = GNOME_CANVAS_COLOR_A (0xff, 0xff, 0xff, 0xff); - - gnome_canvas_item_set (subheader, - "fill_color_rgba", color, - NULL); - gnome_canvas_item_show (subheader); - gnome_canvas_update_now (canvas); - - first = FALSE; - } else { - guint color; - guint tmp; - - g_object_get (subheader, "fill_color_rgba", &color, NULL); - tmp = color & 0xFF; - - if (tmp < 15) { - /* make sure it's black */ - color = GNOME_CANVAS_COLOR_A (0x0, 0x0, 0x0, 0x0); - g_object_set (subheader, - "fill_color_rgba", color, NULL); - gnome_canvas_update_now (canvas); - - display_contributors (canvas); - - return FALSE; - } - - tmp -= 15; - color = GNOME_CANVAS_COLOR_A (tmp, tmp, tmp, tmp); - g_object_set (subheader, "fill_color_rgba", color, NULL); - gnome_canvas_update_now (canvas); - } - - return TRUE; -} - -/* introduction messages */ -static gboolean display_introduction_message (gpointer data); - -static gboolean -animate_text (gpointer data) -{ - AnimationData *ani_data = (AnimationData *)data; - - gnome_canvas_item_move (ani_data->item->parent, 0.0, -10.0); - - if (ani_data->item->parent->y1 <= 120.0) { - g_timeout_add_seconds (5, - display_introduction_message, - ani_data->canvas); - g_free (ani_data); - return FALSE; - } - - return TRUE; -} - -static gboolean -display_introduction_message (gpointer data) -{ - GnomeCanvas *canvas = GNOME_CANVAS (data); - AnimationData *ani_data; - - static GnomeCanvasItem *intro = NULL; - /* FIXME: this is a workaround for a libgnomecanvas bug. See bug #329165. */ - static GnomeCanvasItem *intro_rect = NULL; - static GnomeCanvasItem *intro_text = NULL; - static gint intro_i = 0; - - if (!introduction_messages || !introduction_messages[intro_i]) { - /* just hide the intro now, the canvas will - * take care of disposing it (if we do an _unref here - * it crashes for some reason, too lazy to find out why) - */ - if (intro) - gnome_canvas_item_hide (GNOME_CANVAS_ITEM (intro)); - intro = NULL; - - g_timeout_add (100, display_subheader, canvas); - return FALSE; - } - - if (!intro_text) { - gdouble tmp; - - intro = - gnome_canvas_item_new (GNOME_CANVAS_GROUP (canvas->root), - gnome_canvas_group_get_type (), - NULL); - - intro_rect = - gnome_canvas_item_new (GNOME_CANVAS_GROUP (intro), - gnome_canvas_rect_get_type (), - "fill_color", "White", - "x1", -4.0, - "y1", -4.0, - "x2", 304.0, - "y2", 84.0, - NULL); - - intro_text = - gnome_canvas_item_new (GNOME_CANVAS_GROUP (intro), - gnome_canvas_rich_text_get_type (), - "text", introduction_messages[intro_i], - "editable", FALSE, - /* FIXME */ - "width", 300.0, - "height", 80.0, - "grow_height", TRUE, - "cursor_visible", FALSE, - NULL); - - g_object_get (intro_text, "height", &tmp, NULL); - gnome_canvas_item_move (intro, - (canvas_width - 300.0) / 2.0, - canvas_height - version_info_height - tmp); - gnome_canvas_update_now (canvas); - } else { - gdouble y; - gdouble tmp; - - gnome_canvas_item_set (intro_text, - "text", introduction_messages[intro_i], - NULL); - - g_object_get (intro_text, "height", &tmp, NULL); - y = intro->y1; - - gnome_canvas_item_move (intro, - 0.0, - canvas_height - version_info_height - y - tmp); - gnome_canvas_update_now (canvas); - } - - ani_data = g_new0 (AnimationData, 1); - - ani_data->canvas = canvas; - ani_data->item = intro_text; - - g_timeout_add (15, animate_text, ani_data); - intro_i++; - - return FALSE; -} - -static void -start_animations (GtkWidget *widget, - gpointer user_data) -{ - display_introduction_message (GNOME_CANVAS (widget)); -} - -/* loading funky stuff */ -static void -show_error_dialog (const gchar *message) -{ - GtkWidget *dialog; - - dialog = gtk_message_dialog_new (NULL, - GTK_DIALOG_DESTROY_WITH_PARENT, - GTK_MESSAGE_ERROR, - GTK_BUTTONS_CLOSE, - message); - - gtk_window_set_resizable (GTK_WINDOW (dialog), FALSE); - gtk_widget_show (dialog); - - gtk_dialog_run (GTK_DIALOG (dialog)); - - gtk_widget_destroy (dialog); -} - -static GdkPixbuf * -load_random_header (void) -{ - GdkPixbuf *pixbuf; - GList *files = NULL, *i; - GError *error = NULL; - gchar *text; - gchar *directory; - gint selected; - - DIR *dir; - struct dirent *d; - - directory = gnome_program_locate_file (NULL, - GNOME_FILE_DOMAIN_APP_DATADIR, - "headers", - TRUE, NULL); - - if (!directory) { - show_error_dialog (_("Could not locate the directory with header images.")); - - return NULL; - } - - dir = opendir (directory); - if (!dir) { - char *message; - - message = g_strdup_printf (_("Failed to open directory with header images: %s"), - strerror (errno)); - show_error_dialog (message); - g_free (message); - g_free (directory); - - return NULL; - } - - while ((d = readdir (dir))) { - if (g_str_has_suffix (d->d_name, ".gif") || - g_str_has_suffix (d->d_name, ".png")) - files = g_list_prepend (files, g_strdup (d->d_name)); - } - - closedir (dir); - - selected = g_random_int_range (0, g_list_length (files)); - - text = g_strdup_printf ("%s/%s", directory, - (char *)g_list_nth_data (files, selected)); - g_free (directory); - - pixbuf = gdk_pixbuf_new_from_file (text, &error); - g_free (text); - - for (i = files; i; i = i->next) - g_free (i->data); - g_list_free (files); - - if (error) { - char *message; - - message = g_strdup_printf (_("Unable to load header image: %s"), - error->message); - show_error_dialog (message); - - g_free (message); - g_error_free (error); - - return NULL; - } - - return pixbuf; -} - -static GdkPixbuf * -load_logo (void) -{ - gchar *file; - GdkPixbuf *pixbuf; - GError *error = NULL; - - file = gnome_program_locate_file (NULL, - GNOME_FILE_DOMAIN_APP_DATADIR, - "gnome-64.png", - TRUE, NULL); - if (!file) { - show_error_dialog (_("Could not locate the GNOME logo.")); - - return NULL; - } - - pixbuf = gdk_pixbuf_new_from_file (file, &error); - if (error) { - char *message; - - message = g_strdup_printf (_("Unable to load '%s': %s"), - file, - error->message); - g_free (file); - show_error_dialog (message); - - g_free (message); - g_error_free (error); - - return NULL; - } - - g_free (file); - - return pixbuf; -} - -/* href item impl */ -typedef struct { - GnomeCanvasItem *item; - - const gchar *text; - const gchar *url; -} HRefItem; - -static gboolean -href_item_event_callback (GnomeCanvasItem *item, - GdkEvent *event, - gpointer user_data) -{ - HRefItem *href = (HRefItem *) user_data; - - switch (event->type) { - case GDK_ENTER_NOTIFY: { - GdkCursor *cursor; - - cursor = gdk_cursor_new (GDK_HAND2); - - gdk_window_set_cursor (GTK_WIDGET (item->canvas)->window, cursor); - gdk_cursor_unref (cursor); - } - break; - case GDK_LEAVE_NOTIFY: - gdk_window_set_cursor (GTK_WIDGET (item->canvas)->window, NULL); - break; - - case GDK_BUTTON_PRESS: { - GError *error= NULL; - - gnome_url_show (href->url, &error); - if (error) { - char *message; - - message = g_strdup_printf (_("Could not open the address \"%s\": %s"), - href->url, error->message); - show_error_dialog (message); - - g_free (message); - g_error_free (error); - } - - return TRUE; - } - - default: - break; - } - - return FALSE; -} - -static HRefItem * -href_item_new (GnomeCanvasGroup *group, - const gchar *text, - const gchar *url, - gdouble *current_x, - gdouble *current_y) -{ - HRefItem *item; - gdouble tmp; - - item = g_new0 (HRefItem, 1); - item->text = g_strdup (text); - item->url = g_strdup (url); - - item->item = - gnome_canvas_item_new (group, - gnome_canvas_text_get_type (), - "text", text, - "anchor", GTK_ANCHOR_NW, - "x", *current_x, - "y", *current_y, - "underline", PANGO_UNDERLINE_SINGLE, - "weight", PANGO_WEIGHT_BOLD, - "fill_color", "#000000", - NULL); - g_signal_connect (item->item, "event", - G_CALLBACK (href_item_event_callback), item); - - g_object_get (item->item, "text_width", &tmp, NULL); - *current_x += tmp + 5.0; - - return item; -} - -static GnomeCanvasItem * -create_dot (GnomeCanvasGroup *group, - gdouble *current_x, - gdouble *current_y, - gdouble dot_delta) -{ - GnomeCanvasItem *item; - gdouble tmp; - - item = gnome_canvas_item_new (group, - gnome_canvas_text_get_type (), - "text", ".", - "anchor", GTK_ANCHOR_NW, - "x", *current_x, - "y", *current_y - dot_delta, - "weight", PANGO_WEIGHT_BOLD, - "fill_color", "#000000", - NULL); - - g_object_get (item, "text_width", &tmp, NULL); - *current_x += tmp + 5.0; - - return item; -} - -/* the canvas */ -static char * -strip_newlines (const char *str) -{ - char **strv; - char *tmp; - - if (!str) - return NULL; - - strv = g_strsplit (str, "\n", -1); - tmp = g_strjoinv (" ", strv); - g_strfreev (strv); - tmp = g_strchug (tmp); - - return tmp; -} - -/* FIXME: This could possibly be done more smoothly. The locale matching stuff - * needs to be abstracted out, since it must be useful in a number of places. */ - -/* The language selection stuff here is not blindingly efficient, but it is - * only done once for a small number of paragraphs, so I (Malcolm) think we can - * just pay the penalty rather than going for something more complex. - * - * The problem we are solving here is, for each paragraph, find the translated - * version that matches the highest preference possible from amongst the user's - * LC_MESSAGES settings. Note that the translated paragraphs may not appear in - * the same order as the LC_MESSAGE preferences, so a certain amount of messing - * around is required (in particular, we cannot quit after finding the first - * match). - */ - -/* FIXME: this code is atrocious */ - -static void -get_description_messages (xmlNodePtr node) -{ - xmlNodePtr paras; - GSList *list = NULL, *l; - const char * const * langs; - gint i; - gboolean started = FALSE; - char *best_value = NULL; - - langs = g_get_language_names (); - paras = node->children; - - while (paras) { - while (paras) { - xmlChar *value = xmlNodeGetContent (paras); - char *tmp; - xmlChar *cur, *best = NULL; - - if (paras->type == XML_ELEMENT_NODE && - xmlStrEqual (paras->name, (const xmlChar *) "p") && - value && value[0]) { - cur = xmlNodeGetLang (paras); - - tmp = strip_newlines ((const gchar *) value); - - if (!started) { - started = TRUE; - if (!cur) { - best = xmlCharStrdup ("C"); - } - else { - best = xmlStrdup (cur); - } - best_value = g_strdup (tmp); - } - else { - guint i; - - if (!cur || xmlStrEqual (cur, (const xmlChar *) "C")) { - break; - } - /* See if the current lanaguage occurs - * earlier than the previous best. */ - for (i = 0; langs[i] != NULL; ++i) { - if (xmlStrEqual ((const xmlChar *) langs[i], - best)) { - break; - } - else if (xmlStrEqual ((const xmlChar *) langs[i], - cur)) { - xmlFree (best); - best = xmlStrdup (cur); - g_free (best_value); - best_value = g_strdup (tmp); - /* If this is the first - * language on the - * list of choices, - * stop. We are not - * going to go any - * better. */ - if (i == 0) { - break; - } - } - } - } - g_free (tmp); - xmlFree (cur); - xmlFree (best); - } - - paras = paras->next; - xmlFree (value); - } - list = g_slist_prepend (list, best_value); - started = FALSE; - } - - list = g_slist_reverse (list); - - introduction_messages = g_new (char *, g_slist_length (list) + 1); - - for (i = 0, l = list; l; l = l->next, i++) - introduction_messages[i] = l->data; - - introduction_messages[i] = NULL; - - g_slist_free (list); -} - -static char* -create_date_string (const char *value) -{ - char *result; - GDate *date; - int day; - int month; - int year; - - /* YYYY-MM-DD */ - if (sscanf (value, "%d-%d-%d", &year, &month, &day) < 3) - return NULL; - - date = g_date_new (); - - g_date_set_dmy (date, day, month, year); - - result = g_new0 (char, 24); - g_date_strftime (result, 24, "%x", date); - - g_date_free (date); - - return result; -} - -static gboolean -get_version_info (char **version_string, - char **distributor_string, - char **build_date_string) -{ - gchar *file; - - xmlDocPtr about; - xmlNodePtr node; - xmlNodePtr bits; - - char *platform = NULL; - char *minor = NULL; - char *micro = NULL; - - file = gnome_program_locate_file (NULL, - GNOME_FILE_DOMAIN_APP_DATADIR, - "gnome-version.xml", - TRUE, NULL); - if (!file) - return FALSE; - - about = xmlParseFile (file); - g_free (file); - - if (!about) - return FALSE; - - node = about->children; - - if (g_ascii_strcasecmp ((const char *) node->name, "gnome-version")) { - xmlFreeDoc (about); - return FALSE; - } - - bits = node->children; - - while (bits) { - char *name = (char *)bits->name; - char *value; - - if (!g_ascii_strcasecmp (name, "description")) - get_description_messages (bits); - - value = (char *)xmlNodeGetContent (bits); - - if (!g_ascii_strcasecmp (name, "platform") - && value && value[0]) - platform = g_strdup (value); - else if (!g_ascii_strcasecmp (name, "minor") && value && value[0]) - minor = g_strdup (value); - else if (!g_ascii_strcasecmp (name, "micro") && value && value[0]) - micro = g_strdup (value); - else if (!g_ascii_strcasecmp (name, "distributor") && value && value[0]) - *distributor_string = g_strdup (value); - else if (!g_ascii_strcasecmp (name, "date") && value && value[0]) - *build_date_string = create_date_string (value); - - bits = bits->next; - xmlFree (value); - } - - xmlFreeDoc (about); - - if (!minor) - *version_string = g_strdup (platform); - - if (!*version_string && !micro) - *version_string = g_strconcat (platform, ".", minor, NULL); - - if (!*version_string) - *version_string = g_strconcat (platform, ".", minor, ".", - micro, NULL); - - g_free (platform); - g_free (minor); - g_free (micro); - - return TRUE; -} - -static gboolean -display_version_info_on_term (void) -{ - char *version_string = NULL; - char *distributor_string = NULL; - char *build_date_string = NULL; - - if (!get_version_info (&version_string, - &distributor_string, - &build_date_string)) { - g_printerr (_("Could not get information about GNOME version.")); - return FALSE; - } - - g_print (_("%s: %s\n"), _("Version"), version_string); - g_print (_("%s: %s\n"), _("Distributor"), distributor_string); - g_print (_("%s: %s\n"), _("Build Date"), build_date_string); - - g_free (version_string); - g_free (distributor_string); - g_free (build_date_string); - - return TRUE; -} - -static void -display_version_info (GnomeCanvasGroup *group) -{ - char *version_string = NULL; - char *distributor_string = NULL; - char *build_date_string = NULL; - char *format = NULL; - char *text = NULL; - - GnomeCanvasItem *info; - gdouble height = 0.0; - - if (!get_version_info (&version_string, - &distributor_string, - &build_date_string)) - show_error_dialog (_("Could not get information about GNOME version.")); - - info = gnome_canvas_item_new (group, - gnome_canvas_group_get_type (), - "x", 10.0, - NULL); - - format = g_strdup_printf ("<b>%s</b>%%s", _("%s: ")); - - if (version_string && version_string[0]) { - gdouble tmp; - GnomeCanvasItem *item; - - text = g_strdup_printf (format, - _("Version"), version_string); - item = gnome_canvas_item_new (GNOME_CANVAS_GROUP (info), - gnome_canvas_text_get_type (), - "markup", text, - "anchor", GTK_ANCHOR_NW, - "x", 0.0, - "y", height, - NULL); - g_free (text); - - g_object_get (item, "text_height", &tmp, NULL); - height += tmp + 4.0; - } - - if (distributor_string && distributor_string[0]) { - gdouble tmp; - GnomeCanvasItem *item; - - text = g_strdup_printf (format, - _("Distributor"), distributor_string); - item = gnome_canvas_item_new (GNOME_CANVAS_GROUP (info), - gnome_canvas_text_get_type (), - "markup", text, - "anchor", GTK_ANCHOR_NW, - "x", 0.0, - "y", height, - NULL); - g_free (text); - - g_object_get (item, "text_height", &tmp, NULL); - height += tmp + 4.0; - } - - if (build_date_string && build_date_string[0]) { - gdouble tmp; - GnomeCanvasItem *item; - - text = g_strdup_printf (format, - _("Build Date"), build_date_string); - item = gnome_canvas_item_new (GNOME_CANVAS_GROUP (info), - gnome_canvas_text_get_type (), - "markup", text, - "anchor", GTK_ANCHOR_NW, - "x", 0.0, - "y", height, - NULL); - g_free (text); - - g_object_get (item, "text_height", &tmp, NULL); - height += tmp + 4.0; - } - - g_free (format); - g_free (version_string); - g_free (distributor_string); - g_free (build_date_string); - - gnome_canvas_item_set (info, "y", canvas_height - height, NULL); - version_info_height = height; -} - -static GtkWidget * -create_canvas (void) -{ - GdkColor color = {0, 0xffff, 0xffff, 0xffff}; - GtkWidget *canvas; - - HRefItem *href; - gchar *text; - - GnomeCanvasItem *item; - GnomeCanvasGroup *root; - GnomeCanvasPoints *points; - - GdkPixbuf *header; - GdkPixbuf *logo; - - gdouble current_x; - gdouble current_y; - gdouble dot_delta; - gdouble tmp; - - /* set up a canvas */ - canvas = gnome_canvas_new (); - - gnome_canvas_set_scroll_region (GNOME_CANVAS (canvas), 0, 0, - canvas_width, canvas_height); - gtk_widget_set_size_request (canvas, canvas_width, canvas_height); - - gdk_colormap_alloc_color (gtk_widget_get_colormap (GTK_WIDGET (canvas)), - &color, TRUE, TRUE); - - /* euhm? */ - gtk_widget_modify_bg (GTK_WIDGET (canvas), GTK_STATE_NORMAL, &color); - - root = GNOME_CANVAS_GROUP (GNOME_CANVAS (canvas)->root); - - /* the header */ - header = load_random_header (); - if (!header) - /* emergency stop complete with leaks */ - return NULL; - - item = gnome_canvas_item_new (root, - gnome_canvas_pixbuf_get_type (), - "x", 0.0, - "y", 0.0, - "pixbuf", header, - NULL); - - /* load logo in advance, we need it's size */ - logo = load_logo (); - if (!logo) - /* emergency stop complete with leaks */ - return NULL; - - /* and a coulple o' links */ - current_x = 10.0 + (gdouble)gdk_pixbuf_get_width (logo) + 10.0; - current_y = (gdouble)gdk_pixbuf_get_height (header) + 5.0; - - href = href_item_new (root, - _("About GNOME"), - "http://www.gnome.org/about/", - ¤t_x, ¤t_y); - - /* make a nice guess for the dot delta */ - g_object_get (href->item, "text_height", &tmp, NULL); - dot_delta = tmp / 4.5; - - /* draw a dot */ - item = create_dot (root, ¤t_x, ¤t_y, dot_delta); - - /* and more items on a likewise way. - */ - href = href_item_new (root, - _("News"), - "http://news.gnome.org", - ¤t_x, ¤t_y); - item = create_dot (root, ¤t_x, ¤t_y, dot_delta); - - /* - * FIXME: this used to be 'users' and it would be great to make it - * users again once there is a user-centric page on the we site. - */ - href = href_item_new (root, - _("Software"), - "http://www.gnome.org/softwaremap", - ¤t_x, ¤t_y); - item = create_dot (root, ¤t_x, ¤t_y, dot_delta); - - href = href_item_new (root, - _("Developers"), - "http://developer.gnome.org/", - ¤t_x, ¤t_y); - item = create_dot (root, ¤t_x, ¤t_y, dot_delta); - - href = href_item_new (root, - _("Friends of GNOME"), - "http://www.gnome.org/friends/", - ¤t_x, ¤t_y); - item = create_dot (root, ¤t_x, ¤t_y, dot_delta); - - href = href_item_new (root, - _("Contact"), - "http://www.gnome.org/contact/", - ¤t_x, ¤t_y); - - /* resize */ - canvas_width = current_x; - gnome_canvas_set_scroll_region (GNOME_CANVAS (canvas), 0, 0, - canvas_width, canvas_height); - gtk_widget_set_size_request (GTK_WIDGET (canvas), - canvas_width, canvas_height); - - /* and a nice black stripe */ - points = gnome_canvas_points_new (2); - points->coords[0] = 0.0; - points->coords[1] = gdk_pixbuf_get_height (header); - points->coords[2] = current_x; - points->coords[3] = gdk_pixbuf_get_height (header); - - item = gnome_canvas_item_new (root, - gnome_canvas_line_get_type (), - "points", points, - "fill_color", "#666666", - "width_pixels", 1, - NULL); - - gnome_canvas_points_free (points); - - /* the gnome logo */ - item = gnome_canvas_item_new (root, - gnome_canvas_pixbuf_get_type (), - "x", 10.0, - "y", 10.0, - "pixbuf", logo, - NULL); - - /* and some introduction text */ - text = g_strdup_printf ("<big><big><b>%s</b></big></big>", - _("Welcome to the GNOME Desktop")); - item = gnome_canvas_item_new (root, - gnome_canvas_text_get_type (), - "markup", text, - "anchor", GTK_ANCHOR_NW, - "y", current_y + 25.0, - "fill_color", "#000000", - NULL); - g_free (text); - - g_object_get (item, "text_width", &tmp, NULL); - gnome_canvas_item_set (item, - "x", (canvas_width - tmp) / 2.0, - NULL); - - - text = g_strdup_printf ("<big><b>%s</b></big>", - _("Brought to you by:")); - item = gnome_canvas_item_new (root, - gnome_canvas_text_get_type (), - "markup", text, - "anchor", GTK_ANCHOR_NW, - "y", current_y + 55.0, - NULL); - subheader = item; - gnome_canvas_item_hide (item); - g_free (text); - - g_object_get (item, "text_width", &tmp, NULL); - gnome_canvas_item_set (item, - "x", (canvas_width - tmp) / 2.0, - NULL); - - /* and the version info */ - display_version_info (root); - - /* pfff done */ - return canvas; -} - -/* the dialog */ -static gboolean -quit_callback (GtkWidget *widget, - gpointer user_data) -{ - gtk_main_quit (); - - return FALSE; -} - -static void -response_callback (GtkDialog *dialog, - int reponse_id, - gpointer user_data) -{ - if (reponse_id == GTK_RESPONSE_CLOSE) - quit_callback (GTK_WIDGET (dialog), NULL); -} - -static GtkWidget * -create_about_dialog (void) -{ - GtkWidget *dialog; - GtkWidget *canvas; - - dialog = gtk_dialog_new_with_buttons (_("About the GNOME Desktop"), - NULL, 0, - GTK_STOCK_CLOSE, - GTK_RESPONSE_CLOSE, - NULL); - gtk_dialog_set_default_response (GTK_DIALOG (dialog), - GTK_RESPONSE_CLOSE); - - g_signal_connect (dialog, "delete_event", - G_CALLBACK (quit_callback), NULL); - g_signal_connect (dialog, "response", - G_CALLBACK (response_callback), NULL); - - gtk_window_set_position (GTK_WINDOW (dialog), GTK_WIN_POS_CENTER); - g_object_set (dialog, - "allow_shrink", FALSE, - "allow_grow", FALSE, - NULL); - - canvas = create_canvas (); - if (!canvas) { - gtk_widget_destroy (dialog); - return NULL; - } - - /* start animations once the canvas has been mapped */ - g_signal_connect (canvas, "map", - G_CALLBACK (start_animations), NULL); - - gtk_container_set_border_width - (GTK_CONTAINER (GTK_DIALOG (dialog)->vbox), GNOME_PAD_SMALL); - gtk_box_pack_start (GTK_BOX (GTK_DIALOG (dialog)->vbox), - canvas, TRUE, TRUE, 0); - - return dialog; -} - -static gboolean gnome_version = FALSE; - -static const GOptionEntry options[] = { - { "gnome-version", 0, 0, G_OPTION_ARG_NONE, &gnome_version, N_("Display information on this GNOME version"), NULL }, - { NULL } -}; - -/* main */ -int -main (int argc, char **argv) -{ - GOptionContext *context; - GnomeProgram *program; - GtkWidget *dialog; - - bindtextdomain (GETTEXT_PACKAGE, GNOMELOCALEDIR); - bind_textdomain_codeset (GETTEXT_PACKAGE, "UTF-8"); - textdomain (GETTEXT_PACKAGE); - - context = g_option_context_new (NULL); - g_option_context_add_main_entries (context, options, GETTEXT_PACKAGE); - - program = gnome_program_init ("gnome-about", VERSION, - LIBGNOMEUI_MODULE, - argc, argv, - GNOME_PARAM_APP_DATADIR, DATADIR, - GNOME_PARAM_GOPTION_CONTEXT, context, - NULL); - - if (gnome_version) { - g_object_unref (program); - return (display_version_info_on_term ()) ? 0 : 1; - } - - gtk_window_set_default_icon_from_file (GNOME_ICONDIR - "/gnome-logo-icon-transparent.png", NULL); - - dialog = create_about_dialog (); - if (!dialog) - return -1; - - gtk_widget_show_all (dialog); - - contributors_init (); - - gtk_main (); - - contributors_free (); - g_object_unref (program); - - return 0; -} diff --git a/gnome-about/gnome-about.in b/gnome-about/gnome-about.in new file mode 100644 index 00000000..6acecea2 --- /dev/null +++ b/gnome-about/gnome-about.in @@ -0,0 +1,1007 @@ +#!@PYTHON@ +# coding=utf-8 + +''' +gnome-about + + # Pretty About Dialog for the GNOME Desktop # + +Copyright (C) 2007 Guillaume Seguin <guillaume@segu.in> + +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., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA. + +Authors: + Guillaume Seguin <guillaume@segu.in> + Vincent Untz <vuntz@gnome.org> (get_language_names () helper function) +''' + +import pygtk +pygtk.require ('2.0') + +import gobject +from gobject.option import OptionGroup, OptionParser, make_option +import gtk + +import gnome +import cairo +from math import pi + +import os, sys, random, time, gettext + +import xml.dom.minidom + +PACKAGE = "@PACKAGE_NAME@" +VERSION = "@PACKAGE_VERSION@" +GETTEXT_PACKAGE = "@GETTEXT_PACKAGE@" + +LOCALEDIR = "@LOCALEDIR@" +DATADIR = "@DATADIR@" +ICONDIR = "@ICONDIR@" + +LOGO_FILE = "gnome-64.png" + +gettext.install (GETTEXT_PACKAGE, LOCALEDIR, unicode = True) + +header_links = [ + (_("About GNOME"), "http://www.gnome.org/about/"), + (_("News"), "http://news.gnome.org/"), + (_("GNOME Library"), "http://library.gnome.org/"), + (_("Friends of GNOME"), "http://www.gnome.org/friends/"), + (_("Contact"), "http://www.gnome.org/contact/"), +] + +translated_contributors = [ + _("The Mysterious GEGL"), + _("The Squeaky Rubber GNOME"), + _("Wanda The GNOME Fish") +] + +default_link_color = gtk.gdk.Color (0, 0, 65535) + +def locate_file (program, file): + '''Wrap gnome_program_locate_file to avoid ugly duplication''' + file = program.locate_file (gnome.FILE_DOMAIN_APP_DATADIR, file, True) + if not file: + return False + return file[0] + +def cleanup_date (date): + '''Parse a date as found in gnome-version.xml and nicely format it''' + try: + return time.strftime ("%x", time.strptime (date, "%Y-%m-%d")) + except: + return False + +# Imported from GNOME's Sabayon +# (sabayon/admin-tool/lockdown/disabledapplets.py) +# There's no wrapper for g_get_language_names (). Ugly workaround: +# Note that we don't handle locale alias... +def get_language_names (): + if "LANGUAGE" in os.environ.keys () and os.environ["LANGUAGE"] != "": + env_lang = os.environ["LANGUAGE"].split () + elif "LC_ALL" in os.environ.keys () and os.environ["LC_ALL"] != "": + env_lang = os.environ["LC_ALL"].split () + elif "LC_MESSAGES" in os.environ.keys () and os.environ["LC_MESSAGES"] != "": + env_lang = os.environ["LC_MESSAGES"].split () + elif "LANG" in os.environ.keys () and os.environ["LANG"] != "": + env_lang = os.environ["LANG"].split () + else: + env_lang = [] + + env_lang.reverse () + languages = [] + + for language in env_lang: + start_pos = 0 + mask = 0 + uscore_pos = language.find ("_") + if uscore_pos != -1: + start_pos = uscore_pos + mask += 1 << 2 + dot_pos = language.find (".", start_pos) + if dot_pos != -1: + start_pos = dot_pos + mask += 1 << 0 + at_pos = language.find ("@", start_pos) + if at_pos != -1: + start_pos = at_pos + mask += 1 << 1 + + if uscore_pos != -1: + lang = language[:uscore_pos] + elif dot_pos != -1: + lang = language[:dot_pos] + elif at_pos != -1: + lang = language[:at_pos] + else: + lang = language + + if uscore_pos != -1: + if dot_pos != -1: + territory = language[uscore_pos:dot_pos] + elif at_pos != -1: + territory = language[uscore_pos:at_pos] + else: + territory = language[uscore_pos:] + else: + territory = "" + + if dot_pos != -1: + if at_pos != -1: + codeset = language[dot_pos:at_pos] + else: + codeset = language[dot_pos:] + else: + codeset = "" + + if at_pos != -1: + modifier = language[at_pos:] + else: + modifier = "" + + for i in range (mask + 1): + if i & ~mask == 0: + newlang = lang + if (i & 1 << 2): + newlang += territory + if (i & 1 << 0): + newlang += codeset + if (i & 1 << 1): + newlang += modifier + languages.insert (0, newlang) + + return languages + +class GettableList (list): + '''Dumb wrapper around Python list with a get () method to iterate \ +the list''' + + current = 0 + + def get (self): + if not len (self): + return None + if self.current == -1: + item = None + else: + item = self[self.current] + self.current += 1 + if self.current == len (self): + self.current = -1 + else: + self.current = self.current % len (self) + return item + +class GnomeContributors (GettableList): + '''Randomized contributors list''' + + program = None + current = 0 + + def __init__ (self, program): + '''Initiate object and load contributors lists''' + super (GnomeContributors, self).__init__ () + self.program = program + map (self.append, translated_contributors) + self.load_from_file ("contributors.list") + self.load_from_file ("foundation-members.list") + random.shuffle (self) # Randomize list... + + def load_from_file (self, file): + '''Load a list of contributors and validate it''' + def validate_contributor (contrib): + try: + contrib.encode ("utf8") + return len (contrib) > 0 and contrib[0] != "#" + except Exception, e: + print e + return False + + path = locate_file (self.program, file) + + if not path: + print '''Warning: "%s" file not found.''' % file + return + + f = open (path, "r") + try: + data = f.readlines () + finally: + f.close () + + '''Cleanup list''' + data = map (lambda s: s.rstrip (), data) + + '''Check that the file begins with the correct header''' + if not data or data[0] != "# gnome-about contributors - format 1": + return + + '''Filter the contributors list and append it''' + contributors = filter (validate_contributor, data) + map (self.append, contributors) + + def get (self): + '''Return a contributor from the currently randomized list. \ + If we hit the end of the list, randomize it again''' + contributors_count = len (self) + if not contributors_count: + print "Warning: empty contributors list." + return + contributor = self[self.current] + self.current += 1 + if self.current >= contributors_count: + '''Remove the last item of the list and reinsert it after \ + shuffling to make sure it won't get displayed twice in a row''' + self.pop () + random.shuffle (self) + index = random.randint (contributors_count / 2, + contributors_count) + self.insert (index, contributor) + self.current = 0 + return contributor + +# Animation is done using: +# * A custom gtk.Label, for catching mouse press events +# * A gtk.Alignment, for positionning and scrolling (the actual animation) +# * A gtk.Layout, so that the Alignment can be wider than what's displayed +# and thus let the Label appear and disappear smoothly + +class AnimatedLabel (gtk.Layout): + '''Pretty animated label''' + + items = None # items must either be a GettableList object + # or expose a get () method''' + timeout = 0 + format = "" # format must be a valid formatting string for a single %s + + item = None + next = None + + current = None + label = None + source = None + state = 0 # 0 = appearing ; 1 = landed ; 2 = vanishing + pos = 0.0 + + width = 0 + height = 0 + + def __init__ (self, items, width, height, timeout, format = "%s"): + '''Initiate object''' + super (AnimatedLabel, self).__init__ () + self.items = items + self.next = self.items.get () # Pop the first item + self.width = width + self.height = height + self.timeout = timeout + self.format = format + self.set_size_request (width, height) + self.connect ("button-press-event", self.on_button_press) + self.connect ("map", self.reset_animation) + + def reset_animation (self, *args): + '''Reset label and fire animation timer''' + self.reset_label () + if not self.label: + return + self.source = gobject.timeout_add (5, self.animate) + + def make_label (self): + '''Build the label widgets''' + self.label = WindowedLabel () + self.label.connect ("button-press-event", self.on_button_press) + self.label.set_justify (gtk.JUSTIFY_FILL) + self.label.set_line_wrap (True) + self.label.set_markup (self.format % self.item) + self.label.set_selectable (True) + + def reset_label (self): + '''Drop current label if any and create the new one''' + if self.current: + self.remove (self.current) + self.current = None + self.label = None + self.item = self.next + self.next = self.items.get () + if not self.item: + return False + self.make_label () + self.state = -1 + + def on_button_press (self, widget, event): + '''Switch to next item upon left click''' + if event.button != 1 or not self.current: + return + # Remove the current timeout if any to avoid bad side effects + if self.source: + gobject.source_remove (self.source) + self.animate () + return True + +class VertAnimatedLabel (AnimatedLabel): + '''Vertically animated label''' + + rewind_text = "" + last_label_height = 0 + + def rewind_animate (self): + '''Animation function for the rewind step''' + self.source = None + if self.state == -2: + self.item = self.rewind_text + self.make_label () + label_height = self.label.size_request ()[1] + total_height = self.height + label_height + self.pos = float (self.last_label_height) / total_height + self.current.set (0.5, self.pos, 0, 0) + self.state = 0 + self.source = gobject.timeout_add (10, self.rewind_animate) + elif self.state == 0: + if self.pos < 1.0: + '''Move towards the bottom position''' + self.pos = min (1.0, self.pos + 0.01) + self.current.set (0.5, self.pos, 0, 0) + self.source = gobject.timeout_add (10, self.rewind_animate) + else: + '''Bottommost position reached''' + self.rewind_text = "" + self.reset_animation () + return False + + def animate (self): + '''The actual animation function''' + self.source = None + if self.state == -2: + self.rewind_animate () + elif self.state == -1: + self.rewind_text += "\n\n%s" % self.item + self.state = 0 + self.animate () + elif self.state == 0: + if self.pos: + '''Move towards the top position''' + self.pos = max (0, self.pos - 0.02) + label_height = self.label.size_request ()[1] + total_height = self.height + label_height + real_pos = float (self.pos * self.height + label_height) \ + / total_height + self.current.set (0.5, real_pos, 0, 0) + self.source = gobject.timeout_add (5, self.animate) + else: + '''Topmost position reached''' + self.state = 1 + self.pos = 1.0 + self.source = gobject.timeout_add (self.timeout, self.animate) + elif self.state == 1: + '''Dont let selected labels vanish until they are unselected''' + if self.label.get_selection_bounds () == (): + self.state = 2 + self.source = gobject.timeout_add (5, self.animate) + elif self.state == 2: + if not self.next: + self.state = -2 + self.last_label_height = self.label.size_request ()[1] + self.reset_animation () + self.source = gobject.timeout_add (1, self.animate) + elif self.pos: + '''Move out of the visible region of the Layout''' + self.pos = max (0, self.pos - 0.02) + label_height = self.label.size_request ()[1] + total_height = self.height + label_height + real_pos = float (self.pos * label_height) \ + / total_height + self.current.set (0.5, real_pos, 0, 0) + self.source = gobject.timeout_add (5, self.animate) + else: + '''Label has disappeared, bye bye''' + self.reset_animation () + return False + + def make_label (self): + '''Build a new label widget''' + super (VertAnimatedLabel, self).make_label () + if not self.label: + return + self.label.set_size_request (self.width, -1) + self.current = gtk.Alignment (0.0, 1.0) + label_height = self.label.size_request ()[1] + height = self.size_request ()[1] + self.current.set_size_request (-1, 2 * label_height + height) + self.current.add (self.label) + self.put (self.current, 0, - label_height) + self.pos = 1.0 + self.show_all () + +class HorzAnimatedLabel (AnimatedLabel): + '''Horizontally animated label''' + + def animate (self): + '''The actual animation function''' + self.source = None + if self.state == -2: + self.reset_animation () + elif self.state <= 0: + if self.pos != 0.5: + '''Move towards the center position''' + self.pos = max (0.5, self.pos - 0.02) + self.current.set (self.pos, 0.5, 0, 0) + self.source = gobject.timeout_add (5, self.animate) + else: + '''Center position reached, switch to return mode''' + self.state = 1 + self.source = gobject.timeout_add (self.timeout, self.animate) + elif self.state == 1: + '''Dont let selected labels vanish until they are unselected''' + if self.label.get_selection_bounds () == (): + self.state = 2 + self.source = gobject.timeout_add (5, self.animate) + elif self.state == 2: + if self.pos: + '''Disappear by moving left''' + self.pos = max (0, self.pos - 0.02) + self.current.set (self.pos, 0.5, 0, 0) + self.source = gobject.timeout_add (5, self.animate) + else: + '''Left position reached, let's move on''' + self.reset_animation () + return False + + def make_label (self): + '''Build a new label widget''' + super (HorzAnimatedLabel, self).make_label () + if not self.label: + return + self.label.set_size_request (-1, self.height) + self.current = gtk.Alignment (1.0, 0.0) + label_width = self.label.size_request ()[0] + width = self.size_request ()[0] + self.current.set_size_request (2 * label_width + width, -1) + self.current.add (self.label) + self.put (self.current, - label_width, 0) + self.pos = 1.0 + self.show_all () + +class WindowedLabel (gtk.Label): + '''Custom gtk.Label with an overlapping input-only gtk.gdk.Window''' + + event_window = None + + def __init__ (self, debug = False): + '''Initialize object and plug all signals''' + self.debug = debug + super (WindowedLabel, self).__init__ () + + def do_realize (self): + '''Create a custom GDK window with which we will be able to play''' + gtk.Label.do_realize (self) + event_mask = self.get_events () | gtk.gdk.BUTTON_PRESS_MASK \ + | gtk.gdk.BUTTON_RELEASE_MASK \ + | gtk.gdk.KEY_PRESS_MASK + self.event_window = gtk.gdk.Window (parent = self.get_parent_window (), + window_type = gtk.gdk.WINDOW_CHILD, + wclass = gtk.gdk.INPUT_ONLY, + event_mask = event_mask, + x = self.allocation.x, + y = self.allocation.y, + width = self.allocation.width, + height = self.allocation.height) + self.event_window.set_user_data (self) + + def do_unrealize (self): + '''Destroy event window on unrealize''' + self.event_window.set_user_data (None) + self.event_window.destroy () + gtk.Label.do_unrealize (self) + + def do_size_allocate (self, allocation): + '''Move & resize the event window to fit the Label's one''' + gtk.Label.do_size_allocate (self, allocation) + if self.flags () & gtk.REALIZED: + self.event_window.move_resize (allocation.x, allocation.y, + allocation.width, allocation.height) + + def do_map (self): + '''Show event window''' + gtk.Label.do_map (self) + self.event_window.show () + '''Raise the event window to make sure it is over the Label's one''' + self.event_window.raise_ () + + def do_unmap (self): + '''Hide event window on unmap''' + self.event_window.hide () + gtk.Label.do_unmap (self) + +gobject.type_register (WindowedLabel) + +class HyperLink (WindowedLabel): + '''Clickable www link label''' + + url = "" + menu = None + selection = None + + def __init__ (self, label, url): + '''Initialize object''' + super (HyperLink, self).__init__ () + markup = "<b><u>%s</u></b>" % label + self.set_markup (markup) + self.set_selectable (True) + self.url = url + self.create_menu () + link_color = self.style_get_property ("link-color") + if not link_color: + link_color = default_link_color + self.modify_fg (gtk.STATE_NORMAL, link_color) + + def open_url (self, *args): + '''Use GNOME API to open the url''' + try: + gnome.url_show (self.url) + except Exception, e: + print '''Warning: could not open "%s": %s''' % self.url, e + + def copy_url (self, *args): + '''Copy URL to Clipboard''' + clipboard = gtk.clipboard_get ("CLIPBOARD") + clipboard.set_text (self.url) + + def create_menu (self): + '''Create the popup menu that will be displayed upon right click''' + self.menu = gtk.Menu () + open_item = gtk.ImageMenuItem (_("_Open URL")) + open_image = gtk.image_new_from_stock (gtk.STOCK_OPEN, + gtk.ICON_SIZE_MENU) + open_item.set_image (open_image) + open_item.connect ("activate", self.open_url) + open_item.show () + self.menu.append (open_item) + copy_item = gtk.ImageMenuItem (_("_Copy URL")) + copy_image = gtk.image_new_from_stock (gtk.STOCK_COPY, + gtk.ICON_SIZE_MENU) + copy_item.set_image (copy_image) + copy_item.connect ("activate", self.copy_url) + copy_item.show () + self.menu.append (copy_item) + + def display_menu (self, button, time, place = False): + '''Display utility popup menu''' + if place: + alloc = self.get_allocation () + pos = self.event_window.get_origin () + x = pos[0] + y = pos[1] + alloc.height + func = lambda *a: (x, y, True) + else: + func = None + self.menu.popup (None, None, func, button, time) + + def do_map (self): + '''Select the HAND2 cursor on map''' + WindowedLabel.do_map (self) + cursor = gtk.gdk.Cursor (gtk.gdk.HAND2) + self.event_window.set_cursor (cursor) + + def do_button_press_event (self, event): + '''Update selection bounds infos or display popup menu''' + if event.button == 1: + self.selection = self.get_selection_bounds () + elif event.button == 3: + self.display_menu (event.button, event.time) + return True + WindowedLabel.do_button_press_event (self, event) + + def do_button_release_event (self, event): + '''Open url if selection hasn't changed since initial press''' + if event.button == 1: + selection = self.get_selection_bounds () + if selection == self.selection: + self.open_url () + return True + WindowedLabel.do_button_release_event (self, event) + + def do_key_press_event (self, event): + '''Open url when Return key is pressed''' + if event.keyval == gtk.keysyms.Return: + self.open_url () + return True + elif event.keyval == gtk.keysyms.Menu \ + or (event.keyval == gtk.keysyms.F10 \ + and event.state & gtk.accelerator_get_default_mod_mask() == \ + gtk.gdk.SHIFT_MASK): + self.display_menu (event.keyval, event.time, place = True) + return True + WindowedLabel.do_key_press_event (self, event) + +gobject.type_register (HyperLink) + +class GnomeLogo (gtk.Widget): + + def __init__ (self, parent, file): + gtk.Widget.__init__ (self) + + self._surface = cairo.ImageSurface.create_from_png (file) + + text_color = parent.get_style ().text[gtk.STATE_NORMAL] + + cr = cairo.Context (self._surface) + cr.set_source_rgb (text_color.red, text_color.green, text_color.blue) + cr.set_operator (cairo.OPERATOR_ATOP) + cr.paint () + + width = self._surface.get_width () + height = self._surface.get_height () + self.set_size_request (width, height) + + self.set_flags(self.flags() | gtk.NO_WINDOW) + + def do_expose_event (self, event): + cr = self.window.cairo_create () + + cr.set_operator (cairo.OPERATOR_OVER) + cr.set_source_surface (self._surface, 10, 10) + cr.paint () + +gobject.type_register (GnomeLogo) + +class GnomeAboutHeader (gtk.Layout): + '''Pretty header for gnome-about''' + + program = None + links = [] + + width = 0 + height = 0 + + def __init__ (self, program, links): + '''Initialize object, plug map signal''' + super (GnomeAboutHeader, self).__init__ () + self.program = program + self.links = links + + def do_realize (self): + '''Load header and build links''' + gtk.Layout.do_realize (self) + + current_x = 0 + current_y = 0 + base_y = 0 + + header = self.load_header () + if header: + self.put (header, 0, 0) + current_y = header.get_pixbuf ().get_height () + base_y = current_y + 4 + line = self.create_line () + image = gtk.Image () + image.set_from_pixmap (line, None) + self.put (image, 0, current_y) + + logo = self.load_logo () + if logo: + self.put (logo, 0, 0) + logo_size = logo.get_size_request () + current_x += logo_size[0] + 25 + current_y = logo_size[1] + 20 + + dot = self.create_dot () + + def make_link_widget (link): + '''Helper function which makes an HyperLink and shows it''' + label = HyperLink (link[0], link[1]) + label.show_all () + return label + + widgets = map (make_link_widget, self.links) + put_widgets = 0 + for widget in widgets: + if put_widgets > 0: + if dot: + image = gtk.Image () + image.set_from_pixmap (dot, None) + self.put (image, current_x + 5, base_y + 6) + current_x += 16 + self.put (widget, current_x, base_y) + current_x += widget.size_request ()[0] + put_widgets += 1 + + self.width = current_x + 10 + self.height = current_y + self.set_size_request (self.width, self.height) + self.show_all () + + def load_header (self): + '''Load a random header image as a gtk.Image''' + + directory = locate_file (self.program, "headers") + if not directory: + print "Warning: header images directory not found." + return None + + try: + headers = os.listdir (directory) + except: + print "Warning: failed to read header images directory." + return None + + headers = filter (lambda s: s[-4:] in (".png", ".gif"), headers) + header = random.choice (headers) + + file = os.path.join (directory, header) + try: + pixbuf = gtk.gdk.pixbuf_new_from_file (file) + except: + print '''Warning: failed to load header image "%s".''' % file + return None + + image = gtk.Image () + image.set_from_pixbuf (pixbuf) + + return image + + def load_logo (self): + '''Load a GNOME foot logo as a gtk.Image''' + + file = locate_file (self.program, LOGO_FILE) + if not file: + print '''Warning: GNOME logo file "%s" not found.''' % LOGO_FILE + return None + + try: + logo = GnomeLogo (self, file) + except Exception: + print '''Warning: failed to load GNOME logo image "%s".''' % file + return None + + return logo + + def create_dot (self): + '''Create a pixmap containing a simple dot''' + pixmap = gtk.gdk.Pixmap (self.window, 6, 6) + context = pixmap.cairo_create () + context.set_operator (cairo.OPERATOR_SOURCE) + context.set_source_color (self.style.bg[self.state]) + context.paint () + context.set_operator (cairo.OPERATOR_OVER) + context.set_source_color (self.style.fg[self.state]) + context.arc (3, 3, 2.3, 0, 2 * pi) + context.fill () + return pixmap + + def create_line (self, width = 2000, height = 1): + '''Create a pixmap containing a simple line''' + pixmap = gtk.gdk.Pixmap (self.window, width, height) + context = pixmap.cairo_create () + context.set_operator (cairo.OPERATOR_SOURCE) + context.set_source_rgb (0, 0, 0) + context.paint () + return pixmap + +gobject.type_register (GnomeAboutHeader) + +class GnomeAbout (gtk.Dialog): + '''Super pretty About Dialog for the GNOME Desktop''' + + program = None + header = None + contributors = None + description_messages = GettableList () + system_infos = [] + + def __init__ (self, ui = True): + '''Initialize underlying gnome.Program, Contributors list, UI...''' + super (GnomeAbout, self).__init__ (_("About the GNOME Desktop"), + buttons = (gtk.STOCK_CLOSE, + gtk.RESPONSE_CLOSE)) + + defs = {gnome.PARAM_APP_DATADIR : DATADIR} + self.program = gnome.program_init ("gnome-about", VERSION, + properties = defs) + # Immediately fetch system infos to load description messages + self.system_infos = self.get_system_infos () + + if not ui: + return + + self.contributors = GnomeContributors (self.program) + + icon_file = ICONDIR + "/gnome-logo-icon-transparent.png" + try: + self.set_icon_from_file (icon_file) + except gobject.GError: + pass + + self.create_ui () + + self.set_default_response (gtk.RESPONSE_CLOSE) + self.set_position (gtk.WIN_POS_CENTER_ALWAYS) + map (lambda prop: self.set_property (prop[0], prop[1]), + [("allow-grow", False), ("allow-shrink", False)]) + self.connect ("delete-event", gtk.main_quit) + self.connect ("response", self.response_callback) + + def gnome_version (self): + '''Output basic GNOME version information to console''' + def print_info (info): + infos_dict = {"name": info[0], "value": info[1]} + # Translators: %(name)s and %(value)s should not be translated: + # it's a way to identify a string, so just handle them like %s + print _("%(name)s: %(value)s") % infos_dict + map (print_info, self.system_infos) + + def create_ui (self): + '''Fill our Dialog with some lovely widgets''' + self.set_has_separator (False) + main_box = self.get_children ()[0] # Get the internal Dialog VBox + + '''Pretty header''' + self.header = GnomeAboutHeader (self.program, header_links) + main_box.pack_start (self.header) + + welcome_label = WindowedLabel () + welcome_label.set_markup ("<big><big><b>%s</b></big></big>" % \ + _("Welcome to the GNOME Desktop")) + main_box.pack_start (welcome_label) + + descriptions_label = VertAnimatedLabel (self.description_messages, + 300, 120, 8000, "%s") + welcome_label.connect ("button-press-event", + descriptions_label.on_button_press) + box = gtk.EventBox () + alignment = gtk.Alignment (0.5, 0.5) + alignment.set_padding (10, 10, 0, 0) + alignment.add (descriptions_label) + box.connect ("button-press-event", descriptions_label.on_button_press) + box.add (alignment) + main_box.pack_start (box) + + by_label = WindowedLabel (True) + by_label.set_markup ("<big><b>%s</b></big>" % _("Brought to you by:")) + main_box.pack_start (by_label) + + alignment = gtk.Alignment (0.5, 0.5) + '''Realize the header right now to get everything (especially the +contributors list) correctly positionned and sized.''' + self.header.realize () + width = self.header.width + label = HorzAnimatedLabel (self.contributors, width, + 30, 2500, "<b>%s</b>") + by_label.connect ("button-press-event", label.on_button_press) + label.show_all () + alignment.add (label) + + main_box.pack_start (alignment) + + '''System info labels''' + def make_info_label (info): + if not info[1]: + return False + label = gtk.Label () + infos_dict = {"name": info[0], "value": info[1]} + # Translators: %(name)s and %(value)s should not be translated: + # it's a way to identify a string, so just handle them like %s + label.set_markup (_("<b>%(name)s:</b> %(value)s") % infos_dict) + label.set_selectable (True) + label.connect ("focus-out-event", + lambda l, e: l.select_region (-1, -1)) + alignment = gtk.Alignment (0, 0.5) + alignment.set_padding (0, 4, 8, 0) + alignment.add (label) + return alignment + + info_labels = map (make_info_label, self.system_infos) + info_labels = filter (lambda l: l <> False, info_labels) + map (lambda l: main_box.pack_start (l, False, False), info_labels) + + main_box.show_all () + + def get_system_infos (self): + '''Fetch various system infos''' + file = locate_file (self.program, "gnome-version.xml") + if not file: + print '''Warning: "gnome-version.xml" file not found.''' + return [] + + f = open (file, "r") + try: + document = xml.dom.minidom.parse (f) + finally: + f.close () + + if document.firstChild.nodeName != "gnome-version": + print '''Warning: corrupted "gnome-version.xml".''' + return [] + + infos = { + "platform" : "", + "minor" : "", + "micro" : "", + "distributor" : "", + "date" : "" + } + + for node in document.firstChild.childNodes: + if node.nodeName in infos: + infos[node.nodeName] = node.firstChild.nodeValue + elif node.nodeName == "description": + self.load_description_messages (node) + + '''Format version''' + if not len (infos["minor"]): + version = infos["platform"] + elif not len (infos["micro"]): + version = "%s.%s" % (infos["platform"], infos["minor"]) + else: + version = "%s.%s.%s" % (infos["platform"], infos["minor"], + infos["micro"]) + + return [ + (_("Version"), version), + (_("Distributor"), infos["distributor"]), + (_("Build Date"), cleanup_date (infos["date"])) + ] + + def load_description_messages (self, node): + '''Find the best translation of each description message''' + languages = get_language_names () + [""] + def desc_filter_func (node): + '''Helper filter function for XML descriptions''' + return node.nodeName == "p" \ + and node.getAttribute ("xml:lang") in languages + def desc_sort_func (a, b): + '''Helper sorting function to sort translated messages''' + return cmp (languages.index (a), languages.index (b)) + descs = filter (desc_filter_func, node.childNodes) + raw_descs = filter (lambda n: n.getAttribute ("xml:lang") == "", descs) + i = -1 + translations = [] + for desc in raw_descs: + new_i = descs.index (desc) + if i != - 1: + translations.append (descs[i:new_i]) + i = new_i + messages = GettableList () + for block in translations: + sorted_descs = sorted (block, cmp = desc_sort_func, + key = lambda n: n.getAttribute ("xml:lang")) + best = sorted_descs[0].firstChild.nodeValue + messages.append (best) + self.description_messages = messages + + def response_callback (self, widget, response): + '''Handle dialog response when Close button is triggered''' + if response == gtk.RESPONSE_CLOSE: + gtk.main_quit () + +if __name__ == "__main__": + parser = OptionParser ( + option_list = [ + make_option ("--gnome-version", + action="store_true", + dest="gnome_version", + help=_("Display information on this GNOME version")), + ]) + #FIXME: http://bugzilla.gnome.org/show_bug.cgi?id=496278 + parser.parse_args (sys.argv) + if parser.values.gnome_version: + about = GnomeAbout (ui = False) + about.gnome_version () + else: + about = GnomeAbout () + about.show_all () + try: + gtk.main () + except KeyboardInterrupt: + pass |