/* * Copyright (C) 2004-2006 Jimmy Do * * 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 */ #ifdef HAVE_CONFIG_H #include #endif #include "timer-applet.h" #include #include #include #include /* for GNOME_STOCK_TIMER */ #include #include #include "about-dialog.h" #include "add-preset-dialog.h" #include "gloo/gloo-pulse-button.h" #include "layout-utils.h" #include "manage-presets-window.h" #include "prefs-dialog.h" /* tree view column constants */ #include "presets-manager.h" #include "start-timer-dialog.h" #define DEFAULT_NOTIFICATION_SOUND_PATH SOUNDS_DIR "/clicked.wav" #define TIMER_APPLET_MENU_FILE_NAME "GNOME_TimerApplet.xml" #define PRESETS_SUBMENU_PATH "/popups/popup/Presets" #define TA_MENU_COMMAND_PAUSE_TIMER "/commands/PauseTimer" #define TA_MENU_COMMAND_CONTINUE_TIMER "/commands/ContinueTimer" #define TA_MENU_COMMAND_RESTART_TIMER "/commands/RestartTimer" #define TA_MENU_COMMAND_STOP_TIMER "/commands/StopTimer" #define SEPARATOR1_PATH "/popups/popup/Separator1" #define TIMER_TIMEOUT_INTERVAL 500 #define IDLE_TOOLTIP _("Click to start timer") #ifdef ENABLE_TRACING #define TRACE_MSG(msg) (g_print (msg "\n")) #else #define TRACE_MSG(msg) ((void) (0)) #endif /** * Callback prototypes */ static void on_pause_timer_activate (BonoboUIComponent *component, TimerApplet *timer_applet, const gchar *cname); static void on_continue_timer_activate (BonoboUIComponent *component, TimerApplet *timer_applet, const gchar *cname); static void on_restart_timer_activate (BonoboUIComponent *component, TimerApplet *timer_applet, const gchar *cname); static void on_stop_timer_activate (BonoboUIComponent *component, TimerApplet *timer_applet, const gchar *cname); static void on_presets_submenu_item_activate (BonoboUIComponent *uic, TimerApplet *timer_applet, const gchar *verb); static void on_manage_presets_activate (BonoboUIComponent *component, TimerApplet *timer_applet, const gchar *cname); static void on_preferences_activate (BonoboUIComponent *component, TimerApplet *timer_applet, const gchar *cname); static void on_help_activate (BonoboUIComponent *component, TimerApplet *timer_applet, const gchar *cname); static void on_about_activate (BonoboUIComponent *component, TimerApplet *timer_applet, const gchar *cname); static void on_main_button_clicked (GtkWidget *button, TimerApplet *timer_applet); static gboolean on_button_press_hack (GtkWidget *widget, GdkEventButton *event, TimerApplet *timer_applet); static void on_preferences_change (GConfClient *client, guint cnxn_id, GConfEntry *entry, TimerApplet *timer_applet); static void on_gloo_timer_state_changed (GlooTimer *timer, gpointer user_data); static void on_gloo_timer_time_changed (GlooTimer *timer, gpointer user_data); static void on_presets_changed (GObject *obj, TimerApplet *timer_applet); static void on_applet_change_background (PanelApplet *applet, PanelAppletBackgroundType type, GdkColor *color, GdkPixmap *pixmap, gpointer user_data); static void on_applet_change_orient (PanelApplet *applet, PanelAppletOrient orient, TimerApplet *timer_applet); static void on_applet_change_size (PanelApplet *applet, gint size, TimerApplet *timer_applet); static void on_applet_instance_destroy (GtkWidget *applet, TimerApplet *timer_applet); static const BonoboUIVerb timer_applet_verbs [] = { BONOBO_UI_UNSAFE_VERB ("PauseTimer", on_pause_timer_activate), BONOBO_UI_UNSAFE_VERB ("ContinueTimer", on_continue_timer_activate), BONOBO_UI_UNSAFE_VERB ("RestartTimer", on_restart_timer_activate), BONOBO_UI_UNSAFE_VERB ("StopTimer", on_stop_timer_activate), BONOBO_UI_UNSAFE_VERB ("ManagePresets", on_manage_presets_activate), BONOBO_UI_UNSAFE_VERB ("Preferences", on_preferences_activate), BONOBO_UI_UNSAFE_VERB ("Help", on_help_activate), BONOBO_UI_UNSAFE_VERB ("About", on_about_activate), BONOBO_UI_VERB_END }; static GtkWidget *manage_presets_window = NULL; static GtkWidget *about_dialog; /** * Set the timer button's tooltip to the given string */ static void set_tooltip (TimerApplet *timer_applet, gchar *tooltip_str) { gtk_tooltips_set_tip (GTK_TOOLTIPS (timer_applet->main_tooltips), timer_applet->main_button, tooltip_str, tooltip_str); } /** * A hack used in construct_applet_content to remove the gap * between the applet and the right-click context menu */ static void force_no_focus_padding (GtkWidget *widget) { gboolean first_time = TRUE;; if (first_time) { gtk_rc_parse_string ("\n" " style \"timer-applet-button-style\"\n" " {\n" " GtkWidget::focus-line-width=0\n" " GtkWidget::focus-padding=0\n" " }\n" "\n" " widget \"*.timer-applet-button\" style \"timer-applet-button-style\"\n" "\n"); first_time = FALSE; } gtk_widget_set_name (widget, "timer-applet-button"); } /** * Updates the optional remaining time label and the tooltip to * reflect the give amount of time. */ static void set_ui_time (TimerApplet *timer_applet, guint seconds_remaining) { g_assert (timer_applet != NULL); g_assert (seconds_remaining >= 0); if (timer_applet->main_remaining_time_label) { gchar *applet_time_str; applet_time_str = construct_time_str (seconds_remaining, FALSE /* don't always show hh:mm:ss */, FALSE /* don't shorten zero */); g_assert (GTK_IS_LABEL (timer_applet->main_remaining_time_label)); gtk_label_set_text (GTK_LABEL (timer_applet->main_remaining_time_label), applet_time_str); g_free (applet_time_str); applet_time_str = NULL; } { gchar *tooltip_str; gchar *tooltip_time_str; const gchar *timer_name; /* Always show hours:minutes:seconds in tooltip */ tooltip_time_str = construct_time_str (seconds_remaining, TRUE /* always show hh:mm:ss */, FALSE /* don't shorten zero */); timer_name = gloo_timer_get_name (timer_applet->gloo_timer); if (strlen (timer_name) > 0) { /* () */ tooltip_str = g_strdup_printf (_("%s (%s)"), tooltip_time_str, timer_name); } else { tooltip_str = g_strdup_printf ("%s", tooltip_time_str); } g_free (tooltip_time_str); tooltip_time_str = NULL; set_tooltip (timer_applet, tooltip_str); g_free (tooltip_str); tooltip_str = NULL; } } /** * Construct a string representing the current time. * User should free the string when done using it. */ static gchar * construct_current_time_str (void) { time_t current_time; struct tm *time_info; char time_str[256]; gchar *utf8_str; time (¤t_time); time_info = localtime (¤t_time); g_assert (time_info); { size_t num_chars; /* "hh:mm AM/PM" format string for strftime */ num_chars = strftime (time_str, sizeof (time_str), _("%I:%M %p"), time_info); g_assert (num_chars > 0); } utf8_str = g_locale_to_utf8 (time_str, -1, NULL, NULL, NULL); return utf8_str; } static void check_separator1_visibility (TimerApplet *timer_applet) { BonoboUIComponent *popup_component; gboolean show_sep; g_assert (timer_applet); g_assert (timer_applet->applet); popup_component = panel_applet_get_popup_component (PANEL_APPLET (timer_applet->applet)); /* Show the separator when we have more than zero presets (which means the Presets menu is visible) * or when the timer is in any state besides GLOO_TIMER_STATE_IDLE (which means that at least one * menu command will be visible). */ show_sep = gtk_tree_model_iter_n_children (GTK_TREE_MODEL (timer_applet->gloo_presets), NULL) > 0 || gloo_timer_get_state (timer_applet->gloo_timer) != GLOO_TIMER_STATE_IDLE; bonobo_ui_component_set_prop (popup_component, SEPARATOR1_PATH, "hidden", show_sep ? "0" : "1", NULL); } static void set_popup_menu_item_states (TimerApplet *timer_applet, gboolean show_pause, gboolean show_continue, gboolean show_restart, gboolean show_stop) { BonoboUIComponent *popup_component; g_assert (timer_applet); g_assert (timer_applet->applet); popup_component = panel_applet_get_popup_component (PANEL_APPLET (timer_applet->applet)); bonobo_ui_component_set_prop (popup_component, TA_MENU_COMMAND_PAUSE_TIMER, "hidden", show_pause ? "0" : "1", NULL); bonobo_ui_component_set_prop (popup_component, TA_MENU_COMMAND_CONTINUE_TIMER, "hidden", show_continue ? "0" : "1", NULL); bonobo_ui_component_set_prop (popup_component, TA_MENU_COMMAND_RESTART_TIMER, "hidden", show_restart ? "0" : "1", NULL); bonobo_ui_component_set_prop (popup_component, TA_MENU_COMMAND_STOP_TIMER, "hidden", show_stop ? "0" : "1", NULL); check_separator1_visibility (timer_applet); } static void play_notify_sound (TimerApplet *timer_applet) { gboolean use_custom_sound; gchar *notification_sound_path; if (!panel_applet_gconf_get_bool (timer_applet->applet, PLAY_NOTIFICATION_SOUND_KEY, NULL)) { return; } use_custom_sound = panel_applet_gconf_get_bool (timer_applet->applet, USE_CUST_NOTIFICATION_SOUND_KEY, NULL); if (use_custom_sound) { g_print ("Using custom notification sound.\n"); notification_sound_path = panel_applet_gconf_get_string (timer_applet->applet, CUST_NOTIFICATION_SOUND_PATH_KEY, NULL); g_assert (notification_sound_path); if (strlen (notification_sound_path) == 0) { g_print ("No custom notification sound specified. Using default sound.\n"); g_free (notification_sound_path); notification_sound_path = g_strdup (DEFAULT_NOTIFICATION_SOUND_PATH); } } else { g_print ("Using default notification sound.\n"); notification_sound_path = g_strdup (DEFAULT_NOTIFICATION_SOUND_PATH); } g_assert (notification_sound_path); g_print ("Playing notification sound: %s\n", notification_sound_path); gnome_sound_play (notification_sound_path); g_free (notification_sound_path); notification_sound_path = NULL; } /** * Sets the user interface to a state indicating * it's running. */ static void set_ui_to_running_state (TimerApplet *timer_applet) { g_assert (timer_applet); g_assert (timer_applet->main_button); gloo_pulse_button_stop_pulsing (GLOO_PULSE_BUTTON (timer_applet->main_button)); set_popup_menu_item_states (timer_applet, TRUE /* show pause */, FALSE /* hide continue */, TRUE /* show restart */, TRUE /* show stop */); g_assert (timer_applet->main_button_layout_box); g_object_set (G_OBJECT (timer_applet->main_button_layout_box), "sensitive", TRUE, NULL); } /** * Sets the user interface to a state that * indicates that it's paused. */ static void set_ui_to_paused_state (TimerApplet *timer_applet) { g_assert (timer_applet != NULL); g_assert (timer_applet->main_button); g_assert (timer_applet->main_button_layout_box); g_object_set (G_OBJECT (timer_applet->main_button_layout_box), "sensitive", FALSE, NULL); gloo_pulse_button_stop_pulsing (GLOO_PULSE_BUTTON (timer_applet->main_button)); set_popup_menu_item_states (timer_applet, FALSE /* hide pause */, TRUE /* show continue */, TRUE /* show restart */, TRUE /* show stop */); g_assert (timer_applet->gloo_timer); set_ui_time (timer_applet, gloo_timer_get_remaining_time (timer_applet->gloo_timer)); set_tooltip (timer_applet, _("Paused. Click to continue timer.")); } /** * Sets the user interface to a state that indicates * that it's idle (neither running nor paused). */ static void set_ui_to_idle_state (TimerApplet *timer_applet) { g_assert (timer_applet); g_assert (timer_applet->main_button); g_assert (timer_applet->main_button_layout_box); g_object_set (G_OBJECT (timer_applet->main_button_layout_box), "sensitive", FALSE, NULL); gloo_pulse_button_stop_pulsing (GLOO_PULSE_BUTTON (timer_applet->main_button)); if (timer_applet->main_remaining_time_label) { gtk_label_set_text (GTK_LABEL (timer_applet->main_remaining_time_label), ""); } set_popup_menu_item_states (timer_applet, FALSE /* hide pause */, FALSE /* hide continue */, FALSE /* hide restart */, FALSE /* hide stop */); set_tooltip (timer_applet, IDLE_TOOLTIP); } /** * Notify the user that the timer is done by displaying a dialog box * and optionally playing a sound */ static void set_ui_to_finished_state (TimerApplet *timer_applet) { gchar *cur_time_str; gchar *timer_done_str; const gchar *timer_name; g_assert (timer_applet != NULL); set_popup_menu_item_states (timer_applet, FALSE /* hide pause */, FALSE /* hide continue */, TRUE /* show restart */, TRUE /* show stop */); cur_time_str = construct_current_time_str (); timer_name = gloo_timer_get_name (timer_applet->gloo_timer); if (strlen (timer_name) > 0) { /* "" finished at .\nClick to stop timer. */ timer_done_str = g_strdup_printf (_("\"%s\" finished at %s.\nClick to stop timer."), timer_name, cur_time_str); } else { /* Timer finished at .\nClick to stop timer. */ timer_done_str = g_strdup_printf (_("Timer finished at %s.\nClick to stop timer."), cur_time_str); } g_free (cur_time_str); cur_time_str = NULL; set_tooltip (timer_applet, timer_done_str); g_free (timer_done_str); timer_done_str = NULL; play_notify_sound (timer_applet); if (timer_applet->main_remaining_time_label) { gtk_label_set_text (GTK_LABEL (timer_applet->main_remaining_time_label), _("Finished")); } /* Start pulsing button to get user's attention. */ gloo_pulse_button_start_pulsing (GLOO_PULSE_BUTTON (timer_applet->main_button)); } static void update_ui_to_current_gloo_timer_state (TimerApplet *timer_applet) { TRACE_MSG ("update_ui_to_current_gloo_timer_state"); g_assert (timer_applet); g_assert (timer_applet->gloo_timer); switch (gloo_timer_get_state (timer_applet->gloo_timer)) { case GLOO_TIMER_STATE_IDLE: set_ui_to_idle_state (timer_applet); break; case GLOO_TIMER_STATE_PAUSED: set_ui_to_paused_state (timer_applet); break; case GLOO_TIMER_STATE_FINISHED: set_ui_to_finished_state (timer_applet); break; case GLOO_TIMER_STATE_RUNNING: set_ui_to_running_state (timer_applet); break; default: g_assert_not_reached (); break; } } /** * Fills in the applet widget with a button that contains an icon and optional label. */ static void construct_applet_content (TimerApplet *timer_applet) { GtkWidget *button_layout_box; GtkTooltips *tooltips; gint panel_size; PanelAppletOrient panel_orient; TRACE_MSG ("construct_applet_content"); g_assert (timer_applet->main_button == NULL); g_assert (timer_applet->main_image == NULL); g_assert (timer_applet->main_remaining_time_label == NULL); panel_size = panel_applet_get_size (timer_applet->applet); panel_orient = panel_applet_get_orient (timer_applet->applet); if (panel_orient == PANEL_APPLET_ORIENT_LEFT || panel_orient == PANEL_APPLET_ORIENT_RIGHT || panel_size >= GNOME_Vertigo_PANEL_MEDIUM) { /* Layout the timer image and, optionally, * remaining time vertically */ button_layout_box = gtk_vbox_new (FALSE, 0); } else { /* Layout the timer image and, optionally, * remaining time horizontally */ button_layout_box = gtk_hbox_new (FALSE, 2); } timer_applet->main_button_layout_box = button_layout_box; timer_applet->main_image = gtk_image_new_from_stock (GNOME_STOCK_TIMER, GTK_ICON_SIZE_SMALL_TOOLBAR); gtk_box_pack_start (GTK_BOX (button_layout_box), timer_applet->main_image, TRUE, TRUE, 0); gtk_widget_show (timer_applet->main_image); if (panel_applet_gconf_get_bool (timer_applet->applet, SHOW_REMAINING_TIME_KEY, NULL)) { timer_applet->main_remaining_time_label = gtk_label_new (""); gtk_box_pack_start (GTK_BOX (button_layout_box), timer_applet->main_remaining_time_label, TRUE, TRUE, 0); gtk_widget_show (timer_applet->main_remaining_time_label); } timer_applet->main_button = g_object_new (GLOO_TYPE_PULSE_BUTTON, NULL); g_object_set (G_OBJECT (timer_applet->main_button), "relief", GTK_RELIEF_NONE, NULL); gtk_container_add (GTK_CONTAINER (timer_applet->main_button), button_layout_box); gtk_widget_show (button_layout_box); tooltips = gtk_tooltips_new (); gtk_tooltips_set_tip (GTK_TOOLTIPS (tooltips), timer_applet->main_button, "unknown", "unknown remaining time"); timer_applet->main_tooltips = tooltips; gtk_container_add (GTK_CONTAINER (timer_applet->applet), timer_applet->main_button); gtk_widget_show (timer_applet->main_button); /* FIXME: hack from charpicker applet - fixes gap when opening right-click menu */ force_no_focus_padding (timer_applet->main_button); g_signal_connect (G_OBJECT (timer_applet->main_button), "clicked", G_CALLBACK (on_main_button_clicked), timer_applet); /* FIXME: hack from charpicker applet - fixes to allow middle- and right-clicks on applet */ g_signal_connect (G_OBJECT (timer_applet->main_button), "button_press_event", G_CALLBACK (on_button_press_hack), timer_applet); /* Update UI to reflect current GlooTimer state. */ update_ui_to_current_gloo_timer_state (timer_applet); } /** * Destroys the current applet contents and reconstructs the applet. * This function is used when the user switches between showing or not showing * the remaining time label. */ static void update_main_button (TimerApplet *timer_applet) { TRACE_MSG ("update_main_button\n"); g_assert (timer_applet); g_assert (timer_applet->main_button); gtk_widget_destroy (timer_applet->main_button); timer_applet->main_button = NULL; timer_applet->main_image = NULL; timer_applet->main_remaining_time_label = NULL; construct_applet_content (timer_applet); } static void update_presets_submenu (TimerApplet *timer_applet) { BonoboUIComponent *popup_component; popup_component = panel_applet_get_popup_component (PANEL_APPLET (timer_applet->applet)); /* Remove existing menu items. */ int i; for (i = 0;; i++) { gchar *item_path = g_strdup_printf (PRESETS_SUBMENU_PATH "/Preset_%d", i); if (bonobo_ui_component_path_exists (popup_component, item_path, NULL)) { bonobo_ui_component_rm (popup_component, item_path, NULL); g_print ("Removed presets submenu item: %s\n", item_path); g_free (item_path); item_path = NULL; } else { g_free (item_path); item_path = NULL; break; } } { GtkTreeIter iter; gboolean valid; gint preset_index; preset_index = 0; valid = gtk_tree_model_get_iter_first (GTK_TREE_MODEL (timer_applet->gloo_presets), &iter); while (valid) { gchar *preset_name; guint preset_duration; gchar *preset_display_name; gtk_tree_model_get (GTK_TREE_MODEL (timer_applet->gloo_presets), &iter, GLOO_PRESETS_NAME_COL, &preset_name, GLOO_PRESETS_DURATION_COL, &preset_duration, -1); g_assert (preset_name != NULL); preset_display_name = construct_display_name (preset_name, preset_duration); g_free (preset_name); preset_name = NULL; gchar *verb = g_strdup_printf ("Preset_%d", preset_index); BonoboUINode *node = bonobo_ui_node_new ("menuitem"); bonobo_ui_node_set_attr (node, "name", verb); bonobo_ui_node_set_attr (node, "verb", verb); bonobo_ui_node_set_attr (node, "label", preset_display_name); g_print ("Added presets submenu item: %s\n", verb); bonobo_ui_component_set_tree (popup_component, PRESETS_SUBMENU_PATH, node, NULL); bonobo_ui_component_add_verb (popup_component, verb, (BonoboUIVerbFn)on_presets_submenu_item_activate, timer_applet); g_free (verb); verb = NULL; g_free (preset_display_name); preset_display_name = NULL; valid = gtk_tree_model_iter_next (GTK_TREE_MODEL (timer_applet->gloo_presets), &iter); preset_index++; } if (preset_index > 0) { /* If there is 1 or more presets, then show the menu. */ bonobo_ui_component_set_prop (popup_component, PRESETS_SUBMENU_PATH, "hidden", "0", NULL); } else { /* Otherwise, hide the menu. */ bonobo_ui_component_set_prop (popup_component, PRESETS_SUBMENU_PATH, "hidden", "1", NULL); } check_separator1_visibility (timer_applet); } } /** * Shows a dialog box to ask whether the user would like to * continue a paused timer. This function continues the timer or * shows the "Start Timer" dialog, depending on what button * the user clicks on. * Requires that the timer is paused. */ static void ask_user_continue_timer (TimerApplet *timer_applet) { GtkWidget *ask_continue_dialog; g_assert (gloo_timer_get_state (timer_applet->gloo_timer) == GLOO_TIMER_STATE_PAUSED); ask_continue_dialog = gtk_dialog_new_with_buttons (_("Continue Timer"), NULL, GTK_DIALOG_DESTROY_WITH_PARENT, GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL, _("_Start Over"), GTK_RESPONSE_NO, _("Co_ntinue Timer"), GTK_RESPONSE_YES, NULL); gtk_dialog_set_default_response (GTK_DIALOG (ask_continue_dialog), GTK_RESPONSE_YES); { GtkWidget *main_vbox; GtkWidget *hbox; GtkWidget *image; GtkWidget *label; gchar *remaining_time_str; const gchar *timer_name; gchar *dialog_header_text; gchar *dialog_body_text; gchar *dialog_text; main_vbox = GTK_DIALOG (ask_continue_dialog)->vbox; hbox = gtk_hbox_new (FALSE, 0); image = gtk_image_new_from_stock (GTK_STOCK_DIALOG_QUESTION, GTK_ICON_SIZE_DIALOG); remaining_time_str = construct_time_str (gloo_timer_get_remaining_time (timer_applet->gloo_timer), TRUE /* always show hh:mm:ss */, FALSE /* don't shorten zero */); timer_name = gloo_timer_get_name (timer_applet->gloo_timer); if (strlen (timer_name) > 0) { /* Continue the "" timer? */ dialog_header_text = g_strdup_printf(_("Continue the \"%s\" timer?"), timer_name); } else { dialog_header_text = g_strdup_printf(_("Continue the timer?")); } /* The timer is currently paused. Would you like to continue countdown from ? */ dialog_body_text = g_strdup_printf(_("The timer is currently paused. Would you like to continue countdown from %s?"), remaining_time_str); dialog_text = g_strdup_printf("%s\n\n%s", dialog_header_text, dialog_body_text); g_free (dialog_header_text); dialog_header_text = NULL; g_free (dialog_body_text); dialog_body_text = NULL; g_free (remaining_time_str); remaining_time_str = NULL; label = gtk_label_new (dialog_text); g_free (dialog_text); dialog_text = NULL; gtk_box_pack_start (GTK_BOX (hbox), image, FALSE, FALSE, 0); gtk_box_pack_start (GTK_BOX (hbox), label, FALSE, FALSE, 0); gtk_box_pack_start (GTK_BOX (main_vbox), hbox, FALSE, FALSE, 0); g_object_set (G_OBJECT (ask_continue_dialog), "border-width", 6, "has-separator", FALSE, "resizable", FALSE, NULL); g_object_set (G_OBJECT (main_vbox), "spacing", 12, NULL); g_object_set (G_OBJECT (hbox), "spacing", 12, "border-width", 6, NULL); g_object_set (G_OBJECT (image), "yalign", 0.0, NULL); g_object_set (G_OBJECT (label), "use-markup", TRUE, "wrap", TRUE, "yalign", 0.0, NULL); gtk_widget_show (image); gtk_widget_show (label); gtk_widget_show (hbox); } { gint dialog_result; dialog_result = gtk_dialog_run (GTK_DIALOG (ask_continue_dialog)); gtk_widget_destroy (ask_continue_dialog); ask_continue_dialog = NULL; switch (dialog_result) { case GTK_RESPONSE_YES: /* Continue countdown. */ gloo_timer_start (timer_applet->gloo_timer); break; case GTK_RESPONSE_NO: start_timer_dialog_open_with_name_and_duration (timer_applet, gloo_timer_get_name (timer_applet->gloo_timer), gloo_timer_get_duration (timer_applet->gloo_timer)); break; case GTK_RESPONSE_CANCEL: /* do nothing */ break; case GTK_RESPONSE_DELETE_EVENT: /* do nothing */ break; default: g_assert_not_reached (); } } } static void libnotify_notify_finished (TimerApplet *timer_applet) { #ifdef HAVE_LIBNOTIFY g_assert (timer_applet); if (!notify_is_initted ()) { notify_init ("TimerApplet"); } if (timer_applet->notify) { notify_notification_close (timer_applet->notify, NULL); g_object_unref (timer_applet->notify); timer_applet->notify = NULL; } { gchar *cur_time_str; const gchar *timer_name; gchar *timer_done_str; cur_time_str = construct_current_time_str (); timer_name = gloo_timer_get_name (timer_applet->gloo_timer); if (strlen (timer_name) > 0) { /* "" finished at */ timer_done_str = g_strdup_printf (_("\"%s\" finished at %s"), timer_name, cur_time_str); } else { /* Timer finished at */ timer_done_str = g_strdup_printf (_("Timer finished at %s"), cur_time_str); } g_free (cur_time_str); cur_time_str = NULL; g_assert (!timer_applet->notify); timer_applet->notify = notify_notification_new (_("Timer Finished") /* summary */, timer_done_str /* message */, NULL /* icon */, GTK_WIDGET (timer_applet->applet)); g_free (timer_done_str); timer_done_str = NULL; } notify_notification_set_urgency (timer_applet->notify, NOTIFY_URGENCY_LOW); notify_notification_show (timer_applet->notify, NULL); #endif /* HAVE_LIBNOTIFY */ } /** * Start the timer with the given preset name and duration. * preset_name can be an empty string to indicate a one-time timer. * preset_name cannot be NULL. */ void timer_applet_start_timer (TimerApplet *timer_applet, const gchar *preset_name, guint timer_duration) { g_assert (timer_applet != NULL); g_assert (preset_name != NULL); if (gloo_timer_get_state (timer_applet->gloo_timer) != GLOO_TIMER_STATE_IDLE) { gloo_timer_reset (timer_applet->gloo_timer); } gloo_timer_set_duration (timer_applet->gloo_timer, timer_duration); gloo_timer_set_name (timer_applet->gloo_timer, preset_name); gloo_timer_start (timer_applet->gloo_timer); } GlooPresets *global_gloo_presets = NULL; /** * Initialize the given timer_applet */ void timer_applet_init (TimerApplet *timer_applet, PanelApplet *panel_applet) { BonoboUIComponent *popup_component; g_assert (timer_applet); g_assert (panel_applet); timer_applet->applet = panel_applet; panel_applet_set_flags (timer_applet->applet, PANEL_APPLET_EXPAND_MINOR); timer_applet->gloo_timer = g_object_new (GLOO_TYPE_TIMER, NULL); if (global_gloo_presets == NULL) { /* Perform one-time initialization */ global_gloo_presets = g_object_new (GLOO_TYPE_PRESETS, NULL); gnome_window_icon_set_default_from_file (PIXMAPS_DIR "/timer-applet-icon.png"); g_assert (manage_presets_window == NULL); manage_presets_window = manage_presets_window_new (global_gloo_presets); g_assert (manage_presets_window); about_dialog = NULL; } timer_applet->gloo_presets = global_gloo_presets; #ifdef HAVE_LIBNOTIFY timer_applet->notify = NULL; #endif timer_applet->main_button = NULL; timer_applet->main_button_layout_box = NULL; timer_applet->main_image = NULL; timer_applet->main_remaining_time_label = NULL; timer_applet->main_tooltips = NULL; timer_applet->start_timer_dialog = NULL; timer_applet->start_timer_presets_manager_widget = NULL; { GConfClient *gconf_client; gchar *prefs_key; gconf_client = gconf_client_get_default (); /* Apply the GConf schema to this instance's GConf preferences */ panel_applet_add_preferences (timer_applet->applet, "/schemas/apps/timer-applet/prefs", NULL); prefs_key = panel_applet_get_preferences_key (timer_applet->applet); g_print ("Adding notification for preferences key: %s\n", prefs_key); timer_applet->gconf_notification_id = gconf_client_notify_add (gconf_client, prefs_key, (GConfClientNotifyFunc) on_preferences_change, timer_applet, NULL, NULL); g_free (prefs_key); prefs_key = NULL; g_object_unref (G_OBJECT (gconf_client)); } timer_applet->prefs_dialog = prefs_dialog_new (timer_applet->applet); g_assert (timer_applet->prefs_dialog); construct_applet_content (timer_applet); /* Setup right-click menu */ panel_applet_setup_menu_from_file (timer_applet->applet, NULL, TIMER_APPLET_MENU_FILE_NAME, NULL, timer_applet_verbs, timer_applet); popup_component = panel_applet_get_popup_component (PANEL_APPLET (timer_applet->applet)); set_popup_menu_item_states (timer_applet, FALSE /* hide pause */, FALSE /* hide continue */, FALSE /* hide restart */, FALSE /* hide stop */); start_timer_dialog_create (timer_applet); update_presets_submenu (timer_applet); timer_applet->presets_changed_handler_id = g_signal_connect (G_OBJECT (timer_applet->gloo_presets), "presets-changed", (GCallback)on_presets_changed, timer_applet); g_assert (timer_applet->applet); g_signal_connect (G_OBJECT (timer_applet->gloo_timer), "state-changed", G_CALLBACK (on_gloo_timer_state_changed), timer_applet); g_signal_connect (G_OBJECT (timer_applet->gloo_timer), "time-changed", G_CALLBACK (on_gloo_timer_time_changed), timer_applet); g_signal_connect (G_OBJECT (timer_applet->applet), "change-background", G_CALLBACK (on_applet_change_background), NULL); g_signal_connect (G_OBJECT (timer_applet->applet), "change-orient", G_CALLBACK (on_applet_change_orient), timer_applet); g_signal_connect (G_OBJECT (timer_applet->applet), "change-size", G_CALLBACK (on_applet_change_size), timer_applet); g_signal_connect (G_OBJECT (timer_applet->applet), "destroy", G_CALLBACK (on_applet_instance_destroy), timer_applet); gtk_widget_show (GTK_WIDGET (timer_applet->applet)); } /** * Callbacks for context menu */ static void on_pause_timer_activate (BonoboUIComponent *component, TimerApplet *timer_applet, const gchar *cname) { g_assert (timer_applet != NULL); g_assert (timer_applet->gloo_timer != NULL); gloo_timer_stop (timer_applet->gloo_timer); } static void on_continue_timer_activate (BonoboUIComponent *component, TimerApplet *timer_applet, const gchar *cname) { g_assert (timer_applet != NULL); g_assert (timer_applet->gloo_timer != NULL); gloo_timer_start (timer_applet->gloo_timer); } static void on_restart_timer_activate (BonoboUIComponent *component, TimerApplet *timer_applet, const gchar *cname) { g_assert (timer_applet); gloo_timer_reset (timer_applet->gloo_timer); gloo_timer_start (timer_applet->gloo_timer); } static void on_stop_timer_activate (BonoboUIComponent *component, TimerApplet *timer_applet, const gchar *cname) { g_assert (timer_applet); gloo_timer_reset (timer_applet->gloo_timer); } static void on_presets_submenu_item_activate (BonoboUIComponent *uic, TimerApplet *timer_applet, const gchar *verb) { int preset_index; g_assert (timer_applet); if (sscanf (verb, "Preset_%d", &preset_index) != 1) { g_print ("ERROR: Invalid preset submenu verb: %s\n", verb); return; } GtkTreeIter iter; { GtkTreePath *path = gtk_tree_path_new_from_indices (preset_index, -1); gboolean iter_set = gtk_tree_model_get_iter (GTK_TREE_MODEL (timer_applet->gloo_presets), &iter, path); g_assert (iter_set); gtk_tree_path_free (path); } { gchar *preset_name; guint preset_duration; gtk_tree_model_get (GTK_TREE_MODEL (timer_applet->gloo_presets), &iter, GLOO_PRESETS_NAME_COL, &preset_name, GLOO_PRESETS_DURATION_COL, &preset_duration, -1); if (gloo_timer_get_state (timer_applet->gloo_timer) != GLOO_TIMER_STATE_IDLE) { gloo_timer_reset (timer_applet->gloo_timer); } gloo_timer_set_duration (timer_applet->gloo_timer, preset_duration); gloo_timer_set_name (timer_applet->gloo_timer, preset_name); gloo_timer_start (timer_applet->gloo_timer); g_print ("Activated this preset: %s (index = %d)\n", preset_name, preset_index); g_free (preset_name); preset_name = NULL; } } static void on_manage_presets_activate (BonoboUIComponent *component, TimerApplet *timer_applet, const gchar *cname) { g_assert (timer_applet); manage_presets_window_open (GTK_DIALOG (manage_presets_window)); } static void on_preferences_activate (BonoboUIComponent *component, TimerApplet *timer_applet, const gchar *cname) { g_assert (timer_applet->prefs_dialog); prefs_dialog_open (GTK_DIALOG (timer_applet->prefs_dialog)); } static void on_help_activate (BonoboUIComponent *component, TimerApplet *timer_applet, const gchar *cname) { GError *err = NULL; g_assert (timer_applet); if (!gnome_help_display ("timer-applet.xml", NULL, &err)) { g_assert (err); g_print ("Could not display help for Timer Applet: %s\n", err->message); g_error_free (err); err = NULL; } } static void on_about_activate (BonoboUIComponent *component, TimerApplet *timer_applet, const gchar *cname) { g_assert (timer_applet); /* NOTE: about_dialog is automatically set to NULL * through a callback when the dialog is closed. */ about_dialog_show (&about_dialog); } /** * Other callbacks */ static void on_main_button_clicked (GtkWidget *button, TimerApplet *timer_applet) { GlooTimerState cur_state; g_assert (timer_applet != NULL); g_assert (timer_applet->gloo_timer != NULL); cur_state = gloo_timer_get_state (timer_applet->gloo_timer); if (cur_state == GLOO_TIMER_STATE_RUNNING) { /* Pause timer. */ gloo_timer_stop (timer_applet->gloo_timer); } else if (cur_state == GLOO_TIMER_STATE_PAUSED) { ask_user_continue_timer (timer_applet); } else if (cur_state == GLOO_TIMER_STATE_FINISHED) { g_assert (timer_applet->main_button); g_assert (gloo_pulse_button_is_pulsing (GLOO_PULSE_BUTTON (timer_applet->main_button))); gloo_timer_reset (timer_applet->gloo_timer); } else { start_timer_dialog_open (timer_applet); } } /** * A hack used in construct_applet_content to allow middle-clicks and right-clicks in the applet */ static gboolean on_button_press_hack (GtkWidget *widget, GdkEventButton *event, TimerApplet *timer_applet) { if (event->button == 3 || event->button == 2) { gtk_propagate_event (GTK_WIDGET (timer_applet->applet), (GdkEvent *)event); return TRUE; } return FALSE; } /** * Called when GConf notices a change caused by someone editing a preference in the * GConf configuration editor or through another Timer Applet's preferences dialog box. * This is called for each instance of Timer Applet. */ static void on_preferences_change (GConfClient *client, guint cnxn_id, GConfEntry *entry, TimerApplet *timer_applet) { TRACE_MSG ("on_preferences_change"); if (timer_applet->applet) { prefs_dialog_update (GTK_DIALOG (timer_applet->prefs_dialog)); /* If this handler is called while the applet is being removed from the panel, * prefs_dialog_update() can cause the applet's "destroy" handler to be run * before the code below gets to execute. * Specifically, the "destroy" handler is run when prefs_dialog_update() changes * the "active" property of one of its checkboxes from TRUE to FALSE or FALSE to * TRUE. The "destroy" handler, however, does NOT run if a checkbox's value is * unchanged, such as setting it to TRUE when it's already TRUE or setting it to * FALSE when it's already FALSE. * * Therefore, we check again to see if timer_applet->applet is NULL, which indicates * that the applet's "destroy" handler has already been run. */ if (timer_applet->applet) { update_main_button (timer_applet); } } } static void on_gloo_timer_state_changed (GlooTimer *timer, gpointer user_data) { TimerApplet *timer_applet; g_assert (timer != NULL); g_assert (user_data != NULL); timer_applet = (TimerApplet *)user_data; g_print ("GlooTimer state changed to: %d\n", gloo_timer_get_state (timer)); if (gloo_timer_get_state (timer) == GLOO_TIMER_STATE_FINISHED) { libnotify_notify_finished (timer_applet); } #if HAVE_LIBNOTIFY /* Changing to a non-finished state causes any open * notification to be closed. */ else if (timer_applet->notify) { notify_notification_close (timer_applet->notify, NULL); g_object_unref (timer_applet->notify); timer_applet->notify = NULL; } #endif update_ui_to_current_gloo_timer_state (timer_applet); } static void on_gloo_timer_time_changed (GlooTimer *timer, gpointer user_data) { TimerApplet *timer_applet; g_assert (timer != NULL); g_assert (user_data != NULL); timer_applet = (TimerApplet *)user_data; set_ui_time (timer_applet, gloo_timer_get_remaining_time (timer)); } static void on_presets_changed (GObject *obj, TimerApplet *timer_applet) { TRACE_MSG ("on_presets_changed"); g_assert (timer_applet); update_presets_submenu (timer_applet); } static void on_applet_change_background (PanelApplet *applet, PanelAppletBackgroundType type, GdkColor *color, GdkPixmap *pixmap, gpointer user_data) { GtkRcStyle *rc_style; GtkStyle *style; gtk_widget_set_style (GTK_WIDGET (applet), NULL); rc_style = gtk_rc_style_new (); gtk_widget_modify_style (GTK_WIDGET (applet), rc_style); gtk_rc_style_unref (rc_style); switch (type) { case PANEL_NO_BACKGROUND: break; case PANEL_COLOR_BACKGROUND: gtk_widget_modify_bg (GTK_WIDGET (applet), GTK_STATE_NORMAL, color); break; case PANEL_PIXMAP_BACKGROUND: style = gtk_style_copy (GTK_WIDGET (applet)->style); if (style->bg_pixmap[GTK_STATE_NORMAL]) { g_object_unref (style->bg_pixmap[GTK_STATE_NORMAL]); } style->bg_pixmap[GTK_STATE_NORMAL] = g_object_ref (pixmap); gtk_widget_set_style (GTK_WIDGET (applet), style); g_object_unref (style); break; } } static void on_applet_change_orient (PanelApplet *applet, PanelAppletOrient orient, TimerApplet *timer_applet) { TRACE_MSG ("on_applet_change_orient"); update_main_button (timer_applet); } static void on_applet_change_size (PanelApplet *applet, gint size, TimerApplet *timer_applet) { TRACE_MSG ("on_applet_change_size"); update_main_button (timer_applet); } static void on_applet_instance_destroy (GtkWidget *applet, TimerApplet *timer_applet) { /* When the applet gets destroyed, we set timer_applet->applet to NULL to indicate * that we want on_timer_timeout to remove itself by returning FALSE */ timer_applet->applet = NULL; /* Remove GConf notification */ { GConfClient *gconf_client; gconf_client = gconf_client_get_default (); gconf_client_notify_remove (gconf_client, timer_applet->gconf_notification_id); timer_applet->gconf_notification_id = 0; g_object_unref (G_OBJECT (gconf_client)); } g_signal_handler_disconnect (G_OBJECT (timer_applet->gloo_presets), timer_applet->presets_changed_handler_id); timer_applet->presets_changed_handler_id = 0; }