1 /* gtkdatatextview - data textview widget, based on GtkTextView
2  * Copyright 2011  Fredy Paquet <fredy@opag.ch>
3  *
4  * This library is free software; you can redistribute it and/or
5  * modify it under the terms of the GNU Library General Public
6  * License as published by the Free Software Foundation; either
7  * version 2 of the License, or (at your option) any later version.
8  *
9  * This library is distributed in the hope that it will be useful,
10  * but WITHOUT ANY WARRANTY; without even the implied warranty of
11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
12  * Library General Public License for more details.
13  *
14  * You should have received a copy of the GNU Library General Public
15  * License along with this library; if not, write to the
16  * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
17  * Boston, MA 02111-1307, USA.
18  */
19 
20 #include <stdlib.h>
21 #include <stdio.h>
22 #include <string.h>
23 #include <math.h>
24 #include <gtk/gtk.h>
25 
26 #define __GTKEXTRA_H_INSIDE__
27 
28 #include "gtkextra-compat.h"
29 #include "gtkdatatextview.h"
30 #include "gtkdataformat.h"
31 
32 /**
33  * SECTION: gtkdatatextview
34  * @short_description: a data textview widget, based on
35  *      	     GtkTextView
36  *
37  * GtkDataTextView provides additional properties:
38  *
39  * - #GtkDataTextView:description - no functionality, a place for
40  *   private information that cannot be put anywhere else
41  *
42  * - #GtkDataTextView:max-length - set the maximum character
43  *   length for the contents of the widget.
44  *
45  * - #GtkDataTextView:max-length-bytes - set the maximum byte
46  *   length for the contents of the widget.
47  *
48  * The main reason for this widget is to provide a length limit
49  * for text contents, required by SQL database systems. There
50  * is always a limit, no matter what you do.
51  *
52  * Some database systems may handle character length of UTF-8
53  * strings correctly, others may not. Choose the appropriate
54  * limit for your system, characters or bytes.
55  *
56  * Note that setting a byte length limit > 0 on a datatextview
57  * may slow down text insertions. The  byte length limit is
58  * imposed upon gtk_text_buffer_get_text() including invisible
59  * content. See gtk_text_buffer_get_text() for details.
60  *
61  * Since: 3.0.6
62  */
63 
64 #undef GTK_DATA_TEXT_VIEW_DEBUG
65 
66 #ifdef DEBUG
67 #define GTK_DATA_TEXT_VIEW_DEBUG  0  /* define to activate debug output */
68 #endif
69 
70 #if GTK_DATA_TEXT_VIEW_DEBUG
71 #define GTK_DATA_TEXT_VIEW_DEBUG_SIGNAL  0  /* debug signal handlers */
72 #endif
73 
74 #define GTK_DATA_TEXT_VIEW_BUFFER_MAX_SIZE (G_MAXINT / 2)
75 #define GTK_DATA_TEXT_VIEW_COUNT_HIDDEN_BYTES  TRUE
76 
77 #define GTK_DATA_TEXT_VIEW_CUT_INSERTED_TEXT 1  /* 1=cut pasted text to max len, 0=refuse */
78 
79 enum
80 {
81     PROP_0,
82     PROP_DATA_TEXT_VIEW_DESCRIPTION,
83     PROP_DATA_TEXT_VIEW_MAX_LENGTH,
84     PROP_DATA_TEXT_VIEW_MAX_LENGTH_BYTES,
85 } GTK_DATA_TEXT_VIEW_PROPERTIES;
86 
87 enum
88 {
89     LAST_SIGNAL
90 } GTK_DATA_TEXT_VIEW_SIGNALS;
91 
92 static void gtk_data_text_view_class_init(GtkDataTextViewClass *klass);
93 static void gtk_data_text_view_init(GtkDataTextView *data);
94 
95 static GtkTextViewClass *parent_class = NULL;
96 
97 /**
98  * gtk_data_text_view_get_description:
99  * @data_text_view: a #GtkDataTextView
100  *
101  * Retrieves the #GtkDataTextView description.
102  *
103  * Returns: a pointer to the contents of the widget as a
104  *      string. This string points to internally allocated
105  *      storage in the widget and must not be freed, modified or
106  *      stored.
107  *
108  * Since: 3.0.6
109  **/
110 const gchar *
gtk_data_text_view_get_description(GtkDataTextView * data_text_view)111 gtk_data_text_view_get_description(GtkDataTextView *data_text_view)
112 {
113     g_return_val_if_fail(GTK_IS_DATA_TEXT_VIEW(data_text_view), NULL);
114     return data_text_view->description;
115 }
116 
117 /**
118  * gtk_data_text_view_set_description:
119  * @data_text_view:  a #GtkDataTextView
120  * @description:  the description or NULL
121  *
122  * Sets the GtkDataTextView description.
123  *
124  * Since: 3.0.6
125  */
gtk_data_text_view_set_description(GtkDataTextView * data_text_view,const gchar * description)126 void gtk_data_text_view_set_description(GtkDataTextView *data_text_view,
127     const gchar *description)
128 {
129     g_return_if_fail(data_text_view != NULL);
130     g_return_if_fail(GTK_IS_DATA_TEXT_VIEW(data_text_view));
131 
132     if (data_text_view->description)
133 	g_free(data_text_view->description);
134     data_text_view->description = g_strdup(description);
135 }
136 
137 /**
138  * gtk_data_text_view_get_max_length:
139  * @data_text_view: a #GtkDataTextView
140  *
141  * Retrieves the maximum character length for the contents of
142  * #GtkDataTextView.
143  *
144  * Returns: maximum byte length or 0.
145  *
146  * Since: 3.0.6
147  **/
148 gint
gtk_data_text_view_get_max_length(GtkDataTextView * data_text_view)149 gtk_data_text_view_get_max_length(GtkDataTextView *data_text_view)
150 {
151     g_return_val_if_fail(GTK_IS_DATA_TEXT_VIEW(data_text_view), 0);
152     return data_text_view->max_length;
153 }
154 
155 /**
156  * gtk_data_text_view_set_max_length:
157  * @data_text_view:  a #GtkDataTextView
158  * @max_length:  maximum character length or 0
159  *
160  * Sets the maximum character length for the contents of the
161  * #GtkDataTextView. Existing content will not be truncted.
162  *
163  * Since: 3.0.6
164  */
gtk_data_text_view_set_max_length(GtkDataTextView * data_text_view,gint max_length)165 void gtk_data_text_view_set_max_length(GtkDataTextView *data_text_view,
166     gint max_length)
167 {
168     g_return_if_fail(data_text_view != NULL);
169     g_return_if_fail(GTK_IS_DATA_TEXT_VIEW(data_text_view));
170 
171     if (max_length < 0)
172 	max_length = 0;
173 
174     if (max_length > GTK_DATA_TEXT_VIEW_BUFFER_MAX_SIZE)
175 	max_length = GTK_DATA_TEXT_VIEW_BUFFER_MAX_SIZE;
176 
177     data_text_view->max_length = max_length;
178 }
179 
180 /**
181  * gtk_data_text_view_get_max_length_bytes:
182  * @data_text_view: a #GtkDataTextView
183  *
184  * Retrieves the maximum byte length for the contents of
185  * #GtkDataTextView data_format.
186  *
187  * Returns: maximum byte length or 0.
188  *
189  * Since: 3.0.6
190  **/
191 gint
gtk_data_text_view_get_max_length_bytes(GtkDataTextView * data_text_view)192 gtk_data_text_view_get_max_length_bytes(GtkDataTextView *data_text_view)
193 {
194     g_return_val_if_fail(GTK_IS_DATA_TEXT_VIEW(data_text_view), 0);
195     return data_text_view->max_length_bytes;
196 }
197 
198 /**
199  * gtk_data_text_view_set_max_length_bytes:
200  * @data_text_view:  a #GtkDataTextView
201  * @max_length_bytes:  maximum byte length or 0
202  *
203  * Sets the maximum byte length for the contents of the
204  * GtkDataTextView. Existing content will not be truncted.
205  *
206  * Since: 3.0.6
207  */
gtk_data_text_view_set_max_length_bytes(GtkDataTextView * data_text_view,gint max_length_bytes)208 void gtk_data_text_view_set_max_length_bytes(GtkDataTextView *data_text_view,
209     gint max_length_bytes)
210 {
211     g_return_if_fail(data_text_view != NULL);
212     g_return_if_fail(GTK_IS_DATA_TEXT_VIEW(data_text_view));
213 
214     if (max_length_bytes < 0)
215 	max_length_bytes = 0;
216 
217     if (max_length_bytes > GTK_DATA_TEXT_VIEW_BUFFER_MAX_SIZE)
218 	max_length_bytes = GTK_DATA_TEXT_VIEW_BUFFER_MAX_SIZE;
219 
220     data_text_view->max_length_bytes = max_length_bytes;
221 }
222 
223 
224 
225 
226 
227 GType
gtk_data_text_view_get_type(void)228 gtk_data_text_view_get_type(void)
229 {
230     static GType data_text_view_type = 0;
231 
232     if (!data_text_view_type)
233     {
234 	static const GInterfaceInfo interface_info = {
235 	    (GInterfaceInitFunc)NULL,
236 	    (GInterfaceFinalizeFunc)NULL,
237 	    (gpointer)NULL
238 	};
239 
240 	data_text_view_type = g_type_register_static_simple(
241 	    gtk_text_view_get_type(),
242 	    "GtkDataTextView",
243 	    sizeof(GtkDataTextViewClass),
244 	    (GClassInitFunc)gtk_data_text_view_class_init,
245 	    sizeof(GtkDataTextView),
246 	    (GInstanceInitFunc)gtk_data_text_view_init,
247 	    0);
248 
249 	g_type_add_interface_static(data_text_view_type,
250 	    GTK_TYPE_BUILDABLE,
251 	    &interface_info);
252     }
253     return (data_text_view_type);
254 }
255 
256 
257 static void
gtk_data_text_view_set_property(GObject * object,guint prop_id,const GValue * value,GParamSpec * pspec)258 gtk_data_text_view_set_property(GObject *object,
259     guint prop_id,
260     const GValue *value,
261     GParamSpec *pspec)
262 {
263     GtkDataTextView *data_text_view = GTK_DATA_TEXT_VIEW(object);
264 
265     switch(prop_id)
266     {
267 	case PROP_DATA_TEXT_VIEW_DESCRIPTION:
268 	    {
269 		const gchar *description = g_value_get_string(value);
270 
271 		if (!gtk_widget_get_realized(GTK_WIDGET(data_text_view)))
272 		{
273 		    if (data_text_view->description)
274 			g_free(data_text_view->description);
275 		    data_text_view->description = g_strdup(description);
276 		}
277 		else
278 		{
279 		    gtk_data_text_view_set_description(data_text_view, description);
280 		}
281 	    }
282 	    break;
283 
284 	case PROP_DATA_TEXT_VIEW_MAX_LENGTH:
285 	    gtk_data_text_view_set_max_length(data_text_view, g_value_get_int(value));
286 	    break;
287 
288 	case PROP_DATA_TEXT_VIEW_MAX_LENGTH_BYTES:
289 	    gtk_data_text_view_set_max_length_bytes(data_text_view, g_value_get_int(value));
290 	    break;
291 
292 	default:
293 	    G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec);
294 	    break;
295     }
296 }
297 
298 static void
gtk_data_text_view_get_property(GObject * object,guint prop_id,GValue * value,GParamSpec * pspec)299 gtk_data_text_view_get_property(GObject *object,
300     guint prop_id,
301     GValue *value,
302     GParamSpec *pspec)
303 {
304     GtkDataTextView *data_text_view = GTK_DATA_TEXT_VIEW(object);
305 
306     switch(prop_id)
307     {
308 	case PROP_DATA_TEXT_VIEW_DESCRIPTION:
309 	    g_value_set_string(value, data_text_view->description);
310 	    break;
311 
312 	case PROP_DATA_TEXT_VIEW_MAX_LENGTH:
313 	    g_value_set_int(value, gtk_data_text_view_get_max_length(data_text_view));
314 	    break;
315 
316 	case PROP_DATA_TEXT_VIEW_MAX_LENGTH_BYTES:
317 	    g_value_set_int(value, gtk_data_text_view_get_max_length_bytes(data_text_view));
318 	    break;
319 
320 	default:
321 	    G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec);
322 	    break;
323     }
324 }
325 
326 static void
gtk_data_text_view_class_init(GtkDataTextViewClass * klass)327 gtk_data_text_view_class_init(GtkDataTextViewClass *klass)
328 {
329     GObjectClass *gobject_class = G_OBJECT_CLASS(klass);
330 #if 0
331     GtkWidgetClass *widget_class = GTK_WIDGET_CLASS(klass);
332     GtkObjectClass *object_class = GTK_OBJECT_CLASS (klass);
333     GtkTextViewClass *text_view_class = GTK_TEXT_VIEW_CLASS (klass);
334 #endif
335 
336     parent_class = g_type_class_ref(gtk_text_view_get_type());
337 
338     gobject_class->set_property = gtk_data_text_view_set_property;
339     gobject_class->get_property = gtk_data_text_view_get_property;
340 
341     /**
342      * GtkDataTextView:description:
343      *
344      * Description of the GtkDataTextView, no functionality, a place
345      * for private information that cannot be put anywhere else.
346     *
347     * Since: 3.0.6
348      */
349     g_object_class_install_property(gobject_class,
350 	PROP_DATA_TEXT_VIEW_DESCRIPTION,
351 	g_param_spec_string("description",
352 	    "Description",
353 	    "Description of textview contents",
354 	    "" /* default value */,
355 	    G_PARAM_READWRITE));
356 
357     /**
358      * GtkDataTextView:max-length:
359      *
360      * Set the maximum length in characters for the GtkDataTextView.
361      * For details see #gtk_data_text_view_set_max_length.
362      *
363      * Sometimes, systems cannot handle UTF-8 string length
364      * correctly, to overcome this problem, you can use the maximum
365      * string length in bytes. When setting both limits, max-length
366      *  and max-length-bytes, both must be fulfilled.
367      *
368      * Since: 3.0.6
369      */
370     g_object_class_install_property(gobject_class,
371 	PROP_DATA_TEXT_VIEW_MAX_LENGTH,
372 	g_param_spec_int("max-length",
373 	    "Maximum character length",
374 	    "The maximum number of characters for this textview. Zero if no maximum",
375 
376 	    0, GTK_DATA_TEXT_VIEW_BUFFER_MAX_SIZE,
377 	    0 /* default value */,
378 	    G_PARAM_READWRITE));
379 
380     /**
381      * GtkDataTextView:max-length-bytes:
382      *
383      * Set the maximum length in bytes for the GtkDataTextView. For
384      * details see #gtk_data_text_view_set_max_length_bytes.
385      *
386      * Sometimes, systems cannot handle UTF-8 string length
387      * correctly, to overcome this problem, you can use the maximum
388      * string length in bytes. When setting both limits, max-length
389      *  and max-length-bytes, both must be fulfilled.
390      *
391      * Since: 3.0.6
392      */
393     g_object_class_install_property(gobject_class,
394 	PROP_DATA_TEXT_VIEW_MAX_LENGTH_BYTES,
395 	g_param_spec_int("max-length-bytes",
396 	    "Maximum bytes length",
397 	    "The maximum number of bytes for this textview. Zero if no maximum",
398 
399 	    0, GTK_DATA_TEXT_VIEW_BUFFER_MAX_SIZE,
400 	    0 /* default value */,
401 	    G_PARAM_READWRITE));
402 }
403 
404 /* Signal interception */
405 
_gtk_data_text_view_insert_text_handler(GtkTextBuffer * textbuffer,GtkTextIter * location,gchar * new_text,gint new_text_len_bytes,gpointer user_data)406 static void _gtk_data_text_view_insert_text_handler(GtkTextBuffer *textbuffer,
407     GtkTextIter *location, gchar *new_text, gint new_text_len_bytes,
408     gpointer user_data)
409 {
410     GtkDataTextView *data_text_view = GTK_DATA_TEXT_VIEW(user_data);
411     GtkTextView *text_view = GTK_TEXT_VIEW(user_data);
412     GtkTextBuffer *buffer = gtk_text_view_get_buffer(text_view);
413 
414     if (new_text_len_bytes < 0) new_text_len_bytes = strlen(new_text);
415 
416     gint max_len_chars = data_text_view->max_length;
417 
418     if (max_len_chars)
419     {
420 	gint old_length_chars = gtk_text_buffer_get_char_count(buffer);
421 	gint new_text_length_chars = g_utf8_strlen(new_text, new_text_len_bytes);
422 
423 #if GTK_DATA_TEXT_VIEW_DEBUG_SIGNAL > 0
424 	g_debug("_gtk_data_text_view_insert_text_handler(chars): cl %d max %d new %d",
425 	    old_length_chars, max_len_chars, new_text_length_chars);
426 #endif
427 
428 	if (old_length_chars + new_text_length_chars > max_len_chars)
429 	{
430 #if GTK_DATA_TEXT_VIEW_CUT_INSERTED_TEXT > 0
431 	    gint remaining_chars = max_len_chars - old_length_chars;
432 	    if (remaining_chars > 0)
433 	    {
434 		gchar *cp = g_malloc0(new_text_len_bytes);
435 		g_utf8_strncpy(cp, new_text, remaining_chars);
436 		gtk_text_buffer_insert(textbuffer, location, cp, -1);
437 		g_free(cp);
438 	    }
439 #endif
440 	    gdk_beep();
441 	    g_signal_stop_emission_by_name(textbuffer, "insert-text");
442 	}
443     }
444 
445     gint max_len_bytes = data_text_view->max_length_bytes;
446 
447     if (max_len_bytes)
448     {
449 	GtkTextIter start, end;
450 
451 	gtk_text_buffer_get_start_iter(buffer, &start);
452 	gtk_text_buffer_get_end_iter(buffer, &end);
453 
454 	gchar *old_text = gtk_text_buffer_get_text(buffer,
455 	    &start, &end, GTK_DATA_TEXT_VIEW_COUNT_HIDDEN_BYTES);
456 	gint old_length_bytes = old_text ? strlen(old_text) : 0;
457 	g_free(old_text);
458 
459 #if GTK_DATA_TEXT_VIEW_DEBUG_SIGNAL > 0
460 	g_debug("_gtk_data_text_view_insert_text_handler(bytes): cl %d max %d new %d",
461 	    old_length_bytes, max_len_bytes, new_text_len_bytes);
462 #endif
463 
464 	if (old_length_bytes + new_text_len_bytes > max_len_bytes)
465 	{
466 #if GTK_DATA_TEXT_VIEW_CUT_INSERTED_TEXT > 0
467 	    gint remaining_bytes = max_len_bytes - old_length_bytes;
468 	    if (remaining_bytes > 0)
469 	    {
470 		gchar *bpxx = &new_text[remaining_bytes];  /* byte position, may be invalid */
471 		gchar *cpe = g_utf8_find_prev_char(new_text, bpxx);
472 		if (cpe)
473 		{
474 		    gchar *cpn = g_utf8_find_next_char(cpe, NULL);
475 		    if (cpn && cpn <= bpxx) cpe = bpxx;
476 
477 		    gchar *cp = g_malloc0(new_text_len_bytes);
478 		    strncpy(cp, new_text, cpe-new_text);
479 		    gtk_text_buffer_insert(textbuffer, location, cp, -1);
480 		    g_free(cp);
481 		}
482 	    }
483 #endif
484 	    gdk_beep();
485 	    g_signal_stop_emission_by_name(textbuffer, "insert-text");
486 	}
487     }
488 }
489 
490 
491 static void
gtk_data_text_view_init(GtkDataTextView * data_text_view)492 gtk_data_text_view_init(GtkDataTextView *data_text_view)
493 {
494 #if 0
495     GtkWidget *widget = GTK_WIDGET(data_text_view);
496 #endif
497 
498     data_text_view->description = NULL;
499     data_text_view->max_length = 0;
500     data_text_view->max_length_bytes = 0;
501 
502 #if GTK_DATA_TEXT_VIEW_DEBUG > 0
503     g_debug("gtk_data_text_view_init");
504 #endif
505 
506     GtkTextBuffer *buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(data_text_view));
507 
508     g_signal_connect(buffer, "insert-text",
509 	G_CALLBACK(_gtk_data_text_view_insert_text_handler),
510 	data_text_view);
511 }
512 
513 /**
514  * gtk_data_text_view_new:
515  *
516  * Creates a new GtkDataTextView Widget.
517  *
518  * Returns: the new GtkDataTextView Widget
519  *
520  * Since: 3.0.6
521  */
522 GtkDataTextView *
gtk_data_text_view_new(void)523 gtk_data_text_view_new(void)
524 {
525     GtkDataTextView *data_text_view = GTK_DATA_TEXT_VIEW(
526 	gtk_widget_new(gtk_data_text_view_get_type(), NULL));
527 
528     return (data_text_view);
529 }
530 
531 
532