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