diff options
author | Perberos <[email protected]> | 2011-12-01 22:56:10 -0300 |
---|---|---|
committer | Perberos <[email protected]> | 2011-12-01 22:56:10 -0300 |
commit | c51ef797a707f4e2c6f9688d4378f2b0e9898a66 (patch) | |
tree | 019ae92bb53c19b30077545cb14743cbd1b57aef /applets/fish/fish.c | |
download | mate-panel-c51ef797a707f4e2c6f9688d4378f2b0e9898a66.tar.bz2 mate-panel-c51ef797a707f4e2c6f9688d4378f2b0e9898a66.tar.xz |
moving from https://github.com/perberos/mate-desktop-environment
Diffstat (limited to 'applets/fish/fish.c')
-rw-r--r-- | applets/fish/fish.c | 2012 |
1 files changed, 2012 insertions, 0 deletions
diff --git a/applets/fish/fish.c b/applets/fish/fish.c new file mode 100644 index 00000000..5ce55f61 --- /dev/null +++ b/applets/fish/fish.c @@ -0,0 +1,2012 @@ +/* fish.c: + * + * Copyright (C) 1998-2002 Free Software Foundation, Inc. + * Copyright (C) 2002-2005 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; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, + * Boston, MA 02111-1307, USA. + * + * Authors: + * George Lebl <[email protected]> + * Mark McLoughlin <[email protected]> + * Vincent Untz <[email protected]> + */ + +#include <config.h> + +#include <math.h> +#include <string.h> +#include <time.h> + +#include <cairo.h> + +#include <glib/gi18n.h> +#include <glib-object.h> +#include <gtk/gtk.h> +#include <gdk/gdkkeysyms.h> + +#include <mate-panel-applet.h> +#include <mate-panel-applet-mateconf.h> +#include <mateconf/mateconf-client.h> + +#define FISH_APPLET(o) \ + (G_TYPE_CHECK_INSTANCE_CAST((o), fish_applet_get_type(), FishApplet)) +#define FISH_IS_APPLET(o) \ + (G_TYPE_CHECK_INSTANCE_TYPE((o), FISH_TYPE_APPLET)) + +#define FISH_ICON "mate-panel-fish" + +#define N_FISH_PREFS 6 + +#define LOCKDOWN_COMMANDLINE_KEY "/desktop/mate/lockdown/disable_command_line" +#define N_FISH_LOCKDOWN 1 + +#define N_FISH_LISTENERS (N_FISH_PREFS + N_FISH_LOCKDOWN) + +typedef struct { + MatePanelApplet applet; + + MateConfClient *client; + + char *name; + char *image; + char *command; + int n_frames; + gdouble speed; + gboolean rotate; + + MatePanelAppletOrient orientation; + + GtkWidget *frame; + GtkWidget *drawing_area; + GtkRequisition requisition; + GdkRectangle prev_allocation; + GdkPixmap *pixmap; + guint timeout; + int current_frame; + gboolean in_applet; + + GdkPixbuf *pixbuf; + + GtkWidget *preferences_dialog; + GtkWidget *name_entry; + GtkWidget *command_label; + GtkWidget *command_entry; + GtkWidget *preview_image; + GtkWidget *image_chooser; + GtkWidget *frames_spin; + GtkWidget *speed_spin; + GtkWidget *rotate_toggle; + + GtkWidget *fortune_dialog; + GtkWidget *fortune_view; + GtkWidget *fortune_label; + GtkWidget *fortune_cmd_label; + GtkTextBuffer *fortune_buffer; + + unsigned int source_id; + GIOChannel *io_channel; + + gboolean april_fools; + + guint listeners [N_FISH_LISTENERS]; +} FishApplet; + +typedef struct { + MatePanelAppletClass klass; +} FishAppletClass; + + +static gboolean load_fish_image (FishApplet *fish); +static void update_pixmap (FishApplet *fish); +static void something_fishy_going_on (FishApplet *fish, const char *message); +static void display_fortune_dialog (FishApplet *fish); +static void set_tooltip (FishApplet *fish); + +static GType fish_applet_get_type (void); + +static GObjectClass *parent_class; + +static int fools_day = 0; +static int fools_month = 0; +static int fools_hour_start = 0; +static int fools_hour_end = 0; + +static char* get_image_path(FishApplet* fish) +{ + char *path; + + if (g_path_is_absolute (fish->image)) + path = g_strdup (fish->image); + else + path = g_strdup_printf ("%s/%s", FISH_ICONDIR, fish->image); + + return path; +} + +static void show_help(FishApplet* fish, const char* link_id) +{ + GError *error = NULL; + char *uri; +#define FISH_HELP_DOC "fish" + + if (link_id) + uri = g_strdup_printf ("ghelp:%s?%s", FISH_HELP_DOC, link_id); + else + uri = g_strdup_printf ("ghelp:%s", FISH_HELP_DOC); + + gtk_show_uri (gtk_widget_get_screen (GTK_WIDGET (fish)), uri, + gtk_get_current_event_time (), &error); + + g_free (uri); + + if (error && + g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED)) + g_error_free (error); + else if (error) { + GtkWidget *dialog; + char *primary; + + primary = g_markup_printf_escaped ( + _("Could not display help document '%s'"), + FISH_HELP_DOC); + dialog = gtk_message_dialog_new ( + NULL, + GTK_DIALOG_DESTROY_WITH_PARENT, + GTK_MESSAGE_ERROR, + GTK_BUTTONS_CLOSE, + "%s", primary); + + gtk_message_dialog_format_secondary_text ( + GTK_MESSAGE_DIALOG (dialog), + "%s", error->message); + + g_error_free (error); + g_free (primary); + + g_signal_connect (dialog, "response", + G_CALLBACK (gtk_widget_destroy), + NULL); + + gtk_window_set_icon_name (GTK_WINDOW (dialog), FISH_ICON); + gtk_window_set_screen (GTK_WINDOW (dialog), + gtk_widget_get_screen (GTK_WIDGET (fish))); + /* we have no parent window */ + gtk_window_set_skip_taskbar_hint (GTK_WINDOW (dialog), FALSE); + gtk_window_set_title (GTK_WINDOW (dialog), + _("Error displaying help document")); + + gtk_widget_show (dialog); + } +} + +static void name_value_changed(GtkEntry* entry, FishApplet* fish) +{ + const char *text; + + text = gtk_entry_get_text (entry); + + if (!text || !text [0]) + return; + + mate_panel_applet_mateconf_set_string ( + MATE_PANEL_APPLET (fish), "name", text, NULL); +} + +static void image_value_changed(GtkFileChooser* chooser, FishApplet* fish) +{ char *path; + char *image; + char *path_mateconf; + + path = gtk_file_chooser_get_filename (GTK_FILE_CHOOSER (chooser)); + + if (!path || !path[0]) { + g_free (path); + return; + } + + path_mateconf = get_image_path (fish); + if (!strcmp (path, path_mateconf)) { + g_free (path); + g_free (path_mateconf); + return; + } + g_free (path_mateconf); + + if (!strncmp (path, FISH_ICONDIR, strlen (FISH_ICONDIR))) { + image = path + strlen (FISH_ICONDIR); + while (*image && *image == G_DIR_SEPARATOR) + image++; + } else + image = path; + + mate_panel_applet_mateconf_set_string (MATE_PANEL_APPLET (fish), "image", + image, NULL); + + g_free (path); +} + +static void command_value_changed(GtkEntry* entry, FishApplet *fish) +{ + const char *text; + + text = gtk_entry_get_text (entry); + + if (!text || !text [0]) { + mate_panel_applet_mateconf_set_string (MATE_PANEL_APPLET (fish), + "command", "", NULL); + return; + } + + if (!strncmp (text, "ps ", 3) || + !strcmp (text, "ps") || + !strncmp (text, "who ", 4) || + !strcmp (text, "who") || + !strcmp (text, "uptime") || + !strncmp (text, "tail ", 5)) { + static gboolean message_given = FALSE; + char *message; + const char *warning_format = + _("Warning: The command " + "appears to be something actually useful.\n" + "Since this is a useless applet, you " + "may not want to do this.\n" + "We strongly advise you against " + "using %s for anything\n" + "which would make the applet " + "\"practical\" or useful."); + + if ( ! message_given) { + message = g_strdup_printf (warning_format, fish->name); + + something_fishy_going_on (fish, message); + + g_free (message); + + message_given = TRUE; + } + } + + mate_panel_applet_mateconf_set_string ( + MATE_PANEL_APPLET (fish), "command", text, NULL); +} + +static void n_frames_value_changed(GtkSpinButton* button, FishApplet* fish) +{ + mate_panel_applet_mateconf_set_int ( + MATE_PANEL_APPLET (fish), "frames", + gtk_spin_button_get_value_as_int (button), NULL); +} + +static void speed_value_changed (GtkSpinButton* button, FishApplet* fish) +{ + mate_panel_applet_mateconf_set_float ( + MATE_PANEL_APPLET (fish), "speed", + gtk_spin_button_get_value (button), NULL); +} + +static void rotate_value_changed(GtkToggleButton* toggle, FishApplet* fish) +{ + mate_panel_applet_mateconf_set_bool ( + MATE_PANEL_APPLET (fish), "rotate", + gtk_toggle_button_get_active (toggle), NULL); +} + +static gboolean delete_event(GtkWidget* widget, FishApplet* fish) +{ + gtk_widget_hide (widget); + + return TRUE; +} + +static void handle_response(GtkWidget* widget, int id, FishApplet* fish) +{ + if (id == GTK_RESPONSE_HELP) { + show_help (fish, "fish-settings"); + return; + } + + gtk_widget_hide (fish->preferences_dialog); +} + +static void setup_sensitivity(FishApplet* fish, GtkBuilder* builder, const char* wid, const char* label, const char* label_post, const char* key) +{ + MatePanelApplet *applet = (MatePanelApplet *) fish; + char *fullkey; + GtkWidget *w; + + fullkey = mate_panel_applet_mateconf_get_full_key (applet, key); + + if (mateconf_client_key_is_writable (fish->client, fullkey, NULL)) { + g_free (fullkey); + return; + } + g_free (fullkey); + + w = GTK_WIDGET (gtk_builder_get_object (builder, wid)); + g_assert (w != NULL); + gtk_widget_set_sensitive (w, FALSE); + + if (label != NULL) { + w = GTK_WIDGET (gtk_builder_get_object (builder, label)); + g_assert (w != NULL); + gtk_widget_set_sensitive (w, FALSE); + } + if (label_post != NULL) { + w = GTK_WIDGET (gtk_builder_get_object (builder, label_post)); + g_assert (w != NULL); + gtk_widget_set_sensitive (w, FALSE); + } + +} + +static void chooser_preview_update(GtkFileChooser* file_chooser, gpointer data) +{ + GtkWidget *preview; + char *filename; + GdkPixbuf *pixbuf; + gboolean have_preview; + + preview = GTK_WIDGET (data); + filename = gtk_file_chooser_get_preview_filename (file_chooser); + + if (filename == NULL) + return; + + pixbuf = gdk_pixbuf_new_from_file_at_size (filename, 128, 128, NULL); + have_preview = (pixbuf != NULL); + g_free (filename); + + gtk_image_set_from_pixbuf (GTK_IMAGE (preview), pixbuf); + if (pixbuf) + g_object_unref (pixbuf); + + gtk_file_chooser_set_preview_widget_active (file_chooser, + have_preview); +} + +static void display_preferences_dialog(GtkAction* action, FishApplet* fish) +{ + GtkBuilder *builder; + GError *error; + GtkWidget *button; + GtkFileFilter *filter; + GtkWidget *chooser_preview; + char *path; + + if (fish->preferences_dialog) { + gtk_window_set_screen (GTK_WINDOW (fish->preferences_dialog), + gtk_widget_get_screen (GTK_WIDGET (fish))); + gtk_window_present (GTK_WINDOW (fish->preferences_dialog)); + return; + } + + builder = gtk_builder_new (); + gtk_builder_set_translation_domain (builder, GETTEXT_PACKAGE); + + error = NULL; + gtk_builder_add_from_file (builder, FISH_BUILDERDIR "/fish.ui", &error); + if (error) { + g_warning ("Error loading preferences: %s", error->message); + g_error_free (error); + return; + } + + fish->preferences_dialog = GTK_WIDGET (gtk_builder_get_object (builder, "fish_preferences_dialog")); + + g_object_add_weak_pointer (G_OBJECT (fish->preferences_dialog), + (void**) &fish->preferences_dialog); + + gtk_window_set_wmclass (GTK_WINDOW (fish->preferences_dialog), + "fish", "Fish"); + gtk_window_set_icon_name (GTK_WINDOW (fish->preferences_dialog), + FISH_ICON); + gtk_dialog_set_default_response ( + GTK_DIALOG (fish->preferences_dialog), GTK_RESPONSE_OK); + + fish->name_entry = GTK_WIDGET (gtk_builder_get_object (builder, "name_entry")); + gtk_entry_set_text (GTK_ENTRY (fish->name_entry), fish->name); + + g_signal_connect (fish->name_entry, "changed", + G_CALLBACK (name_value_changed), fish); + + setup_sensitivity (fish, builder, + "name_entry" /* wid */, + "name_label" /* label */, + NULL /* label_post */, + "name" /* key */); + + fish->preview_image = GTK_WIDGET (gtk_builder_get_object (builder, "preview_image")); + if (fish->pixbuf) + gtk_image_set_from_pixbuf (GTK_IMAGE (fish->preview_image), + fish->pixbuf); + + fish->image_chooser = GTK_WIDGET (gtk_builder_get_object (builder, "image_chooser")); + filter = gtk_file_filter_new (); + gtk_file_filter_set_name (filter, _("Images")); + gtk_file_filter_add_pixbuf_formats (filter); + gtk_file_chooser_add_filter (GTK_FILE_CHOOSER (fish->image_chooser), + filter); + gtk_file_chooser_set_filter (GTK_FILE_CHOOSER (fish->image_chooser), + filter); + chooser_preview = gtk_image_new (); + gtk_file_chooser_set_preview_widget (GTK_FILE_CHOOSER (fish->image_chooser), + chooser_preview); + g_signal_connect (fish->image_chooser, "update-preview", + G_CALLBACK (chooser_preview_update), chooser_preview); + path = get_image_path (fish); + gtk_file_chooser_set_filename (GTK_FILE_CHOOSER (fish->image_chooser), + path); + g_free (path); + + g_signal_connect (fish->image_chooser, "selection-changed", + G_CALLBACK (image_value_changed), fish); + + setup_sensitivity (fish, builder, + "image_chooser" /* wid */, + "image_label" /* label */, + NULL /* label_post */, + "image" /* key */); + + fish->command_label = GTK_WIDGET (gtk_builder_get_object (builder, "command_label")); + fish->command_entry = GTK_WIDGET (gtk_builder_get_object (builder, "command_entry")); + gtk_entry_set_text (GTK_ENTRY (fish->command_entry), fish->command); + + g_signal_connect (fish->command_entry, "changed", + G_CALLBACK (command_value_changed), fish); + + setup_sensitivity (fish, builder, + "command_entry" /* wid */, + "command_label" /* label */, + NULL /* label_post */, + "command" /* key */); + + if (mateconf_client_get_bool (fish->client, + LOCKDOWN_COMMANDLINE_KEY, + NULL)) { + gtk_widget_set_sensitive (fish->command_label, FALSE); + gtk_widget_set_sensitive (fish->command_entry, FALSE); + } + + fish->frames_spin = GTK_WIDGET (gtk_builder_get_object (builder, "frames_spin")); + gtk_spin_button_set_value (GTK_SPIN_BUTTON (fish->frames_spin), + fish->n_frames); + + g_signal_connect (fish->frames_spin, "value_changed", + G_CALLBACK (n_frames_value_changed), fish); + + setup_sensitivity (fish, builder, + "frames_spin" /* wid */, + "frames_label" /* label */, + "frames_post_label" /* label_post */, + "frames" /* key */); + + fish->speed_spin = GTK_WIDGET (gtk_builder_get_object (builder, "speed_spin")); + gtk_spin_button_set_value (GTK_SPIN_BUTTON (fish->speed_spin), fish->speed); + + g_signal_connect (fish->speed_spin, "value_changed", + G_CALLBACK (speed_value_changed), fish); + + setup_sensitivity (fish, builder, + "speed_spin" /* wid */, + "speed_label" /* label */, + "speed_post_label" /* label_post */, + "speed" /* key */); + + fish->rotate_toggle = GTK_WIDGET (gtk_builder_get_object (builder, "rotate_toggle")); + gtk_toggle_button_set_active ( + GTK_TOGGLE_BUTTON (fish->rotate_toggle), fish->rotate); + + g_signal_connect (fish->rotate_toggle, "toggled", + G_CALLBACK (rotate_value_changed), fish); + + setup_sensitivity (fish, builder, + "rotate_toggle" /* wid */, + NULL /* label */, + NULL /* label_post */, + "rotate" /* key */); + + g_signal_connect (fish->preferences_dialog, "delete_event", + G_CALLBACK (delete_event), fish); + g_signal_connect (fish->preferences_dialog, "response", + G_CALLBACK (handle_response), fish); + + button = GTK_WIDGET (gtk_builder_get_object (builder, "done_button")); + g_signal_connect_swapped (button, "clicked", + (GCallback) gtk_widget_hide, + fish->preferences_dialog); + + gtk_window_set_screen (GTK_WINDOW (fish->preferences_dialog), + gtk_widget_get_screen (GTK_WIDGET (fish))); + gtk_window_set_resizable (GTK_WINDOW (fish->preferences_dialog), FALSE); + gtk_window_present (GTK_WINDOW (fish->preferences_dialog)); + + g_object_unref (builder); +} + +static void display_help_dialog(GtkAction* action, FishApplet* fish) +{ + show_help(fish, NULL); +} + +static void display_about_dialog(GtkAction* action, FishApplet* fish) +{ + const char* author_format = _("%s the Fish"); + const char* about_format = _("%s has no use what-so-ever. " + "It only takes up disk space and " + "compilation time, and if loaded it also " + "takes up precious panel space and " + "memory. Anybody found using it should be " + "promptly sent for a psychiatric " + "evaluation."); + const char* documenters [] = { + "Telsa Gwynne <[email protected]>", + "Sun MATE Documentation Team <[email protected]>", + NULL + }; + + char* authors[3]; + char* descr; + char copyright[] = \ + "Copyright \xc2\xa9 1998-2002 Free Software Foundation, Inc."; + + authors[0] = g_strdup_printf(author_format, fish->name); + authors[1] = _("(with minor help from George)"); + authors[2] = NULL; + + descr = g_strdup_printf(about_format, fish->name); + + gtk_show_about_dialog(NULL, + "program-name", _("Fish"), + "authors", authors, + "comments", descr, + "copyright", copyright, + "documenters", documenters, + "logo-icon-name", FISH_ICON, + "translator-credits", _("translator-credits"), + "version", VERSION, // "3.4.7.4ac19" + "website", "http://matsusoft.com.ar/projects/mate/", + NULL); + + g_free(descr); + g_free(authors[0]); +} + +static void set_ally_name_desc(GtkWidget* widget, FishApplet* fish) +{ + const char *name_format = _("%s the Fish"); + const char *desc_format = _("%s the Fish, a contemporary oracle"); + AtkObject *obj; + char *desc, *name; + + obj = gtk_widget_get_accessible (widget); + /* Return immediately if GAIL is not loaded */ + if (!GTK_IS_ACCESSIBLE (obj)) + return; + + name = g_strdup_printf (name_format, fish->name); + atk_object_set_name (obj, name); + g_free (name); + + desc = g_strdup_printf (desc_format, fish->name); + atk_object_set_description (obj, desc); + g_free (desc); +} + +static void something_fishy_going_on(FishApplet* fish, const char* message) +{ + GtkWidget *dialog; + + dialog = gtk_message_dialog_new (NULL, + GTK_DIALOG_DESTROY_WITH_PARENT, + GTK_MESSAGE_ERROR, + GTK_BUTTONS_CLOSE, + "%s", message); + + g_signal_connect (dialog, "response", + G_CALLBACK (gtk_widget_destroy), + NULL); + + gtk_window_set_icon_name (GTK_WINDOW (dialog), FISH_ICON); + gtk_window_set_resizable (GTK_WINDOW (dialog), FALSE); + gtk_window_set_screen (GTK_WINDOW (dialog), + gtk_widget_get_screen (GTK_WIDGET (fish))); + gtk_widget_show (dialog); +} + +static gboolean locate_fortune_command (FishApplet* fish, int* argcp, char*** argvp) +{ + char *prog = NULL; + + if (fish->command + && g_shell_parse_argv (fish->command, argcp, argvp, NULL)) { + prog = g_find_program_in_path ((*argvp)[0]); + if (prog) { + g_free (prog); + return TRUE; + } + + g_strfreev (*argvp); + } + + prog = g_find_program_in_path ("fortune"); + if (prog) { + g_free (prog); + if (g_shell_parse_argv ("fortune", argcp, argvp, NULL)) + return FALSE; + } + + if (g_file_test ("/usr/games/fortune", G_FILE_TEST_IS_EXECUTABLE) + && g_shell_parse_argv ("/usr/games/fortune", argcp, argvp, NULL)) + return FALSE; + + something_fishy_going_on (fish, + _("Unable to locate the command to execute")); + *argvp = NULL; + return FALSE; +} + +#define FISH_RESPONSE_SPEAK 1 +static inline void fish_close_channel(FishApplet* fish) +{ + if (fish->io_channel) { + g_io_channel_shutdown (fish->io_channel, TRUE, NULL); + g_io_channel_unref (fish->io_channel); + } + fish->io_channel = NULL; +} + +static void handle_fortune_response(GtkWidget* widget, int id, FishApplet* fish) +{ + if (id == FISH_RESPONSE_SPEAK) + display_fortune_dialog (fish); + else { + /* if there is still a pipe, close it: if we hide the widget, + * the * output can't be seen */ + if (fish->source_id) + g_source_remove (fish->source_id); + fish->source_id = 0; + fish_close_channel (fish); + gtk_widget_hide (fish->fortune_dialog); + } +} + +static void update_fortune_dialog(FishApplet* fish) +{ + char *label_text; + char *text; + + if (!fish->fortune_dialog || !fish->name) + return; + + /* xgettext:no-c-format */ + text = g_strdup_printf (_("%s the Fish"), fish->name); + gtk_window_set_title (GTK_WINDOW (fish->fortune_dialog), text); + g_free (text); + + /* xgettext:no-c-format */ + label_text = g_strdup_printf (_("%s the Fish Says:"), fish->name); + + text = g_strdup_printf ("<big><big>%s</big></big>", label_text); + gtk_label_set_markup (GTK_LABEL (fish->fortune_label), text); + g_free (text); + + g_free (label_text); + + set_ally_name_desc (fish->fortune_view, fish); +} + +static void insert_fortune_text(FishApplet* fish, const char* text) +{ + GtkTextIter iter; + + gtk_text_buffer_get_iter_at_offset (fish->fortune_buffer, &iter, -1); + + gtk_text_buffer_insert_with_tags_by_name (fish->fortune_buffer, &iter, + text, -1, "monospace_tag", + NULL); + + while (gtk_events_pending ()) + gtk_main_iteration (); +} + +static void clear_fortune_text(FishApplet* fish) +{ + GtkTextIter begin, end; + + gtk_text_buffer_get_iter_at_offset (fish->fortune_buffer, &begin, 0); + gtk_text_buffer_get_iter_at_offset (fish->fortune_buffer, &end, -1); + + gtk_text_buffer_delete (fish->fortune_buffer, &begin, &end); + gtk_text_buffer_remove_tag_by_name (fish->fortune_buffer, + "monospace_tag", &begin, &end); + + /* insert an empty line */ + insert_fortune_text (fish, "\n"); +} + +static gboolean fish_read_output(GIOChannel* source, GIOCondition condition, gpointer data) +{ + char output[4096]; + char *utf8_output; + gsize bytes_read; + GError *error = NULL; + GIOStatus status; + FishApplet *fish; + + fish = (FishApplet *) data; + + if (!(condition & G_IO_IN)) { + fish->source_id = 0; + fish_close_channel (fish); + return FALSE; + } + + status = g_io_channel_read_chars (source, output, 4096, &bytes_read, + &error); + + if (error) { + char *message; + + message = g_strdup_printf (_("Unable to read output from command\n\nDetails: %s"), + error->message); + something_fishy_going_on (fish, message); + g_free (message); + g_error_free (error); + fish->source_id = 0; + fish_close_channel (fish); + return FALSE; + } + + if (status == G_IO_STATUS_AGAIN) + return TRUE; + + if (bytes_read > 0) { + /* The output is not guarantied to be in UTF-8 format, most + * likely it's just in ASCII-7 or in the user locale + */ + if (!g_utf8_validate (output, -1, NULL)) + utf8_output = g_locale_to_utf8 (output, bytes_read, + NULL, NULL, NULL); + else + utf8_output = g_strndup (output, bytes_read); + + if (utf8_output) + insert_fortune_text (fish, utf8_output); + + g_free (utf8_output); + } + + if (status == G_IO_STATUS_EOF) { + fish->source_id = 0; + fish_close_channel (fish); + } + return (status != G_IO_STATUS_EOF); +} + +static void display_fortune_dialog(FishApplet* fish) +{ + GError *error = NULL; + gboolean user_command; + int output; + const char *charset; + int argc; + char **argv; + + /* if there is still a pipe, close it */ + if (fish->source_id) + g_source_remove (fish->source_id); + fish->source_id = 0; + fish_close_channel (fish); + + user_command = locate_fortune_command (fish, &argc, &argv); + if (!argv) + return; + + if (!fish->fortune_dialog) { + GtkWidget *scrolled; + GtkWidget *vbox; + GdkScreen *screen; + int screen_width; + int screen_height; + + fish->fortune_dialog = + gtk_dialog_new_with_buttons ( + "", NULL, 0, + _("_Speak again"), FISH_RESPONSE_SPEAK, + GTK_STOCK_CLOSE, GTK_RESPONSE_CLOSE, + NULL); + + gtk_window_set_icon_name (GTK_WINDOW (fish->fortune_dialog), + FISH_ICON); + gtk_dialog_set_has_separator ( + GTK_DIALOG (fish->fortune_dialog), FALSE); + + gtk_dialog_set_default_response ( + GTK_DIALOG (fish->fortune_dialog), GTK_RESPONSE_CLOSE); + + g_signal_connect (fish->fortune_dialog, "delete_event", + G_CALLBACK (delete_event), fish); + g_signal_connect (fish->fortune_dialog, "response", + G_CALLBACK (handle_fortune_response), fish); + + gtk_window_set_wmclass (GTK_WINDOW (fish->fortune_dialog), "fish", "Fish"); + + screen = gtk_widget_get_screen (GTK_WIDGET (fish)); + + screen_width = gdk_screen_get_width (screen); + screen_height = gdk_screen_get_height (screen); + + gtk_window_set_default_size (GTK_WINDOW (fish->fortune_dialog), + MIN (600, screen_width * 0.9), + MIN (350, screen_height * 0.9)); + + fish->fortune_view = gtk_text_view_new (); + gtk_text_view_set_editable (GTK_TEXT_VIEW (fish->fortune_view), FALSE); + gtk_text_view_set_cursor_visible (GTK_TEXT_VIEW (fish->fortune_view), FALSE); + gtk_text_view_set_left_margin (GTK_TEXT_VIEW (fish->fortune_view), 10); + gtk_text_view_set_right_margin (GTK_TEXT_VIEW (fish->fortune_view), 10); + fish->fortune_buffer = + gtk_text_view_get_buffer (GTK_TEXT_VIEW (fish->fortune_view)); + + gtk_text_buffer_create_tag (GTK_TEXT_BUFFER (fish->fortune_buffer), + "monospace_tag", "family", + "Monospace", NULL); + + scrolled = gtk_scrolled_window_new (NULL, NULL); + gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (scrolled), + GTK_POLICY_AUTOMATIC, + GTK_POLICY_AUTOMATIC); + gtk_scrolled_window_set_shadow_type (GTK_SCROLLED_WINDOW (scrolled), + GTK_SHADOW_IN); + + gtk_container_add (GTK_CONTAINER (scrolled), fish->fortune_view); + + fish->fortune_label = gtk_label_new (""); + gtk_label_set_ellipsize (GTK_LABEL (fish->fortune_label), + PANGO_ELLIPSIZE_MIDDLE); + fish->fortune_cmd_label = gtk_label_new (""); + gtk_misc_set_alignment (GTK_MISC (fish->fortune_cmd_label), + 0, 0.5); + + vbox = gtk_dialog_get_content_area (GTK_DIALOG (fish->fortune_dialog)); + gtk_box_pack_start (GTK_BOX (vbox), + fish->fortune_label, + FALSE, FALSE, 6); + + gtk_box_pack_start (GTK_BOX (vbox), + scrolled, + TRUE, TRUE, 6); + + gtk_box_pack_start (GTK_BOX (vbox), + fish->fortune_cmd_label, + FALSE, FALSE, 6); + + update_fortune_dialog (fish); + + /* We don't show_all for the dialog since fortune_cmd_label + * might need to be hidden + * The dialog will be shown with gtk_window_present later */ + gtk_widget_show (scrolled); + gtk_widget_show (fish->fortune_view); + gtk_widget_show (fish->fortune_label); + } + + if (!user_command) { + char *command; + char * text; + + command = g_markup_printf_escaped ("<tt>%s</tt>", argv[0]); + text = g_strdup_printf (_("The configured command is not " + "working and has been replaced by: " + "%s"), command); + gtk_label_set_markup (GTK_LABEL (fish->fortune_cmd_label), + text); + g_free (command); + g_free (text); + gtk_widget_show (fish->fortune_cmd_label); + } else { + gtk_widget_hide (fish->fortune_cmd_label); + } + + clear_fortune_text (fish); + + gdk_spawn_on_screen_with_pipes (gtk_widget_get_screen (GTK_WIDGET (fish)), + NULL, argv, NULL, + G_SPAWN_SEARCH_PATH|G_SPAWN_STDERR_TO_DEV_NULL, + NULL, NULL, NULL, NULL, &output, NULL, + &error); + + if (error) { + char *message; + + message = g_strdup_printf (_("Unable to execute '%s'\n\nDetails: %s"), + argv[0], error->message); + something_fishy_going_on (fish, message); + g_free (message); + g_error_free (error); + g_strfreev (argv); + return; + } + + fish->io_channel = g_io_channel_unix_new (output); + /* set the correct encoding if the locale is not using UTF-8 */ + if (!g_get_charset (&charset)) + g_io_channel_set_encoding(fish->io_channel, charset, &error); + if (error) { + char *message; + + message = g_strdup_printf (_("Unable to read from '%s'\n\nDetails: %s"), + argv[0], error->message); + something_fishy_going_on (fish, message); + g_free (message); + g_error_free (error); + g_strfreev (argv); + return; + } + + g_strfreev (argv); + + fish->source_id = g_io_add_watch (fish->io_channel, + G_IO_IN|G_IO_ERR|G_IO_HUP|G_IO_NVAL, + fish_read_output, fish); + + gtk_window_set_screen (GTK_WINDOW (fish->fortune_dialog), + gtk_widget_get_screen (GTK_WIDGET (fish))); + gtk_window_present (GTK_WINDOW (fish->fortune_dialog)); +} + +static void name_changed_notify(MateConfClient* client, guint cnxn_id, MateConfEntry* entry, FishApplet* fish) +{ + const char *value; + + if (!entry->value || entry->value->type != MATECONF_VALUE_STRING) + return; + + value = mateconf_value_get_string (entry->value); + + if (!value [0] || (fish->name && !strcmp (fish->name, value))) + return; + + if (fish->name) + g_free (fish->name); + fish->name = g_strdup (value); + + update_fortune_dialog (fish); + set_tooltip (fish); + set_ally_name_desc (GTK_WIDGET (fish), fish); + + if (fish->name_entry && + strcmp (gtk_entry_get_text (GTK_ENTRY (fish->name_entry)), fish->name)) + gtk_entry_set_text (GTK_ENTRY (fish->name_entry), fish->name); +} + +static void image_changed_notify(MateConfClient* client, guint cnxn_id, MateConfEntry* entry, FishApplet* fish) +{ + const char *value; + + if (!entry->value || entry->value->type != MATECONF_VALUE_STRING) + return; + + value = mateconf_value_get_string (entry->value); + + if (!value [0] || (fish->image && !strcmp (fish->image, value))) + return; + + if (fish->image) + g_free (fish->image); + fish->image = g_strdup (value); + + load_fish_image (fish); + update_pixmap (fish); + + if (fish->image_chooser) { + char *path_mateconf; + char *path_chooser; + + path_mateconf = get_image_path (fish); + path_chooser = gtk_file_chooser_get_filename (GTK_FILE_CHOOSER (fish->image_chooser)); + if (strcmp (path_mateconf, path_chooser)) + gtk_file_chooser_set_filename (GTK_FILE_CHOOSER (fish->image_chooser), + path_mateconf); + + g_free (path_mateconf); + g_free (path_chooser); + } +} + +static void command_changed_notify(MateConfClient* client, guint cnxn_id, MateConfEntry* entry, FishApplet* fish) +{ + const char *value; + + if (!entry->value || entry->value->type != MATECONF_VALUE_STRING) + return; + + value = mateconf_value_get_string (entry->value); + + if (fish->command && !strcmp (fish->command, value)) + return; + + if (fish->command) + g_free (fish->command); + fish->command = g_strdup (value); + + if (fish->command_entry && + strcmp (gtk_entry_get_text (GTK_ENTRY (fish->command_entry)), fish->command)) + gtk_entry_set_text (GTK_ENTRY (fish->command_entry), fish->command); +} + +static void n_frames_changed_notify(MateConfClient* client, guint cnxn_id, MateConfEntry* entry, FishApplet* fish) +{ + int value; + + if (!entry->value || entry->value->type != MATECONF_VALUE_INT) + return; + + value = mateconf_value_get_int (entry->value); + + if (fish->n_frames == value) + return; + + fish->n_frames = value; + + if (fish->n_frames <= 0) + fish->n_frames = 1; + + update_pixmap (fish); + + if (fish->frames_spin && + gtk_spin_button_get_value_as_int (GTK_SPIN_BUTTON (fish->frames_spin)) != fish->n_frames) + gtk_spin_button_set_value (GTK_SPIN_BUTTON (fish->frames_spin), fish->n_frames); +} + +static char* get_location(void) +{ + static char location [256]; + char *buffer; + FILE *zone; + int i, len, count; + + /* Old method : works for glibc < 2.2 */ + zone = fopen("/etc/timezone", "r"); + if (zone) { + count = fscanf (zone, "%255s", location); + fclose (zone); + /* if we could read it, we return what we got */ + if (count == 1) + return location; + } + + /* New method : works for glibc 2.2 */ + /* FIXME: this is broken for many distros, see the clock code */ + buffer = g_file_read_link ("/etc/localtime", NULL); + if (!buffer) + return NULL; + + len = strlen (buffer); + for (i = len, count = 0; (i > 0) && (count != 2); i--) + if (buffer [i] == '/') + count++; + + if (count != 2) { + return NULL; + g_free (buffer); + } + + memcpy (location, &buffer [i + 2], len - i - 2); + g_free (buffer); + + return location; +} + +static void init_fools_day(void) +{ + const char *spanish_timezones [] = { + "Europe/Madrid", + "Africa/Ceuta", + "Atlantic/Canary", + "America/Mexico_City", + "Mexico/BajaSur", + "Mexico/BajaNorte", + "Mexico/General", + NULL + }; + char *location; + int i; + + if (!(location = get_location ())) + return; + + fools_day = 1; /* 1st */ + fools_month = 3; /* April */ + fools_hour_start = 0; /* Midnight */ + fools_hour_end = 12; /* Apparently jokes should stop at midday */ + + for (i = 0; spanish_timezones [i]; i++) + if (!g_ascii_strcasecmp (spanish_timezones [i], location)) { + /* Hah!, We are in Spain or Mexico + * Spanish fool's day is 28th December + */ + fools_day = 28; + fools_month = 11; + return; + } +} + +static void check_april_fools(FishApplet* fish) +{ + struct tm *tm; + time_t now; + + time (&now); + tm = localtime (&now); + + if (fish->april_fools && + (tm->tm_mon != fools_month || + tm->tm_mday != fools_day || + tm->tm_hour >= fools_hour_end)) { + fish->april_fools = FALSE; + update_pixmap (fish); + } else if (tm->tm_mon == fools_month && + tm->tm_mday == fools_day && + tm->tm_hour >= fools_hour_start && + tm->tm_hour <= fools_hour_end) { + fish->april_fools = TRUE; + update_pixmap (fish); + } +} + +static gboolean timeout_handler(gpointer data) +{ + FishApplet *fish = (FishApplet *) data; + + check_april_fools (fish); + + if (fish->april_fools) + return TRUE; + + fish->current_frame++; + if (fish->current_frame >= fish->n_frames) + fish->current_frame = 0; + + gtk_widget_queue_draw (fish->drawing_area); + + return TRUE; +} + +static void setup_timeout(FishApplet *fish) +{ + if (fish->timeout) + g_source_remove (fish->timeout); + + fish->timeout = g_timeout_add (fish->speed * 1000, + timeout_handler, + fish); +} + +static void speed_changed_notify(MateConfClient* client, guint cnxn_id, MateConfEntry* entry, FishApplet* fish) +{ + gdouble value; + + if (!entry->value || entry->value->type != MATECONF_VALUE_FLOAT) + return; + + value = mateconf_value_get_float (entry->value); + + if (fish->speed == value) + return; + fish->speed = value; + + setup_timeout (fish); + + if (fish->speed_spin && + gtk_spin_button_get_value (GTK_SPIN_BUTTON (fish->frames_spin)) != fish->speed) + gtk_spin_button_set_value (GTK_SPIN_BUTTON (fish->speed_spin), fish->speed); +} + +static void rotate_changed_notify(MateConfClient* client, guint cnxn_id, MateConfEntry* entry, FishApplet* fish) +{ + gboolean value; + + if (!entry->value || entry->value->type != MATECONF_VALUE_BOOL) + return; + + value = mateconf_value_get_bool (entry->value); + + if (fish->rotate == value) + return; + fish->rotate = value; + + if (fish->orientation == MATE_PANEL_APPLET_ORIENT_LEFT || + fish->orientation == MATE_PANEL_APPLET_ORIENT_RIGHT) + update_pixmap (fish); + + if (fish->rotate_toggle && + gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (fish->rotate_toggle)) != fish->rotate) + gtk_toggle_button_set_active ( + GTK_TOGGLE_BUTTON (fish->rotate_toggle), fish->rotate); +} + +static void fish_disable_commande_line_notify(MateConfClient* client, guint cnxn_id, MateConfEntry* entry, FishApplet* fish) +{ + gboolean locked_down; + + if (!entry->value || entry->value->type != MATECONF_VALUE_BOOL) + return; + + locked_down = !mateconf_value_get_bool (entry->value); + + if (fish->command_label != NULL) + gtk_widget_set_sensitive (fish->command_label, locked_down); + if (fish->command_entry != NULL) + gtk_widget_set_sensitive (fish->command_entry, locked_down); +} + +static void setup_mateconf(FishApplet* fish) +{ + MatePanelApplet *applet = (MatePanelApplet *) fish; + char *key; + int i = 0; + + key = mate_panel_applet_mateconf_get_full_key (applet, "name"); + fish->listeners [i++] = mateconf_client_notify_add ( + fish->client, key, + (MateConfClientNotifyFunc) name_changed_notify, + fish, NULL, NULL); + g_free (key); + + key = mate_panel_applet_mateconf_get_full_key (applet, "image"); + fish->listeners [i++] = mateconf_client_notify_add ( + fish->client, key, + (MateConfClientNotifyFunc) image_changed_notify, + fish, NULL, NULL); + g_free (key); + + key = mate_panel_applet_mateconf_get_full_key (applet, "command"); + fish->listeners [i++] = mateconf_client_notify_add ( + fish->client, key, + (MateConfClientNotifyFunc) command_changed_notify, + fish, NULL, NULL); + g_free (key); + + key = mate_panel_applet_mateconf_get_full_key (applet, "frames"); + fish->listeners [i++] = mateconf_client_notify_add ( + fish->client, key, + (MateConfClientNotifyFunc) n_frames_changed_notify, + fish, NULL, NULL); + g_free (key); + + key = mate_panel_applet_mateconf_get_full_key (applet, "speed"); + fish->listeners [i++] = mateconf_client_notify_add ( + fish->client, key, + (MateConfClientNotifyFunc) speed_changed_notify, + fish, NULL, NULL); + g_free (key); + + key = mate_panel_applet_mateconf_get_full_key (applet, "rotate"); + fish->listeners [i++] = mateconf_client_notify_add ( + fish->client, key, + (MateConfClientNotifyFunc) rotate_changed_notify, + fish, NULL, NULL); + g_free (key); + + fish->listeners [i++] = mateconf_client_notify_add ( + fish->client, + LOCKDOWN_COMMANDLINE_KEY, + (MateConfClientNotifyFunc) fish_disable_commande_line_notify, + fish, NULL, NULL); + + g_assert (i == N_FISH_LISTENERS); +} + +static gboolean load_fish_image(FishApplet* fish) +{ + GdkPixbuf *pixbuf; + GError *error = NULL; + char *path = NULL; + + if (!fish->image) + return FALSE; + + path = get_image_path (fish); + + pixbuf = gdk_pixbuf_new_from_file (path, &error); + if (error) { + g_warning ("Cannot load '%s': %s", path, error->message); + g_error_free (error); + g_free (path); + return FALSE; + } + + if (fish->pixbuf) + g_object_unref (fish->pixbuf); + fish->pixbuf = pixbuf; + + if (fish->preview_image) + gtk_image_set_from_pixbuf (GTK_IMAGE (fish->preview_image), + fish->pixbuf); + + g_free (path); + + return TRUE; +} + +static void update_pixmap(FishApplet* fish) +{ + GtkWidget *widget = fish->drawing_area; + GtkAllocation allocation; + int width = -1; + int height = -1; + int pixbuf_width = -1; + int pixbuf_height = -1; + gboolean rotate = FALSE; + cairo_t *cr; + cairo_matrix_t matrix; + cairo_pattern_t *pattern; + + gtk_widget_get_allocation (widget, &allocation); + + if (!gtk_widget_get_realized (widget) || + allocation.width <= 0 || + allocation.height <= 0) + return; + + if (!fish->pixbuf && !load_fish_image (fish)) + return; + + if (fish->rotate && + (fish->orientation == MATE_PANEL_APPLET_ORIENT_LEFT || + fish->orientation == MATE_PANEL_APPLET_ORIENT_RIGHT)) + rotate = TRUE; + + pixbuf_width = gdk_pixbuf_get_width (fish->pixbuf); + pixbuf_height = gdk_pixbuf_get_height (fish->pixbuf); + + if (fish->orientation == MATE_PANEL_APPLET_ORIENT_UP || + fish->orientation == MATE_PANEL_APPLET_ORIENT_DOWN) { + height = allocation.height; + width = pixbuf_width * ((gdouble) height / pixbuf_height); + fish->requisition.width = width / fish->n_frames; + fish->requisition.height = height; + } else { + if (!rotate) { + width = allocation.width * fish->n_frames; + height = pixbuf_height * ((gdouble) width / pixbuf_width); + fish->requisition.width = width; + fish->requisition.height = height; + } else { + width = allocation.width; + height = pixbuf_width * ((gdouble) width / pixbuf_height); + fish->requisition.width = width; + fish->requisition.height = height / fish->n_frames; + } + } + + g_assert (width != -1 && height != -1); + + if (width == 0 || height == 0) + return; + + if (fish->pixmap) + g_object_unref (fish->pixmap); + fish->pixmap = gdk_pixmap_new (gtk_widget_get_window (widget), + width, height, -1); + + gtk_widget_queue_resize (widget); + + g_assert (pixbuf_width != -1 && pixbuf_height != -1); + + cr = gdk_cairo_create (fish->pixmap); + + cairo_set_source_rgb (cr, 1, 1, 1); + cairo_paint (cr); + + gdk_cairo_set_source_pixbuf (cr, fish->pixbuf, 0, 0); + pattern = cairo_get_source (cr); + cairo_pattern_set_filter (pattern, CAIRO_FILTER_BEST); + + cairo_matrix_init_identity (&matrix); + + if (fish->april_fools) { + cairo_matrix_translate (&matrix, + pixbuf_width - 1, pixbuf_height - 1); + cairo_matrix_rotate (&matrix, M_PI); + } + + if (rotate) { + if (fish->orientation == MATE_PANEL_APPLET_ORIENT_RIGHT) { + cairo_matrix_translate (&matrix, pixbuf_width - 1, 0); + cairo_matrix_rotate (&matrix, M_PI * 0.5); + } else { + cairo_matrix_translate (&matrix, 0, pixbuf_height - 1); + cairo_matrix_rotate (&matrix, M_PI * 1.5); + } + cairo_matrix_scale (&matrix, + (double) (pixbuf_height - 1) / width, + (double) (pixbuf_width - 1) / height); + } else { + cairo_matrix_scale (&matrix, + (double) (pixbuf_width - 1) / width, + (double) (pixbuf_height - 1) / height); + } + + cairo_pattern_set_matrix (pattern, &matrix); + + cairo_rectangle (cr, 0, 0, width, height); + cairo_fill (cr); + + if (fish->april_fools) { + cairo_set_source_rgb (cr, 1, 0.5, 0); + cairo_paint_with_alpha (cr, 0.25); + } + + cairo_destroy (cr); +} + +static gboolean fish_applet_expose_event(GtkWidget* widget, GdkEventExpose* event, FishApplet* fish) +{ + GdkWindow *window; + GtkStyle *style; + GtkStateType state; + int width, height; + int src_x, src_y; + + g_return_val_if_fail (fish->pixmap != NULL, FALSE); + + g_assert (fish->n_frames > 0); + + window = gtk_widget_get_window (widget); + style = gtk_widget_get_style (widget); + state = gtk_widget_get_state (widget); + + #if GTK_CHECK_VERSION(3, 0, 0) + width = gdk_window_get_width(fish->pixmap); + height = gdk_window_get_height(fish->pixmap); + #else + gdk_drawable_get_size(fish->pixmap, &width, &height); + #endif + + + src_x = event->area.x; + src_y = event->area.y; + + if (fish->rotate) { + if (fish->orientation == MATE_PANEL_APPLET_ORIENT_RIGHT) + src_y += ((height * (fish->n_frames - 1 - fish->current_frame)) / fish->n_frames); + else if (fish->orientation == MATE_PANEL_APPLET_ORIENT_LEFT) + src_y += ((height * fish->current_frame) / fish->n_frames); + else + src_x += ((width * fish->current_frame) / fish->n_frames); + } else + src_x += ((width * fish->current_frame) / fish->n_frames); + + gdk_draw_drawable (window, + style->fg_gc [state], + fish->pixmap, + src_x, src_y, + event->area.x, event->area.y, + event->area.width, event->area.height); + + return FALSE; +} + +static void fish_applet_size_request(GtkWidget* widget, GtkRequisition* requisition, FishApplet* fish) +{ + *requisition = fish->requisition; +} + +static void fish_applet_size_allocate(GtkWidget* widget, GtkAllocation* allocation, FishApplet* fish) +{ + GtkAllocation widget_allocation; + + gtk_widget_get_allocation (widget, &widget_allocation); + + if (widget_allocation.width != fish->prev_allocation.width || + widget_allocation.height != fish->prev_allocation.height) + update_pixmap (fish); + + fish->prev_allocation = *allocation; +} + +static void fish_applet_realize(GtkWidget* widget, FishApplet* fish) +{ + if (!fish->pixmap) + update_pixmap (fish); +} + +static void fish_applet_unrealize(GtkWidget* widget, FishApplet* fish) +{ + if (fish->pixmap) + g_object_unref (fish->pixmap); + fish->pixmap = NULL; +} + +static void fish_applet_change_orient(MatePanelApplet* applet, MatePanelAppletOrient orientation) +{ + FishApplet *fish = (FishApplet *) applet; + + if (fish->orientation == orientation) + return; + + fish->orientation = orientation; + + if (fish->pixmap) + update_pixmap (fish); +} + +static void change_water(FishApplet* fish) +{ + GtkWidget *dialog; + + dialog = gtk_message_dialog_new ( + NULL, 0, GTK_MESSAGE_INFO, + GTK_BUTTONS_OK, + _("The water needs changing")); + gtk_message_dialog_format_secondary_text (GTK_MESSAGE_DIALOG (dialog), + _("Look at today's date!")); + gtk_window_set_icon_name (GTK_WINDOW (dialog), FISH_ICON); + gtk_window_set_wmclass (GTK_WINDOW (dialog), "fish", "Fish"); + gtk_window_set_screen (GTK_WINDOW (dialog), + gtk_widget_get_screen (GTK_WIDGET (fish))); + + gtk_widget_show_all (dialog); + + g_signal_connect (dialog, "response", + G_CALLBACK (gtk_widget_destroy), NULL); +} + +static gboolean handle_keypress(GtkWidget* widget, GdkEventKey* event, FishApplet* fish) +{ + switch (event->keyval) { + case GDK_space: + case GDK_KP_Space: + case GDK_Return: + case GDK_KP_Enter: + case GDK_ISO_Enter: + case GDK_3270_Enter: + if (fish->april_fools) { + change_water (fish); + return TRUE; + } + + display_fortune_dialog (fish); + break; + default: + return FALSE; + break; + } + + return TRUE; +} + +static gboolean fish_enter_notify(GtkWidget* widget, GdkEventCrossing* event) +{ + FishApplet *fish; + GtkWidget *event_widget; + + fish = FISH_APPLET (widget); + event_widget = gtk_get_event_widget ((GdkEvent*) event); + + if ((event_widget == widget) && + (event->detail != GDK_NOTIFY_INFERIOR)) + fish->in_applet = TRUE; + + return FALSE; +} + +static gboolean fish_leave_notify(GtkWidget* widget, GdkEventCrossing* event) +{ + FishApplet *fish; + GtkWidget *event_widget; + + fish = FISH_APPLET (widget); + event_widget = gtk_get_event_widget ((GdkEvent*) event); + + if ((event_widget == widget) && + (event->detail != GDK_NOTIFY_INFERIOR)) + fish->in_applet = FALSE; + + return FALSE; +} + +static gboolean handle_button_release(FishApplet* fish, GdkEventButton* event) +{ + if (!fish->in_applet || event->button != 1) + return FALSE; + + if (fish->april_fools) { + change_water (fish); + return TRUE; + } + + display_fortune_dialog (fish); + + return TRUE; +} + +static void set_tooltip(FishApplet* fish) +{ + const char *desc_format = _("%s the Fish, the fortune teller"); + char *desc; + + desc = g_markup_printf_escaped (desc_format, fish->name); + gtk_widget_set_tooltip_markup (GTK_WIDGET (fish), desc); + g_free (desc); +} + +static void setup_fish_widget(FishApplet* fish) +{ + GtkWidget *widget = (GtkWidget *) fish; + + fish->frame = gtk_frame_new (NULL); + gtk_frame_set_shadow_type (GTK_FRAME (fish->frame), GTK_SHADOW_IN); + gtk_container_add (GTK_CONTAINER (widget), fish->frame); + + fish->drawing_area = gtk_drawing_area_new (); + gtk_container_add (GTK_CONTAINER (fish->frame), fish->drawing_area); + + g_signal_connect (fish->drawing_area, "realize", + G_CALLBACK (fish_applet_realize), fish); + g_signal_connect (fish->drawing_area, "unrealize", + G_CALLBACK (fish_applet_unrealize), fish); + g_signal_connect (fish->drawing_area, "size-request", + G_CALLBACK (fish_applet_size_request), fish); + g_signal_connect (fish->drawing_area, "size-allocate", + G_CALLBACK (fish_applet_size_allocate), fish); + g_signal_connect (fish->drawing_area, "expose-event", + G_CALLBACK (fish_applet_expose_event), fish); + + gtk_widget_add_events (widget, GDK_ENTER_NOTIFY_MASK | + GDK_LEAVE_NOTIFY_MASK | + GDK_BUTTON_RELEASE_MASK); + + g_signal_connect_swapped (widget, "enter_notify_event", + G_CALLBACK (fish_enter_notify), fish); + g_signal_connect_swapped (widget, "leave_notify_event", + G_CALLBACK (fish_leave_notify), fish); + g_signal_connect_swapped (widget, "button_release_event", + G_CALLBACK (handle_button_release), fish); + + gtk_widget_add_events (fish->drawing_area, GDK_BUTTON_RELEASE_MASK); + g_signal_connect_swapped (fish->drawing_area, "button_release_event", + G_CALLBACK (handle_button_release), fish); + + load_fish_image (fish); + + update_pixmap (fish); + + setup_timeout (fish); + + set_tooltip (fish); + set_ally_name_desc (GTK_WIDGET (fish), fish); + + g_signal_connect (fish, "key_press_event", + G_CALLBACK (handle_keypress), fish); + + gtk_widget_show_all (widget); +} + +static const GtkActionEntry fish_menu_verbs[] = { + { "FishPreferences", GTK_STOCK_PROPERTIES, N_("_Preferences"), + NULL, NULL, + G_CALLBACK (display_preferences_dialog) }, + { "FishHelp", GTK_STOCK_HELP, N_("_Help"), + NULL, NULL, + G_CALLBACK (display_help_dialog) }, + { "FishAbout", GTK_STOCK_ABOUT, N_("_About"), + NULL, NULL, + G_CALLBACK (display_about_dialog) } +}; + +static void fish_migrate_to_210(FishApplet* fish) +{ + char *new_image; + + g_assert (fish->image); + + if (!strncmp (fish->image, "fish/", 5)) { + new_image = g_strdup (fish->image + 5); + g_free (fish->image); + fish->image = new_image; + mate_panel_applet_mateconf_set_string (MATE_PANEL_APPLET (fish), "image", + fish->image, NULL); + } +} + +static gboolean fish_applet_fill(FishApplet* fish) +{ + MatePanelApplet* applet = (MatePanelApplet*) fish; + GtkActionGroup* action_group; + gchar* ui_path; + GError* error = NULL; + + fish->orientation = mate_panel_applet_get_orient (applet); + + mate_panel_applet_add_preferences(applet, "/schemas/apps/fish_applet/prefs", NULL); + + setup_mateconf(fish); + + fish->name = mate_panel_applet_mateconf_get_string(applet, "name", &error); + + if (error) + { + g_warning ("Error getting 'name' preference: %s", error->message); + g_error_free (error); + error = NULL; + } + + if (!fish->name) + { + fish->name = g_strdup ("Wanda"); /* Fallback */ + } + + fish->image = mate_panel_applet_mateconf_get_string (applet, "image", &error); + + if (error) { + g_warning ("Error getting 'image' preference: %s", error->message); + g_error_free (error); + error = NULL; + } + + if (!fish->image) + fish->image = g_strdup ("fishanim.png"); /* Fallback */ + + fish_migrate_to_210 (fish); + + fish->command = mate_panel_applet_mateconf_get_string (applet, "command", &error); + + if (error) { + g_warning ("Error getting 'command' preference: %s", error->message); + g_error_free (error); + error = NULL; + } + + fish->n_frames = mate_panel_applet_mateconf_get_int (applet, "frames", &error); + + if (error) { + g_warning ("Error getting 'frames' preference: %s", error->message); + g_error_free (error); + error = NULL; + + fish->n_frames = 3; /* Fallback */ + } + if (fish->n_frames <= 0) + fish->n_frames = 1; + + fish->speed = mate_panel_applet_mateconf_get_float (applet, "speed", &error); + + if (error) { + g_warning ("Error getting 'speed' preference: %s", error->message); + g_error_free (error); + error = NULL; + + fish->speed = 1.0; /* Fallback */ + } + + fish->rotate = mate_panel_applet_mateconf_get_bool (applet, "rotate", &error); + + if (error) { + g_warning ("Error getting 'rotate' preference: %s", error->message); + g_error_free (error); + error = NULL; + + fish->rotate = FALSE; /* Fallback */ + } + + action_group = gtk_action_group_new ("Fish Applet Actions"); + gtk_action_group_set_translation_domain (action_group, GETTEXT_PACKAGE); + gtk_action_group_add_actions (action_group, + fish_menu_verbs, + G_N_ELEMENTS (fish_menu_verbs), + fish); + ui_path = g_build_filename (FISH_MENU_UI_DIR, "fish-menu.xml", NULL); + mate_panel_applet_setup_menu_from_file (applet, ui_path, action_group); + g_free (ui_path); + + if (mate_panel_applet_get_locked_down (applet)) { + GtkAction *action; + + action = gtk_action_group_get_action (action_group, "FishPreferences"); + gtk_action_set_visible (action, FALSE); + } + g_object_unref (action_group); + + #ifndef FISH_INPROCESS + gtk_window_set_default_icon_name(FISH_ICON); + #endif + + setup_fish_widget(fish); + + return TRUE; +} + +static gboolean fishy_factory(MatePanelApplet* applet, const char* iid, gpointer data) +{ + gboolean retval = FALSE; + + if (!strcmp(iid, "FishApplet")) + { + retval = fish_applet_fill(FISH_APPLET(applet)); + } + + return retval; +} + +static void fish_applet_destroy(GtkObject* object) +{ + FishApplet* fish = (FishApplet*) object; + int i; + + if (fish->timeout) + { + g_source_remove (fish->timeout); + } + + fish->timeout = 0; + + for (i = 0; i < N_FISH_LISTENERS; i++) + { + if (fish->client && fish->listeners [i] != 0) + { + mateconf_client_notify_remove(fish->client, fish->listeners [i]); + } + + fish->listeners [i] = 0; + } + + if (fish->name) + g_free (fish->name); + fish->name = NULL; + + if (fish->image) + g_free (fish->image); + fish->image = NULL; + + if (fish->command) + g_free (fish->command); + fish->command = NULL; + + if (fish->client) + g_object_unref (fish->client); + fish->client = NULL; + + if (fish->pixmap) + g_object_unref (fish->pixmap); + fish->pixmap = NULL; + + if (fish->pixbuf) + g_object_unref (fish->pixbuf); + fish->pixbuf = NULL; + + if (fish->preferences_dialog) + gtk_widget_destroy (fish->preferences_dialog); + fish->preferences_dialog = NULL; + + if (fish->fortune_dialog) + gtk_widget_destroy (fish->fortune_dialog); + fish->fortune_dialog = NULL; + + if (fish->source_id) + g_source_remove (fish->source_id); + fish->source_id = 0; + + fish_close_channel (fish); + + GTK_OBJECT_CLASS (parent_class)->destroy (object); +} + +static void fish_applet_instance_init(FishApplet* fish, FishAppletClass* klass) +{ + int i; + + fish->client = mateconf_client_get_default(); + + fish->name = NULL; + fish->image = NULL; + fish->command = NULL; + fish->n_frames = 1; + fish->speed = 0.0; + fish->rotate = FALSE; + + fish->orientation = MATE_PANEL_APPLET_ORIENT_UP; + + fish->frame = NULL; + fish->drawing_area = NULL; + fish->pixmap = NULL; + fish->timeout = 0; + fish->current_frame = 0; + fish->in_applet = FALSE; + + fish->requisition.width = -1; + fish->requisition.height = -1; + + fish->prev_allocation.x = -1; + fish->prev_allocation.y = -1; + fish->prev_allocation.width = -1; + fish->prev_allocation.height = -1; + + fish->pixbuf = NULL; + + fish->preferences_dialog = NULL; + fish->name_entry = NULL; + fish->command_label = NULL; + fish->command_entry = NULL; + fish->preview_image = NULL; + fish->image_chooser = NULL; + fish->frames_spin = NULL; + fish->speed_spin = NULL; + fish->rotate_toggle = NULL; + + fish->fortune_dialog = NULL; + fish->fortune_view = NULL; + fish->fortune_label = NULL; + fish->fortune_cmd_label = NULL; + fish->fortune_buffer = NULL; + + fish->source_id = 0; + fish->io_channel = NULL; + + for (i = 0; i < N_FISH_LISTENERS; i++) + fish->listeners [i] = 0; + + fish->april_fools = FALSE; + + mate_panel_applet_set_flags (MATE_PANEL_APPLET (fish), MATE_PANEL_APPLET_EXPAND_MINOR); + + mate_panel_applet_set_background_widget(MATE_PANEL_APPLET(fish), GTK_WIDGET(fish)); +} + +static void fish_applet_class_init(FishAppletClass* klass) +{ + MatePanelAppletClass* applet_class = (MatePanelAppletClass*) klass; + GtkObjectClass* gtkobject_class = (GtkObjectClass*) klass; + + parent_class = g_type_class_peek_parent(klass); + + applet_class->change_orient = fish_applet_change_orient; + + gtkobject_class->destroy = fish_applet_destroy; + + init_fools_day(); +} + +static GType fish_applet_get_type(void) +{ + static GType type = 0; + + if (!type) + { + static const GTypeInfo info = { + sizeof(MatePanelAppletClass), + NULL, NULL, + (GClassInitFunc) fish_applet_class_init, + NULL, NULL, + sizeof(FishApplet), + 0, + (GInstanceInitFunc) fish_applet_instance_init, + NULL + }; + + type = g_type_register_static(PANEL_TYPE_APPLET, "FishApplet", &info, 0); + } + + return type; +} + +#ifdef FISH_INPROCESS + MATE_PANEL_APPLET_IN_PROCESS_FACTORY("FishAppletFactory", fish_applet_get_type(), "That-stupid-fish", fishy_factory, NULL) +#else + MATE_PANEL_APPLET_OUT_PROCESS_FACTORY("FishAppletFactory", fish_applet_get_type(), "That-stupid-fish", fishy_factory, NULL) +#endif |