Tizen 2.1 base
[platform/core/uifw/ise-engine-sunpinyin.git] / wrapper / xim / skin.c
1 /*
2  * Copyright (c) 2010 Mike Qin <mikeandmore@gmail.com>
3  *
4  * The contents of this file are subject to the terms of either the GNU Lesser
5  * General Public License Version 2.1 only ("LGPL") or the Common Development and
6  * Distribution License ("CDDL")(collectively, the "License"). You may not use this
7  * file except in compliance with the License. You can obtain a copy of the CDDL at
8  * http://www.opensource.org/licenses/cddl1.php and a copy of the LGPLv2.1 at
9  * http://www.opensource.org/licenses/lgpl-license.php. See the License for the
10  * specific language governing permissions and limitations under the License. When
11  * distributing the software, include this License Header Notice in each file and
12  * include the full text of the License in the License file as well as the
13  * following notice:
14  *
15  * NOTICE PURSUANT TO SECTION 9 OF THE COMMON DEVELOPMENT AND DISTRIBUTION LICENSE
16  * (CDDL)
17  * For Covered Software in this distribution, this License shall be governed by the
18  * laws of the State of California (excluding conflict-of-law provisions).
19  * Any litigation relating to this License shall be subject to the jurisdiction of
20  * the Federal Courts of the Northern District of California and the state courts
21  * of the State of California, with venue lying in Santa Clara County, California.
22  *
23  * Contributor(s):
24  *
25  * If you wish your version of this file to be governed by only the CDDL or only
26  * the LGPL Version 2.1, indicate your decision by adding "[Contributor]" elects to
27  * include this software in this distribution under the [CDDL or LGPL Version 2.1]
28  * license." If you don't indicate a single choice of license, a recipient has the
29  * option to distribute your version of this file under either the CDDL or the LGPL
30  * Version 2.1, or to extend the choice of license to its licensees as provided
31  * above. However, if you add LGPL Version 2.1 code and therefore, elected the LGPL
32  * Version 2 license, then the option applies only if the new code is made subject
33  * to such option by the copyright holder.
34  */
35
36 #include <stdlib.h>
37 #include <string.h>
38 #include "skin.h"
39
40 struct _skin_window_priv_t
41 {
42     skin_label_t* labels;
43     skin_button_t* btns;
44
45     /* current expose environment */
46     skin_button_t* highlight_btn;
47     skin_button_t* pressdown_btn;
48
49     GCallback      press_cb;
50     GCallback      release_cb;
51     GCallback      motion_cb;
52
53     void*          press_cb_data;
54     void*          release_cb_data;
55     void*          motion_cb_data;
56
57     /* drag to move positions */
58     gboolean       enable_drag_to_move;
59     int            drag_init_x;
60     int            drag_init_y;
61     gboolean       drag_begin;
62 };
63
64 struct _skin_button_priv_t
65 {
66     skin_button_t* next;
67     skin_window_t* parent;
68
69     GCallback press_cb;
70     GCallback release_cb;
71
72     void*     press_cb_data;
73     void*     release_cb_data;
74 };
75
76 struct _skin_label_priv_t
77 {
78     skin_label_t*  next;
79     skin_window_t* wind;
80 };
81
82 static void
83 zoom_and_composite(GdkPixbuf* src,
84                    GdkPixbuf* dst,
85                    int        x,
86                    int        y,
87                    int        width,
88                    int        height,
89                    int        dst_x,
90                    int        dst_y,
91                    int        dst_width,
92                    int        dst_height)
93 {
94     if (width <= 0 || height <= 0 || dst_width <= 0 || dst_height <= 0)
95         return;
96     GdkPixbuf* sub = gdk_pixbuf_new_subpixbuf(src, x, y, width, height);
97     double x_scale = 1.0 * dst_width / width;
98     double y_scale = 1.0 * dst_height / height;
99
100     gdk_pixbuf_scale(sub, dst, dst_x, dst_y, dst_width, dst_height,
101                      dst_x, dst_y, x_scale, y_scale, GDK_INTERP_BILINEAR);
102     g_object_unref(sub);
103 }
104
105 static void
106 cairo_paint_pixbuf(cairo_t*   cr,
107                    GdkPixbuf* buf,
108                    int        off_x,
109                    int        off_y)
110 {
111     gdk_cairo_set_source_pixbuf(cr, buf, off_x, off_y);
112     cairo_paint(cr);
113 }
114
115 static void
116 paint_background_with_mask(GtkWidget*      wid,
117                            skin_window_t*  wind)
118 {
119     GdkPixbuf* bg = wind->background_image;
120     int width, height;
121     int bg_width, bg_height;
122     int top, left, bottom, right;
123
124     width = wid->allocation.width;
125     height = wid->allocation.height;
126     bg_width = gdk_pixbuf_get_width(bg);
127     bg_height = gdk_pixbuf_get_height(bg);
128
129     top = wind->margin_top;
130     left = wind->margin_left;
131     right = wind->margin_right;
132     bottom = wind->margin_bottom;
133
134     GdkPixbuf* newbg = gdk_pixbuf_new(GDK_COLORSPACE_RGB, TRUE, 8,
135                                        width, height);
136
137     // 4 corners
138     gdk_pixbuf_copy_area(bg, 0, 0, left, top, newbg, 0, 0);
139     gdk_pixbuf_copy_area(bg, bg_width - right, 0, right, top,
140                          newbg, width - right, 0);
141     gdk_pixbuf_copy_area(bg, 0, bg_height - bottom, left, bottom,
142                          newbg, 0, height - bottom);
143     gdk_pixbuf_copy_area(bg, bg_width - right, bg_height - bottom,
144                          right, bottom,
145                          newbg, width - right, height - bottom);
146
147     // 4 margins and the center area
148     int content_width = bg_width - left - right;
149     int content_height = bg_height - top - bottom;
150     int new_content_width = width - left - right;
151     int new_content_height = height - top - bottom;
152
153     // top
154     zoom_and_composite(bg, newbg, left, 0, content_width, top,
155                        left, 0, new_content_width, top);
156     // bottom
157     zoom_and_composite(bg, newbg,
158                        left, bg_height - bottom, content_width, bottom,
159                        left, height - bottom, new_content_width, bottom);
160     // left
161     zoom_and_composite(bg, newbg,
162                        0, top, left, content_height,
163                        0, top, left, new_content_height);
164     // right
165     zoom_and_composite(bg, newbg,
166                        bg_width - right, top, right, content_height,
167                        width - right, top, right, new_content_height);
168     // center
169     zoom_and_composite(bg, newbg,
170                        left, top, content_width, content_height,
171                        left, top, new_content_width, new_content_height);
172
173     gdk_window_clear(wid->window);
174     // paint it
175     cairo_t* cr = gdk_cairo_create(wid->window);
176     cairo_set_operator(cr, CAIRO_OPERATOR_SOURCE);
177     cairo_set_source_rgba(cr, 0.0, 0.0, 0.0, 0.0);
178     cairo_paint(cr);
179     cairo_paint_pixbuf(cr, newbg, 0, 0);
180     cairo_destroy(cr);
181     // set the mask
182     GdkBitmap* mask;
183     gdk_pixbuf_render_pixmap_and_mask(newbg, NULL, &mask,
184                                       wind->alpha_mask_threshold);
185     gdk_window_shape_combine_mask(wid->window, mask, 0, 0);
186
187     g_object_unref(newbg);
188 }
189
190 static void
191 paint_buttons(GtkWidget*     wid,
192               skin_window_t* wind)
193 {
194     cairo_t* cr = gdk_cairo_create(wid->window);
195     skin_button_t* btn = wind->priv->btns;
196     for (; btn != NULL; btn = btn->priv->next) {
197         if (btn == wind->priv->pressdown_btn) {
198             cairo_paint_pixbuf(cr, btn->pressdown_image, btn->x, btn->y);
199         } else if (btn == wind->priv->highlight_btn) {
200             cairo_paint_pixbuf(cr, btn->highlight_image, btn->x, btn->y);
201         } else {
202             cairo_paint_pixbuf(cr, btn->normal_image, btn->x, btn->y);
203         }
204     }
205     cairo_destroy(cr);
206 }
207
208 static void
209 paint_labels(GtkWidget*     wid,
210              skin_window_t* wind)
211 {
212     cairo_t* cr = gdk_cairo_create(wid->window);
213     skin_label_t* label = wind->priv->labels;
214     for (; label != NULL; label = label->priv->next) {
215         cairo_set_source_rgba(cr, label->color_r, label->color_g,
216                               label->color_b, label->color_a);
217         if (label->layout == NULL) {
218             label->layout = pango_cairo_create_layout(cr);
219             pango_layout_set_font_description(
220                 label->layout, pango_font_description_from_string(label->font));
221         }
222         pango_layout_set_text(label->layout, label->text, -1);
223         cairo_move_to(cr, label->x, label->y);
224         pango_cairo_layout_path(cr, label->layout);
225         cairo_fill(cr);
226     }
227     cairo_destroy(cr);
228 }
229
230 static gboolean
231 window_on_expose(GtkWidget*      wid,
232                  GdkEventExpose* evt,
233                  skin_window_t*  wind)
234 {
235     paint_background_with_mask(wid, wind);
236     paint_labels(wid, wind);
237     paint_buttons(wid, wind);
238     return TRUE;
239 }
240
241 static gboolean
242 window_on_configure(GtkWidget*         wid,
243                     GdkEventConfigure* evt,
244                     skin_window_t*     wind)
245 {
246     gtk_widget_queue_draw(wid);
247     return FALSE;
248 }
249
250 static skin_button_t*
251 find_button(skin_window_t* wind, int pos_x, int pos_y)
252 {
253     skin_button_t* btn = wind->priv->btns;
254     for (; btn != NULL; btn = btn->priv->next) {
255         int x = btn->x, y = btn->y, width = btn->width, height = btn->height;
256         if (pos_x > x && pos_y > y && pos_x < x + width && pos_y < y + height) {
257             return btn;
258         }
259     }
260     return NULL;
261 }
262
263 static gboolean
264 set_expose_env(skin_window_t* wind,
265                skin_button_t* highlight,
266                skin_button_t* down)
267 {
268     gboolean ret = FALSE;
269     if (wind->priv->highlight_btn != highlight) {
270         ret |= TRUE;
271         wind->priv->highlight_btn = highlight;
272     }
273     if (wind->priv->pressdown_btn != down) {
274         ret |= TRUE;
275         wind->priv->pressdown_btn = down;
276     }
277     return ret;
278 }
279
280 typedef gboolean (*motion_cb_t) (GtkWidget*, GdkEventMotion*, void*);
281 typedef gboolean (*mouse_cb_t) (GtkWidget*, GdkEventButton*, void*);
282
283 static gboolean
284 window_on_motion(GtkWidget*      wid,
285                  GdkEventMotion* evt,
286                  skin_window_t*  wind)
287 {
288     skin_button_t* btn = find_button(wind, evt->x, evt->y);
289     gboolean need_redraw = FALSE;
290     if (btn) {
291         if ((evt->state & GDK_BUTTON1_MASK) != 0
292             || (evt->state & GDK_BUTTON2_MASK) != 0
293             || (evt->state & GDK_BUTTON3_MASK) != 0) {
294             need_redraw = set_expose_env(wind, NULL, btn);
295         } else {
296             need_redraw = set_expose_env(wind, btn, NULL);
297         }
298     } else {
299         /* move it if drag */
300         if (wind->priv->enable_drag_to_move && wind->priv->drag_begin) {
301             gtk_window_move(GTK_WINDOW(wind->widget),
302                             evt->x_root - wind->priv->drag_init_x,
303                             evt->y_root - wind->priv->drag_init_y);
304         }
305         need_redraw = set_expose_env(wind, NULL, NULL);
306         motion_cb_t cb = (motion_cb_t) wind->priv->motion_cb;
307         if (cb) {
308             cb(wid, evt, wind->priv->motion_cb_data);
309         }
310     }
311     if (need_redraw) {
312         gtk_widget_queue_draw(wid);
313     }
314     return TRUE;
315 }
316
317 static gboolean
318 window_on_press_or_release(GtkWidget*      wid,
319                            GdkEventButton* evt,
320                            skin_window_t*  wind,
321                            gboolean        press)
322 {
323     skin_button_t* btn = find_button(wind, evt->x, evt->y);
324     gboolean need_redraw = FALSE;
325     skin_button_t* highlight_btn = NULL;
326     skin_button_t* pressdown_btn = NULL;
327     mouse_cb_t btncb = NULL;
328     mouse_cb_t wincb = NULL;
329
330     if (press) {
331         wincb = (mouse_cb_t) wind->priv->press_cb;
332         pressdown_btn = btn;
333         if (!btn) {
334             /* update drag init positions */
335             wind->priv->drag_init_x = evt->x;
336             wind->priv->drag_init_y = evt->y;
337             wind->priv->drag_begin = wind->priv->enable_drag_to_move;
338         } else {
339             btncb = (mouse_cb_t) btn->priv->press_cb;
340         }
341     } else {
342         wincb = (mouse_cb_t) wind->priv->release_cb;
343         highlight_btn = btn;
344         if (!btn) {
345             wind->priv->drag_begin = FALSE;
346         } else {
347             btncb = (mouse_cb_t) btn->priv->release_cb;
348         }
349     }
350
351     if (btn) {
352         need_redraw = set_expose_env(wind, highlight_btn, pressdown_btn);
353         if (btncb) {
354             btncb(wid, evt, btn->priv->press_cb_data);
355         }
356     } else {
357         if (wincb) {
358             wincb(wid, evt, wind->priv->press_cb_data);
359         }
360     }
361     if (need_redraw) {
362         gtk_widget_queue_draw(wid);
363     }
364     return TRUE;
365 }
366
367 static gboolean
368 window_on_press(GtkWidget*      wid,
369                 GdkEventButton* evt,
370                 skin_window_t*  wind)
371 {
372     return window_on_press_or_release(wid, evt, wind, TRUE);
373 }
374
375 static gboolean
376 window_on_release(GtkWidget* wid,
377                   GdkEventButton* evt,
378                   skin_window_t* wind)
379 {
380     return window_on_press_or_release(wid, evt, wind, FALSE);
381 }
382
383 skin_window_t*
384 skin_window_new(GtkWindow* widget,
385                 GdkPixbuf* background_image,
386                 int        margin_top,
387                 int        margin_left,
388                 int        margin_bottom,
389                 int        margin_right,
390                 int        alpha_mask_threshold)
391 {
392     skin_window_t* wind = malloc(sizeof(skin_window_t));
393     wind->widget = GTK_WIDGET(widget);
394     wind->background_image = background_image;
395     wind->margin_top = margin_top;
396     wind->margin_left = margin_left;
397     wind->margin_bottom = margin_bottom;
398     wind->margin_right = margin_right;
399     wind->alpha_mask_threshold = alpha_mask_threshold;
400     wind->priv = malloc(sizeof(skin_window_priv_t));
401     memset(wind->priv, 0, sizeof(skin_window_priv_t));
402     gtk_window_set_default_size(widget,
403                                 gdk_pixbuf_get_width(background_image),
404                                 gdk_pixbuf_get_height(background_image));
405
406     /* set rgba */
407     GdkScreen* screen = gdk_screen_get_default();
408     GdkColormap* cmap = gdk_screen_get_rgba_colormap(screen);
409     if (cmap) {
410         gtk_widget_set_colormap(wind->widget, cmap);
411     } else {
412         fprintf(stderr, "Cannot set rgba colormap!\n");
413     }
414
415     /* signal expose */
416     gtk_widget_set_events(wind->widget,
417                           GDK_EXPOSURE_MASK | GDK_POINTER_MOTION_MASK
418                           | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK);
419     gtk_widget_realize(wind->widget);
420
421     g_signal_connect(wind->widget, "expose-event",
422                      G_CALLBACK(window_on_expose), wind);
423     g_signal_connect(wind->widget, "configure-event",
424                      G_CALLBACK(window_on_configure), wind);
425     g_signal_connect(wind->widget, "motion-notify-event",
426                      G_CALLBACK(window_on_motion), wind);
427     g_signal_connect(wind->widget, "button-press-event",
428                      G_CALLBACK(window_on_press), wind);
429     g_signal_connect(wind->widget, "button-release-event",
430                      G_CALLBACK(window_on_release), wind);
431     return wind;
432 }
433
434 void
435 skin_window_destroy(skin_window_t* wind)
436 {
437     gtk_widget_destroy(wind->widget);
438     free(wind->priv);
439     free(wind);
440 }
441
442 void
443 skin_window_add_button(skin_window_t* wind,
444                        skin_button_t* btn,
445                        int            x,
446                        int            y)
447 {
448     /* append to the buttons list */
449     btn->priv->next = wind->priv->btns;
450     wind->priv->btns = btn;
451
452     btn->priv->parent = wind;
453     btn->x = x;
454     btn->y = y;
455 }
456
457 void
458 skin_window_set_drag_to_move(skin_window_t* wind,
459                              gboolean       drag_to_move)
460 {
461     wind->priv->enable_drag_to_move = drag_to_move;
462 }
463
464 skin_button_t*
465 skin_button_new(GdkPixbuf* normal_image,
466                 GdkPixbuf* highlight_image,
467                 GdkPixbuf* pressdown_image)
468 {
469     skin_button_t* btn = malloc(sizeof(skin_button_t));
470     btn->normal_image = normal_image;
471     btn->highlight_image = highlight_image;
472     btn->pressdown_image = pressdown_image;
473     btn->width = gdk_pixbuf_get_width(normal_image);
474     btn->height = gdk_pixbuf_get_height(normal_image);
475     btn->priv = malloc(sizeof(skin_button_priv_t));
476     memset(btn->priv, 0, sizeof(skin_button_priv_t));
477     return btn;
478 }
479
480 void
481 skin_button_destroy(skin_button_t* btn)
482 {
483     free(btn->priv);
484     free(btn);
485 }
486
487 void
488 skin_button_set_image(skin_button_t* btn,
489                       GdkPixbuf* normal_image,
490                       GdkPixbuf* highlight_image,
491                       GdkPixbuf* pressdown_image)
492 {
493     /* since we're redraw it, we'd better check if we really changed
494        the pixbuf. This can save a lot of time.
495     */
496     gboolean need_set = (btn->normal_image != normal_image) ||
497         (btn->highlight_image != highlight_image) ||
498         (btn->pressdown_image != pressdown_image);
499
500     if (!need_set)
501         return;
502
503     btn->normal_image = normal_image;
504     btn->highlight_image = highlight_image;
505     btn->pressdown_image = pressdown_image;
506     if (btn->priv->parent) {
507         gtk_widget_queue_draw(btn->priv->parent->widget);
508     }
509 }
510
511 skin_label_t*
512 skin_label_new(char*  font,
513                char*  text,
514                double color_r,
515                double color_g,
516                double color_b,
517                double color_a)
518 {
519     skin_label_t* label = malloc(sizeof(skin_label_t));
520     label->color_r = color_r;
521     label->color_g = color_g;
522     label->color_b = color_b;
523     label->color_a = color_a;
524     label->font = strdup(font);
525     if (text) {
526         label->text = strdup(text);
527     } else {
528         label->text = NULL;
529     }
530     label->layout = NULL;
531     label->priv = malloc(sizeof(skin_label_priv_t));
532     memset(label->priv, 0, sizeof(skin_label_priv_t));
533
534     return label;
535 }
536
537 void
538 skin_label_destroy(skin_label_t* label)
539 {
540     free(label->text);
541     free(label->font);
542     free(label->priv);
543     if (label->layout) {
544         g_object_unref(label->layout);
545     }
546     free(label);
547 }
548
549 void
550 skin_window_add_label(skin_window_t* wind,
551                       skin_label_t*  label,
552                       int            x,
553                       int            y)
554 {
555     label->x = x;
556     label->y = y;
557     label->priv->wind = wind;
558     label->priv->next = wind->priv->labels;
559     wind->priv->labels = label;
560
561     cairo_t* cr = gdk_cairo_create(wind->widget->window);
562     label->layout = pango_cairo_create_layout(cr);
563     pango_layout_set_font_description(
564         label->layout, pango_font_description_from_string(label->font));
565     cairo_destroy(cr);
566 }
567
568 void
569 skin_label_set_text(skin_label_t* label, const char* text)
570 {
571     free(label->text);
572     if (text) {
573         label->text = strdup(text);
574         pango_layout_set_text(label->layout, label->text, -1);
575     } else {
576         label->text = NULL;
577     }
578     if (label->layout && label->priv->wind) {
579         gtk_widget_queue_draw(label->priv->wind->widget);
580     }
581 }
582
583 #define SET_CB_IMPL(name, event)                                        \
584     void skin_##name##_set_##event##_cb(skin_##name##_t* wid,           \
585                                         GCallback        cb,            \
586                                         void*            data)          \
587     { wid->priv->event##_cb = cb; wid->priv->event##_cb_data = data; }  \
588
589
590 SET_CB_IMPL(window, press);
591 SET_CB_IMPL(window, release);
592 SET_CB_IMPL(window, motion);
593 SET_CB_IMPL(button, press);
594 SET_CB_IMPL(button, release);