62edec5f40f5f0620ca1882ec157fa6ac8a0d868
[hildon] / hildon / hildon-text-view.c
1 /*
2  * This file is a part of hildon
3  *
4  * Copyright (C) 2008 Nokia Corporation, all rights reserved.
5  *
6  * This program is free software; you can redistribute it and/or modify
7  * it under the terms of the GNU Lesser Public License as published by
8  * the Free Software Foundation; version 2 of the license.
9  *
10  * This program is distributed in the hope that it will be useful,
11  * but WITHOUT ANY WARRANTY; without even the implied warranty of
12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13  * GNU Lesser Public License for more details.
14  *
15  */
16
17 /**
18  * SECTION:hildon-text-view
19  * @short_description: Text view within the Hildon framework.
20  *
21  * The #HildonTextView is a text
22  * view derived from the #GtkTextView widget that provides
23  * additional commodities specific to the Hildon framework.
24  *
25  * Besides all the features inherited from #GtkTextView, a
26  * #HildonTextView can also have a placeholder text. This text will be
27  * shown if the text view is empty and doesn't have the input focus,
28  * but it's otherwise ignored. Thus, calls to
29  * hildon_text_view_get_buffer() will never return the placeholder
30  * text, not even when it's being displayed.
31  *
32  * Although #HildonTextView is derived from #GtkTextView,
33  * gtk_text_view_get_buffer() and gtk_text_view_set_buffer() must
34  * never be used to get/set the buffer in this
35  * widget. hildon_text_view_get_buffer() and
36  * hildon_text_view_set_buffer() must be used instead.
37  *
38  * <example>
39  * <title>Creating a HildonTextView with a placeholder</title>
40  * <programlisting>
41  * GtkWidget *
42  * create_text_view (void)
43  * {
44  *     GtkWidget *text_view;
45  * <!-- -->
46  *     text_view = hildon_text_view_new ();
47  *     hildon_text_view_set_placeholder (HILDON_TEXT_VIEW (text_view),
48  *                                       "Type some text here");
49  * <!-- -->
50  *     return text_view;
51  * }
52  * </programlisting>
53  * </example>
54  */
55
56 #include                                        "hildon-text-view.h"
57 #include                                        "hildon-helper.h"
58 #include <math.h>
59
60 #define HILDON_TEXT_VIEW_DRAG_THRESHOLD 16.0
61
62 G_DEFINE_TYPE                                   (HildonTextView, hildon_text_view, GTK_TYPE_TEXT_VIEW);
63
64 #define                                         HILDON_TEXT_VIEW_GET_PRIVATE(obj) \
65                                                 (G_TYPE_INSTANCE_GET_PRIVATE ((obj), \
66                                                 HILDON_TYPE_TEXT_VIEW, HildonTextViewPrivate));
67
68 typedef struct                                  _HildonTextViewPrivate HildonTextViewPrivate;
69
70 struct                                          _HildonTextViewPrivate
71 {
72     GtkTextBuffer *main_buffer;                   /* Used to show the "real" contents */
73     GtkTextBuffer *placeholder_buffer;   /* Internal, used to display the placeholder */
74     gulong changed_id;               /* ID of the main_buffer::changed signal handler */
75     gdouble x;                                                      /* tap x position */
76     gdouble y;                                                      /* tap y position */
77 };
78
79 /* Function used to decide whether to show the placeholder or not */
80 static void
81 hildon_text_view_refresh_contents               (GtkWidget *text_view)
82 {
83     HildonTextViewPrivate *priv = HILDON_TEXT_VIEW_GET_PRIVATE (text_view);
84     gint bufsize = gtk_text_buffer_get_char_count (priv->main_buffer);
85
86     if ((bufsize > 0) || GTK_WIDGET_HAS_FOCUS (text_view)) {
87         /* Display the main buffer if it contains text or the widget is focused */
88         hildon_helper_set_logical_color (text_view, GTK_RC_TEXT, GTK_STATE_NORMAL, "ReversedTextColor");
89         gtk_text_view_set_buffer (GTK_TEXT_VIEW (text_view), priv->main_buffer);
90     } else {
91         /* Otherwise, display the placeholder */
92         hildon_helper_set_logical_color (text_view, GTK_RC_TEXT, GTK_STATE_NORMAL, "ReversedSecondaryTextColor");
93         gtk_text_view_set_buffer (GTK_TEXT_VIEW (text_view), priv->placeholder_buffer);
94     }
95 }
96
97 /**
98  * hildon_text_view_set_buffer:
99  * @text_view: a #HildonTextView
100  * @buffer: a #GtkTextBuffer
101  *
102  * Sets @buffer as the buffer being displayed by @text_view. The
103  * previous buffer displayed by the text view is unreferenced, and a
104  * reference is added to @buffer. If you owned a reference to @buffer
105  * before passing it to this function, you must remove that reference
106  * yourself
107  *
108  * Note that you must never use gtk_text_view_set_buffer() to set the
109  * buffer of a #HildonTextView.
110  *
111  * Since: 2.2
112  */
113 void
114 hildon_text_view_set_buffer                     (HildonTextView *text_view,
115                                                  GtkTextBuffer  *buffer)
116 {
117     HildonTextViewPrivate *priv;
118
119     g_return_if_fail (HILDON_IS_TEXT_VIEW (text_view));
120     g_return_if_fail (GTK_IS_TEXT_BUFFER (buffer));
121
122     priv = HILDON_TEXT_VIEW_GET_PRIVATE (text_view);
123
124     /* If this is the same buffer, don't do anything */
125     if (buffer == priv->main_buffer)
126         return;
127
128     /* Disconnect the signal handler from the old buffer */
129     g_signal_handler_disconnect (priv->main_buffer, priv->changed_id);
130
131     /* Replace the old buffer with the new one */
132     g_object_unref (priv->main_buffer);
133     priv->main_buffer = g_object_ref (buffer);
134
135     /* Attach a callback to the new text buffer */
136     priv->changed_id =
137         g_signal_connect_swapped (priv->main_buffer, "changed",
138                                   G_CALLBACK (hildon_text_view_refresh_contents), text_view);
139
140     /* Refresh textview contents */
141     hildon_text_view_refresh_contents (GTK_WIDGET (text_view));
142 }
143
144 /**
145  * hildon_text_view_get_buffer:
146  * @text_view: a #HildonTextView
147  *
148  * Returns the text buffer in @text_view. The reference count is not
149  * incremented; the caller of this function won't own a new reference.
150  *
151  * Note that you must never use gtk_text_view_get_buffer() to get the
152  * buffer from a #HildonTextView.
153  *
154  * Also note that placeholder text (set using
155  * hildon_text_view_set_placeholder()) is never contained in this
156  * buffer.
157  *
158  * Returns: a #GtkTextBuffer
159  *
160  * Since: 2.2
161  */
162 GtkTextBuffer *
163 hildon_text_view_get_buffer                     (HildonTextView *text_view)
164 {
165     HildonTextViewPrivate *priv;
166
167     g_return_val_if_fail (HILDON_IS_TEXT_VIEW (text_view), NULL);
168
169     priv = HILDON_TEXT_VIEW_GET_PRIVATE (text_view);
170
171     /* Always return priv->main_buffer even if the placeholder is
172      * being displayed */
173     return priv->main_buffer;
174 }
175
176 /**
177  * hildon_text_view_set_placeholder:
178  * @text_view: a #HildonTextView
179  * @text: the new text
180  *
181  * Sets the placeholder text in @text_view to @text.
182  *
183  * Since: 2.2
184  */
185 void
186 hildon_text_view_set_placeholder                (HildonTextView *text_view,
187                                                  const gchar    *text)
188 {
189     HildonTextViewPrivate *priv;
190
191     g_return_if_fail (HILDON_IS_TEXT_VIEW (text_view) && text != NULL);
192
193     priv = HILDON_TEXT_VIEW_GET_PRIVATE (text_view);
194
195     gtk_text_buffer_set_text (priv->placeholder_buffer, text, -1);
196 }
197
198 /**
199  * hildon_text_view_new:
200  *
201  * Creates a new text view.
202  *
203  * Returns: a new #HildonTextView
204  *
205  * Since: 2.2
206  */
207 GtkWidget *
208 hildon_text_view_new                            (void)
209 {
210     GtkWidget *entry = g_object_new (HILDON_TYPE_TEXT_VIEW, NULL);
211
212     return entry;
213 }
214
215 static gboolean
216 hildon_text_view_focus_in_event                 (GtkWidget     *widget,
217                                                  GdkEventFocus *event)
218 {
219     hildon_text_view_refresh_contents (widget);
220
221     if (GTK_WIDGET_CLASS (hildon_text_view_parent_class)->focus_in_event) {
222         return GTK_WIDGET_CLASS (hildon_text_view_parent_class)->focus_in_event (widget, event);
223     } else {
224         return FALSE;
225     }
226 }
227
228 static gboolean
229 hildon_text_view_focus_out_event                (GtkWidget     *widget,
230                                                  GdkEventFocus *event)
231 {
232     hildon_text_view_refresh_contents (widget);
233
234     if (GTK_WIDGET_CLASS (hildon_text_view_parent_class)->focus_out_event) {
235         return GTK_WIDGET_CLASS (hildon_text_view_parent_class)->focus_out_event (widget, event);
236     } else {
237         return FALSE;
238     }
239 }
240
241 static gint
242 hildon_text_view_button_press_event             (GtkWidget        *widget,
243                                                  GdkEventButton   *event)
244 {
245     HildonTextViewPrivate *priv = HILDON_TEXT_VIEW_GET_PRIVATE (widget);
246
247     gtk_widget_grab_focus (widget);
248
249     if (GTK_TEXT_VIEW (widget)->editable &&
250         hildon_gtk_im_context_filter_event (GTK_TEXT_VIEW (widget)->im_context, (GdkEvent*)event)) {
251         GTK_TEXT_VIEW (widget)->need_im_reset = TRUE;
252         return TRUE;
253     }
254
255     if (event->button == 1 && event->type == GDK_BUTTON_PRESS) {
256         priv->x = event->x;
257         priv->y = event->y;
258
259         return TRUE;
260     }
261
262     return FALSE;
263 }
264
265 static gint
266 hildon_text_view_button_release_event           (GtkWidget        *widget,
267                                                  GdkEventButton   *event)
268 {
269     GtkTextView *text_view = GTK_TEXT_VIEW (widget);
270     HildonTextViewPrivate *priv = HILDON_TEXT_VIEW_GET_PRIVATE (widget);
271     GtkTextIter iter;
272     gint x, y;
273
274     if (text_view->editable &&
275         hildon_gtk_im_context_filter_event (text_view->im_context, (GdkEvent*)event)) {
276         text_view->need_im_reset = TRUE;
277         return TRUE;
278     }
279
280     if (event->button == 1 && event->type == GDK_BUTTON_RELEASE) {
281         if (fabs (priv->x - event->x) < HILDON_TEXT_VIEW_DRAG_THRESHOLD &&
282             fabs (priv->y - event->y) < HILDON_TEXT_VIEW_DRAG_THRESHOLD) {
283             GtkTextWindowType window_type;
284
285             window_type = gtk_text_view_get_window_type (text_view, event->window);
286             gtk_text_view_window_to_buffer_coords (text_view,
287                                                    window_type,
288                                                    event->x, event->y,
289                                                    &x, &y);
290             gtk_text_view_get_iter_at_location (text_view, &iter, x, y);
291             if (gtk_text_buffer_get_char_count (priv->main_buffer))
292                 gtk_text_buffer_place_cursor (priv->main_buffer, &iter);
293
294             gtk_widget_grab_focus (GTK_WIDGET (text_view));
295
296             return TRUE;
297         }
298     }
299     return FALSE;
300 }
301
302 static void
303 hildon_text_view_finalize                       (GObject *object)
304 {
305     HildonTextViewPrivate *priv = HILDON_TEXT_VIEW_GET_PRIVATE (object);
306
307     g_signal_handler_disconnect (priv->main_buffer, priv->changed_id);
308     g_object_unref (priv->main_buffer);
309     g_object_unref (priv->placeholder_buffer);
310
311     if (G_OBJECT_CLASS (hildon_text_view_parent_class)->finalize)
312         G_OBJECT_CLASS (hildon_text_view_parent_class)->finalize (object);
313 }
314
315 static void
316 hildon_text_view_class_init                     (HildonTextViewClass *klass)
317 {
318     GObjectClass *gobject_class = (GObjectClass *)klass;
319     GtkWidgetClass *widget_class = (GtkWidgetClass *)klass;
320
321     gobject_class->finalize = hildon_text_view_finalize;
322     widget_class->focus_in_event = hildon_text_view_focus_in_event;
323     widget_class->focus_out_event = hildon_text_view_focus_out_event;
324     widget_class->motion_notify_event = NULL;
325     widget_class->button_press_event = hildon_text_view_button_press_event;
326     widget_class->button_release_event = hildon_text_view_button_release_event;
327
328     g_type_class_add_private (klass, sizeof (HildonTextViewPrivate));
329 }
330
331 static void
332 hildon_text_view_init                           (HildonTextView *self)
333 {
334     HildonTextViewPrivate *priv = HILDON_TEXT_VIEW_GET_PRIVATE (self);
335
336     priv->main_buffer = gtk_text_buffer_new (NULL);
337     priv->placeholder_buffer = gtk_text_buffer_new (NULL);
338
339     hildon_text_view_refresh_contents (GTK_WIDGET (self));
340
341     priv->changed_id =
342         g_signal_connect_swapped (priv->main_buffer, "changed",
343                                   G_CALLBACK (hildon_text_view_refresh_contents), self);
344 }