2009-03-12 Alberto Garcia <agarcia@igalia.com>
[hildon] / src / hildon-color-chooser.c
1 /*
2  * This file is a part of hildon
3  *
4  * Copyright (C) 2005, 2006 Nokia Corporation, all rights reserved.
5  *
6  * Author: Kuisma Salonen <kuisma.salonen@nokia.com>
7  * Contact: Rodrigo Novo <rodrigo.novo@nokia.com>
8  *
9  * This library is free software; you can redistribute it and/or
10  * modify it under the terms of the GNU Lesser General Public License
11  * as published by the Free Software Foundation; version 2.1 of
12  * the License, or (at your option) any later version.
13  *
14  * This library is distributed in the hope that it will be useful, but
15  * WITHOUT ANY WARRANTY; without even the implied warranty of
16  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
17  * Lesser General Public License for more details.
18  *
19  * You should have received a copy of the GNU Lesser General Public
20  * License along with this library; if not, write to the Free Software
21  * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
22  * 02110-1301 USA
23  *
24  */
25
26 /**
27  * SECTION:hildon-color-chooser
28  * @short_description: A widget used to select a color from an HSV colorspace.
29  * @see_also: #HildonColorChooserDialog
30  *
31  * HildonColorChooser is a widget that displays an HSV colorspace. 
32  * The user can manipulate the colorspace and easily select and shade of any color
33  * he wants. 
34  *
35  * Normally you should not need to use this widget directly. Using #HildonColorButton or
36  * #HildonColorChooserDialog is much more handy. 
37  * 
38  */
39
40 #undef                                          HILDON_DISABLE_DEPRECATED
41
42 #include                                        "hildon-color-chooser.h"
43 #include                                        "hildon-color-chooser-private.h"
44
45 static GtkWidgetClass*                          parent_class = NULL;
46
47 /* "crosshair" is hardcoded for now */
48 static gchar crosshair[64]                      = { 0, 0, 0, 2, 2, 0, 0, 0,
49                                                     0, 2, 2, 3, 3, 2, 2, 0,
50                                                     0, 2, 3, 0, 0, 3, 2, 0,
51                                                     2, 3, 0, 0, 0, 0, 3, 2,
52                                                     2, 3, 0, 0, 0, 0, 3, 2,
53                                                     0, 2, 3, 0, 0, 3, 2, 0,
54                                                     0, 2, 2, 3, 3, 2, 2, 0,
55                                                     0, 0, 0, 2, 2, 0, 0, 0};
56
57 static void 
58 hildon_color_chooser_init                       (HildonColorChooser *self);
59
60 static void 
61 hildon_color_chooser_class_init                 (HildonColorChooserClass *klass);
62
63 static void 
64 hildon_color_chooser_dispose                    (HildonColorChooser *self);
65
66 static void 
67 hildon_color_chooser_size_request               (GtkWidget *widget, 
68                                                  GtkRequisition *req);
69
70 static void 
71 hildon_color_chooser_size_allocate              (GtkWidget *widget, 
72                                                  GtkAllocation *alloc);
73
74 static void
75 hildon_color_chooser_realize                    (GtkWidget *widget);
76
77 static void
78 hildon_color_chooser_unrealize                  (GtkWidget *widget);
79
80 static void 
81 hildon_color_chooser_map                        (GtkWidget *widget);
82
83 static void 
84 hildon_color_chooser_unmap                      (GtkWidget *widget);
85
86 static gboolean 
87 hildon_color_chooser_expose                     (GtkWidget *widget, 
88                                                  GdkEventExpose *event);
89
90 static gboolean
91 hildon_color_chooser_button_press               (GtkWidget *widget, 
92                                                  GdkEventButton *event);
93
94 static gboolean
95 hildon_color_chooser_button_release             (GtkWidget *widget, 
96                                                  GdkEventButton *event);
97
98 static gboolean 
99 hildon_color_chooser_pointer_motion             (GtkWidget *widget, 
100                                                  GdkEventMotion *event);
101
102 static void 
103 get_border                                      (GtkWidget *w, 
104                                                  char *name, 
105                                                  GtkBorder *b);
106
107 static void 
108 init_borders                                    (GtkWidget *w, 
109                                                  GtkBorder *inner, 
110                                                  GtkBorder *outer);
111
112 inline void 
113 inline_clip_to_alloc                            (void *s, 
114                                                  GtkAllocation *a);
115
116 inline void 
117 inline_sub_times                                (GTimeVal *result, 
118                                                  GTimeVal *greater, 
119                                                  GTimeVal *lesser);
120
121 inline void 
122 inline_limited_expose                           (HildonColorChooser *self);
123
124 inline void 
125 inline_draw_hue_bar                             (GtkWidget *widget, 
126                                                  int x, 
127                                                  int y, 
128                                                  int w, 
129                                                  int h, 
130                                                  int sy, 
131                                                  int sh);
132
133 inline void
134 inline_draw_hue_bar_dimmed                      (GtkWidget *widget, 
135                                                  int x, 
136                                                  int y, 
137                                                  int w, 
138                                                  int h, 
139                                                  int sy, 
140                                                  int sh);
141
142 inline void 
143 inline_draw_sv_plane                            (HildonColorChooser *self, 
144                                                  int x, 
145                                                  int y, 
146                                                  int w, 
147                                                  int h);
148
149 inline void 
150 inline_draw_sv_plane_dimmed                     (HildonColorChooser *self, 
151                                                  int x, 
152                                                  int y, 
153                                                  int w, 
154                                                  int h);
155
156 inline void 
157 inline_draw_crosshair                           (unsigned char *buf, 
158                                                  int x, 
159                                                  int y, 
160                                                  int w, 
161                                                  int h);
162
163 inline void 
164 inline_h2rgb                                    (unsigned short hue, 
165                                                  unsigned long *rgb);
166
167 static gboolean
168 hildon_color_chooser_expose_timer               (gpointer data);
169
170 static void
171 hildon_color_chooser_set_property               (GObject *object, 
172                                                  guint param_id,
173                                                  const GValue *value, 
174                                                  GParamSpec *pspec);
175
176 static void
177 hildon_color_chooser_get_property               (GObject *object, 
178                                                  guint param_id,
179                                                  GValue *value, 
180                                                  GParamSpec *pspec);
181
182 #define                                         EXPOSE_INTERVAL 50000
183
184 #define                                         FULL_COLOR8 0xff
185
186 #define                                         FULL_COLOR 0x00ffffff
187
188 enum 
189 {
190     COLOR_CHANGED,
191     LAST_SIGNAL
192 };
193
194 enum
195 {
196     PROP_0,
197     PROP_COLOR
198 };
199
200 static guint                                    color_chooser_signals [LAST_SIGNAL] = { 0 };
201
202 GType G_GNUC_CONST
203 hildon_color_chooser_get_type                   (void)
204 {
205     static GType chooser_type = 0;
206
207     if (!chooser_type) {
208         static const GTypeInfo chooser_info =
209         {
210             sizeof (HildonColorChooserClass),
211             NULL,
212             NULL,
213             (GClassInitFunc) hildon_color_chooser_class_init,
214             NULL,
215             NULL,
216             sizeof (HildonColorChooser),
217             0,
218             (GInstanceInitFunc) hildon_color_chooser_init,
219             NULL
220         };
221
222         chooser_type = g_type_register_static (GTK_TYPE_WIDGET,
223                 "HildonColorChooser",
224                 &chooser_info, 0);
225     }
226
227     return chooser_type;
228 }
229
230 static void
231 hildon_color_chooser_init                       (HildonColorChooser *sel)
232 {
233
234     GTK_WIDGET_SET_FLAGS (sel, GTK_NO_WINDOW);
235     HildonColorChooserPrivate *priv = HILDON_COLOR_CHOOSER_GET_PRIVATE (sel);
236     g_assert (priv);
237
238     priv->currhue = 0;
239     priv->currsat = 0;
240     priv->currval = 0;
241
242     priv->mousestate = 0;
243     priv->mousein = FALSE;
244
245     g_get_current_time (&priv->expose_info.last_expose_time);
246
247     priv->expose_info.last_expose_hue = priv->currhue;
248     priv->expose_info.expose_queued = 0;
249
250     priv->dimmed_plane = NULL;
251     priv->dimmed_bar = NULL;
252 }
253
254 static void
255 hildon_color_chooser_class_init                 (HildonColorChooserClass *klass)
256 {
257     GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
258     GObjectClass *object_class = G_OBJECT_CLASS (klass);
259
260     parent_class = g_type_class_peek_parent (klass);
261     
262     object_class->dispose               = (gpointer) hildon_color_chooser_dispose;
263     object_class->get_property          = hildon_color_chooser_get_property;
264     object_class->set_property          = hildon_color_chooser_set_property;
265
266     widget_class->size_request          = hildon_color_chooser_size_request;
267     widget_class->size_allocate         = hildon_color_chooser_size_allocate;
268     widget_class->realize               = hildon_color_chooser_realize;
269     widget_class->unrealize             = hildon_color_chooser_unrealize;
270     widget_class->map                   = hildon_color_chooser_map;
271     widget_class->unmap                 = hildon_color_chooser_unmap;
272     widget_class->expose_event          = hildon_color_chooser_expose;
273     widget_class->button_press_event    = hildon_color_chooser_button_press;
274     widget_class->button_release_event  = hildon_color_chooser_button_release;
275     widget_class->motion_notify_event   = hildon_color_chooser_pointer_motion;
276
277     gtk_widget_class_install_style_property (widget_class,
278                                              g_param_spec_boxed ("inner_size",
279                                                                  "Inner sizes",
280                                                                  "Sizes of SV plane, H bar and spacing",
281                                                                  GTK_TYPE_BORDER,
282                                                                  G_PARAM_READABLE));
283
284     gtk_widget_class_install_style_property (widget_class,
285                                              g_param_spec_boxed ("outer_border",
286                                                                  "Outer border",
287                                                                  "The outer border for the chooser",
288                                                                  GTK_TYPE_BORDER,
289                                                                  G_PARAM_READABLE));
290
291     gtk_widget_class_install_style_property (widget_class,
292                                              g_param_spec_boxed ("graphic_border",
293                                                                  "Graphical borders",
294                                                                  "Size of graphical border",
295                                                                  GTK_TYPE_BORDER,
296                                                                  G_PARAM_READABLE));
297
298     /**
299      * HildonColorChooser:color:
300      *
301      * The currently selected color.
302      */
303     g_object_class_install_property (object_class, PROP_COLOR,
304             g_param_spec_boxed ("color",
305                 "Current Color",
306                 "The selected color",
307                 GDK_TYPE_COLOR,
308                 G_PARAM_READWRITE));
309
310     color_chooser_signals[COLOR_CHANGED] = g_signal_new("color-changed", 
311             G_OBJECT_CLASS_TYPE (object_class),
312             G_SIGNAL_RUN_FIRST, 
313             G_STRUCT_OFFSET (HildonColorChooserClass, color_changed),
314             NULL, 
315             NULL, 
316             g_cclosure_marshal_VOID__VOID, 
317             G_TYPE_NONE, 
318             0);
319
320     g_type_class_add_private (klass, sizeof (HildonColorChooserPrivate));
321 }
322
323 static void
324 hildon_color_chooser_dispose                    (HildonColorChooser *sel)
325 {
326     HildonColorChooserPrivate *priv = HILDON_COLOR_CHOOSER_GET_PRIVATE (sel);
327     g_assert (priv);
328
329     if (priv->dimmed_bar != NULL) {
330         g_object_unref (priv->dimmed_bar);
331         priv->dimmed_bar = NULL;
332     }
333
334     if (priv->dimmed_plane != NULL) {
335         g_object_unref (priv->dimmed_plane);
336         priv->dimmed_plane = NULL;
337     }
338
339     G_OBJECT_CLASS (parent_class)->dispose (G_OBJECT (sel));
340 }
341
342 static void
343 hildon_color_chooser_size_request               (GtkWidget *widget, 
344                                                  GtkRequisition *req)
345 {
346     GtkBorder inner, outer;
347
348     init_borders (widget, &inner, &outer);
349
350     req->width = inner.left + inner.top + inner.bottom + outer.left + outer.right;
351     req->height = inner.right + outer.top + outer.bottom;
352 }
353
354 static void 
355 hildon_color_chooser_size_allocate              (GtkWidget *widget, 
356                                                  GtkAllocation *alloc)
357 {
358     HildonColorChooserPrivate *priv = HILDON_COLOR_CHOOSER_GET_PRIVATE (widget);
359     GtkBorder outer, inner;
360
361     g_assert (priv);
362
363     widget->allocation = *alloc;
364     
365     init_borders(widget, &inner, &outer);
366
367     priv->hba.height = alloc->height - outer.top - outer.bottom;
368     priv->hba.y = alloc->y + outer.top;
369     priv->hba.width = inner.top;
370     priv->hba.x = alloc->x + alloc->width - outer.right - inner.top;
371
372     priv->spa.x = alloc->x + outer.left;
373     priv->spa.y = alloc->y + outer.top;
374     priv->spa.height = alloc->height - outer.top - outer.bottom;
375     priv->spa.width = alloc->width - outer.left - outer.right - inner.top - inner.bottom;
376
377     if (GTK_WIDGET_REALIZED (widget)) {
378         gdk_window_move_resize (priv->event_window, 
379                 widget->allocation.x, 
380                 widget->allocation.y, 
381                 widget->allocation.width, 
382                 widget->allocation.height);
383     }
384 }
385
386 static void
387 hildon_color_chooser_realize                    (GtkWidget *widget)
388 {
389     HildonColorChooserPrivate *priv = HILDON_COLOR_CHOOSER_GET_PRIVATE (widget);
390
391     g_assert (priv);
392     GdkWindowAttr attributes;
393     gint attributes_mask;
394
395     attributes.x = widget->allocation.x;
396     attributes.y = widget->allocation.y;
397     attributes.width = widget->allocation.width;
398     attributes.height = widget->allocation.height;
399     attributes.wclass = GDK_INPUT_ONLY;
400     attributes.window_type = GDK_WINDOW_CHILD;
401
402     attributes.event_mask = gtk_widget_get_events (widget) | 
403         GDK_BUTTON_PRESS_MASK           | 
404         GDK_BUTTON_RELEASE_MASK         | 
405         GDK_POINTER_MOTION_MASK         |
406         GDK_POINTER_MOTION_HINT_MASK    | 
407         GDK_BUTTON_MOTION_MASK          |
408         GDK_BUTTON1_MOTION_MASK;
409
410     attributes.visual = gtk_widget_get_visual (widget);
411     attributes.colormap = gtk_widget_get_colormap (widget);
412
413     attributes_mask = GDK_WA_X | GDK_WA_Y | GDK_WA_VISUAL | GDK_WA_WMCLASS;
414     priv->event_window = gdk_window_new (widget->parent->window, &attributes, attributes_mask);
415
416
417     gdk_window_set_user_data (priv->event_window, widget);
418
419     GTK_WIDGET_CLASS (parent_class)->realize (widget);
420 }
421
422 static void
423 hildon_color_chooser_unrealize                  (GtkWidget *widget)
424 {
425     HildonColorChooserPrivate *priv = HILDON_COLOR_CHOOSER_GET_PRIVATE (widget);
426
427     g_assert (priv);
428
429     if (priv->event_window) {
430         gdk_window_set_user_data (priv->event_window, NULL);
431         gdk_window_destroy (priv->event_window);
432         priv->event_window = NULL;
433     }
434
435     GTK_WIDGET_CLASS(parent_class)->unrealize(widget);
436 }
437
438 static void 
439 hildon_color_chooser_map                        (GtkWidget *widget)
440 {
441     HildonColorChooserPrivate *priv = HILDON_COLOR_CHOOSER_GET_PRIVATE (widget);
442
443     g_assert (priv);
444     GTK_WIDGET_CLASS(parent_class)->map(widget);
445
446     if (priv->event_window) {
447         gdk_window_show (priv->event_window);
448     }
449 }
450
451 static void 
452 hildon_color_chooser_unmap                      (GtkWidget *widget)
453 {
454     HildonColorChooserPrivate *priv = HILDON_COLOR_CHOOSER_GET_PRIVATE (widget);
455
456     g_assert (priv);
457
458     if (priv->event_window) {
459         gdk_window_hide (priv->event_window);
460     }
461
462     GTK_WIDGET_CLASS (parent_class)->unmap (widget);
463 }
464
465 inline void 
466 inline_clip_to_alloc                            (void *s, 
467                                                  GtkAllocation *a)
468 {
469     struct {
470         int x, y, w, h;
471     } *area = s;
472
473
474     if (area->x < a->x) {
475         area->w -= a->x - area->x;
476         area->x = a->x;
477     } if (area->y < a->y) {
478         area->h -= a->y - area->y;
479         area->y = a->y;
480     }
481     if (area->x + area->w > a->x + a->width) 
482         area->w = a->width - (area->x - a->x);
483
484     if (area->y + area->h > a->y + a->height) 
485         area->h = a->height - (area->y - a->y);
486 }
487
488 static gboolean 
489 hildon_color_chooser_expose                     (GtkWidget *widget, 
490                                                  GdkEventExpose *event)
491 {
492     HildonColorChooser *sel = HILDON_COLOR_CHOOSER (widget);
493     HildonColorChooserPrivate *priv = HILDON_COLOR_CHOOSER_GET_PRIVATE (widget);
494
495     g_assert (priv);
496
497     GtkBorder graphical_border;
498
499     struct {
500         int x, y, w, h;
501     } area;
502
503
504     if(! GTK_WIDGET_REALIZED (widget)) {
505         return FALSE;
506     }
507
508     get_border (widget, "graphic_border", &graphical_border);
509     
510     if (event->area.width || event->area.height) {
511
512         gdk_draw_rectangle (widget->window,
513                 widget->style->black_gc,
514                 FALSE,
515                 priv->hba.x - 2, 
516                 priv->hba.y - 2, 
517                 priv->hba.width + 3,
518                 priv->hba.height + 3);
519
520         gdk_draw_rectangle (widget->window,
521                 widget->style->black_gc,
522                 FALSE,
523                 priv->spa.x - 2, 
524                 priv->spa.y - 2, 
525                 priv->spa.width + 3,
526                 priv->spa.height + 3);
527     }
528
529     if (priv->expose_info.expose_queued) {
530         if (GTK_WIDGET_SENSITIVE (widget)) {
531             inline_draw_hue_bar (widget, priv->hba.x, priv->hba.y, priv->hba.width, priv->hba.height, priv->hba.y, priv->hba.height);
532
533             inline_draw_sv_plane (sel, priv->spa.x, priv->spa.y, priv->spa.width, priv->spa.height);
534         } else {
535             inline_draw_hue_bar_dimmed (widget, priv->hba.x, priv->hba.y, priv->hba.width, priv->hba.height, priv->hba.y, priv->hba.height);
536
537             inline_draw_sv_plane_dimmed (sel, priv->spa.x, priv->spa.y, priv->spa.width, priv->spa.height);
538         }
539
540         priv->expose_info.expose_queued = 0;
541
542         g_get_current_time (&priv->expose_info.last_expose_time);
543
544     } else {
545         /* clip hue bar region */
546         area.x = event->area.x;
547         area.y = event->area.y;
548         area.w = event->area.width;
549         area.h = event->area.height;
550
551         inline_clip_to_alloc (&area, &priv->hba);
552
553         if(GTK_WIDGET_SENSITIVE (widget)) {
554             inline_draw_hue_bar (widget, area.x, area.y, area.w, area.h, priv->hba.y, priv->hba.height);
555         } else {
556             inline_draw_hue_bar_dimmed (widget, area.x, area.y, area.w, area.h, priv->hba.y, priv->hba.height);
557         }
558         
559         area.x = event->area.x;
560         area.y = event->area.y;
561         area.w = event->area.width;
562         area.h = event->area.height;
563
564         inline_clip_to_alloc (&area, &priv->spa);
565
566         if (GTK_WIDGET_SENSITIVE (widget)) {
567             inline_draw_sv_plane (sel, area.x, area.y, area.w, area.h);
568         } else {
569             inline_draw_sv_plane_dimmed (sel, area.x, area.y, area.w, area.h);
570         }
571     }
572
573     return FALSE;
574 }
575
576
577 inline void 
578 inline_sub_times                                (GTimeVal *result, 
579                                                  GTimeVal *greater, 
580                                                  GTimeVal *lesser)
581 {
582     result->tv_sec = greater->tv_sec - lesser->tv_sec;
583     result->tv_usec = greater->tv_usec - lesser->tv_usec;
584
585     if (result->tv_usec < 0) {
586         result->tv_sec--;
587         result->tv_usec += 1000000;
588     }
589 }
590
591 inline void 
592 inline_limited_expose                           (HildonColorChooser *sel)
593 {
594     GTimeVal curr_time, result;
595     GdkEventExpose event;
596     HildonColorChooserPrivate *priv; 
597    
598     if (! GTK_WIDGET_REALIZED (GTK_WIDGET (sel))) {
599         return;
600     }
601
602     priv = HILDON_COLOR_CHOOSER_GET_PRIVATE (sel);
603     g_assert (priv);
604
605     if(priv->currhue == priv->expose_info.last_expose_hue) {
606         return; /* no need to redraw */
607     }
608
609     priv->expose_info.last_expose_hue = priv->currhue;
610
611     g_get_current_time (&curr_time);
612
613     inline_sub_times (&result, &curr_time, &priv->expose_info.last_expose_time);
614
615     if(result.tv_sec != 0 || result.tv_usec >= EXPOSE_INTERVAL) {
616
617         priv->expose_info.expose_queued = 1;
618
619         event.type = GDK_EXPOSE;
620         event.area.width = 0;
621         event.area.height = 0;
622         event.window = GTK_WIDGET(sel)->window;
623
624         gtk_widget_send_expose(GTK_WIDGET(sel), (GdkEvent *)&event);
625
626     } else if(! priv->expose_info.expose_queued) {
627         priv->expose_info.expose_queued = 1;
628         g_timeout_add ((EXPOSE_INTERVAL - result.tv_usec) / 1000, hildon_color_chooser_expose_timer, sel);
629     }
630 }
631
632 static gboolean 
633 hildon_color_chooser_button_press               (GtkWidget *widget, 
634                                                  GdkEventButton *event)
635 {
636     HildonColorChooser *sel = HILDON_COLOR_CHOOSER (widget);
637     HildonColorChooserPrivate *priv = HILDON_COLOR_CHOOSER_GET_PRIVATE (widget);
638
639     g_assert (priv);
640     int x, y, tmp;
641
642     x = (int) event->x + widget->allocation.x;
643     y = (int) event->y + widget->allocation.y;
644
645     if (x >= priv->spa.x && x <= priv->spa.x + priv->spa.width &&
646         y >= priv->spa.y && y <= priv->spa.y + priv->spa.height) {
647
648         tmp = y - priv->spa.y;
649         priv->currsat = tmp * 0xffff / priv->spa.height;
650         tmp = x - priv->spa.x;
651         priv->currval = tmp * 0xffff / priv->spa.width;
652
653         g_signal_emit (sel, color_chooser_signals[COLOR_CHANGED], 0);
654         gtk_widget_queue_draw (widget);
655
656         priv->mousestate = 1;
657         priv->mousein = TRUE;
658
659         gtk_grab_add(widget);
660
661     } else if (x >= priv->hba.x && x <= priv->hba.x + priv->hba.width &&
662                y >= priv->hba.y && y <= priv->hba.y + priv->hba.height) {
663
664         tmp = y - priv->hba.y;
665         priv->currhue = tmp * 0xffff / priv->hba.height;
666
667         g_signal_emit (sel, color_chooser_signals[COLOR_CHANGED], 0);
668         inline_limited_expose (sel);
669
670         priv->mousestate = 2;
671         priv->mousein = TRUE;
672
673         gtk_grab_add (widget);
674     }
675
676     return FALSE;
677 }
678
679 static gboolean
680 hildon_color_chooser_button_release             (GtkWidget *widget, 
681                                                  GdkEventButton *event)
682 {
683     HildonColorChooserPrivate *priv = HILDON_COLOR_CHOOSER_GET_PRIVATE (widget);
684
685     g_assert (priv);
686
687     if (priv->mousestate) {
688         gtk_grab_remove (widget);
689     }
690
691     priv->mousestate = 0;
692     priv->mousein = FALSE;
693
694     return FALSE;
695 }
696
697 static gboolean 
698 hildon_color_chooser_pointer_motion             (GtkWidget *widget, 
699                                                  GdkEventMotion *event)
700 {
701     HildonColorChooser *sel = HILDON_COLOR_CHOOSER (widget);
702     HildonColorChooserPrivate *priv = HILDON_COLOR_CHOOSER_GET_PRIVATE (widget);
703
704     GdkModifierType mods;
705     gint x, y, tmp;
706
707     g_assert (priv);
708
709     if (event->is_hint || (event->window != widget->window))
710         gdk_window_get_pointer (widget->window, &x, &y, &mods);
711
712     if (priv->mousestate == 1) {
713         if (x >= priv->spa.x && x <= priv->spa.x + priv->spa.width &&
714             y >= priv->spa.y && y <= priv->spa.y + priv->spa.height) {
715
716             priv->currsat = (((long)(y - priv->spa.y)) * 0xffff) / priv->spa.height;
717             priv->currval = (((long)(x - priv->spa.x)) * 0xffff) / priv->spa.width;
718
719             g_signal_emit (sel, color_chooser_signals[COLOR_CHANGED], 0);
720             gtk_widget_queue_draw(widget);
721
722         } else if (priv->mousein == TRUE) {
723         }
724
725     } else if (priv->mousestate == 2) {
726         if (x >= priv->hba.x && x <= priv->hba.x + priv->hba.width &&
727             y >= priv->hba.y && y <= priv->hba.y + priv->hba.height) {
728             tmp = y - priv->hba.y;
729             tmp *= 0xffff;
730             tmp /= priv->hba.height;
731
732             if(tmp != priv->currhue) {
733                 priv->currhue = tmp;
734
735                 g_signal_emit (sel, color_chooser_signals[COLOR_CHANGED], 0);
736                 inline_limited_expose (sel);
737             }
738
739         } else if (priv->mousein == TRUE) {
740         }
741     }
742
743     return FALSE;
744 }
745
746 static void 
747 get_border                                      (GtkWidget *w, 
748                                                  char *name, 
749                                                  GtkBorder *b)
750 {
751     GtkBorder *tb = NULL;
752
753     gtk_widget_style_get (w, name, &tb, NULL);
754
755     if (tb) {
756         *b = *tb;
757         gtk_border_free (tb);
758     } else {
759         b->left = 0;
760         b->right = 0;
761         b->top = 0;
762         b->bottom = 0;
763     }
764 }
765
766 static void 
767 init_borders                                    (GtkWidget *w, 
768                                                  GtkBorder *inner, 
769                                                  GtkBorder *outer)
770 {
771     GtkBorder *tb;
772
773     get_border (w, "outer_border", outer);
774
775     gtk_widget_style_get (w, "inner_size", &tb, NULL);
776
777     if (tb) {
778         *inner = *tb;
779         gtk_border_free (tb);
780     } else {
781         inner->left = 64;
782         inner->right = 64;
783         inner->top = 12;
784         inner->bottom = 2;
785     }
786
787     if (inner->left < 2) inner->left = 2;
788     if (inner->right < 2) inner->right = 2;
789     if (inner->top < 2) inner->top = 2;
790 }
791
792 /**
793  * hildon_color_chooser_set_color:
794  * @chooser: a #HildonColorChooser
795  * @color: a color to be set
796  *
797  * Sets the color selected in the widget.
798  * Will move the crosshair pointer to indicate the passed color.
799  */
800 void 
801 hildon_color_chooser_set_color                  (HildonColorChooser *chooser, 
802                                                  GdkColor *color)
803 {
804     unsigned short hue, sat, val;
805     unsigned long min, max;
806     signed long tmp, diff;
807     HildonColorChooserPrivate *priv = HILDON_COLOR_CHOOSER_GET_PRIVATE (chooser);
808
809     g_assert (priv);
810
811     /* ugly nesting */
812     min = MIN (MIN (color->red, color->green), color->blue);
813     max = MAX (MAX (color->red, color->green), color->blue);
814     diff = max - min;
815     
816     val = max;
817
818     if (val > 0 && diff != 0) {
819         sat = (diff * 0x0000ffff) / max;
820
821         if (color->red == max) {
822             tmp = (signed) color->green - (signed) color->blue;
823             tmp *= 10922;
824             tmp /= diff;
825             if(tmp < 0) {
826                 tmp += 65532;
827             }
828             hue = tmp;
829         } else if (color->green == max) {
830             hue = (((signed long) color->blue - (signed long)color->red) * 10922 / diff) + 21844;
831         } else {
832             hue = (((signed long) color->red -(signed long) color->green) * 10922 / diff) + 43688;
833         }
834     } else {
835         hue = 0;
836         sat = 0;
837     }
838
839     priv->currhue = hue;
840     priv->currsat = sat;
841     priv->currval = val;
842
843     inline_limited_expose (chooser);
844     g_signal_emit (chooser, color_chooser_signals[COLOR_CHANGED], 0);
845 }
846
847 inline void
848 inline_h2rgb                                    (unsigned short hue, 
849                                                  unsigned long *rgb)
850 {
851     unsigned short hue_rotation, hue_value;
852
853     hue_rotation  = hue / 10922;
854     hue_value     = hue % 10922;
855
856     switch (hue_rotation) {
857
858         case 0:
859         case 6:
860             rgb[0] = FULL_COLOR;
861             rgb[1] = hue_value * 6*256;
862             rgb[2] = 0;
863             break;
864
865         case 1:
866             rgb[0] = FULL_COLOR - (hue_value * 6*256);
867             rgb[1] = FULL_COLOR;
868             rgb[2] = 0;
869             break;
870
871         case 2:
872             rgb[0] = 0;
873             rgb[1] = FULL_COLOR;
874             rgb[2] = hue_value * 6*256;
875             break;
876
877         case 3:
878             rgb[0] = 0;
879             rgb[1] = FULL_COLOR - (hue_value * 6*256);
880             rgb[2] = FULL_COLOR;
881             break;
882
883         case 4:
884             rgb[0] = hue_value * 6*256;
885             rgb[1] = 0;
886             rgb[2] = FULL_COLOR;
887             break;
888
889         case 5:
890             rgb[0] = FULL_COLOR;
891             rgb[1] = 0;
892             rgb[2] = FULL_COLOR - (hue_value * 6*256);
893             break;
894
895         default:
896             rgb[0] = 0;
897             rgb[1] = 0;
898             rgb[2] = 0;
899             break;
900     }
901 }
902
903 static void 
904 intern_h2rgb8                                   (unsigned short hue, 
905                                                  unsigned char *rgb)
906 {
907     unsigned short hue_rotation, hue_value;
908
909     hue >>= 8;
910     hue_rotation  = hue / 42;
911     hue_value     = hue % 42;
912
913     switch (hue_rotation) {
914         case 0:
915         case 6:
916             rgb[0] = FULL_COLOR8;
917             rgb[1] = hue_value * 6;
918             rgb[2] = 0;
919             break;
920
921         case 1:
922             rgb[0] = FULL_COLOR8 - (hue_value * 6);
923             rgb[1] = FULL_COLOR8;
924             rgb[2] = 0;
925             break;
926
927         case 2:
928             rgb[0] = 0;
929             rgb[1] = FULL_COLOR8;
930             rgb[2] = hue_value * 6;
931             break;
932
933         case 3:
934             rgb[0] = 0;
935             rgb[1] = FULL_COLOR8 - (hue_value * 6);
936             rgb[2] = FULL_COLOR8;
937             break;
938
939         case 4:
940             rgb[0] = hue_value * 6;
941             rgb[1] = 0;
942             rgb[2] = FULL_COLOR8;
943             break;
944
945         case 5:
946             rgb[0] = FULL_COLOR8;
947             rgb[1] = 0;
948             rgb[2] = FULL_COLOR8 - (hue_value * 6);
949             break;
950
951         default:
952             rgb[0] = 0;
953             rgb[1] = 0;
954             rgb[2] = 0;
955             break;
956     }
957 }
958
959 /* optimization: do not ask hue for each round but have bilinear vectors */
960 /* rethink: benefits from handling data 8 bit? (no shift round) */
961 inline void 
962 inline_draw_hue_bar                             (GtkWidget *widget, 
963                                                  int x, 
964                                                  int y, 
965                                                  int w, 
966                                                  int h, 
967                                                  int sy, 
968                                                  int sh)
969 {
970     HildonColorChooserPrivate *priv = HILDON_COLOR_CHOOSER_GET_PRIVATE (widget);
971
972     unsigned short hvec, hcurr;
973     unsigned char *buf, *ptr, tmp[3];
974     int i, j, tmpy;
975     g_assert (priv);
976
977     if (w <= 0 || h <= 0) {
978         return;
979     }
980
981     buf = (unsigned char *) g_malloc (w * h * 3);
982
983     hvec = 65535 / sh;
984     hcurr = hvec * (y - sy);
985     
986     ptr = buf;
987
988     for (i = 0; i < h; i++) {
989         intern_h2rgb8 (hcurr, tmp);
990
991         for (j = 0; j < w; j++) {
992             ptr[0] = tmp[0];
993             ptr[1] = tmp[1];
994             ptr[2] = tmp[2];
995             ptr += 3;
996         }
997
998         hcurr += hvec;
999     }
1000
1001
1002     gdk_draw_rgb_image (widget->parent->window, 
1003             widget->style->fg_gc[0], 
1004             x, y, 
1005             w, h, 
1006             GDK_RGB_DITHER_NONE, buf, w * 3);
1007
1008     tmpy = priv->hba.y + (priv->currhue * priv->hba.height / 0xffff);
1009     gdk_draw_line (widget->parent->window, widget->style->fg_gc[GTK_WIDGET_STATE(widget)], priv->hba.x, tmpy, priv->hba.x + priv->hba.width - 1, tmpy);
1010
1011     if ((((priv->currhue * priv->hba.height) & 0xffff) > 0x8000) && (tmpy < (priv->hba.y + priv->hba.height))) {
1012         gdk_draw_line (widget->parent->window, widget->style->fg_gc[GTK_WIDGET_STATE(widget)], 
1013                 priv->hba.x, tmpy+1, priv->hba.x + priv->hba.width - 1, tmpy+1);
1014     } else if (tmpy > priv->hba.y) {
1015         gdk_draw_line(widget->parent->window, widget->style->fg_gc[GTK_WIDGET_STATE(widget)], priv->hba.x, 
1016                 tmpy-1, priv->hba.x + priv->hba.width - 1, tmpy-1);
1017     }
1018
1019     g_free(buf);
1020 }
1021
1022 inline void 
1023 inline_draw_hue_bar_dimmed                      (GtkWidget *widget, 
1024                                                  int x, 
1025                                                  int y, 
1026                                                  int w, 
1027                                                  int h, 
1028                                                  int sy, 
1029                                                  int sh)
1030 {
1031     HildonColorChooser *sel = HILDON_COLOR_CHOOSER (widget);
1032     HildonColorChooserPrivate *priv;
1033
1034     if (w <= 0 || h <= 0) {
1035         return;
1036     }
1037
1038     priv = HILDON_COLOR_CHOOSER_GET_PRIVATE (sel);
1039     g_assert (priv);
1040
1041     /* We need to create (and cache) the pixbuf if we don't 
1042      * have it yet */
1043     if (priv->dimmed_bar == NULL) {
1044         int i, j;
1045         unsigned short hvec, hcurr, avg;
1046         unsigned char *buf, *ptr, tmp[3];
1047         buf = (unsigned char *) g_malloc (w * h * 3);
1048
1049         hvec = 65535 / sh;
1050         hcurr = hvec * (y - sy);
1051         ptr = buf;
1052
1053         for (i = 0; i < h; i++) {
1054             intern_h2rgb8 (hcurr, tmp);
1055
1056             for(j = 0; j < w; j++) {
1057                 avg = ((unsigned short) tmp[0]*3 + (unsigned short) tmp[1]*2 + (unsigned short) tmp[2])/6;
1058                 ptr[0] = ((((i % 2) + j) % 2) == 0) ? MIN ((avg * 0.7) + 180, 255) : MIN ((avg * 0.7) + 120, 255);
1059                 ptr[1] = ((((i % 2) + j) % 2) == 0) ? MIN ((avg * 0.7) + 180, 255) : MIN ((avg * 0.7) + 120, 255);
1060                 ptr[2] = ((((i % 2) + j) % 2) == 0) ? MIN ((avg * 0.7) + 180, 255) : MIN ((avg * 0.7) + 120, 255);
1061                 ptr += 3;
1062             }
1063
1064             hcurr += hvec;
1065         }
1066
1067         priv->dimmed_bar = gdk_pixbuf_new_from_data (buf, GDK_COLORSPACE_RGB, FALSE, 8, w, h, w * 3, (gpointer) g_free, buf);
1068     }
1069
1070     gdk_draw_pixbuf (widget->parent->window, widget->style->fg_gc [0], priv->dimmed_bar, 0, 0, x, y, w, h, GDK_RGB_DITHER_NONE, 0, 0);
1071 }
1072
1073 inline void 
1074 inline_draw_crosshair                           (unsigned char *buf, 
1075                                                  int x, 
1076                                                  int y, 
1077                                                  int w, 
1078                                                  int h)
1079 {
1080     int i, j, sx, sy;
1081
1082     /* bad "clipping", clip the loop to save cpu */
1083     for(i = 0; i < 8; i++) {
1084         for(j = 0; j < 8; j++) {
1085             sx = j + x; sy = i + y;
1086
1087             if (sx >= 0 && sx < w && sy >= 0 && sy < h) {
1088                 if (crosshair[j + 8*i]) {
1089                     if (crosshair[j + 8*i] & 0x1) {
1090                         buf[(sx)*3+(sy)*w*3+0] = 255;
1091                         buf[(sx)*3+(sy)*w*3+1] = 255;
1092                         buf[(sx)*3+(sy)*w*3+2] = 255;
1093                     } else {
1094                         buf[(sx)*3+(sy)*w*3+0] = 0;
1095                         buf[(sx)*3+(sy)*w*3+1] = 0;
1096                         buf[(sx)*3+(sy)*w*3+2] = 0;
1097                     }
1098                 }
1099             }
1100         }
1101     }
1102 }
1103
1104 inline void 
1105 inline_draw_sv_plane                            (HildonColorChooser *sel, 
1106                                                  int x, 
1107                                                  int y, 
1108                                                  int w, 
1109                                                  int h)
1110 {
1111     GtkWidget *widget = GTK_WIDGET (sel);
1112     unsigned char *buf, *ptr;
1113     unsigned long rgbx[3] = { 0x00ffffff, 0x00ffffff, 0x00ffffff }, rgbtmp[3];
1114     signed long rgby[3];
1115     HildonColorChooserPrivate *priv;
1116     int i, j;
1117     int tmp;
1118
1119     if (w <= 0 || h <= 0) {
1120         return;
1121     }
1122
1123     priv = HILDON_COLOR_CHOOSER_GET_PRIVATE (sel);
1124     g_assert (priv);
1125     tmp = priv->spa.width * priv->spa.height;
1126
1127     buf = (unsigned char *) g_malloc (w * h * 3);
1128     ptr = buf;
1129
1130     inline_h2rgb (priv->currhue, rgbtmp);
1131
1132     rgby[0] = rgbtmp[0] - rgbx[0];
1133     rgby[1] = rgbtmp[1] - rgbx[1];
1134     rgby[2] = rgbtmp[2] - rgbx[2];
1135
1136     rgbx[0] /= priv->spa.width;
1137     rgbx[1] /= priv->spa.width;
1138     rgbx[2] /= priv->spa.width;
1139
1140     rgby[0] /= tmp;
1141     rgby[1] /= tmp;
1142     rgby[2] /= tmp;
1143
1144     rgbx[0] += (y - priv->spa.y) * rgby[0];
1145     rgbx[1] += (y - priv->spa.y) * rgby[1];
1146     rgbx[2] += (y - priv->spa.y) * rgby[2];
1147
1148     for(i = 0; i < h; i++) {
1149         rgbtmp[0] = rgbx[0] * (x - priv->spa.x);
1150         rgbtmp[1] = rgbx[1] * (x - priv->spa.x);
1151         rgbtmp[2] = rgbx[2] * (x - priv->spa.x);
1152
1153         for(j = 0; j < w; j++) {
1154             ptr[0] = rgbtmp[0] >> 16;
1155             ptr[1] = rgbtmp[1] >> 16;
1156             ptr[2] = rgbtmp[2] >> 16;
1157             rgbtmp[0] += rgbx[0];
1158             rgbtmp[1] += rgbx[1];
1159             rgbtmp[2] += rgbx[2];
1160             ptr += 3;
1161         }
1162
1163         rgbx[0] += rgby[0];
1164         rgbx[1] += rgby[1];
1165         rgbx[2] += rgby[2];
1166     }
1167
1168     inline_draw_crosshair (buf, 
1169             (priv->spa.width * priv->currval / 0xffff) - x + priv->spa.x - 4, 
1170             (priv->spa.height * priv->currsat / 0xffff) - y + priv->spa.y - 4, 
1171             w, h);
1172
1173     gdk_draw_rgb_image (widget->parent->window, widget->style->fg_gc[0], x, y, w, h, GDK_RGB_DITHER_NONE, buf, w * 3);
1174     g_free(buf);
1175 }
1176
1177 inline void 
1178 inline_draw_sv_plane_dimmed                     (HildonColorChooser *sel, 
1179                                                  int x, 
1180                                                  int y, 
1181                                                  int w, 
1182                                                  int h)
1183 {
1184     GtkWidget *widget = GTK_WIDGET (sel);
1185     HildonColorChooserPrivate *priv = HILDON_COLOR_CHOOSER_GET_PRIVATE (sel);
1186
1187     g_assert (priv);
1188
1189     if (w <= 0 || h <= 0) {
1190         return;
1191     }
1192
1193     /* We need to create (and cache) the pixbuf if we don't 
1194      * have it yet */
1195     if (priv->dimmed_plane == NULL) {
1196         unsigned char *buf, *ptr;
1197         unsigned long rgbx[3] = { 0x00ffffff, 0x00ffffff, 0x00ffffff }, rgbtmp[3];
1198         unsigned long avg;
1199         signed long rgby[3];
1200         int tmp = priv->spa.width * priv->spa.height, i, j;
1201
1202         buf = (unsigned char *) g_malloc (w * h * 3);
1203
1204         ptr = buf;
1205
1206         /* possibe optimization: as we are drawing grayscale plane, there might
1207            be some simpler algorithm to do this*/
1208         rgbtmp[0] = 0x00ffffff;
1209         rgbtmp[1] = 0x00000000;
1210         rgbtmp[2] = 0x00000000;
1211
1212         rgby[0] = rgbtmp[0] - rgbx[0];
1213         rgby[1] = rgbtmp[1] - rgbx[1];
1214         rgby[2] = rgbtmp[2] - rgbx[2];
1215
1216         rgbx[0] /= priv->spa.width;
1217         rgbx[1] /= priv->spa.width;
1218         rgbx[2] /= priv->spa.width;
1219
1220         rgby[0] /= tmp;
1221         rgby[1] /= tmp;
1222         rgby[2] /= tmp;
1223
1224         rgbx[0] += (y - priv->spa.y) * rgby[0];
1225         rgbx[1] += (y - priv->spa.y) * rgby[1];
1226         rgbx[2] += (y - priv->spa.y) * rgby[2];
1227
1228         for(i = 0; i < h; i++) {
1229             rgbtmp[0] = rgbx[0] * (x - priv->spa.x);
1230             rgbtmp[1] = rgbx[1] * (x - priv->spa.x);
1231             rgbtmp[2] = rgbx[2] * (x - priv->spa.x);
1232
1233             for(j = 0; j < w; j++) {
1234                 avg = (rgbtmp[0] + rgbtmp[1] + rgbtmp[2])/3;
1235                 avg >>= 16;
1236                 ptr[0] = ((((i % 2) + j) % 2) == 0) ? MIN ((avg * 0.7) + 180, 255) : MIN ((avg * 0.7) + 120, 255);
1237                 ptr[1] = ((((i % 2) + j) % 2) == 0) ? MIN ((avg * 0.7) + 180, 255) : MIN ((avg * 0.7) + 120, 255);
1238                 ptr[2] = ((((i % 2) + j) % 2) == 0) ? MIN ((avg * 0.7) + 180, 255) : MIN ((avg * 0.7) + 120, 255);
1239                 rgbtmp[0] += rgbx[0];
1240                 rgbtmp[1] += rgbx[1];
1241                 rgbtmp[2] += rgbx[2];
1242                 ptr += 3;
1243             }
1244
1245             rgbx[0] += rgby[0];
1246             rgbx[1] += rgby[1];
1247             rgbx[2] += rgby[2];
1248         }
1249
1250         priv->dimmed_plane = gdk_pixbuf_new_from_data (buf, GDK_COLORSPACE_RGB, FALSE, 8, w, h, w * 3, (gpointer) g_free, buf);
1251     }
1252
1253     gdk_draw_pixbuf (widget->parent->window, widget->style->fg_gc [0], priv->dimmed_plane, 0, 0, x, y, w, h, GDK_RGB_DITHER_NONE, 0, 0);
1254 }
1255
1256
1257 static gboolean 
1258 hildon_color_chooser_expose_timer               (gpointer data)
1259 {
1260     HildonColorChooser *sel = HILDON_COLOR_CHOOSER (data);
1261     HildonColorChooserPrivate *priv = HILDON_COLOR_CHOOSER_GET_PRIVATE (sel);
1262
1263     g_assert (priv);
1264
1265     if (priv->expose_info.expose_queued) {
1266         gtk_widget_queue_draw (GTK_WIDGET (data));
1267     }
1268
1269     return FALSE;
1270 }
1271
1272 /**
1273  * hildon_color_chooser_get_color:
1274  * @chooser: a #HildonColorChooser
1275  * @color: a color structure to fill with the currently selected color
1276  *
1277  * Retrives the currently selected color in the chooser.
1278  *
1279  */
1280 void
1281 hildon_color_chooser_get_color                  (HildonColorChooser *chooser, 
1282                                                  GdkColor *color)
1283 {
1284     HildonColorChooserPrivate *priv;
1285     GdkVisual *system_visual = gdk_visual_get_system ();
1286     unsigned long rgb[3], rgb2[3];
1287
1288     g_return_if_fail (HILDON_IS_COLOR_CHOOSER (chooser));
1289     g_return_if_fail (color != NULL);
1290
1291     priv = HILDON_COLOR_CHOOSER_GET_PRIVATE (chooser);
1292     g_assert (priv);
1293
1294     inline_h2rgb (priv->currhue, rgb);
1295
1296     rgb2[0] = 0xffffff - rgb[0];
1297     rgb2[1] = 0xffffff - rgb[1];
1298     rgb2[2] = 0xffffff - rgb[2];
1299
1300     color->red   = ((rgb[0] >> 8) + ((rgb2[0] >> 8) * (0xffff - priv->currsat) / 0xffff)) * priv->currval / 0xffff;
1301     color->green = ((rgb[1] >> 8) + ((rgb2[1] >> 8) * (0xffff - priv->currsat) / 0xffff)) * priv->currval / 0xffff;
1302     color->blue  = ((rgb[2] >> 8) + ((rgb2[2] >> 8) * (0xffff - priv->currsat) / 0xffff)) * priv->currval / 0xffff;
1303
1304     color->pixel = ((color->red >> (16 - system_visual->red_prec)) << system_visual->red_shift) |
1305         ((color->green >> (16 - system_visual->green_prec)) << system_visual->green_shift) |
1306         ((color->blue >> (16 - system_visual->blue_prec)) << system_visual->blue_shift);
1307 }
1308
1309 GtkWidget*
1310 hildon_color_chooser_new                        (void)
1311 {
1312     return (GtkWidget *) g_object_new (HILDON_TYPE_COLOR_CHOOSER, NULL);
1313 }
1314
1315 static void
1316 hildon_color_chooser_set_property               (GObject *object, 
1317                                                  guint param_id,
1318                                                  const GValue *value, 
1319                                                  GParamSpec *pspec)
1320 {
1321     g_return_if_fail (HILDON_IS_COLOR_CHOOSER (object));
1322
1323     switch (param_id) 
1324     {
1325
1326         case PROP_COLOR: {
1327             GdkColor *color = g_value_get_boxed (value);
1328             hildon_color_chooser_set_color ((HildonColorChooser *) object, color);
1329             } break;
1330
1331         default:
1332             G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec);
1333             break;
1334     }
1335 }
1336
1337 static void
1338 hildon_color_chooser_get_property               (GObject *object, 
1339                                                  guint param_id,
1340                                                  GValue *value, 
1341                                                  GParamSpec *pspec)
1342 {
1343     g_return_if_fail (HILDON_IS_COLOR_CHOOSER (object));
1344
1345     switch (param_id) 
1346     {
1347
1348         case PROP_COLOR: {
1349             GdkColor color;
1350             hildon_color_chooser_get_color ((HildonColorChooser *) object, &color);
1351             g_value_set_boxed (value, &color);
1352             } break;
1353
1354         default:
1355             G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec);
1356             break;
1357     }
1358 }
1359
1360