0f1a3fcc3b535b9ac7237e8122142e536aaed604
[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-text-utils.h>
38 #include <modest-recpt-view.h>
39
40 static GObjectClass *parent_class = NULL;
41
42 /* signals */
43 enum {
44         ACTIVATE_SIGNAL,
45         LAST_SIGNAL
46 };
47
48 typedef struct _ModestRecptViewPriv ModestRecptViewPriv;
49
50 struct _ModestRecptViewPriv
51 {
52         GtkWidget *text_view;
53         gboolean button_pressed;
54         gdouble pressed_x, pressed_y;
55         gint line_height;
56 };
57
58 #define MODEST_RECPT_VIEW_GET_PRIVATE(o)        \
59         (G_TYPE_INSTANCE_GET_PRIVATE ((o), MODEST_TYPE_RECPT_VIEW, ModestRecptViewPriv))
60
61 static guint signals[LAST_SIGNAL] = {0};
62
63
64 /**
65  * modest_recpt_view_new:
66  *
67  * Return value: a new #ModestRecptView instance implemented for Gtk+
68  **/
69 GtkWidget*
70 modest_recpt_view_new (void)
71 {
72         ModestRecptView *self = g_object_new (MODEST_TYPE_RECPT_VIEW, NULL);
73
74         return GTK_WIDGET (self);
75 }
76
77 void
78 modest_recpt_view_set_recipients (ModestRecptView *recpt_view, const gchar *recipients)
79 {
80         GtkTextBuffer *buffer = NULL;
81         ModestRecptViewPriv *priv = MODEST_RECPT_VIEW_GET_PRIVATE (recpt_view);
82
83         buffer = gtk_text_view_get_buffer (GTK_TEXT_VIEW (priv->text_view));
84         gtk_text_buffer_set_text (buffer, recipients, -1);
85         gtk_widget_queue_resize (GTK_WIDGET (recpt_view));
86
87 }
88
89 static gint
90 button_press_event (GtkWidget *widget,
91                     GdkEventButton *event,
92                     gpointer user_data)
93 {
94         ModestRecptViewPriv *priv = MODEST_RECPT_VIEW_GET_PRIVATE (MODEST_RECPT_VIEW (user_data));
95
96         if (event->type == GDK_BUTTON_PRESS && event->button == 1) {
97                 priv->button_pressed = TRUE;
98                 priv->pressed_x = event->x;
99                 priv->pressed_y = event->y;
100         }
101         return TRUE;
102 }
103
104 static gint
105 button_release_event (GtkWidget *widget,
106                       GdkEventButton *event,
107                       gpointer user_data)
108 {
109         ModestRecptViewPriv *priv = MODEST_RECPT_VIEW_GET_PRIVATE (MODEST_RECPT_VIEW (user_data));
110
111         if (event->type != GDK_BUTTON_RELEASE)
112                 return TRUE;
113
114         if ((priv->button_pressed) &&
115             (event->type == GDK_BUTTON_RELEASE) &&
116             (priv->pressed_x == event->x) &&
117             (priv->pressed_y == event->y)) {
118                 priv->button_pressed = FALSE;
119                 if (event->button == 1) {
120                         gint buffer_x, buffer_y;
121                         int index;
122                         GtkTextIter iter;
123                         gtk_text_view_window_to_buffer_coords (GTK_TEXT_VIEW (priv->text_view), GTK_TEXT_WINDOW_WIDGET,
124                                                                event->x, event->y, &buffer_x, &buffer_y);
125                         gtk_text_view_get_iter_at_location (GTK_TEXT_VIEW (priv->text_view), &iter, buffer_x, buffer_y);
126                         index = gtk_text_iter_get_offset (&iter);
127                         
128                         if (!gtk_text_iter_is_end (&iter)) {
129                                 int selection_start, selection_end;
130                                 gboolean selected = FALSE;
131                                 GtkTextIter start_iter, end_iter;
132                                 GtkTextBuffer *buffer;
133
134                                 buffer = gtk_text_view_get_buffer (GTK_TEXT_VIEW (priv->text_view));
135                                 if (gtk_text_buffer_get_selection_bounds (buffer, &start_iter, &end_iter) &&
136                                     gtk_text_iter_in_range (&iter, &start_iter, &end_iter)) {
137                                         selected = TRUE;
138                                 } else {
139                                         gchar *text = NULL;
140                                         GtkTextIter start_iter, end_iter;
141
142                                         gtk_text_buffer_get_start_iter (buffer, &start_iter);
143                                         gtk_text_buffer_get_end_iter (buffer, &end_iter);
144                                         text = gtk_text_buffer_get_text (buffer, &start_iter, &end_iter, FALSE);
145
146                                         modest_text_utils_address_range_at_position (text,
147                                                                                      index,
148                                                                                      &selection_start, &selection_end);
149                                         /* TODO: now gtk label tries to select more than the label as usual,
150                                          *  and we force it to recover the selected region for the defined area.
151                                          *  It should be fixed (maybe preventing gtklabel to manage selections
152                                          *  in parallel with us
153                                          */
154                                         gtk_text_buffer_get_iter_at_offset (buffer, &start_iter, selection_start);
155                                         gtk_text_buffer_get_iter_at_offset (buffer, &end_iter, selection_end);
156                                         gtk_text_buffer_select_range (buffer, &start_iter, &end_iter);
157                                         
158                                         if (text)
159                                                 g_free (text);
160                                                                       
161                                 }
162
163                                 if (selected) {
164                                         gchar *selection;
165
166                                         gtk_text_buffer_get_selection_bounds (buffer, &start_iter, &end_iter);
167                                         selection = gtk_text_buffer_get_text (buffer, &start_iter, &end_iter, FALSE);
168                                         g_signal_emit (G_OBJECT (user_data), signals[ACTIVATE_SIGNAL], 0, selection);
169                                         g_free (selection);
170                                 }
171
172                         }
173                         return TRUE;
174                 }
175         }
176         priv->button_pressed = FALSE;
177         return TRUE;
178 }
179
180 static void
181 text_view_size_request (GtkWidget *widget,
182                         GtkRequisition *requisition,
183                         gpointer user_data)
184 {
185         GtkTextBuffer *buffer = NULL;
186         GtkTextIter iter;
187         int line;
188         GdkRectangle iter_rectangle;
189         GtkWidget *text_view = GTK_WIDGET (user_data);
190         GtkAdjustment *adj = NULL;
191         ModestRecptViewPriv *priv = MODEST_RECPT_VIEW_GET_PRIVATE (widget);
192
193         buffer = gtk_text_view_get_buffer (GTK_TEXT_VIEW (text_view));
194
195         gtk_text_buffer_get_start_iter (buffer, &iter);
196         gtk_text_view_get_iter_location (GTK_TEXT_VIEW (text_view), &iter, &iter_rectangle);
197
198         for (line = 0; line < 2; line++) {
199                 if (!gtk_text_view_forward_display_line (GTK_TEXT_VIEW (text_view), &iter))
200                         break;
201         }
202
203         gtk_text_buffer_get_start_iter (buffer, &iter);
204
205         gtk_text_view_scroll_to_iter (GTK_TEXT_VIEW (text_view), &iter, 0.0, TRUE, 0.0, 0.0);
206
207         adj = gtk_scrolled_window_get_vadjustment (GTK_SCROLLED_WINDOW (widget));
208         if (adj != NULL) {
209                 g_object_set (G_OBJECT (adj), "page-increment", (gdouble) iter_rectangle.height, "step-increment", (gdouble) iter_rectangle.height, NULL);
210                 gtk_adjustment_changed (adj);
211         }
212
213         if (line > 0) {
214                 requisition->height = iter_rectangle.height * 2;
215         } else {
216                 requisition->height = iter_rectangle.height;
217         }
218
219         priv->line_height = iter_rectangle.height;
220
221 }
222
223 static void
224 view_size_allocate (GtkWidget *widget,
225                     GtkAllocation *allocation,
226                     gpointer user_data)
227 {
228         GtkAdjustment *adj = NULL;
229         ModestRecptViewPriv *priv = MODEST_RECPT_VIEW_GET_PRIVATE (widget);
230
231         adj = gtk_scrolled_window_get_vadjustment (GTK_SCROLLED_WINDOW (widget));
232         if (adj != NULL) {              
233                 g_object_set (G_OBJECT (adj), "page-increment", (gdouble) priv->line_height, "step-increment", (gdouble) priv->line_height, NULL);
234         }
235         gtk_adjustment_changed (adj);
236 }
237
238 static void
239 modest_recpt_view_instance_init (GTypeInstance *instance, gpointer g_class)
240 {
241         ModestRecptViewPriv *priv = MODEST_RECPT_VIEW_GET_PRIVATE (instance);
242
243         gtk_scrolled_window_set_hadjustment (GTK_SCROLLED_WINDOW (instance), NULL);
244         gtk_scrolled_window_set_vadjustment (GTK_SCROLLED_WINDOW (instance), NULL);
245
246         priv->text_view = gtk_text_view_new ();
247
248         gtk_text_view_set_editable (GTK_TEXT_VIEW (priv->text_view), FALSE);
249         gtk_text_view_set_wrap_mode (GTK_TEXT_VIEW (priv->text_view), GTK_WRAP_WORD_CHAR);
250         gtk_text_view_set_pixels_above_lines (GTK_TEXT_VIEW (priv->text_view), 0);
251         gtk_text_view_set_pixels_below_lines (GTK_TEXT_VIEW (priv->text_view), 0);
252         gtk_text_view_set_justification (GTK_TEXT_VIEW (priv->text_view), GTK_JUSTIFY_LEFT);
253         gtk_text_view_set_left_margin (GTK_TEXT_VIEW (priv->text_view), 0);
254         gtk_text_view_set_right_margin (GTK_TEXT_VIEW (priv->text_view), 0);
255         gtk_text_view_set_cursor_visible (GTK_TEXT_VIEW (priv->text_view), FALSE);
256
257         gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (instance), GTK_POLICY_NEVER, GTK_POLICY_AUTOMATIC);
258
259         gtk_container_add (GTK_CONTAINER (instance), priv->text_view);
260
261         g_signal_connect (G_OBJECT (instance), "size-request", G_CALLBACK (text_view_size_request), priv->text_view);
262         g_signal_connect (G_OBJECT (instance), "size-allocate", G_CALLBACK (view_size_allocate), NULL);
263         g_signal_connect (G_OBJECT (priv->text_view), "button-press-event", G_CALLBACK (button_press_event), instance);
264         g_signal_connect (G_OBJECT (priv->text_view), "button-release-event", G_CALLBACK (button_release_event), instance);
265
266         return;
267 }
268
269 static void
270 modest_recpt_view_finalize (GObject *object)
271 {
272         (*parent_class->finalize) (object);
273
274         return;
275 }
276
277 static void 
278 modest_recpt_view_class_init (ModestRecptViewClass *klass)
279 {
280         GObjectClass *object_class;
281         GtkWidgetClass *widget_class;
282
283         parent_class = g_type_class_peek_parent (klass);
284         object_class = (GObjectClass*) klass;
285         widget_class = GTK_WIDGET_CLASS (klass);
286
287         object_class->finalize = modest_recpt_view_finalize;
288
289         klass->activate = NULL;
290
291         g_type_class_add_private (object_class, sizeof (ModestRecptViewPriv));
292
293         signals[ACTIVATE_SIGNAL] =
294                 g_signal_new ("activate",
295                               G_TYPE_FROM_CLASS (object_class),
296                               G_SIGNAL_RUN_FIRST | G_SIGNAL_ACTION,
297                               G_STRUCT_OFFSET(ModestRecptViewClass, activate),
298                               NULL, NULL,
299                               g_cclosure_marshal_VOID__STRING,
300                               G_TYPE_NONE, 1, G_TYPE_STRING);
301         
302         return;
303 }
304
305 GType 
306 modest_recpt_view_get_type (void)
307 {
308         static GType type = 0;
309
310         if (G_UNLIKELY(type == 0))
311         {
312                 static const GTypeInfo info = 
313                 {
314                   sizeof (ModestRecptViewClass),
315                   NULL,   /* base_init */
316                   NULL,   /* base_finalize */
317                   (GClassInitFunc) modest_recpt_view_class_init,   /* class_init */
318                   NULL,   /* class_finalize */
319                   NULL,   /* class_data */
320                   sizeof (ModestRecptView),
321                   0,      /* n_preallocs */
322                   modest_recpt_view_instance_init    /* instance_init */
323                 };
324
325                 type = g_type_register_static (GTK_TYPE_SCROLLED_WINDOW,
326                         "ModestRecptView",
327                         &info, 0);
328
329         }
330
331         return type;
332 }