1 /**
2  * notifier-impl.c
3  * Lightweight Notifier implementation
4  *
5  * Copyright (c) 2005
6  *	Jim Huang <jserv.tw@gmail.com>
7  *	PCMan (Hong Jen Yee) <pcman.tw@gmail.com>
8  *
9  * Licensed under the GNU GPL v2.  See COPYING.
10  */
11 
12 #ifdef HAVE_CONFIG_H
13 #include <config.h>
14 #endif
15 
16 #include <gtk/gtk.h>
17 #include <stdio.h>
18 #include <fcntl.h>
19 
20 #include <string.h>
21 
22 #include "notifier/api.h"
23 #include "notifier/working_area.h"
24 
25 /* Customizable settings */
26 #define NHEIGHT 80
27 #define MAX_SLOTS 10
28 #define STEPS 20
29 #define SPEED 50
30 
31 /* after you have moved the mouse over the window, it will disappear
32    in this many microseconds. */
33 #define WAIT_PERIOD 3000
34 #define TIMEOUT     6000
35 
36 static int width, height, max_slots;
37 static int slots[MAX_SLOTS] = {-1, -1, -1, -1, -1, -1, -1, -1, -1, -1};
38 static int notifier_initialized = 0;
39 static int popup_timeout = TIMEOUT;
40 static GdkPixbuf *icon_pixbuf;
41 static GdkRectangle working_area;
42 
43 struct sWin {
44 	GtkWidget *win, *context;
45 	int slot;
46 	int size;
47 	gulong handlerid;
48 	GtkWidget* parent;
49 	gulong parent_handler_id;
50 	guint timeout_id;
51 	guint ani_timer_id;
52 };
53 
54 typedef struct sWin Win;
55 
update_working_area()56 static void update_working_area()
57 {
58 	get_desktop_working_area(&working_area);
59 
60 	/* Adjust again according to the dimension of working area */
61 	height = working_area.height;
62 	width = working_area.width;
63 	max_slots = MAX((height - NHEIGHT) / NHEIGHT, MAX_SLOTS);
64 }
65 
get_slot(GtkWidget * win)66 static int get_slot(GtkWidget * win)
67 {
68 	int i;
69 	for (i = 0; i < max_slots; i++)
70 		if (slots[i] == -1) {
71 			slots[i] = 1;
72 			return i;
73 		}
74 	return 0;
75 }
76 
slow_hide_win(gpointer data)77 static int slow_hide_win(gpointer data)
78 {
79 	Win *win = (Win *) data;
80 	int x_diff;
81 	int x, y;
82 
83 	if (win->size == 0) {
84 		gtk_widget_destroy(win->win);
85 		return FALSE;
86 	}
87 
88 	gtk_window_get_position(GTK_WINDOW(win->win), &x, &y);
89 	y += STEPS;
90 	win->size -= STEPS;
91 	x_diff = width - win->win->allocation.width;
92 	if (x_diff < 0)
93 		x_diff = 0;
94 	gtk_window_move(GTK_WINDOW(win->win), x_diff, y);
95 	return TRUE;
96 }
97 
wait_win(gpointer data)98 static int wait_win(gpointer data)
99 {
100 	Win *win = (Win *)data;
101 	win->ani_timer_id = gtk_timeout_add(SPEED, slow_hide_win, data);
102 	win->timeout_id = 0;
103 	return FALSE;
104 }
105 
mouseout_win(GtkWidget * widget,GdkEventButton * event,gpointer data)106 static int mouseout_win(GtkWidget *widget,
107 			GdkEventButton *event, gpointer data)
108 {
109 	Win *win = (Win *) data;
110 	g_signal_handler_disconnect(G_OBJECT(win->win), win->handlerid);
111 	gtk_container_set_border_width(GTK_CONTAINER(win->win), 5);
112 	if (win->timeout_id)
113 		g_source_remove(win->timeout_id);
114 	win->timeout_id = gtk_timeout_add(WAIT_PERIOD, wait_win, data);
115 	return TRUE;
116 }
117 
mouseover_win(GtkWidget * widget,GdkEventButton * event,gpointer data)118 static int mouseover_win(GtkWidget * widget,
119 			 GdkEventButton * event, gpointer data)
120 {
121 	Win *win = (Win *) data;
122 	g_signal_handler_disconnect(G_OBJECT(win->win), win->handlerid);
123 	win->handlerid =
124 		g_signal_connect(G_OBJECT(win->win),
125 				 "leave-notify-event",
126 				 G_CALLBACK(mouseout_win), data);
127 	if (win->timeout_id) {
128 		g_source_remove(win->timeout_id);
129 		win->timeout_id = 0;
130 	}
131 	return TRUE;
132 }
133 
slow_show_win(gpointer data)134 static int slow_show_win(gpointer data)
135 {
136 	Win *win = (Win *) data;
137 	int x_diff;
138 	int x, y;
139 
140 	if (win->size == NHEIGHT) {
141 		win->handlerid =
142 		    g_signal_connect(G_OBJECT(win->win),
143 				     "enter-notify-event",
144 				     G_CALLBACK(mouseover_win), data);
145 
146 		/* Trace animation timeout */
147 		win->ani_timer_id = 0;
148 		win->timeout_id = gtk_timeout_add(
149 			popup_timeout, wait_win, data);
150 		return FALSE;
151 	}
152 
153 	gtk_window_get_position(GTK_WINDOW(win->win), &x, &y);
154 	y -= STEPS;
155 	win->size += STEPS;
156 	x_diff = width - win->win->allocation.width;
157 	if (x_diff < 0)
158 		x_diff = 0;
159 	gtk_window_move(GTK_WINDOW(win->win), x_diff, y);
160 	return TRUE;
161 }
162 
begin_animation(GtkWidget * win,GtkWidget * context)163 static Win* begin_animation(GtkWidget * win, GtkWidget * context)
164 {
165 	int slot, begin;
166 	Win *w;
167 
168 	update_working_area();
169 
170 	slot = get_slot(win);
171 	begin = working_area.y + height - slot * NHEIGHT;
172 	w = g_new0(Win, 1);
173 
174 	w->win = win;
175 	w->context = context;
176 	w->slot = slot;
177 	w->size = 0;
178 
179 	gtk_widget_realize(win);
180 	gtk_window_move(
181 		GTK_WINDOW(win),
182 		working_area.x + width - win->allocation.width, begin);
183 	gtk_widget_show_all(win);
184 
185 	w->ani_timer_id = gtk_timeout_add(SPEED, slow_show_win, w);
186 	w->timeout_id = 0;
187 
188 	return w;
189 }
190 
191 /*
192  * Popup widgets should be removed when their parents are already destroyed.
193  */
destroy_win(GtkWidget * win,Win * w)194 static void destroy_win(GtkWidget* win, Win* w)
195 {
196 	slots[w->slot] = -1;
197 
198 	g_signal_handler_disconnect(w->parent, w->parent_handler_id);
199 	if (w->timeout_id)
200 		g_source_remove(w->timeout_id);
201 	if (w->ani_timer_id)
202 		g_source_remove(w->ani_timer_id);
203 
204 	g_free(w);
205 }
206 
207 
208 
209 /*
210  * Return GtkWidget of the popup that the caller can get more control such as,
211  * connecting some signal handlers to this widget.
212  */
notify_new(const gchar * _caption_text,const gchar * body_text,GtkWidget * parent,GCallback click_cb,gpointer click_cb_data)213 static GtkWidget* notify_new(
214 		const gchar *_caption_text,
215 		const gchar *body_text,
216 		GtkWidget* parent, GCallback click_cb,
217 		gpointer click_cb_data)
218 {
219 	GtkWidget *win = gtk_window_new(GTK_WINDOW_POPUP);
220 	GtkWidget *context, *body, *frame;
221 	gchar *context_text = context_text = g_markup_escape_text(body_text, strlen(body_text));
222 	gchar *caption_text = g_strdup(_caption_text);
223 	GtkWidget *imageNotify;
224 	GtkWidget *labelNotify;
225 	GtkWidget *labelCaption;
226 	GtkWidget *button;
227 	Win *w;
228 
229 	body = gtk_vbox_new(FALSE, 2);
230 	context = gtk_hbox_new(FALSE, 0);
231 		//gtk_table_new(2, 2, TRUE);
232 
233 	/*
234 	 * load the content
235 	 */
236 	labelNotify = gtk_label_new(NULL);
237 	labelCaption = gtk_label_new(NULL);
238 	gtk_label_set_markup(GTK_LABEL(labelNotify), context_text);
239 	gtk_label_set_markup(GTK_LABEL(labelCaption), caption_text);
240 
241 	if (icon_pixbuf) {
242 		imageNotify = gtk_image_new_from_pixbuf(icon_pixbuf);
243 		gtk_box_pack_start(
244 				GTK_BOX(context),
245 				imageNotify,
246 				FALSE,
247 				FALSE,
248 				5);
249 	}
250 
251 	gtk_box_pack_start(GTK_BOX(context), labelCaption, TRUE, TRUE, 0);
252 	gtk_container_add(GTK_CONTAINER(body), context);
253 	gtk_label_set_line_wrap(GTK_LABEL(labelNotify), TRUE);
254 	gtk_label_set_justify(GTK_LABEL(labelNotify), GTK_JUSTIFY_LEFT);
255 	gtk_misc_set_alignment(GTK_MISC(labelNotify), 0.0, 0.5);
256 	gtk_misc_set_padding(GTK_MISC(labelNotify), 25, 0);
257 	gtk_box_pack_start(GTK_BOX(body), labelNotify, TRUE, TRUE, 0);
258 
259 	button = gtk_button_new();
260 	gtk_container_add(GTK_CONTAINER(win), button);
261 
262 	frame = gtk_frame_new(NULL);
263 	gtk_frame_set_shadow_type(GTK_FRAME(frame), GTK_SHADOW_IN);
264 	gtk_container_set_border_width(GTK_CONTAINER(frame),0);
265 	gtk_container_add(GTK_CONTAINER(button), frame);
266 
267 	gtk_window_set_default_size(GTK_WINDOW(win), win->allocation.width, NHEIGHT);
268 	gtk_container_add(GTK_CONTAINER(frame), body);
269 
270 	if (click_cb)
271 		g_signal_connect(
272 			G_OBJECT(button), "clicked",
273 			click_cb, click_cb_data);
274 
275 	w = begin_animation(win, body);
276 	g_free(context_text);
277 
278 	w->parent = parent;
279 	w->parent_handler_id = !parent ?
280 		0 :
281 		g_signal_connect_swapped(
282 			G_OBJECT(parent), "destroy",
283 			G_CALLBACK(gtk_widget_destroy),
284 			win);
285 	g_signal_connect(G_OBJECT(win), "destroy", G_CALLBACK(destroy_win), w);
286 
287 	return win;
288 }
289 
popup_notifier_notify(const gchar * caption,const gchar * text,GtkWidget * parent,GCallback click_cb,gpointer click_cb_data)290 GtkWidget* popup_notifier_notify(
291 		const gchar *caption,
292 		const gchar *text,
293 		GtkWidget* parent, GCallback click_cb,
294 		gpointer click_cb_data)
295 {
296 	if (! notifier_initialized)
297 		return NULL;
298 
299 	if (text && *text)
300 		return notify_new(
301 			caption, text,
302 			parent,
303 			click_cb, click_cb_data);
304 	return NULL;
305 }
306 
popup_notifier_set_timeout(int popup_timeout_sec)307 void popup_notifier_set_timeout( int popup_timeout_sec )
308 {
309 	popup_timeout = 1000 * popup_timeout_sec;
310 }
311 
popup_notifier_init(GdkPixbuf * pixbuf)312 void popup_notifier_init(GdkPixbuf *pixbuf)
313 {
314 	/* Initialization has been done or not. */
315 	if (notifier_initialized)
316 		return;
317 
318 	icon_pixbuf = pixbuf;
319 	notifier_initialized = 1;
320 }
321