/** * notifier-impl.c * Lightweight Notifier implementation * * Copyright (c) 2005 * Jim Huang * PCMan (Hong Jen Yee) * * Licensed under the GNU GPL v2. See COPYING. */ #ifdef HAVE_CONFIG_H #include #endif #include #include #include #include #include "notifier/api.h" #include "notifier/working_area.h" /* Customizable settings */ #define NHEIGHT 80 #define MAX_SLOTS 10 #define STEPS 20 #define SPEED 50 /* after you have moved the mouse over the window, it will disappear in this many microseconds. */ #define WAIT_PERIOD 3000 #define TIMEOUT 6000 static int width, height, max_slots; static int slots[MAX_SLOTS] = {[0 ... (MAX_SLOTS - 1)] = -1 }; static int notifier_initialized = 0; static int popup_timeout = TIMEOUT; static GdkPixbuf *icon_pixbuf; static GdkRectangle working_area; struct sWin { GtkWidget *win, *context; int slot; int size; gulong handlerid; GtkWidget* parent; gulong parent_handler_id; guint timeout_id; guint ani_timer_id; }; typedef struct sWin Win; static void update_working_area() { get_desktop_working_area(&working_area); /* Adjust again according to the dimension of working area */ height = working_area.height; width = working_area.width; max_slots = MAX((height - NHEIGHT) / NHEIGHT, MAX_SLOTS); } static int get_slot(GtkWidget * win) { int i; for (i = 0; i < max_slots; i++) if (slots[i] == -1) { slots[i] = 1; return i; } return 0; } static int slow_hide_win(gpointer data) { Win *win = (Win *) data; int x_diff; int x, y; if (win->size == 0) { gtk_widget_destroy(win->win); return FALSE; } gtk_window_get_position(GTK_WINDOW(win->win), &x, &y); y += STEPS; win->size -= STEPS; x_diff = width - win->win->allocation.width; if (x_diff < 0) x_diff = 0; gtk_window_move(GTK_WINDOW(win->win), x_diff, y); return TRUE; } static int wait_win(gpointer data) { Win *win = (Win *)data; win->ani_timer_id = gtk_timeout_add(SPEED, slow_hide_win, data); win->timeout_id = 0; return FALSE; } static int mouseout_win(GtkWidget *widget, GdkEventButton *event, gpointer data) { Win *win = (Win *) data; g_signal_handler_disconnect(G_OBJECT(win->win), win->handlerid); gtk_container_set_border_width(GTK_CONTAINER(win->win), 5); if (win->timeout_id) g_source_remove(win->timeout_id); win->timeout_id = gtk_timeout_add(WAIT_PERIOD, wait_win, data); return TRUE; } static int mouseover_win(GtkWidget * widget, GdkEventButton * event, gpointer data) { Win *win = (Win *) data; g_signal_handler_disconnect(G_OBJECT(win->win), win->handlerid); win->handlerid = g_signal_connect(G_OBJECT(win->win), "leave-notify-event", G_CALLBACK(mouseout_win), data); if (win->timeout_id) { g_source_remove(win->timeout_id); win->timeout_id = 0; } return TRUE; } static int slow_show_win(gpointer data) { Win *win = (Win *) data; int x_diff; int x, y; if (win->size == NHEIGHT) { win->handlerid = g_signal_connect(G_OBJECT(win->win), "enter-notify-event", G_CALLBACK(mouseover_win), data); /* Trace animation timeout */ win->ani_timer_id = 0; win->timeout_id = gtk_timeout_add( popup_timeout, wait_win, data); return FALSE; } gtk_window_get_position(GTK_WINDOW(win->win), &x, &y); y -= STEPS; win->size += STEPS; x_diff = width - win->win->allocation.width; if (x_diff < 0) x_diff = 0; gtk_window_move(GTK_WINDOW(win->win), x_diff, y); return TRUE; } static Win* begin_animation(GtkWidget * win, GtkWidget * context) { int slot, begin; Win *w; update_working_area(); slot = get_slot(win); begin = working_area.y + height - slot * NHEIGHT; w = g_new0(Win, 1); w->win = win; w->context = context; w->slot = slot; w->size = 0; gtk_widget_realize(win); gtk_window_move( GTK_WINDOW(win), working_area.x + width - win->allocation.width, begin); gtk_widget_show_all(win); w->ani_timer_id = gtk_timeout_add(SPEED, slow_show_win, w); w->timeout_id = 0; return w; } /* * Popup widgets should be removed when their parents are already destroyed. */ static void destroy_win(GtkWidget* win, Win* w) { slots[w->slot] = -1; g_signal_handler_disconnect(w->parent, w->parent_handler_id); if (w->timeout_id) g_source_remove(w->timeout_id); if (w->ani_timer_id) g_source_remove(w->ani_timer_id); g_free(w); } /* * Return GtkWidget of the popup that the caller can get more control such as, * connecting some signal handlers to this widget. */ static GtkWidget* notify_new( const gchar *_caption_text, const gchar *body_text, GtkWidget* parent, GCallback click_cb, gpointer click_cb_data) { GtkWidget *win = gtk_window_new(GTK_WINDOW_POPUP); GtkWidget *context, *body, *frame; gchar *context_text = context_text = g_markup_escape_text(body_text, strlen(body_text)); gchar *caption_text = g_strdup(_caption_text); GtkWidget *imageNotify; GtkWidget *labelNotify; GtkWidget *labelCaption; GtkWidget *button; Win *w; body = gtk_vbox_new(FALSE, 2); context = gtk_hbox_new(FALSE, 0); //gtk_table_new(2, 2, TRUE); /* * load the content */ labelNotify = gtk_label_new(NULL); labelCaption = gtk_label_new(NULL); gtk_label_set_markup(GTK_LABEL(labelNotify), context_text); gtk_label_set_markup(GTK_LABEL(labelCaption), caption_text); if (icon_pixbuf) { imageNotify = gtk_image_new_from_pixbuf(icon_pixbuf); gtk_box_pack_start( GTK_BOX(context), imageNotify, FALSE, FALSE, 5); } gtk_box_pack_start(GTK_BOX(context), labelCaption, TRUE, TRUE, 0); gtk_container_add(GTK_CONTAINER(body), context); gtk_label_set_line_wrap(GTK_LABEL(labelNotify), TRUE); gtk_label_set_justify(GTK_LABEL(labelNotify), GTK_JUSTIFY_LEFT); gtk_misc_set_alignment(GTK_MISC(labelNotify), 0.0, 0.5); gtk_misc_set_padding(GTK_MISC(labelNotify), 25, 0); gtk_box_pack_start(GTK_BOX(body), labelNotify, TRUE, TRUE, 0); button = gtk_button_new(); gtk_container_add(GTK_CONTAINER(win), button); frame = gtk_frame_new(NULL); gtk_frame_set_shadow_type(GTK_FRAME(frame), GTK_SHADOW_IN); gtk_container_set_border_width(GTK_CONTAINER(frame),0); gtk_container_add(GTK_CONTAINER(button), frame); gtk_window_set_default_size(GTK_WINDOW(win), win->allocation.width, NHEIGHT); gtk_container_add(GTK_CONTAINER(frame), body); if (click_cb) g_signal_connect( G_OBJECT(button), "clicked", click_cb, click_cb_data); w = begin_animation(win, body); g_free(context_text); w->parent = parent; w->parent_handler_id = !parent ? 0 : g_signal_connect_swapped( G_OBJECT(parent), "destroy", G_CALLBACK(gtk_widget_destroy), win); g_signal_connect(G_OBJECT(win), "destroy", G_CALLBACK(destroy_win), w); return win; } GtkWidget* popup_notifier_notify( const gchar *caption, const gchar *text, GtkWidget* parent, GCallback click_cb, gpointer click_cb_data) { if (! notifier_initialized) return NULL; if (text && *text) return notify_new( caption, text, parent, click_cb, click_cb_data); return NULL; } void popup_notifier_set_timeout( int popup_timeout_sec ) { popup_timeout = 1000 * popup_timeout_sec; } void popup_notifier_init(GdkPixbuf *pixbuf) { /* Initialization has been done or not. */ if (notifier_initialized) return; icon_pixbuf = pixbuf; notifier_initialized = 1; }