Remove all calls to hildon_helper_set_logical_* from within Hildon
[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 <math.h>
58
59 #define HILDON_TEXT_VIEW_DRAG_THRESHOLD 16.0
60
61 G_DEFINE_TYPE                                   (HildonTextView, hildon_text_view, GTK_TYPE_TEXT_VIEW);
62
63 #define                                         HILDON_TEXT_VIEW_GET_PRIVATE(obj) \
64                                                 (G_TYPE_INSTANCE_GET_PRIVATE ((obj), \
65                                                 HILDON_TYPE_TEXT_VIEW, HildonTextViewPrivate));
66
67 typedef struct                                  _HildonTextViewPrivate HildonTextViewPrivate;
68
69 struct                                          _HildonTextViewPrivate
70 {
71     GtkTextBuffer *main_buffer;                   /* Used to show the "real" contents */
72     GtkTextBuffer *placeholder_buffer;   /* Internal, used to display the placeholder */
73     gulong changed_id;               /* ID of the main_buffer::changed signal handler */
74     gdouble x;                                                      /* tap x position */
75     gdouble y;                                                      /* tap y position */
76     guint showing_placeholder : 1;          /* Whether the placeholder is being shown */
77     guint setting_style : 1;                /* Whether the logical color is being set */
78 };
79
80
81 static void
82 set_logical_color                               (GtkWidget *widget)
83 {
84     GdkColor color;
85     const gchar *colorname;
86     HildonTextViewPrivate *priv = HILDON_TEXT_VIEW_GET_PRIVATE (widget);
87
88     colorname = priv->showing_placeholder ? "ReversedSecondaryTextColor" : "ReversedTextColor";
89
90     gtk_widget_ensure_style (widget);
91     if (gtk_style_lookup_color (widget->style, colorname, &color) == TRUE) {
92         priv->setting_style = TRUE;
93         gtk_widget_modify_text (widget, GTK_STATE_NORMAL, &color);
94         priv->setting_style = FALSE;
95     }
96 }
97
98 static void
99 hildon_text_view_style_set                      (GtkWidget *widget,
100                                                  GtkStyle  *previous_style)
101 {
102     HildonTextViewPrivate *priv = HILDON_TEXT_VIEW_GET_PRIVATE (widget);
103
104     if (GTK_WIDGET_CLASS (hildon_text_view_parent_class)->style_set)
105         GTK_WIDGET_CLASS (hildon_text_view_parent_class)->style_set (widget, previous_style);
106
107     /* Prevent infinite recursion when calling set_logical_font() and
108      * set_logical_color() */
109     if (priv->setting_style)
110         return;
111
112     set_logical_color (widget);
113 }
114
115 /* Function used to decide whether to show the placeholder or not */
116 static void
117 hildon_text_view_refresh_contents               (GtkWidget *text_view)
118 {
119     HildonTextViewPrivate *priv = HILDON_TEXT_VIEW_GET_PRIVATE (text_view);
120     gint bufsize = gtk_text_buffer_get_char_count (priv->main_buffer);
121
122     /* Display the main buffer if it contains text or the widget is focused */
123     priv->showing_placeholder = (bufsize <= 0) && !(GTK_WIDGET_HAS_FOCUS (text_view));
124
125     set_logical_color (text_view);
126
127     if (priv->showing_placeholder) {
128         gtk_text_view_set_buffer (GTK_TEXT_VIEW (text_view), priv->placeholder_buffer);
129     } else {
130         gtk_text_view_set_buffer (GTK_TEXT_VIEW (text_view), priv->main_buffer);
131     }
132 }
133
134 /**
135  * hildon_text_view_set_buffer:
136  * @text_view: a #HildonTextView
137  * @buffer: a #GtkTextBuffer
138  *
139  * Sets @buffer as the buffer being displayed by @text_view. The
140  * previous buffer displayed by the text view is unreferenced, and a
141  * reference is added to @buffer. If you owned a reference to @buffer
142  * before passing it to this function, you must remove that reference
143  * yourself
144  *
145  * Note that you must never use gtk_text_view_set_buffer() to set the
146  * buffer of a #HildonTextView.
147  *
148  * Since: 2.2
149  */
150 void
151 hildon_text_view_set_buffer                     (HildonTextView *text_view,
152                                                  GtkTextBuffer  *buffer)
153 {
154     HildonTextViewPrivate *priv;
155
156     g_return_if_fail (HILDON_IS_TEXT_VIEW (text_view));
157     g_return_if_fail (GTK_IS_TEXT_BUFFER (buffer));
158
159     priv = HILDON_TEXT_VIEW_GET_PRIVATE (text_view);
160
161     /* If this is the same buffer, don't do anything */
162     if (buffer == priv->main_buffer)
163         return;
164
165     /* Disconnect the signal handler from the old buffer */
166     g_signal_handler_disconnect (priv->main_buffer, priv->changed_id);
167
168     /* Replace the old buffer with the new one */
169     g_object_unref (priv->main_buffer);
170     priv->main_buffer = g_object_ref (buffer);
171
172     /* Attach a callback to the new text buffer */
173     priv->changed_id =
174         g_signal_connect_swapped (priv->main_buffer, "changed",
175                                   G_CALLBACK (hildon_text_view_refresh_contents), text_view);
176
177     /* Refresh textview contents */
178     hildon_text_view_refresh_contents (GTK_WIDGET (text_view));
179 }
180
181 /**
182  * hildon_text_view_get_buffer:
183  * @text_view: a #HildonTextView
184  *
185  * Returns the text buffer in @text_view. The reference count is not
186  * incremented; the caller of this function won't own a new reference.
187  *
188  * Note that you must never use gtk_text_view_get_buffer() to get the
189  * buffer from a #HildonTextView.
190  *
191  * Also note that placeholder text (set using
192  * hildon_text_view_set_placeholder()) is never contained in this
193  * buffer.
194  *
195  * Returns: a #GtkTextBuffer
196  *
197  * Since: 2.2
198  */
199 GtkTextBuffer *
200 hildon_text_view_get_buffer                     (HildonTextView *text_view)
201 {
202     HildonTextViewPrivate *priv;
203
204     g_return_val_if_fail (HILDON_IS_TEXT_VIEW (text_view), NULL);
205
206     priv = HILDON_TEXT_VIEW_GET_PRIVATE (text_view);
207
208     /* Always return priv->main_buffer even if the placeholder is
209      * being displayed */
210     return priv->main_buffer;
211 }
212
213 /**
214  * hildon_text_view_set_placeholder:
215  * @text_view: a #HildonTextView
216  * @text: the new text
217  *
218  * Sets the placeholder text in @text_view to @text.
219  *
220  * Since: 2.2
221  */
222 void
223 hildon_text_view_set_placeholder                (HildonTextView *text_view,
224                                                  const gchar    *text)
225 {
226     HildonTextViewPrivate *priv;
227
228     g_return_if_fail (HILDON_IS_TEXT_VIEW (text_view) && text != NULL);
229
230     priv = HILDON_TEXT_VIEW_GET_PRIVATE (text_view);
231
232     gtk_text_buffer_set_text (priv->placeholder_buffer, text, -1);
233 }
234
235 /**
236  * hildon_text_view_new:
237  *
238  * Creates a new text view.
239  *
240  * Returns: a new #HildonTextView
241  *
242  * Since: 2.2
243  */
244 GtkWidget *
245 hildon_text_view_new                            (void)
246 {
247     GtkWidget *entry = g_object_new (HILDON_TYPE_TEXT_VIEW, NULL);
248
249     return entry;
250 }
251
252 static gboolean
253 hildon_text_view_focus_in_event                 (GtkWidget     *widget,
254                                                  GdkEventFocus *event)
255 {
256     hildon_text_view_refresh_contents (widget);
257
258     if (GTK_WIDGET_CLASS (hildon_text_view_parent_class)->focus_in_event) {
259         return GTK_WIDGET_CLASS (hildon_text_view_parent_class)->focus_in_event (widget, event);
260     } else {
261         return FALSE;
262     }
263 }
264
265 static gboolean
266 hildon_text_view_focus_out_event                (GtkWidget     *widget,
267                                                  GdkEventFocus *event)
268 {
269     hildon_text_view_refresh_contents (widget);
270
271     if (GTK_WIDGET_CLASS (hildon_text_view_parent_class)->focus_out_event) {
272         return GTK_WIDGET_CLASS (hildon_text_view_parent_class)->focus_out_event (widget, event);
273     } else {
274         return FALSE;
275     }
276 }
277
278 static gint
279 hildon_text_view_button_press_event             (GtkWidget        *widget,
280                                                  GdkEventButton   *event)
281 {
282     HildonTextViewPrivate *priv = HILDON_TEXT_VIEW_GET_PRIVATE (widget);
283
284     gtk_widget_grab_focus (widget);
285
286     if (GTK_TEXT_VIEW (widget)->editable &&
287         hildon_gtk_im_context_filter_event (GTK_TEXT_VIEW (widget)->im_context, (GdkEvent*)event)) {
288         GTK_TEXT_VIEW (widget)->need_im_reset = TRUE;
289         return TRUE;
290     }
291
292     if (event->button == 1 && event->type == GDK_BUTTON_PRESS) {
293         priv->x = event->x;
294         priv->y = event->y;
295
296         return TRUE;
297     }
298
299     return FALSE;
300 }
301
302 static gint
303 hildon_text_view_button_release_event           (GtkWidget        *widget,
304                                                  GdkEventButton   *event)
305 {
306     GtkTextView *text_view = GTK_TEXT_VIEW (widget);
307     HildonTextViewPrivate *priv = HILDON_TEXT_VIEW_GET_PRIVATE (widget);
308     GtkTextIter iter;
309     gint x, y;
310
311     if (text_view->editable &&
312         hildon_gtk_im_context_filter_event (text_view->im_context, (GdkEvent*)event)) {
313         text_view->need_im_reset = TRUE;
314         return TRUE;
315     }
316
317     if (event->button == 1 && event->type == GDK_BUTTON_RELEASE) {
318         if (fabs (priv->x - event->x) < HILDON_TEXT_VIEW_DRAG_THRESHOLD &&
319             fabs (priv->y - event->y) < HILDON_TEXT_VIEW_DRAG_THRESHOLD) {
320             GtkTextWindowType window_type;
321
322             window_type = gtk_text_view_get_window_type (text_view, event->window);
323             gtk_text_view_window_to_buffer_coords (text_view,
324                                                    window_type,
325                                                    event->x, event->y,
326                                                    &x, &y);
327             gtk_text_view_get_iter_at_location (text_view, &iter, x, y);
328             if (gtk_text_buffer_get_char_count (priv->main_buffer))
329                 gtk_text_buffer_place_cursor (priv->main_buffer, &iter);
330
331             gtk_widget_grab_focus (GTK_WIDGET (text_view));
332
333             return TRUE;
334         }
335     }
336     return FALSE;
337 }
338
339 static void
340 hildon_text_view_finalize                       (GObject *object)
341 {
342     HildonTextViewPrivate *priv = HILDON_TEXT_VIEW_GET_PRIVATE (object);
343
344     g_signal_handler_disconnect (priv->main_buffer, priv->changed_id);
345     g_object_unref (priv->main_buffer);
346     g_object_unref (priv->placeholder_buffer);
347
348     if (G_OBJECT_CLASS (hildon_text_view_parent_class)->finalize)
349         G_OBJECT_CLASS (hildon_text_view_parent_class)->finalize (object);
350 }
351
352 static void
353 hildon_text_view_class_init                     (HildonTextViewClass *klass)
354 {
355     GObjectClass *gobject_class = (GObjectClass *)klass;
356     GtkWidgetClass *widget_class = (GtkWidgetClass *)klass;
357
358     gobject_class->finalize = hildon_text_view_finalize;
359     widget_class->focus_in_event = hildon_text_view_focus_in_event;
360     widget_class->focus_out_event = hildon_text_view_focus_out_event;
361     widget_class->motion_notify_event = NULL;
362     widget_class->button_press_event = hildon_text_view_button_press_event;
363     widget_class->button_release_event = hildon_text_view_button_release_event;
364     widget_class->style_set = hildon_text_view_style_set;
365
366     g_type_class_add_private (klass, sizeof (HildonTextViewPrivate));
367 }
368
369 static void
370 hildon_text_view_init                           (HildonTextView *self)
371 {
372     HildonTextViewPrivate *priv = HILDON_TEXT_VIEW_GET_PRIVATE (self);
373
374     priv->main_buffer = gtk_text_buffer_new (NULL);
375     priv->placeholder_buffer = gtk_text_buffer_new (NULL);
376     priv->showing_placeholder = FALSE;
377     priv->setting_style = FALSE;
378
379     hildon_text_view_refresh_contents (GTK_WIDGET (self));
380
381     priv->changed_id =
382         g_signal_connect_swapped (priv->main_buffer, "changed",
383                                   G_CALLBACK (hildon_text_view_refresh_contents), self);
384 }