1 | /* gtktextmark.c - mark segments |
2 | * |
3 | * Copyright (c) 1994 The Regents of the University of California. |
4 | * Copyright (c) 1994-1997 Sun Microsystems, Inc. |
5 | * Copyright (c) 2000 Red Hat, Inc. |
6 | * Tk -> Gtk port by Havoc Pennington <hp@redhat.com> |
7 | * |
8 | * This software is copyrighted by the Regents of the University of |
9 | * California, Sun Microsystems, Inc., and other parties. The |
10 | * following terms apply to all files associated with the software |
11 | * unless explicitly disclaimed in individual files. |
12 | * |
13 | * The authors hereby grant permission to use, copy, modify, |
14 | * distribute, and license this software and its documentation for any |
15 | * purpose, provided that existing copyright notices are retained in |
16 | * all copies and that this notice is included verbatim in any |
17 | * distributions. No written agreement, license, or royalty fee is |
18 | * required for any of the authorized uses. Modifications to this |
19 | * software may be copyrighted by their authors and need not follow |
20 | * the licensing terms described here, provided that the new terms are |
21 | * clearly indicated on the first page of each file where they apply. |
22 | * |
23 | * IN NO EVENT SHALL THE AUTHORS OR DISTRIBUTORS BE LIABLE TO ANY |
24 | * PARTY FOR DIRECT, INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL |
25 | * DAMAGES ARISING OUT OF THE USE OF THIS SOFTWARE, ITS DOCUMENTATION, |
26 | * OR ANY DERIVATIVES THEREOF, EVEN IF THE AUTHORS HAVE BEEN ADVISED |
27 | * OF THE POSSIBILITY OF SUCH DAMAGE. |
28 | * |
29 | * THE AUTHORS AND DISTRIBUTORS SPECIFICALLY DISCLAIM ANY WARRANTIES, |
30 | * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF |
31 | * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, AND |
32 | * NON-INFRINGEMENT. THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, |
33 | * AND THE AUTHORS AND DISTRIBUTORS HAVE NO OBLIGATION TO PROVIDE |
34 | * MAINTENANCE, SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS. |
35 | * |
36 | * GOVERNMENT USE: If you are acquiring this software on behalf of the |
37 | * U.S. government, the Government shall have only "Restricted Rights" |
38 | * in the software and related documentation as defined in the Federal |
39 | * Acquisition Regulations (FARs) in Clause 52.227.19 (c) (2). If you |
40 | * are acquiring the software on behalf of the Department of Defense, |
41 | * the software shall be classified as "Commercial Computer Software" |
42 | * and the Government shall have only "Restricted Rights" as defined |
43 | * in Clause 252.227-7013 (c) (1) of DFARs. Notwithstanding the |
44 | * foregoing, the authors grant the U.S. Government and others acting |
45 | * in its behalf permission to use and distribute the software in |
46 | * accordance with the terms specified in this license. |
47 | * |
48 | */ |
49 | |
50 | #include "config.h" |
51 | #include "gtktextbtree.h" |
52 | #include "gtkprivate.h" |
53 | #include "gtkintl.h" |
54 | |
55 | |
56 | /** |
57 | * GtkTextMark: |
58 | * |
59 | * A `GtkTextMark` is a position in a `GtkTextbuffer` that is preserved |
60 | * across modifications. |
61 | * |
62 | * You may wish to begin by reading the |
63 | * [text widget conceptual overview](section-text-widget.html), |
64 | * which gives an overview of all the objects and data types |
65 | * related to the text widget and how they work together. |
66 | * |
67 | * A `GtkTextMark` is like a bookmark in a text buffer; it preserves |
68 | * a position in the text. You can convert the mark to an iterator using |
69 | * [method@Gtk.TextBuffer.get_iter_at_mark]. Unlike iterators, marks remain |
70 | * valid across buffer mutations, because their behavior is defined when |
71 | * text is inserted or deleted. When text containing a mark is deleted, |
72 | * the mark remains in the position originally occupied by the deleted |
73 | * text. When text is inserted at a mark, a mark with “left gravity” will |
74 | * be moved to the beginning of the newly-inserted text, and a mark with |
75 | * “right gravity” will be moved to the end. |
76 | * |
77 | * Note that “left” and “right” here refer to logical direction (left |
78 | * is the toward the start of the buffer); in some languages such as |
79 | * Hebrew the logically-leftmost text is not actually on the left when |
80 | * displayed. |
81 | * |
82 | * Marks are reference counted, but the reference count only controls |
83 | * the validity of the memory; marks can be deleted from the buffer at |
84 | * any time with [method@Gtk.TextBuffer.delete_mark]. Once deleted from |
85 | * the buffer, a mark is essentially useless. |
86 | * |
87 | * Marks optionally have names; these can be convenient to avoid passing |
88 | * the `GtkTextMark` object around. |
89 | * |
90 | * Marks are typically created using the [method@Gtk.TextBuffer.create_mark] |
91 | * function. |
92 | */ |
93 | |
94 | /* |
95 | * Macro that determines the size of a mark segment: |
96 | */ |
97 | #define MSEG_SIZE ((unsigned) (G_STRUCT_OFFSET (GtkTextLineSegment, body) \ |
98 | + sizeof (GtkTextMarkBody))) |
99 | |
100 | static void gtk_text_mark_set_property (GObject *object, |
101 | guint prop_id, |
102 | const GValue *value, |
103 | GParamSpec *pspec); |
104 | static void gtk_text_mark_get_property (GObject *object, |
105 | guint prop_id, |
106 | GValue *value, |
107 | GParamSpec *pspec); |
108 | static void gtk_text_mark_finalize (GObject *object); |
109 | |
110 | static GtkTextLineSegment *gtk_mark_segment_new (GtkTextMark *mark_obj); |
111 | |
112 | G_DEFINE_TYPE (GtkTextMark, gtk_text_mark, G_TYPE_OBJECT) |
113 | |
114 | enum { |
115 | PROP_0, |
116 | PROP_NAME, |
117 | PROP_LEFT_GRAVITY |
118 | }; |
119 | |
120 | static void |
121 | gtk_text_mark_class_init (GtkTextMarkClass *klass) |
122 | { |
123 | GObjectClass *object_class = G_OBJECT_CLASS (klass); |
124 | |
125 | object_class->finalize = gtk_text_mark_finalize; |
126 | object_class->set_property = gtk_text_mark_set_property; |
127 | object_class->get_property = gtk_text_mark_get_property; |
128 | |
129 | /** |
130 | * GtkTextMark:name: |
131 | * |
132 | * The name of the mark or %NULL if the mark is anonymous. |
133 | */ |
134 | g_object_class_install_property (oclass: object_class, |
135 | property_id: PROP_NAME, |
136 | pspec: g_param_spec_string (name: "name" , |
137 | P_("Name" ), |
138 | P_("Mark name" ), |
139 | NULL, |
140 | GTK_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY)); |
141 | |
142 | /** |
143 | * GtkTextMark:left-gravity: |
144 | * |
145 | * Whether the mark has left gravity. |
146 | * |
147 | * When text is inserted at the mark’s current location, if the mark |
148 | * has left gravity it will be moved to the left of the newly-inserted |
149 | * text, otherwise to the right. |
150 | */ |
151 | g_object_class_install_property (oclass: object_class, |
152 | property_id: PROP_LEFT_GRAVITY, |
153 | pspec: g_param_spec_boolean (name: "left-gravity" , |
154 | P_("Left gravity" ), |
155 | P_("Whether the mark has left gravity" ), |
156 | FALSE, |
157 | GTK_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY)); |
158 | } |
159 | |
160 | static void |
161 | gtk_text_mark_init (GtkTextMark *mark) |
162 | { |
163 | mark->segment = gtk_mark_segment_new (mark_obj: mark); |
164 | } |
165 | |
166 | static void |
167 | gtk_text_mark_finalize (GObject *obj) |
168 | { |
169 | GtkTextMark *mark; |
170 | GtkTextLineSegment *seg; |
171 | |
172 | mark = GTK_TEXT_MARK (obj); |
173 | |
174 | seg = mark->segment; |
175 | |
176 | if (seg) |
177 | { |
178 | if (seg->body.mark.tree != NULL) |
179 | g_warning ("GtkTextMark being finalized while still in the buffer; " |
180 | "someone removed a reference they didn't own! Crash " |
181 | "impending" ); |
182 | |
183 | g_free (mem: seg->body.mark.name); |
184 | g_slice_free1 (MSEG_SIZE, mem_block: seg); |
185 | |
186 | mark->segment = NULL; |
187 | } |
188 | |
189 | /* chain parent_class' handler */ |
190 | G_OBJECT_CLASS (gtk_text_mark_parent_class)->finalize (obj); |
191 | } |
192 | |
193 | static void |
194 | gtk_text_mark_set_property (GObject *object, |
195 | guint prop_id, |
196 | const GValue *value, |
197 | GParamSpec *pspec) |
198 | { |
199 | char *tmp; |
200 | GtkTextMark *mark = GTK_TEXT_MARK (object); |
201 | GtkTextLineSegment *seg = mark->segment; |
202 | |
203 | switch (prop_id) |
204 | { |
205 | case PROP_NAME: |
206 | tmp = seg->body.mark.name; |
207 | seg->body.mark.name = g_value_dup_string (value); |
208 | g_free (mem: tmp); |
209 | break; |
210 | |
211 | case PROP_LEFT_GRAVITY: |
212 | if (g_value_get_boolean (value)) |
213 | seg->type = >k_text_left_mark_type; |
214 | else |
215 | seg->type = >k_text_right_mark_type; |
216 | break; |
217 | |
218 | default: |
219 | G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); |
220 | } |
221 | } |
222 | |
223 | static void |
224 | gtk_text_mark_get_property (GObject *object, |
225 | guint prop_id, |
226 | GValue *value, |
227 | GParamSpec *pspec) |
228 | { |
229 | GtkTextMark *mark = GTK_TEXT_MARK (object); |
230 | |
231 | switch (prop_id) |
232 | { |
233 | case PROP_NAME: |
234 | g_value_set_string (value, v_string: gtk_text_mark_get_name (mark)); |
235 | break; |
236 | |
237 | case PROP_LEFT_GRAVITY: |
238 | g_value_set_boolean (value, v_boolean: gtk_text_mark_get_left_gravity (mark)); |
239 | break; |
240 | |
241 | default: |
242 | G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); |
243 | } |
244 | } |
245 | |
246 | /** |
247 | * gtk_text_mark_new: |
248 | * @name: (nullable): mark name |
249 | * @left_gravity: whether the mark should have left gravity |
250 | * |
251 | * Creates a text mark. |
252 | * |
253 | * Add it to a buffer using [method@Gtk.TextBuffer.add_mark]. |
254 | * If @name is %NULL, the mark is anonymous; otherwise, the mark can be |
255 | * retrieved by name using [method@Gtk.TextBuffer.get_mark]. If a mark |
256 | * has left gravity, and text is inserted at the mark’s current location, |
257 | * the mark will be moved to the left of the newly-inserted text. If the |
258 | * mark has right gravity (@left_gravity = %FALSE), the mark will end up |
259 | * on the right of newly-inserted text. The standard left-to-right cursor |
260 | * is a mark with right gravity (when you type, the cursor stays on the |
261 | * right side of the text you’re typing). |
262 | * |
263 | * Returns: new `GtkTextMark` |
264 | */ |
265 | GtkTextMark * |
266 | gtk_text_mark_new (const char *name, |
267 | gboolean left_gravity) |
268 | { |
269 | return g_object_new (GTK_TYPE_TEXT_MARK, |
270 | first_property_name: "name" , name, |
271 | "left-gravity" , left_gravity, |
272 | NULL); |
273 | } |
274 | |
275 | /** |
276 | * gtk_text_mark_get_visible: |
277 | * @mark: a `GtkTextMark` |
278 | * |
279 | * Returns %TRUE if the mark is visible. |
280 | * |
281 | * A cursor is displayed for visible marks. |
282 | * |
283 | * Returns: %TRUE if visible |
284 | */ |
285 | gboolean |
286 | gtk_text_mark_get_visible (GtkTextMark *mark) |
287 | { |
288 | GtkTextLineSegment *seg; |
289 | |
290 | seg = mark->segment; |
291 | |
292 | return seg->body.mark.visible; |
293 | } |
294 | |
295 | /** |
296 | * gtk_text_mark_get_name: |
297 | * @mark: a `GtkTextMark` |
298 | * |
299 | * Returns the mark name. |
300 | * |
301 | * Returns %NULL for anonymous marks. |
302 | * |
303 | * Returns: (nullable): mark name |
304 | */ |
305 | const char * |
306 | gtk_text_mark_get_name (GtkTextMark *mark) |
307 | { |
308 | GtkTextLineSegment *seg; |
309 | |
310 | seg = mark->segment; |
311 | |
312 | return seg->body.mark.name; |
313 | } |
314 | |
315 | /** |
316 | * gtk_text_mark_get_deleted: |
317 | * @mark: a `GtkTextMark` |
318 | * |
319 | * Returns %TRUE if the mark has been removed from its buffer. |
320 | * |
321 | * See [method@Gtk.TextBuffer.add_mark] for a way to add it |
322 | * to a buffer again. |
323 | * |
324 | * Returns: whether the mark is deleted |
325 | */ |
326 | gboolean |
327 | gtk_text_mark_get_deleted (GtkTextMark *mark) |
328 | { |
329 | GtkTextLineSegment *seg; |
330 | |
331 | g_return_val_if_fail (GTK_IS_TEXT_MARK (mark), FALSE); |
332 | |
333 | seg = mark->segment; |
334 | |
335 | if (seg == NULL) |
336 | return TRUE; |
337 | |
338 | return seg->body.mark.tree == NULL; |
339 | } |
340 | |
341 | /** |
342 | * gtk_text_mark_get_buffer: |
343 | * @mark: a `GtkTextMark` |
344 | * |
345 | * Gets the buffer this mark is located inside. |
346 | * |
347 | * Returns %NULL if the mark is deleted. |
348 | * |
349 | * Returns: (transfer none) (nullable): the mark’s `GtkTextBuffer` |
350 | */ |
351 | GtkTextBuffer* |
352 | gtk_text_mark_get_buffer (GtkTextMark *mark) |
353 | { |
354 | GtkTextLineSegment *seg; |
355 | |
356 | g_return_val_if_fail (GTK_IS_TEXT_MARK (mark), NULL); |
357 | |
358 | seg = mark->segment; |
359 | |
360 | if (seg->body.mark.tree == NULL) |
361 | return NULL; |
362 | else |
363 | return _gtk_text_btree_get_buffer (tree: seg->body.mark.tree); |
364 | } |
365 | |
366 | /** |
367 | * gtk_text_mark_get_left_gravity: |
368 | * @mark: a `GtkTextMark` |
369 | * |
370 | * Determines whether the mark has left gravity. |
371 | * |
372 | * Returns: %TRUE if the mark has left gravity, %FALSE otherwise |
373 | */ |
374 | gboolean |
375 | gtk_text_mark_get_left_gravity (GtkTextMark *mark) |
376 | { |
377 | GtkTextLineSegment *seg; |
378 | |
379 | g_return_val_if_fail (GTK_IS_TEXT_MARK (mark), FALSE); |
380 | |
381 | seg = mark->segment; |
382 | |
383 | return seg->type == >k_text_left_mark_type; |
384 | } |
385 | |
386 | static GtkTextLineSegment * |
387 | gtk_mark_segment_new (GtkTextMark *mark_obj) |
388 | { |
389 | GtkTextLineSegment *mark; |
390 | |
391 | mark = g_slice_alloc0 (MSEG_SIZE); |
392 | mark->body.mark.name = NULL; |
393 | mark->type = >k_text_right_mark_type; |
394 | |
395 | mark->byte_count = 0; |
396 | mark->char_count = 0; |
397 | |
398 | mark->body.mark.obj = mark_obj; |
399 | mark_obj->segment = mark; |
400 | |
401 | mark->body.mark.tree = NULL; |
402 | mark->body.mark.line = NULL; |
403 | mark->next = NULL; |
404 | |
405 | mark->body.mark.visible = FALSE; |
406 | mark->body.mark.not_deleteable = FALSE; |
407 | |
408 | return mark; |
409 | } |
410 | |
411 | void |
412 | _gtk_mark_segment_set_tree (GtkTextLineSegment *mark, |
413 | GtkTextBTree *tree) |
414 | { |
415 | g_assert (mark->body.mark.tree == NULL); |
416 | g_assert (mark->body.mark.obj != NULL); |
417 | |
418 | mark->byte_count = 0; |
419 | mark->char_count = 0; |
420 | |
421 | mark->body.mark.tree = tree; |
422 | mark->body.mark.line = NULL; |
423 | mark->next = NULL; |
424 | |
425 | mark->body.mark.not_deleteable = FALSE; |
426 | } |
427 | |
428 | static int mark_segment_delete_func (GtkTextLineSegment *segPtr, |
429 | GtkTextLine *line, |
430 | int treeGone); |
431 | static GtkTextLineSegment *mark_segment_cleanup_func (GtkTextLineSegment *segPtr, |
432 | GtkTextLine *line); |
433 | static void mark_segment_check_func (GtkTextLineSegment *segPtr, |
434 | GtkTextLine *line); |
435 | |
436 | |
437 | /* |
438 | * The following structures declare the "mark" segment types. |
439 | * There are actually two types for marks, one with left gravity |
440 | * and one with right gravity. They are identical except for |
441 | * their gravity property. |
442 | */ |
443 | |
444 | const GtkTextLineSegmentClass gtk_text_right_mark_type = { |
445 | "mark" , /* name */ |
446 | FALSE, /* leftGravity */ |
447 | NULL, /* splitFunc */ |
448 | mark_segment_delete_func, /* deleteFunc */ |
449 | mark_segment_cleanup_func, /* cleanupFunc */ |
450 | NULL, /* lineChangeFunc */ |
451 | mark_segment_check_func /* checkFunc */ |
452 | }; |
453 | |
454 | const GtkTextLineSegmentClass gtk_text_left_mark_type = { |
455 | "mark" , /* name */ |
456 | TRUE, /* leftGravity */ |
457 | NULL, /* splitFunc */ |
458 | mark_segment_delete_func, /* deleteFunc */ |
459 | mark_segment_cleanup_func, /* cleanupFunc */ |
460 | NULL, /* lineChangeFunc */ |
461 | mark_segment_check_func /* checkFunc */ |
462 | }; |
463 | |
464 | /* |
465 | *-------------------------------------------------------------- |
466 | * |
467 | * mark_segment_delete_func -- |
468 | * |
469 | * This procedure is invoked by the text B-tree code whenever |
470 | * a mark lies in a range of characters being deleted. |
471 | * |
472 | * Results: |
473 | * Returns 1 to indicate that deletion has been rejected, |
474 | * or 0 otherwise |
475 | * |
476 | * Side effects: |
477 | * Frees mark if tree is going away |
478 | * |
479 | *-------------------------------------------------------------- |
480 | */ |
481 | |
482 | static gboolean |
483 | mark_segment_delete_func (GtkTextLineSegment *seg, |
484 | GtkTextLine *line, |
485 | gboolean tree_gone) |
486 | { |
487 | if (tree_gone) |
488 | { |
489 | _gtk_text_btree_release_mark_segment (tree: seg->body.mark.tree, segment: seg); |
490 | return FALSE; |
491 | } |
492 | else |
493 | return TRUE; |
494 | } |
495 | |
496 | /* |
497 | *-------------------------------------------------------------- |
498 | * |
499 | * mark_segment_cleanup_func -- |
500 | * |
501 | * This procedure is invoked by the B-tree code whenever a |
502 | * mark segment is moved from one line to another. |
503 | * |
504 | * Results: |
505 | * None. |
506 | * |
507 | * Side effects: |
508 | * The line field of the segment gets updated. |
509 | * |
510 | *-------------------------------------------------------------- |
511 | */ |
512 | |
513 | static GtkTextLineSegment * |
514 | mark_segment_cleanup_func (GtkTextLineSegment *seg, |
515 | GtkTextLine *line) |
516 | { |
517 | /* not sure why Tk did this here and not in LineChangeFunc */ |
518 | seg->body.mark.line = line; |
519 | return seg; |
520 | } |
521 | |
522 | /* |
523 | *-------------------------------------------------------------- |
524 | * |
525 | * mark_segment_check_func -- |
526 | * |
527 | * This procedure is invoked by the B-tree code to perform |
528 | * consistency checks on mark segments. |
529 | * |
530 | * Results: |
531 | * None. |
532 | * |
533 | * Side effects: |
534 | * The procedure panics if it detects anything wrong with |
535 | * the mark. |
536 | * |
537 | *-------------------------------------------------------------- |
538 | */ |
539 | |
540 | static void |
541 | mark_segment_check_func (GtkTextLineSegment *seg, |
542 | GtkTextLine *line) |
543 | { |
544 | if (seg->body.mark.line != line) |
545 | g_error ("mark_segment_check_func: seg->body.mark.line bogus" ); |
546 | } |
547 | |