* Added src/widgets/modest-recpt-view.[ch].
[modest] / src / widgets / modest-recpt-view.c
1 /* Copyright (c) 2007, Nokia Corporation
2  * All rights reserved.
3  *
4  * Redistribution and use in source and binary forms, with or without
5  * modification, are permitted provided that the following conditions are
6  * met:
7  *
8  * * Redistributions of source code must retain the above copyright
9  *   notice, this list of conditions and the following disclaimer.
10  * * Redistributions in binary form must reproduce the above copyright
11  *   notice, this list of conditions and the following disclaimer in the
12  *   documentation and/or other materials provided with the distribution.
13  * * Neither the name of the Nokia Corporation nor the names of its
14  *   contributors may be used to endorse or promote products derived from
15  *   this software without specific prior written permission.
16  *
17  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
18  * IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
19  * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
20  * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER
21  * OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
22  * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
23  * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
24  * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
25  * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
26  * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
27  * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
28  */
29
30 #include <config.h>
31
32 #include <glib/gi18n-lib.h>
33
34 #include <string.h>
35 #include <gtk/gtk.h>
36
37 #include "modest-recpt-view.h"
38
39 static GObjectClass *parent_class = NULL;
40
41 /* signals */
42 enum {
43         ACTIVATE_SIGNAL,
44         LAST_SIGNAL
45 };
46
47 typedef struct _ModestRecptViewPriv ModestRecptViewPriv;
48
49 struct _ModestRecptViewPriv
50 {
51         gboolean button_pressed;
52         gdouble pressed_x, pressed_y;
53 };
54
55 #define MODEST_RECPT_VIEW_GET_PRIVATE(o)        \
56         (G_TYPE_INSTANCE_GET_PRIVATE ((o), MODEST_TYPE_RECPT_VIEW, ModestRecptViewPriv))
57
58 static guint signals[LAST_SIGNAL] = {0};
59
60 /**
61  * modest_recpt_view_new:
62  *
63  * Return value: a new #ModestRecptView instance implemented for Gtk+
64  **/
65 GtkWidget*
66 modest_recpt_view_new (void)
67 {
68         ModestRecptView *self = g_object_new (MODEST_TYPE_RECPT_VIEW, NULL);
69
70         return GTK_WIDGET (self);
71 }
72
73 static void
74 address_bounds_at_position (const gchar *recipients_list, gint position, gint *start, gint *end)
75 {
76         gchar *current = NULL;
77         gint range_start = 0;
78         gint range_end = 0;
79         gint index;
80         gboolean is_quoted = FALSE;
81
82         index = 0;
83         for (current = (gchar *) recipients_list; *current != '\0'; current = g_utf8_find_next_char (current, NULL)) {
84                 gunichar c = g_utf8_get_char (current);
85
86                 if ((c == ',') && (!is_quoted)) {
87                         if (index < position) {
88                                 range_start = index + 1;
89                         } else {
90                                 break;
91                         }
92                 } else if (c == '\"') {
93                         is_quoted = !is_quoted;
94                 } else if ((c == ' ') &&(range_start == index)) {
95                         range_start ++;
96                 }
97                 index ++;
98                 range_end = index;
99         }
100
101         if (start)
102                 *start = range_start;
103         if (end)
104                 *end = range_end;
105 }
106
107 static gboolean
108 button_press_event (GtkWidget *widget,
109                     GdkEventButton *event,
110                     gpointer user_data)
111 {
112         ModestRecptViewPriv *priv = MODEST_RECPT_VIEW_GET_PRIVATE (MODEST_RECPT_VIEW (widget));
113
114         if (!gtk_label_get_selectable (GTK_LABEL (widget)))
115                 return FALSE;
116
117         if (event->type == GDK_BUTTON_PRESS) {
118                 priv->button_pressed = TRUE;
119                 priv->pressed_x = event->x;
120                 priv->pressed_y = event->y;
121         }
122         return FALSE;
123 }
124
125 static gboolean
126 button_release_event (GtkWidget *widget,
127                      GdkEventButton *event,
128                      gpointer user_data)
129 {
130         ModestRecptViewPriv *priv = MODEST_RECPT_VIEW_GET_PRIVATE (MODEST_RECPT_VIEW (widget));
131
132         if (!gtk_label_get_selectable (GTK_LABEL (widget)))
133                 return TRUE;
134
135         if (event->type != GDK_BUTTON_RELEASE)
136                 return TRUE;
137
138         if ((priv->button_pressed) &&
139             (event->type == GDK_BUTTON_RELEASE) && 
140             (priv->pressed_x == event->x) &&
141             (priv->pressed_y == event->y)) {
142                 priv->button_pressed = FALSE;
143                 if (event->button == 1) {
144                         PangoLayout *layout = NULL;
145                         int index;
146                         layout = gtk_label_get_layout (GTK_LABEL (widget));
147                         if (pango_layout_xy_to_index (layout, event->x*PANGO_SCALE, event->y*PANGO_SCALE, &index, NULL)) {
148                                 int selection_start, selection_end;
149                                 gboolean selected = FALSE;
150                                 if (gtk_label_get_selection_bounds (GTK_LABEL (widget),
151                                                                     &selection_start,
152                                                                     &selection_end) &&
153                                     (index >= selection_start)&&(index < selection_end)) {
154                                         selected = TRUE;
155                                 }
156
157                                 address_bounds_at_position (gtk_label_get_text (GTK_LABEL (widget)),
158                                                             index,
159                                                             &selection_start, &selection_end);
160                                 /* TODO: now gtk label tries to select more than the label as usual,
161                                  *  and we force it to recover the selected region for the defined area.
162                                  *  It should be fixed (maybe preventing gtklabel to manage selections
163                                  *  in parallel with us
164                                  */
165                                 gtk_label_select_region (GTK_LABEL (widget), 
166                                                          selection_start,
167                                                          selection_end);
168                                 
169                                 if (selected)
170                                         g_signal_emit (G_OBJECT (widget), signals[ACTIVATE_SIGNAL], 0);
171
172                                 return TRUE;
173                         }
174                 }
175         }
176         priv->button_pressed = FALSE;
177         return TRUE;
178 }
179
180 static void
181 modest_recpt_view_instance_init (GTypeInstance *instance, gpointer g_class)
182 {
183
184         gtk_label_set_justify (GTK_LABEL (instance), GTK_JUSTIFY_LEFT);
185         gtk_label_set_line_wrap (GTK_LABEL (instance), TRUE);
186         gtk_label_set_selectable (GTK_LABEL (instance), TRUE);
187
188         g_signal_connect (G_OBJECT (instance), "button-press-event", G_CALLBACK(button_press_event), NULL);
189         g_signal_connect (G_OBJECT (instance), "button-release-event", G_CALLBACK(button_release_event), NULL);
190
191         return;
192 }
193
194 static void
195 modest_recpt_view_finalize (GObject *object)
196 {
197         (*parent_class->finalize) (object);
198
199         return;
200 }
201
202 static void 
203 modest_recpt_view_class_init (ModestRecptViewClass *klass)
204 {
205         GObjectClass *object_class;
206
207         parent_class = g_type_class_peek_parent (klass);
208         object_class = (GObjectClass*) klass;
209
210         object_class->finalize = modest_recpt_view_finalize;
211
212         klass->activate = NULL;
213
214         g_type_class_add_private (object_class, sizeof (ModestRecptViewPriv));
215
216         signals[ACTIVATE_SIGNAL] =
217                 g_signal_new ("activate",
218                               G_TYPE_FROM_CLASS (object_class),
219                               G_SIGNAL_RUN_FIRST | G_SIGNAL_ACTION,
220                               G_STRUCT_OFFSET(ModestRecptViewClass, activate),
221                               NULL, NULL,
222                               g_cclosure_marshal_VOID__VOID,
223                               G_TYPE_NONE, 0);
224         
225         return;
226 }
227
228 GType 
229 modest_recpt_view_get_type (void)
230 {
231         static GType type = 0;
232
233         if (G_UNLIKELY(type == 0))
234         {
235                 static const GTypeInfo info = 
236                 {
237                   sizeof (ModestRecptViewClass),
238                   NULL,   /* base_init */
239                   NULL,   /* base_finalize */
240                   (GClassInitFunc) modest_recpt_view_class_init,   /* class_init */
241                   NULL,   /* class_finalize */
242                   NULL,   /* class_data */
243                   sizeof (ModestRecptView),
244                   0,      /* n_preallocs */
245                   modest_recpt_view_instance_init    /* instance_init */
246                 };
247
248                 type = g_type_register_static (GTK_TYPE_LABEL,
249                         "ModestRecptView",
250                         &info, 0);
251
252         }
253
254         return type;
255 }