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