1 | /* gtkentrybuffer.c |
2 | * Copyright (C) 2009 Stefan Walter <stef@memberwebs.com> |
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, see <http://www.gnu.org/licenses/>. |
16 | */ |
17 | |
18 | #include "config.h" |
19 | |
20 | #include "gtkentrybuffer.h" |
21 | #include "gtkintl.h" |
22 | #include "gtkmarshalers.h" |
23 | #include "gtkprivate.h" |
24 | #include "gtkwidget.h" |
25 | |
26 | #include <gdk/gdk.h> |
27 | |
28 | #include <string.h> |
29 | |
30 | /** |
31 | * GtkEntryBuffer: |
32 | * |
33 | * A `GtkEntryBuffer` hold the text displayed in a `GtkText` widget. |
34 | * |
35 | * A single `GtkEntryBuffer` object can be shared by multiple widgets |
36 | * which will then share the same text content, but not the cursor |
37 | * position, visibility attributes, icon etc. |
38 | * |
39 | * `GtkEntryBuffer` may be derived from. Such a derived class might allow |
40 | * text to be stored in an alternate location, such as non-pageable memory, |
41 | * useful in the case of important passwords. Or a derived class could |
42 | * integrate with an application’s concept of undo/redo. |
43 | */ |
44 | |
45 | /* Initial size of buffer, in bytes */ |
46 | #define MIN_SIZE 16 |
47 | |
48 | enum { |
49 | PROP_0, |
50 | PROP_TEXT, |
51 | PROP_LENGTH, |
52 | PROP_MAX_LENGTH, |
53 | NUM_PROPERTIES |
54 | }; |
55 | |
56 | static GParamSpec *entry_buffer_props[NUM_PROPERTIES] = { NULL, }; |
57 | |
58 | enum { |
59 | INSERTED_TEXT, |
60 | DELETED_TEXT, |
61 | LAST_SIGNAL |
62 | }; |
63 | |
64 | static guint signals[LAST_SIGNAL] = { 0 }; |
65 | |
66 | typedef struct _GtkEntryBufferPrivate GtkEntryBufferPrivate; |
67 | struct _GtkEntryBufferPrivate |
68 | { |
69 | /* Only valid if this class is not derived */ |
70 | char *normal_text; |
71 | gsize normal_text_size; |
72 | gsize normal_text_bytes; |
73 | guint normal_text_chars; |
74 | |
75 | int max_length; |
76 | }; |
77 | |
78 | G_DEFINE_TYPE_WITH_PRIVATE (GtkEntryBuffer, gtk_entry_buffer, G_TYPE_OBJECT) |
79 | |
80 | /* -------------------------------------------------------------------------------- |
81 | * DEFAULT IMPLEMENTATIONS OF TEXT BUFFER |
82 | * |
83 | * These may be overridden by a derived class, behavior may be changed etc... |
84 | * The normal_text and normal_text_xxxx fields may not be valid when |
85 | * this class is derived from. |
86 | */ |
87 | |
88 | /* Overwrite a memory that might contain sensitive information. */ |
89 | static void |
90 | trash_area (char *area, |
91 | gsize len) |
92 | { |
93 | volatile char *varea = (volatile char *)area; |
94 | while (len-- > 0) |
95 | *varea++ = 0; |
96 | } |
97 | |
98 | static const char * |
99 | gtk_entry_buffer_normal_get_text (GtkEntryBuffer *buffer, |
100 | gsize *n_bytes) |
101 | { |
102 | GtkEntryBufferPrivate *priv = gtk_entry_buffer_get_instance_private (self: buffer); |
103 | |
104 | if (n_bytes) |
105 | *n_bytes = priv->normal_text_bytes; |
106 | |
107 | if (!priv->normal_text) |
108 | return "" ; |
109 | |
110 | return priv->normal_text; |
111 | } |
112 | |
113 | static guint |
114 | gtk_entry_buffer_normal_get_length (GtkEntryBuffer *buffer) |
115 | { |
116 | GtkEntryBufferPrivate *priv = gtk_entry_buffer_get_instance_private (self: buffer); |
117 | |
118 | return priv->normal_text_chars; |
119 | } |
120 | |
121 | static guint |
122 | gtk_entry_buffer_normal_insert_text (GtkEntryBuffer *buffer, |
123 | guint position, |
124 | const char *chars, |
125 | guint n_chars) |
126 | { |
127 | GtkEntryBufferPrivate *pv = gtk_entry_buffer_get_instance_private (self: buffer); |
128 | gsize prev_size; |
129 | gsize n_bytes; |
130 | gsize at; |
131 | |
132 | n_bytes = g_utf8_offset_to_pointer (str: chars, offset: n_chars) - chars; |
133 | |
134 | /* Need more memory */ |
135 | if (n_bytes + pv->normal_text_bytes + 1 > pv->normal_text_size) |
136 | { |
137 | char *et_new; |
138 | |
139 | prev_size = pv->normal_text_size; |
140 | |
141 | /* Calculate our new buffer size */ |
142 | while (n_bytes + pv->normal_text_bytes + 1 > pv->normal_text_size) |
143 | { |
144 | if (pv->normal_text_size == 0) |
145 | pv->normal_text_size = MIN_SIZE; |
146 | else |
147 | { |
148 | if (2 * pv->normal_text_size < GTK_ENTRY_BUFFER_MAX_SIZE) |
149 | pv->normal_text_size *= 2; |
150 | else |
151 | { |
152 | pv->normal_text_size = GTK_ENTRY_BUFFER_MAX_SIZE; |
153 | if (n_bytes > pv->normal_text_size - pv->normal_text_bytes - 1) |
154 | { |
155 | n_bytes = pv->normal_text_size - pv->normal_text_bytes - 1; |
156 | n_bytes = g_utf8_find_prev_char (str: chars, p: chars + n_bytes + 1) - chars; |
157 | n_chars = g_utf8_strlen (p: chars, max: n_bytes); |
158 | } |
159 | break; |
160 | } |
161 | } |
162 | } |
163 | |
164 | /* Could be a password, so can't leave stuff in memory. */ |
165 | et_new = g_malloc (n_bytes: pv->normal_text_size); |
166 | memcpy (dest: et_new, src: pv->normal_text, MIN (prev_size, pv->normal_text_size)); |
167 | trash_area (area: pv->normal_text, len: prev_size); |
168 | g_free (mem: pv->normal_text); |
169 | pv->normal_text = et_new; |
170 | } |
171 | |
172 | /* Actual text insertion */ |
173 | at = g_utf8_offset_to_pointer (str: pv->normal_text, offset: position) - pv->normal_text; |
174 | memmove (dest: pv->normal_text + at + n_bytes, src: pv->normal_text + at, n: pv->normal_text_bytes - at); |
175 | memcpy (dest: pv->normal_text + at, src: chars, n: n_bytes); |
176 | |
177 | /* Book keeping */ |
178 | pv->normal_text_bytes += n_bytes; |
179 | pv->normal_text_chars += n_chars; |
180 | pv->normal_text[pv->normal_text_bytes] = '\0'; |
181 | |
182 | gtk_entry_buffer_emit_inserted_text (buffer, position, chars, n_chars); |
183 | return n_chars; |
184 | } |
185 | |
186 | static guint |
187 | gtk_entry_buffer_normal_delete_text (GtkEntryBuffer *buffer, |
188 | guint position, |
189 | guint n_chars) |
190 | { |
191 | GtkEntryBufferPrivate *pv = gtk_entry_buffer_get_instance_private (self: buffer); |
192 | |
193 | if (position > pv->normal_text_chars) |
194 | position = pv->normal_text_chars; |
195 | if (position + n_chars > pv->normal_text_chars) |
196 | n_chars = pv->normal_text_chars - position; |
197 | |
198 | if (n_chars > 0) |
199 | gtk_entry_buffer_emit_deleted_text (buffer, position, n_chars); |
200 | |
201 | return n_chars; |
202 | } |
203 | |
204 | /* -------------------------------------------------------------------------------- |
205 | * |
206 | */ |
207 | |
208 | static void |
209 | gtk_entry_buffer_real_inserted_text (GtkEntryBuffer *buffer, |
210 | guint position, |
211 | const char *chars, |
212 | guint n_chars) |
213 | { |
214 | g_object_notify_by_pspec (G_OBJECT (buffer), pspec: entry_buffer_props[PROP_TEXT]); |
215 | g_object_notify_by_pspec (G_OBJECT (buffer), pspec: entry_buffer_props[PROP_LENGTH]); |
216 | } |
217 | |
218 | static void |
219 | gtk_entry_buffer_real_deleted_text (GtkEntryBuffer *buffer, |
220 | guint position, |
221 | guint n_chars) |
222 | { |
223 | GtkEntryBufferPrivate *pv = gtk_entry_buffer_get_instance_private (self: buffer); |
224 | gsize start, end; |
225 | |
226 | start = g_utf8_offset_to_pointer (str: pv->normal_text, offset: position) - pv->normal_text; |
227 | end = g_utf8_offset_to_pointer (str: pv->normal_text, offset: position + n_chars) - pv->normal_text; |
228 | |
229 | memmove (dest: pv->normal_text + start, src: pv->normal_text + end, n: pv->normal_text_bytes + 1 - end); |
230 | pv->normal_text_chars -= n_chars; |
231 | pv->normal_text_bytes -= (end - start); |
232 | |
233 | /* |
234 | * Could be a password, make sure we don't leave anything sensitive after |
235 | * the terminating zero. Note, that the terminating zero already trashed |
236 | * one byte. |
237 | */ |
238 | trash_area (area: pv->normal_text + pv->normal_text_bytes + 1, len: end - start - 1); |
239 | |
240 | g_object_notify_by_pspec (G_OBJECT (buffer), pspec: entry_buffer_props[PROP_TEXT]); |
241 | g_object_notify_by_pspec (G_OBJECT (buffer), pspec: entry_buffer_props[PROP_LENGTH]); |
242 | } |
243 | |
244 | /* -------------------------------------------------------------------------------- |
245 | * |
246 | */ |
247 | |
248 | static void |
249 | gtk_entry_buffer_init (GtkEntryBuffer *buffer) |
250 | { |
251 | GtkEntryBufferPrivate *pv = gtk_entry_buffer_get_instance_private (self: buffer); |
252 | |
253 | pv->normal_text = NULL; |
254 | pv->normal_text_chars = 0; |
255 | pv->normal_text_bytes = 0; |
256 | pv->normal_text_size = 0; |
257 | } |
258 | |
259 | static void |
260 | gtk_entry_buffer_finalize (GObject *obj) |
261 | { |
262 | GtkEntryBuffer *buffer = GTK_ENTRY_BUFFER (obj); |
263 | GtkEntryBufferPrivate *pv = gtk_entry_buffer_get_instance_private (self: buffer); |
264 | |
265 | if (pv->normal_text) |
266 | { |
267 | trash_area (area: pv->normal_text, len: pv->normal_text_size); |
268 | g_free (mem: pv->normal_text); |
269 | pv->normal_text = NULL; |
270 | pv->normal_text_bytes = pv->normal_text_size = 0; |
271 | pv->normal_text_chars = 0; |
272 | } |
273 | |
274 | G_OBJECT_CLASS (gtk_entry_buffer_parent_class)->finalize (obj); |
275 | } |
276 | |
277 | static void |
278 | gtk_entry_buffer_set_property (GObject *obj, |
279 | guint prop_id, |
280 | const GValue *value, |
281 | GParamSpec *pspec) |
282 | { |
283 | GtkEntryBuffer *buffer = GTK_ENTRY_BUFFER (obj); |
284 | |
285 | switch (prop_id) |
286 | { |
287 | case PROP_TEXT: |
288 | gtk_entry_buffer_set_text (buffer, chars: g_value_get_string (value), n_chars: -1); |
289 | break; |
290 | case PROP_MAX_LENGTH: |
291 | gtk_entry_buffer_set_max_length (buffer, max_length: g_value_get_int (value)); |
292 | break; |
293 | default: |
294 | G_OBJECT_WARN_INVALID_PROPERTY_ID (obj, prop_id, pspec); |
295 | break; |
296 | } |
297 | } |
298 | |
299 | static void |
300 | gtk_entry_buffer_get_property (GObject *obj, |
301 | guint prop_id, |
302 | GValue *value, |
303 | GParamSpec *pspec) |
304 | { |
305 | GtkEntryBuffer *buffer = GTK_ENTRY_BUFFER (obj); |
306 | |
307 | switch (prop_id) |
308 | { |
309 | case PROP_TEXT: |
310 | g_value_set_string (value, v_string: gtk_entry_buffer_get_text (buffer)); |
311 | break; |
312 | case PROP_LENGTH: |
313 | g_value_set_uint (value, v_uint: gtk_entry_buffer_get_length (buffer)); |
314 | break; |
315 | case PROP_MAX_LENGTH: |
316 | g_value_set_int (value, v_int: gtk_entry_buffer_get_max_length (buffer)); |
317 | break; |
318 | default: |
319 | G_OBJECT_WARN_INVALID_PROPERTY_ID (obj, prop_id, pspec); |
320 | break; |
321 | } |
322 | } |
323 | |
324 | static void |
325 | gtk_entry_buffer_class_init (GtkEntryBufferClass *klass) |
326 | { |
327 | GObjectClass *gobject_class = G_OBJECT_CLASS (klass); |
328 | |
329 | gobject_class->finalize = gtk_entry_buffer_finalize; |
330 | gobject_class->set_property = gtk_entry_buffer_set_property; |
331 | gobject_class->get_property = gtk_entry_buffer_get_property; |
332 | |
333 | klass->get_text = gtk_entry_buffer_normal_get_text; |
334 | klass->get_length = gtk_entry_buffer_normal_get_length; |
335 | klass->insert_text = gtk_entry_buffer_normal_insert_text; |
336 | klass->delete_text = gtk_entry_buffer_normal_delete_text; |
337 | |
338 | klass->inserted_text = gtk_entry_buffer_real_inserted_text; |
339 | klass->deleted_text = gtk_entry_buffer_real_deleted_text; |
340 | |
341 | /** |
342 | * GtkEntryBuffer:text: (attributes org.gtk.Property.get=gtk_entry_buffer_get_text org.gtk.Property.set=gtk_entry_buffer_set_text) |
343 | * |
344 | * The contents of the buffer. |
345 | */ |
346 | entry_buffer_props[PROP_TEXT] = |
347 | g_param_spec_string (name: "text" , |
348 | P_("Text" ), |
349 | P_("The contents of the buffer" ), |
350 | default_value: "" , |
351 | GTK_PARAM_READWRITE); |
352 | |
353 | /** |
354 | * GtkEntryBuffer:length: (attributes org.gtk.Property.get=gtk_entry_buffer_get_length) |
355 | * |
356 | * The length (in characters) of the text in buffer. |
357 | */ |
358 | entry_buffer_props[PROP_LENGTH] = |
359 | g_param_spec_uint (name: "length" , |
360 | P_("Text length" ), |
361 | P_("Length of the text currently in the buffer" ), |
362 | minimum: 0, GTK_ENTRY_BUFFER_MAX_SIZE, default_value: 0, |
363 | GTK_PARAM_READABLE); |
364 | |
365 | /** |
366 | * GtkEntryBuffer:max-length: (attributes org.gtk.Property.get=gtk_entry_buffer_get_max_length org.gtk.Property.set=gtk_entry_buffer_set_max_length) |
367 | * |
368 | * The maximum length (in characters) of the text in the buffer. |
369 | */ |
370 | entry_buffer_props[PROP_MAX_LENGTH] = |
371 | g_param_spec_int (name: "max-length" , |
372 | P_("Maximum length" ), |
373 | P_("Maximum number of characters for this entry. Zero if no maximum" ), |
374 | minimum: 0, GTK_ENTRY_BUFFER_MAX_SIZE, default_value: 0, |
375 | GTK_PARAM_READWRITE|G_PARAM_EXPLICIT_NOTIFY); |
376 | |
377 | g_object_class_install_properties (oclass: gobject_class, n_pspecs: NUM_PROPERTIES, pspecs: entry_buffer_props); |
378 | |
379 | /** |
380 | * GtkEntryBuffer::inserted-text: |
381 | * @buffer: a `GtkEntryBuffer` |
382 | * @position: the position the text was inserted at. |
383 | * @chars: The text that was inserted. |
384 | * @n_chars: The number of characters that were inserted. |
385 | * |
386 | * This signal is emitted after text is inserted into the buffer. |
387 | */ |
388 | signals[INSERTED_TEXT] = g_signal_new (I_("inserted-text" ), |
389 | GTK_TYPE_ENTRY_BUFFER, |
390 | signal_flags: G_SIGNAL_RUN_FIRST, |
391 | G_STRUCT_OFFSET (GtkEntryBufferClass, inserted_text), |
392 | NULL, NULL, |
393 | c_marshaller: _gtk_marshal_VOID__UINT_STRING_UINT, |
394 | G_TYPE_NONE, n_params: 3, |
395 | G_TYPE_UINT, |
396 | G_TYPE_STRING, |
397 | G_TYPE_UINT); |
398 | |
399 | /** |
400 | * GtkEntryBuffer::deleted-text: |
401 | * @buffer: a `GtkEntryBuffer` |
402 | * @position: the position the text was deleted at. |
403 | * @n_chars: The number of characters that were deleted. |
404 | * |
405 | * The text is altered in the default handler for this signal. |
406 | * |
407 | * If you want access to the text after the text has been modified, |
408 | * use %G_CONNECT_AFTER. |
409 | */ |
410 | signals[DELETED_TEXT] = g_signal_new (I_("deleted-text" ), |
411 | GTK_TYPE_ENTRY_BUFFER, |
412 | signal_flags: G_SIGNAL_RUN_LAST, |
413 | G_STRUCT_OFFSET (GtkEntryBufferClass, deleted_text), |
414 | NULL, NULL, |
415 | c_marshaller: _gtk_marshal_VOID__UINT_UINT, |
416 | G_TYPE_NONE, n_params: 2, |
417 | G_TYPE_UINT, |
418 | G_TYPE_UINT); |
419 | } |
420 | |
421 | /* -------------------------------------------------------------------------------- |
422 | * |
423 | */ |
424 | |
425 | /** |
426 | * gtk_entry_buffer_new: |
427 | * @initial_chars: (nullable): initial buffer text |
428 | * @n_initial_chars: number of characters in @initial_chars, or -1 |
429 | * |
430 | * Create a new `GtkEntryBuffer` object. |
431 | * |
432 | * Optionally, specify initial text to set in the buffer. |
433 | * |
434 | * Returns: A new `GtkEntryBuffer` object. |
435 | */ |
436 | GtkEntryBuffer* |
437 | gtk_entry_buffer_new (const char *initial_chars, |
438 | int n_initial_chars) |
439 | { |
440 | GtkEntryBuffer *buffer = g_object_new (GTK_TYPE_ENTRY_BUFFER, NULL); |
441 | if (initial_chars) |
442 | gtk_entry_buffer_set_text (buffer, chars: initial_chars, n_chars: n_initial_chars); |
443 | return buffer; |
444 | } |
445 | |
446 | /** |
447 | * gtk_entry_buffer_get_length: (attributes org.gtk.Method.get_property=length) |
448 | * @buffer: a `GtkEntryBuffer` |
449 | * |
450 | * Retrieves the length in characters of the buffer. |
451 | * |
452 | * Returns: The number of characters in the buffer. |
453 | **/ |
454 | guint |
455 | gtk_entry_buffer_get_length (GtkEntryBuffer *buffer) |
456 | { |
457 | GtkEntryBufferClass *klass; |
458 | |
459 | g_return_val_if_fail (GTK_IS_ENTRY_BUFFER (buffer), 0); |
460 | |
461 | klass = GTK_ENTRY_BUFFER_GET_CLASS (buffer); |
462 | g_return_val_if_fail (klass->get_length != NULL, 0); |
463 | |
464 | return (*klass->get_length) (buffer); |
465 | } |
466 | |
467 | /** |
468 | * gtk_entry_buffer_get_bytes: |
469 | * @buffer: a `GtkEntryBuffer` |
470 | * |
471 | * Retrieves the length in bytes of the buffer. |
472 | * |
473 | * See [method@Gtk.EntryBuffer.get_length]. |
474 | * |
475 | * Returns: The byte length of the buffer. |
476 | **/ |
477 | gsize |
478 | gtk_entry_buffer_get_bytes (GtkEntryBuffer *buffer) |
479 | { |
480 | GtkEntryBufferClass *klass; |
481 | gsize bytes = 0; |
482 | |
483 | g_return_val_if_fail (GTK_IS_ENTRY_BUFFER (buffer), 0); |
484 | |
485 | klass = GTK_ENTRY_BUFFER_GET_CLASS (buffer); |
486 | g_return_val_if_fail (klass->get_text != NULL, 0); |
487 | |
488 | (*klass->get_text) (buffer, &bytes); |
489 | return bytes; |
490 | } |
491 | |
492 | /** |
493 | * gtk_entry_buffer_get_text: (attributes org.gtk.Method.get_property=text) |
494 | * @buffer: a `GtkEntryBuffer` |
495 | * |
496 | * Retrieves the contents of the buffer. |
497 | * |
498 | * The memory pointer returned by this call will not change |
499 | * unless this object emits a signal, or is finalized. |
500 | * |
501 | * Returns: a pointer to the contents of the widget as a |
502 | * string. This string points to internally allocated storage |
503 | * in the buffer and must not be freed, modified or stored. |
504 | */ |
505 | const char * |
506 | gtk_entry_buffer_get_text (GtkEntryBuffer *buffer) |
507 | { |
508 | GtkEntryBufferClass *klass; |
509 | |
510 | g_return_val_if_fail (GTK_IS_ENTRY_BUFFER (buffer), NULL); |
511 | |
512 | klass = GTK_ENTRY_BUFFER_GET_CLASS (buffer); |
513 | g_return_val_if_fail (klass->get_text != NULL, NULL); |
514 | |
515 | return (*klass->get_text) (buffer, NULL); |
516 | } |
517 | |
518 | /** |
519 | * gtk_entry_buffer_set_text: (attributes org.gtk.Method.set_property=text) |
520 | * @buffer: a `GtkEntryBuffer` |
521 | * @chars: the new text |
522 | * @n_chars: the number of characters in @text, or -1 |
523 | * |
524 | * Sets the text in the buffer. |
525 | * |
526 | * This is roughly equivalent to calling |
527 | * [method@Gtk.EntryBuffer.delete_text] and |
528 | * [method@Gtk.EntryBuffer.insert_text]. |
529 | * |
530 | * Note that @n_chars is in characters, not in bytes. |
531 | **/ |
532 | void |
533 | gtk_entry_buffer_set_text (GtkEntryBuffer *buffer, |
534 | const char *chars, |
535 | int n_chars) |
536 | { |
537 | g_return_if_fail (GTK_IS_ENTRY_BUFFER (buffer)); |
538 | g_return_if_fail (chars != NULL); |
539 | |
540 | g_object_freeze_notify (G_OBJECT (buffer)); |
541 | gtk_entry_buffer_delete_text (buffer, position: 0, n_chars: -1); |
542 | gtk_entry_buffer_insert_text (buffer, position: 0, chars, n_chars); |
543 | g_object_thaw_notify (G_OBJECT (buffer)); |
544 | } |
545 | |
546 | /** |
547 | * gtk_entry_buffer_set_max_length: (attributes org.gtk.Method.set_property=max-length) |
548 | * @buffer: a `GtkEntryBuffer` |
549 | * @max_length: the maximum length of the entry buffer, or 0 for no maximum. |
550 | * (other than the maximum length of entries.) The value passed in will |
551 | * be clamped to the range 0-65536. |
552 | * |
553 | * Sets the maximum allowed length of the contents of the buffer. |
554 | * |
555 | * If the current contents are longer than the given length, then |
556 | * they will be truncated to fit. |
557 | */ |
558 | void |
559 | gtk_entry_buffer_set_max_length (GtkEntryBuffer *buffer, |
560 | int max_length) |
561 | { |
562 | GtkEntryBufferPrivate *priv = gtk_entry_buffer_get_instance_private (self: buffer); |
563 | |
564 | g_return_if_fail (GTK_IS_ENTRY_BUFFER (buffer)); |
565 | |
566 | max_length = CLAMP (max_length, 0, GTK_ENTRY_BUFFER_MAX_SIZE); |
567 | |
568 | if (priv->max_length == max_length) |
569 | return; |
570 | |
571 | if (max_length > 0 && gtk_entry_buffer_get_length (buffer) > max_length) |
572 | gtk_entry_buffer_delete_text (buffer, position: max_length, n_chars: -1); |
573 | |
574 | priv->max_length = max_length; |
575 | g_object_notify_by_pspec (G_OBJECT (buffer), pspec: entry_buffer_props[PROP_MAX_LENGTH]); |
576 | } |
577 | |
578 | /** |
579 | * gtk_entry_buffer_get_max_length: (attributes org.gtk.Method.get_property=max-length) |
580 | * @buffer: a `GtkEntryBuffer` |
581 | * |
582 | * Retrieves the maximum allowed length of the text in @buffer. |
583 | * |
584 | * Returns: the maximum allowed number of characters |
585 | * in `GtkEntryBuffer`, or 0 if there is no maximum. |
586 | */ |
587 | int |
588 | gtk_entry_buffer_get_max_length (GtkEntryBuffer *buffer) |
589 | { |
590 | GtkEntryBufferPrivate *priv = gtk_entry_buffer_get_instance_private (self: buffer); |
591 | |
592 | g_return_val_if_fail (GTK_IS_ENTRY_BUFFER (buffer), 0); |
593 | |
594 | return priv->max_length; |
595 | } |
596 | |
597 | /** |
598 | * gtk_entry_buffer_insert_text: |
599 | * @buffer: a `GtkEntryBuffer` |
600 | * @position: the position at which to insert text. |
601 | * @chars: the text to insert into the buffer. |
602 | * @n_chars: the length of the text in characters, or -1 |
603 | * |
604 | * Inserts @n_chars characters of @chars into the contents of the |
605 | * buffer, at position @position. |
606 | * |
607 | * If @n_chars is negative, then characters from chars will be inserted |
608 | * until a null-terminator is found. If @position or @n_chars are out of |
609 | * bounds, or the maximum buffer text length is exceeded, then they are |
610 | * coerced to sane values. |
611 | * |
612 | * Note that the position and length are in characters, not in bytes. |
613 | * |
614 | * Returns: The number of characters actually inserted. |
615 | */ |
616 | guint |
617 | gtk_entry_buffer_insert_text (GtkEntryBuffer *buffer, |
618 | guint position, |
619 | const char *chars, |
620 | int n_chars) |
621 | { |
622 | GtkEntryBufferPrivate *pv = gtk_entry_buffer_get_instance_private (self: buffer); |
623 | GtkEntryBufferClass *klass; |
624 | guint length; |
625 | |
626 | g_return_val_if_fail (GTK_IS_ENTRY_BUFFER (buffer), 0); |
627 | |
628 | length = gtk_entry_buffer_get_length (buffer); |
629 | |
630 | if (n_chars < 0) |
631 | n_chars = g_utf8_strlen (p: chars, max: -1); |
632 | |
633 | /* Bring position into bounds */ |
634 | if (position > length) |
635 | position = length; |
636 | |
637 | /* Make sure not entering too much data */ |
638 | if (pv->max_length > 0) |
639 | { |
640 | if (length >= pv->max_length) |
641 | n_chars = 0; |
642 | else if (length + n_chars > pv->max_length) |
643 | n_chars -= (length + n_chars) - pv->max_length; |
644 | } |
645 | |
646 | if (n_chars == 0) |
647 | return 0; |
648 | |
649 | klass = GTK_ENTRY_BUFFER_GET_CLASS (buffer); |
650 | g_return_val_if_fail (klass->insert_text != NULL, 0); |
651 | |
652 | return (*klass->insert_text) (buffer, position, chars, n_chars); |
653 | } |
654 | |
655 | /** |
656 | * gtk_entry_buffer_delete_text: |
657 | * @buffer: a `GtkEntryBuffer` |
658 | * @position: position at which to delete text |
659 | * @n_chars: number of characters to delete |
660 | * |
661 | * Deletes a sequence of characters from the buffer. |
662 | * |
663 | * @n_chars characters are deleted starting at @position. |
664 | * If @n_chars is negative, then all characters until the |
665 | * end of the text are deleted. |
666 | * |
667 | * If @position or @n_chars are out of bounds, then they |
668 | * are coerced to sane values. |
669 | * |
670 | * Note that the positions are specified in characters, |
671 | * not bytes. |
672 | * |
673 | * Returns: The number of characters deleted. |
674 | */ |
675 | guint |
676 | gtk_entry_buffer_delete_text (GtkEntryBuffer *buffer, |
677 | guint position, |
678 | int n_chars) |
679 | { |
680 | GtkEntryBufferClass *klass; |
681 | guint length; |
682 | |
683 | g_return_val_if_fail (GTK_IS_ENTRY_BUFFER (buffer), 0); |
684 | |
685 | length = gtk_entry_buffer_get_length (buffer); |
686 | if (n_chars < 0) |
687 | n_chars = length; |
688 | if (position > length) |
689 | position = length; |
690 | if (position + n_chars > length) |
691 | n_chars = length - position; |
692 | |
693 | klass = GTK_ENTRY_BUFFER_GET_CLASS (buffer); |
694 | g_return_val_if_fail (klass->delete_text != NULL, 0); |
695 | |
696 | return (*klass->delete_text) (buffer, position, n_chars); |
697 | } |
698 | |
699 | /** |
700 | * gtk_entry_buffer_emit_inserted_text: |
701 | * @buffer: a `GtkEntryBuffer` |
702 | * @position: position at which text was inserted |
703 | * @chars: text that was inserted |
704 | * @n_chars: number of characters inserted |
705 | * |
706 | * Used when subclassing `GtkEntryBuffer`. |
707 | */ |
708 | void |
709 | gtk_entry_buffer_emit_inserted_text (GtkEntryBuffer *buffer, |
710 | guint position, |
711 | const char *chars, |
712 | guint n_chars) |
713 | { |
714 | g_return_if_fail (GTK_IS_ENTRY_BUFFER (buffer)); |
715 | g_signal_emit (instance: buffer, signal_id: signals[INSERTED_TEXT], detail: 0, position, chars, n_chars); |
716 | } |
717 | |
718 | /** |
719 | * gtk_entry_buffer_emit_deleted_text: |
720 | * @buffer: a `GtkEntryBuffer` |
721 | * @position: position at which text was deleted |
722 | * @n_chars: number of characters deleted |
723 | * |
724 | * Used when subclassing `GtkEntryBuffer`. |
725 | */ |
726 | void |
727 | gtk_entry_buffer_emit_deleted_text (GtkEntryBuffer *buffer, |
728 | guint position, |
729 | guint n_chars) |
730 | { |
731 | g_return_if_fail (GTK_IS_ENTRY_BUFFER (buffer)); |
732 | g_signal_emit (instance: buffer, signal_id: signals[DELETED_TEXT], detail: 0, position, n_chars); |
733 | } |
734 | |