update for beta release
[framework/uifw/e17.git] / src / modules / notification / e_mod_popup.c
1 #include "e_mod_main.h"
2
3 /* Popup function protos */
4 static Popup_Data *_notification_popup_new(E_Notification *n);
5 static Popup_Data *_notification_popup_find(unsigned int id);
6 static Popup_Data *_notification_popup_merge(E_Notification *n);
7
8 static int         _notification_popup_place(Popup_Data *popup,
9                                              int         num);
10 static void        _notification_popup_refresh(Popup_Data *popup);
11 static void        _notification_popup_del(unsigned int                 id,
12                                            E_Notification_Closed_Reason reason);
13 static void        _notification_popdown(Popup_Data                  *popup,
14                                          E_Notification_Closed_Reason reason);
15
16 #define POPUP_LIMIT 7
17 static int popups_displayed = 0;
18
19 /* Util function protos */
20 static void _notification_format_message(Popup_Data *popup);
21
22 static int next_pos = 0;
23
24 static Eina_Bool
25 _notification_timer_cb(Popup_Data *popup)
26 {
27    _notification_popup_del(e_notification_id_get(popup->notif),
28                            E_NOTIFICATION_CLOSED_EXPIRED);
29    return EINA_FALSE;
30 }
31
32 int
33 notification_popup_notify(E_Notification *n,
34                           unsigned int    replaces_id,
35                           const char     *appname __UNUSED__)
36 {
37    double timeout;
38    Popup_Data *popup = NULL;
39    char urgency;
40
41    urgency = e_notification_hint_urgency_get(n);
42
43    switch (urgency)
44      {
45       case E_NOTIFICATION_URGENCY_LOW:
46         if (!notification_cfg->show_low) return 0;
47         break;
48       case E_NOTIFICATION_URGENCY_NORMAL:
49         if (!notification_cfg->show_normal) return 0;
50         break;
51       case E_NOTIFICATION_URGENCY_CRITICAL:
52         if (!notification_cfg->show_critical) return 0;
53         break;
54       default:
55         break;
56      }
57
58    if (notification_cfg->ignore_replacement) replaces_id = 0;
59    if (replaces_id && (popup = _notification_popup_find(replaces_id)))
60      {
61         e_notification_ref(n);
62
63         if (popup->notif)
64           e_notification_unref(popup->notif);
65
66         popup->notif = n;
67         _notification_popup_refresh(popup);
68      }
69    else if (!replaces_id)
70      {
71         if ((popup = _notification_popup_merge(n)))
72           _notification_popup_refresh(popup);
73      }
74
75    if (!popup)
76      {
77         popup = _notification_popup_new(n);
78         if (!popup) return 0;
79         notification_cfg->popups = eina_list_append(notification_cfg->popups, popup);
80         edje_object_signal_emit(popup->theme, "notification,new", "notification");
81      }
82
83    if (popup->timer)
84      {
85         ecore_timer_del(popup->timer);
86         popup->timer = NULL;
87      }
88
89    timeout = e_notification_timeout_get(popup->notif);
90
91    if (timeout < 0 || notification_cfg->force_timeout)
92      timeout = notification_cfg->timeout;
93    else timeout = (double)timeout / 1000.0;
94
95    if (timeout > 0)
96      popup->timer = ecore_timer_add(timeout, (Ecore_Task_Cb)_notification_timer_cb, popup);
97
98    return 1;
99 }
100
101 void
102 notification_popup_shutdown(void)
103 {
104    Popup_Data *popup;
105
106    EINA_LIST_FREE(notification_cfg->popups, popup)
107      _notification_popdown(popup, E_NOTIFICATION_CLOSED_REQUESTED);
108 }
109
110 void
111 notification_popup_close(unsigned int id)
112 {
113    _notification_popup_del(id, E_NOTIFICATION_CLOSED_REQUESTED);
114 }
115
116 static Popup_Data *
117 _notification_popup_merge(E_Notification *n)
118 {
119    Eina_List *l, *l2;
120    Eina_List *i, *i2;
121    E_Notification_Action *a, *a2;
122    Popup_Data *popup;
123    const char *str1, *str2;
124    const char *body_old;
125    const char *body_new;
126    char *body_final;
127    size_t len;
128
129    str1 = e_notification_app_name_get(n);
130    if (!str1) return NULL;
131
132    EINA_LIST_FOREACH(notification_cfg->popups, l, popup)
133      {
134         if (!popup->notif) continue;
135         if (!(str2 = e_notification_app_name_get(popup->notif)))
136           continue;
137         if (str1 == str2) break;
138      }
139
140    if (!popup)
141      {
142         /* printf("- no poup to merge\n"); */
143         return NULL;
144      }
145
146    str1 = e_notification_summary_get(n);
147    str2 = e_notification_summary_get(popup->notif);
148
149    if (str1 && str2 && (str1 != str2))
150      {
151         /* printf("- summary doesn match, %s, %s\n", str1, str2); */
152         return NULL;
153      }
154
155    l = e_notification_actions_get(popup->notif);
156    l2 = e_notification_actions_get(n);
157    if ((!!l) + (!!l2) == 1)
158      {
159         /* printf("- actions dont match\n"); */
160         return NULL;
161      }
162    for (i = l, i2 = l2; i && i2; i = i->next, i2 = i2->next)
163      {
164         if ((!!i) + (!!i2) == 1) return NULL;
165         a = i->data, a2 = i2->data;
166         if ((!!a) + (!!a2) == 1) return NULL;
167         if (e_notification_action_id_get(a) != 
168             e_notification_action_id_get(a2)) return NULL;
169         if (e_notification_action_name_get(a) != 
170             e_notification_action_name_get(a2)) return NULL;
171      }
172
173    /* TODO  p->n is not fallback alert..*/
174    /* TODO  both allow merging */
175
176    body_old = e_notification_body_get(popup->notif);
177    body_new = e_notification_body_get(n);
178
179    len = strlen(body_old);
180    len += strlen(body_new);
181    len += 4; /* "<ps>" */
182    if (len < 65536) body_final = alloca(len + 1);
183    else body_final = malloc(len + 1);
184    snprintf(body_final, len + 1, "%s<ps>%s", body_old, body_new);
185    /* printf("set body %s\n", body_final); */
186
187    e_notification_body_set(n, body_final);
188
189    e_notification_unref(popup->notif);
190    popup->notif = n;
191    e_notification_ref(popup->notif);
192    if (len >= 65536) free(body_final);
193
194    return popup;
195 }
196
197
198 static void
199 _notification_theme_cb_deleted(Popup_Data *popup,
200                                Evas_Object *obj __UNUSED__,
201                                const char  *emission __UNUSED__,
202                                const char  *source __UNUSED__)
203 {
204    _notification_popup_refresh(popup);
205    edje_object_signal_emit(popup->theme, "notification,new", "notification");
206 }
207
208 static void
209 _notification_theme_cb_close(Popup_Data *popup,
210                              Evas_Object *obj __UNUSED__,
211                              const char  *emission __UNUSED__,
212                              const char  *source __UNUSED__)
213 {
214    _notification_popup_del(e_notification_id_get(popup->notif),
215                            E_NOTIFICATION_CLOSED_DISMISSED);
216 }
217
218 static void
219 _notification_theme_cb_find(Popup_Data *popup,
220                             Evas_Object *obj __UNUSED__,
221                             const char  *emission __UNUSED__,
222                             const char  *source __UNUSED__)
223 {
224    Eina_List *l;
225    E_Border *bd;
226
227    if (!popup->app_name) return;
228
229    EINA_LIST_FOREACH(e_border_client_list(), l, bd)
230      {
231         size_t len, test;
232
233         len = strlen(popup->app_name);
234         test = eina_strlen_bounded(bd->client.icccm.name, len + 1);
235
236         /* We can't be sure that the app_name really match the application name.
237          * Some plugin put their name instead. But this search gives some good
238          * results.
239          */
240         if (strncasecmp(bd->client.icccm.name, popup->app_name, (test < len) ? test : len))
241           continue;
242
243         e_desk_show(bd->desk);
244         e_border_show(bd);
245         e_border_raise(bd);
246         e_border_focus_set_with_pointer(bd);
247         break;
248      }
249 }
250
251 static Popup_Data *
252 _notification_popup_new(E_Notification *n)
253 {
254    E_Container *con;
255    Popup_Data *popup;
256    char buf[PATH_MAX];
257    const Eina_List *l, *screens;
258    E_Screen *scr;
259    E_Zone *zone;
260
261    if (popups_displayed > POPUP_LIMIT) return 0;
262    popup = E_NEW(Popup_Data, 1);
263    if (!popup) return NULL;
264    e_notification_ref(n);
265    popup->notif = n;
266
267    con = e_container_current_get(e_manager_current_get());
268    screens = e_xinerama_screens_get();
269    if (notification_cfg->dual_screen &&
270        ((notification_cfg->corner == CORNER_BR) ||
271        (notification_cfg->corner == CORNER_TR)))
272      l = eina_list_last(screens);
273    else
274      l = screens;
275    if (l)
276      {
277         scr = eina_list_data_get(l);
278         EINA_SAFETY_ON_NULL_GOTO(scr, error);
279         EINA_LIST_FOREACH(con->zones, l, zone)
280           if ((int)zone->num == scr->screen) break;
281         if ((int)zone->num != scr->screen) goto error;
282      }
283    else
284      zone = e_zone_current_get(con);
285    popup->zone = zone;
286
287    /* Create the popup window */
288    popup->win = e_popup_new(zone, 0, 0, 0, 0);
289    popup->e = popup->win->evas;
290
291    /* Setup the theme */
292    snprintf(buf, sizeof(buf), "%s/e-module-notification.edj",
293             notification_mod->dir);
294    popup->theme = edje_object_add(popup->e);
295
296    if (!e_theme_edje_object_set(popup->theme,
297                                 "base/theme/modules/notification",
298                                 "modules/notification/main"))
299      edje_object_file_set(popup->theme, buf, "modules/notification/main");
300
301    e_popup_edje_bg_object_set(popup->win, popup->theme);
302
303    evas_object_show(popup->theme);
304    edje_object_signal_callback_add
305      (popup->theme, "notification,deleted", "theme",
306      (Edje_Signal_Cb)_notification_theme_cb_deleted, popup);
307    edje_object_signal_callback_add
308      (popup->theme, "notification,close", "theme",
309      (Edje_Signal_Cb)_notification_theme_cb_close, popup);
310    edje_object_signal_callback_add
311      (popup->theme, "notification,find", "theme",
312      (Edje_Signal_Cb)_notification_theme_cb_find, popup);
313
314    _notification_popup_refresh(popup);
315    next_pos = _notification_popup_place(popup, next_pos);
316    e_popup_show(popup->win);
317    e_popup_layer_set(popup->win, 999);
318    popups_displayed++;
319
320    return popup;
321 error:
322    free(popup);
323    e_notification_unref(n);
324    return NULL;
325 }
326
327 static int
328 _notification_popup_place(Popup_Data *popup,
329                           int         pos)
330 {
331    int w, h, sw, sh;
332    int gap = 10;
333    int to_edge = 15;
334
335    sw = popup->zone->w;
336    sh = popup->zone->h;
337    evas_object_geometry_get(popup->theme, NULL, NULL, &w, &h);
338
339    /* XXX for now ignore placement requests */
340
341    switch (notification_cfg->corner)
342      {
343       case CORNER_TL:
344         e_popup_move(popup->win,
345                      to_edge, to_edge + pos);
346         break;
347       case CORNER_TR:
348         e_popup_move(popup->win,
349                      sw - (w + to_edge),
350                      to_edge + pos);
351         break;
352       case CORNER_BL:
353         e_popup_move(popup->win,
354                      to_edge,
355                      (sh - h) - (to_edge + pos));
356         break;
357       case CORNER_BR:
358         e_popup_move(popup->win,
359                      sw - (w + to_edge),
360                      (sh - h) - (to_edge + pos));
361         break;
362       default:
363         break;
364      }
365    return pos + h + gap;
366 }
367
368 static void
369 _notification_popups_place()
370 {
371    Popup_Data *popup;
372    Eina_List *l;
373    int pos = 0;
374
375    EINA_LIST_FOREACH(notification_cfg->popups, l, popup)
376      {
377         pos = _notification_popup_place(popup, pos);
378      }
379
380    next_pos = pos;
381 }
382
383 static void
384 _notification_popup_refresh(Popup_Data *popup)
385 {
386    const char *icon_path;
387    const char *app_icon_max;
388    void *img;
389    int w, h, width = 80, height = 80;
390
391    if (!popup) return;
392
393    popup->app_name = e_notification_app_name_get(popup->notif);
394
395    if (popup->app_icon)
396      {
397         evas_object_del(popup->app_icon);
398         popup->app_icon = NULL;
399      }
400
401    app_icon_max = edje_object_data_get(popup->theme, "app_icon_max");
402    if (app_icon_max)
403      {
404         char *endptr;
405
406         errno = 0;
407         width = strtol(app_icon_max, &endptr, 10);
408         if (errno || (width < 1) || (endptr == app_icon_max))
409           {
410              width = 80;
411              height = 80;
412           }
413         else
414           {
415              endptr++;
416              if (endptr)
417                {
418                   height = strtol(endptr, NULL, 10);
419                   if (errno || (height < 1)) height = 80;
420                }
421              else height = 80;
422           }
423      }
424
425    /* Check if the app specify an icon either by a path or by a hint */
426    img = e_notification_hint_image_data_get(popup->notif);
427    if (!img)
428      {
429         icon_path = e_notification_hint_image_path_get(popup->notif);
430         if ((!icon_path) || (!icon_path[0]))
431           icon_path = e_notification_app_icon_get(popup->notif);
432         if (icon_path)
433           {
434              if (!strncmp(icon_path, "file://", 7)) icon_path += 7;
435              if (!ecore_file_exists(icon_path))
436                {
437                   const char *new_path;
438                   unsigned int size;
439
440                   size = e_util_icon_size_normalize(width * e_scale);
441                   new_path = efreet_icon_path_find(e_config->icon_theme, 
442                                                    icon_path, size);
443                   if (new_path)
444                     icon_path = new_path;
445                   else
446                     {
447                        Evas_Object *o = e_icon_add(popup->e);
448                        if (!e_util_icon_theme_set(o, icon_path)) evas_object_del(o);
449                        else
450                          {
451                             popup->app_icon = o;
452                             w = width;
453                             h = height;
454                          }
455                     }
456                }
457
458              if (!popup->app_icon)
459                {
460                   popup->app_icon = e_icon_add(popup->e);
461                   if (!e_icon_file_set(popup->app_icon, icon_path))
462                     {
463                        evas_object_del(popup->app_icon);
464                        popup->app_icon = NULL;
465                     }
466                   else e_icon_size_get(popup->app_icon, &w, &h);
467                }
468           }
469      }
470    if ((!img) && (!popup->app_icon))
471      img = e_notification_hint_icon_data_get(popup->notif);
472    if (img)
473      {
474         popup->app_icon = e_notification_image_evas_object_add(popup->e, img);
475         evas_object_image_filled_set(popup->app_icon, EINA_TRUE);
476         evas_object_image_alpha_set(popup->app_icon, EINA_TRUE);
477         evas_object_image_size_get(popup->app_icon, &w, &h);
478      }
479
480    if (!popup->app_icon)
481      {
482         char buf[PATH_MAX];
483
484         snprintf(buf, sizeof(buf), "%s/e-module-notification.edj", 
485                  notification_mod->dir);
486         popup->app_icon = edje_object_add(popup->e);
487         if (!e_theme_edje_object_set(popup->app_icon, 
488                                      "base/theme/modules/notification",
489                                      "modules/notification/logo"))
490           edje_object_file_set(popup->app_icon, buf, 
491                                "modules/notification/logo");
492         w = width;
493         h = height;
494      }
495
496    if ((w > width) || (h > height))
497      {
498         int v;
499         v = w > h ? w : h;
500         h = h * height / v;
501         w = w * width / v;
502      }
503    edje_extern_object_min_size_set(popup->app_icon, w, h);
504    edje_extern_object_max_size_set(popup->app_icon, w, h);
505
506    edje_object_calc_force(popup->theme);
507    edje_object_part_swallow(popup->theme, "notification.swallow.app_icon", 
508                             popup->app_icon);
509    edje_object_signal_emit(popup->theme, "notification,icon", "notification");
510
511    /* Fill up the event message */
512    _notification_format_message(popup);
513
514    /* Compute the new size of the popup */
515    edje_object_calc_force(popup->theme);
516    edje_object_size_min_calc(popup->theme, &w, &h);
517    e_popup_resize(popup->win, w, h);
518    evas_object_resize(popup->theme, w, h);
519
520    _notification_popups_place();
521 }
522
523 static Popup_Data *
524 _notification_popup_find(unsigned int id)
525 {
526    Eina_List *l;
527    Popup_Data *popup;
528
529    if (!id) return NULL;
530    EINA_LIST_FOREACH(notification_cfg->popups, l, popup)
531      {
532         if (e_notification_id_get(popup->notif) == id) return popup;
533      }
534    return NULL;
535 }
536
537 static void
538 _notification_popup_del(unsigned int                 id,
539                         E_Notification_Closed_Reason reason)
540 {
541    Popup_Data *popup;
542    Eina_List *l, *l2;
543    int pos = 0;
544
545    EINA_LIST_FOREACH_SAFE(notification_cfg->popups, l, l2, popup)
546      {
547         if (e_notification_id_get(popup->notif) == id)
548           {
549              _notification_popdown(popup, reason);
550              notification_cfg->popups = eina_list_remove_list(notification_cfg->popups, l);
551           }
552         else
553           pos = _notification_popup_place(popup, pos);
554      }
555
556    next_pos = pos;
557 }
558
559 static void
560 _notification_popdown(Popup_Data                  *popup,
561                       E_Notification_Closed_Reason reason)
562 {
563    if (popup->timer) ecore_timer_del(popup->timer);
564    e_popup_hide(popup->win);
565    popups_displayed--;
566    evas_object_del(popup->app_icon);
567    evas_object_del(popup->theme);
568    e_object_del(E_OBJECT(popup->win));
569    e_notification_closed_set(popup->notif, 1);
570    e_notification_daemon_signal_notification_closed
571      (notification_cfg->daemon, e_notification_id_get(popup->notif), reason);
572    e_notification_unref(popup->notif);
573    free(popup);
574 }
575
576 static void
577 _notification_format_message(Popup_Data *popup)
578 {
579    Evas_Object *o = popup->theme;
580    const char *title = e_notification_summary_get(popup->notif);
581    const char *b = e_notification_body_get(popup->notif);
582    edje_object_part_text_set(o, "notification.textblock.message", b);
583    edje_object_part_text_set(o, "notification.text.title", title);
584 }