]> Pileus Git - ~andy/gtk/blob - docs/text_widget.txt
Delete EWMH properties if no private->state flags are set. (#66754)
[~andy/gtk] / docs / text_widget.txt
1 Date: Sun, 14 Sep 1997 20:17:06 -0700 (PDT)
2 From: Josh MacDonald <jmacd@CS.Berkeley.EDU>
3 To: gnome@athena.nuclecu.unam.mx, gtk-list@redhat.com
4 Subject: [gtk-list] gtktext widget internal documentation
5
6
7 Pete convinced me to just write up the text widget and let someone else
8 finish it.  I'm pretty busy and have other commitments now.  Sorry.  I think
9 I'm not the most qualified for some of the remaining work anyway, because I
10 don't really know Gtk and it's event model very well.  Most of the work so 
11 far was possible without knowing Gtk all that well, it was simply a data 
12 structure exercise (though after reading this you might say it was a fairly
13 complicated data structure exercise).  I'm happy to answer questions.
14
15 -josh
16
17
18 High level description:
19
20 There are several layers of data structure to the widget.  They are
21 seperated from each other as much as possible.  The first is a gapped
22 text segment similar to the data structure Emacs uses for representing
23 text.  Then there is a property list, which stores text properties for
24 various ranges of text.  There is no direct relation between the text
25 property list and the gapped text segment.  Finally there is a drawn
26 line parameter cache to speed calculations when drawing and redrawing
27 lines on screen.  In addition to these data structures, there are
28 structures to help iterate over text in the buffer.
29
30 The gapped text segment is quite simple.  It's parameters are (all
31 parameters I mention here are in the structure GtkText):
32
33   guchar* text;
34   guint text_len;
35   guint gap_position;
36   guint gap_size;
37   guint text_end;
38
39 TEXT is the buffer, TEXT_LEN is its allocated length.  TEXT_END is the
40 length of the text, including the gap.  GAP_POSITION is the start of
41 the gap, and GAP_SIZE is the gap's length.  Therefore, TEXT_END -
42 GAP_SIZE is the length of the text in the buffer.  The macro
43 TEXT_LENGTH returns this value.  To get the value of a character in
44 the buffer, use the macro TEXT_INDEX(TEXT,INDEX).  This macro tests
45 whether the index is less than the GAP_POSITION and returns
46 TEXT[INDEX] or returns TEXT[GAP_SIZE+INDEX].  The function
47 MOVE_GAP_TO_POINT positions the gap to a particular index.  The
48 function MAKE_FORWARD_SPACE lengthens the gap to provide room for a
49 certain number of characters.
50
51 The property list is a doubly linked list (GList) of text property
52 data for each contiguous set of characters with similar properties.
53 The data field of the GList points to a TextProperty structure, which
54 contains:
55
56   TextFont* font;
57   GdkColor* back_color;
58   GdkColor* fore_color;
59   guint length;
60
61 Currently, only font and color data are contained in the property
62 list, but it can be extended by modifying the INSERT_TEXT_PROPERTY,
63 TEXT_PROPERTIES_EQUAL, and a few other procedures.  The text property
64 structure does not contain an absolute offset, only a length.  As a
65 result, inserting a character into the buffer simply requires moving
66 the gap to the correct position, making room in the buffer, and either
67 inserting a new property or extending the old one.  This logic is done
68 by INSERT_TEXT_PROPERTY.  A similar procedure exists to delete from
69 the text property list, DELETE_TEXT_PROPERTY.  Since the property
70 structure doesn't contain an offset, insertion into the list is an
71 O(1) operation.  All such operations act on the insertion point, which
72 is the POINT field of the GtkText structure.
73
74 The GtkPropertyMark structure is used for keeping track of the mapping
75 between absolute buffer offsets and positions in the property list.
76 These will be referred to as property marks.  Generally, there are
77 four property marks the system keeps track of.  Two are trivial, the
78 beginning and the end of the buffer are easy to find.  The other two
79 are the insertion point (POINT) and the cursor point (CURSOR_MARK).
80 All operations on the text buffer are done using a property mark as a
81 sort of cursor to keep track of the alignment of the property list and
82 the absolute buffer offset.  The GtkPropertyMark structure contains:
83
84   GList* property;
85   guint offset;
86   guint index;
87
88 PROPERTY is a pointer at the current property list element.  INDEX is
89 the absolute buffer index, and OFFSET is the offset of INDEX from the
90 beginning of PROPERTY.  It is essential to keep property marks valid,
91 or else you will have the wrong text properties at each property mark
92 transition.  An important point is that all property marks are invalid
93 after a buffer modification unless care is taken to keep them
94 accurate.  That is the difficulty of the insert and delete operations,
95 because as the next section describes, line data is cached and by
96 neccesity contains text property marks.  The functions for operating
97 and computing property marks are:
98
99  void advance_mark     (GtkPropertyMark* mark);
100  void decrement_mark   (GtkPropertyMark* mark);
101  void advance_mark_n   (GtkPropertyMark* mark, gint n);
102  void decrement_mark_n (GtkPropertyMark* mark, gint n);
103  void move_mark_n      (GtkPropertyMark* mark, gint n);
104
105  GtkPropertyMark find_mark      (GtkText* text, guint mark_position);
106  GtkPropertyMark find_mark_near (GtkText* text, guint mark_position,
107                                  const GtkPropertyMark* near);
108
109 ADVANCE_MARK and DECREMENT_MARK modify the mark by plus or minus one
110 buffer index.  ADVANCE_MARK_N and DECREMENT_MARK_N modify the mark by
111 plus or minus N indices.  MOVE_MARK_N accepts a positive or negative
112 argument.  FIND_MARK returns a mark at MARK_POSITION using a linear
113 search from the nearest known property mark (the beginning, the end,
114 the point, etc).  FIND_MARK_NEAR also does a linear search, but
115 searches from the NEAR argument.  A number of macros exist at the top
116 of the file for doing things like getting the current text property,
117 or some component of the current property.  See the MARK_* macros.
118
119 Next there is a LineParams structure which contains all the
120 information neccesary to draw one line of text on screen.  When I say
121 "line" here, I do not mean one line of text seperated by newlines,
122 rather I mean one row of text on screen.  It is a matter of policy how
123 visible lines are chosen and there are currently two policies,
124 line-wrap and no-line-wrap.  I suspect it would not be difficult to
125 implement new policies for doing such things as justification.  The
126 LineParams structure includes the following fields:
127
128   guint font_ascent;
129   guint font_descent;
130   guint pixel_width;
131   guint displayable_chars;
132   guint wraps : 1;
133
134   PrevTabCont tab_cont;
135   PrevTabCont tab_cont_next;
136
137   GtkPropertyMark start;
138   GtkPropertyMark end;
139
140 FONT_ASCENT and FONT_DESCENT are the maximum ascent and descent of any
141 character in the line.  PIXEL_WIDTH is the number of pixels wide the
142 drawn region is, though I don't think it's actually being used
143 currently.  You may wish to remove this field, eventually, though I
144 suspect it will come in handy implementing horizontal scrolling.
145 DISPLAYABLE_CHARS is the number of characters in the line actually
146 drawn.  This may be less than the number of characters in the line
147 when line wrapping is off (see below).  The bitflag WRAPS tells
148 whether the next line is a continuation of this line.  START and END
149 are the marks at the beginning and end of the line.  Note that END is
150 the actual last character, not one past it, so the smallest line
151 (containing, for example, one newline) has START == END.  TAB_CONT and
152 TAB_CONT_NEXT are for computation of tab positions.  I will discuss
153 them later.
154
155 A point about the end of the buffer.  You may be tempted to consider
156 working with the buffer as an array of length TEXT_LENGTH(TEXT), but
157 you have to be careful that the editor allows you to position your
158 cursor at the last index of the buffer, one past the last character.
159 The macro LAST_INDEX(TEXT, MARK) returns true if MARK is positioned at
160 this index.  If you see or add a special case in the code for this
161 end-of-buffer case, make sure to use LAST_INDEX if you can.  Very
162 often, the last index is treated as a newline.
163
164 [ One way the last index is special is that, although it is always
165   part of some property, it will never be part of a property of
166   length 1 unless there are no other characters in the text. That
167   is, its properties are always that of the preceding character,
168   if any.
169   
170   There is a fair bit of special case code to mantain this condition -
171   which is needed so that user has control over the properties of
172   characters inserted at the last position. OWT 2/9/98 ]
173
174 Tab stops are variable width.  A list of tab stops is contained in the
175 GtkText structure:
176
177   GList *tab_stops;
178   gint default_tab_width;
179
180 The elements of tab_stops are integers casted to gpointer.  This is a
181 little bogus, but works.  For example:
182
183   text->default_tab_width = 4;
184   text->tab_stops = NULL;
185   text->tab_stops = g_list_prepend (text->tab_stops, (void*)8);
186   text->tab_stops = g_list_prepend (text->tab_stops, (void*)8);
187
188 is how these fields are initialized, currently.  This means that the
189 first two tabs occur at 8 and 16, and every 4 characters thereafter.
190 Tab stops are used in the computation of line geometry (to fill in a
191 LineParams structure), and the width of the space character in the
192 current font is used.  The PrevTabCont structure, of which two are
193 stored per line, is used to compute the geometry of lines which may
194 have wrapped and carried part of a tab with them:
195
196   guint pixel_offset;
197   TabStopMark tab_start;
198
199 PIXEL_OFFSET is the number of pixels at which the line should start,
200 and tab_start is a tab stop mark, which is similar to a property mark,
201 only it keeps track of the mapping between line position (column) and
202 the next tab stop.  A TabStopMark contains:
203
204   GList* tab_stops;
205   gint to_next_tab;
206
207 TAB_STOPS is a pointer into the TAB_STOPS field of the GtkText
208 structure.  TO_NEXT_TAB is the number of characters before the next
209 tab.  The functions ADVANCE_TAB_MARK and ADVANCE_TAB_MARK_N advance
210 these marks.  The LineParams structure contains two PrevTabCont
211 structures, which each contain a tab stop.  The first (TAB_CONT) is
212 for computing the beginning pixel offset, as mentioned above.  The
213 second (TAB_CONT_NEXT) is used to initialize the TAB_CONT field of the
214 next line if it wraps.
215
216 Since computing the parameters of a line are fairly complicated, I
217 have one interface that should be all you ever need to figure out
218 something about a line.  The function FIND_LINE_PARAMS computes the
219 parameters of a single line.  The function LINE_PARAMS_ITERATE is used
220 for computing the properties of some number (> 0) of sequential lines.
221
222 void
223 line_params_iterate (GtkText* text,
224                      const GtkPropertyMark* mark0,
225                      const PrevTabCont* tab_mark0,
226                      gboolean alloc,
227                      gpointer data,
228                      LineIteratorFunction iter);
229
230 where LineIteratorFunction is:
231
232 typedef gint (*LineIteratorFunction) (GtkText* text,
233                                       LineParams* lp,
234                                       gpointer data);
235
236 The arguments are a text widget (TEXT), the property mark at the
237 beginning of the first line (MARK0), the tab stop mark at the
238 beginning of that line (TAB_MARK0), whether to heap-allocate the
239 LineParams structure (ALLOC), some client data (DATA), and a function
240 to call with the parameters of each line.  TAB_MARK0 may be NULL, but
241 if so MARK0 MUST BE A REAL LINE START (not a continued line start; it
242 is preceded by a newline).  If TAB_MARK0 is not NULL, MARK0 may be any
243 line start (continued or not).  See the code for examples.  The
244 function ITER is called with each LineParams computed.  If ALLOC was
245 true, LINE_PARAMS_ITERATE heap-allocates the LineParams and does not
246 free them.  Otherwise, no storage is permanently allocated.  ITER
247 should return TRUE when it wishes to continue no longer.
248
249 There are currently two uses of LINE_PARAMS_ITERATE:
250
251 * Compute the total buffer height for setting the parameters of the
252   scroll bars.  This is done in SET_VERTICAL_SCROLL each time the
253   window is resized.  When horizontal scrolling is added, depending on
254   the policy chosen, the max line width can be computed here as well.
255
256 * Computing geometry of some pixel height worth of lines.  This is
257   done in FETCH_LINES, FETCH_LINES_BACKWARD, FETCH_LINES_FORWARD, etc.
258
259 The GtkText structure contains a cache of the LineParams data for all
260 visible lines:
261
262   GList *current_line;
263   GList *line_start_cache;
264
265   guint first_line_start_index;
266   guint first_cut_pixels;
267   guint first_onscreen_hor_pixel;
268   guint first_onscreen_ver_pixel;
269
270 LINE_START_CACHE is a doubly linked list of LineParams.  CURRENT_LINE
271 is a transient piece of data which is set in varoius places such as
272 the mouse click code.  Generally, it is the line on which the cursor
273 property mark CURSOR_MARK is on.  LINE_START_CACHE points to the first
274 visible line and may contain PREV pointers if the cached data of
275 offscreen lines is kept around.  I haven't come up with a policy.  The
276 cache can keep more lines than are visible if desired, but the result
277 is that inserts and deletes will then become slower as the entire
278 cache has to be "corrected".  Right now it doesn't delete from the
279 cache (it should).  As a result, scrolling through the whole buffer
280 once will fill the cache with an entry for each line, and subsequent
281 modifications will be slower than they should
282 be. FIRST_LINE_START_INDEX is the index of the *REAL* line start of
283 the first line.  That is, if the first visible line is a continued
284 line, this is the index of the real line start (preceded by a
285 newline).  FIRST_CUT_PIXELS is the number of pixels which are not
286 drawn on the first visible line.  If FIRST_CUT_PIXELS is zero, the
287 whole line is visible.  FIRST_ONSCREEN_HOR_PIXEL is not used.
288 FIRST_ONSCREEN_VER_PIXEL is the absolute pixel which starts the
289 visible region.  This is used for setting the vertical scroll bar.
290
291 Other miscellaneous things in the GtkText structure:
292
293 Gtk specific things:
294
295   GtkWidget widget;
296
297   GdkWindow *text_area;
298
299   GtkAdjustment *hadj;
300   GtkAdjustment *vadj;
301
302   GdkGC *gc;
303
304   GdkPixmap* line_wrap_bitmap;
305   GdkPixmap* line_arrow_bitmap;
306
307 These are pretty self explanatory, especially if you know Gtk.
308 LINE_WRAP_BITMAP and LINE_ARROW_BITMAP are two bitmaps used to
309 indicate that a line wraps and is continued offscreen, respectively.
310
311 Some flags:
312
313   guint has_cursor : 1;
314   guint is_editable : 1;
315   guint line_wrap : 1;
316   guint freeze : 1;
317   guint has_selection : 1;
318   guint own_selection : 1;
319
320 HAS_CURSOR is true iff the cursor is visible.  IS_EDITABLE is true iff
321 the user is allowed to modify the buffer.  If IS_EDITABLE is false,
322 HAS_CURSOR is guaranteed to be false.  If IS_EDITABLE is true,
323 HAS_CURSOR starts out false and is set to true the first time the user
324 clicks in the window.  LINE_WRAP is where the line-wrap policy is
325 set.  True means wrap lines, false means continue lines offscreen,
326 horizontally.
327
328 The text properties list:
329
330   GList *text_properties;
331   GList *text_properties_end;
332
333 A scratch area used for constructing a contiguous piece of the buffer
334 which may otherwise span the gap.  It is not strictly neccesary
335 but simplifies the drawing code because it does not need to deal with
336 the gap.
337
338   guchar* scratch_buffer;
339   guint   scratch_buffer_len;
340
341 The last vertical scrollbar position.  Currently this looks the same
342 as FIRST_ONSCREEN_VER_PIXEL.  I can't remember why I have two values.
343 Perhaps someone should clean this up.
344
345   gint last_ver_value;
346
347 The cursor:
348
349   gint            cursor_pos_x;
350   gint            cursor_pos_y;
351   GtkPropertyMark cursor_mark;
352   gchar           cursor_char;
353   gchar           cursor_char_offset;
354   gint            cursor_virtual_x;
355   gint            cursor_drawn_level;
356
357 CURSOR_POS_X and CURSOR_POS_Y are the screen coordinates of the
358 cursor.  CURSOR_MARK is the buffer position.  CURSOR_CHAR is
359 TEXT_INDEX (TEXT, CURSOR_MARK.INDEX) if a drawable character, or 0 if
360 it is whitespace, which is treated specially.  CURSOR_CHAR_OFFSET is
361 the pixel offset above the base of the line at which it should be
362 drawn.  Note that the base of the line is not the "baseline" in the
363 traditional font metric sense.  A line (LineParams) is
364 FONT_ASCENT+FONT_DESCENT high (use the macro LINE_HEIGHT).  The
365 "baseline" is FONT_DESCENT below the base of the line.  I think this
366 requires a drawing.
367
368 0                      AAAAAAA
369 1                      AAAAAAA
370 2                     AAAAAAAAA
371 3                     AAAAAAAAA
372 4                    AAAAA AAAAA
373 5                    AAAAA AAAAA
374 6                   AAAAA   AAAAA
375 7                  AAAAA     AAAAA
376 8                  AAAAA     AAAAA
377 9                 AAAAAAAAAAAAAAAAA
378 10                AAAAAAAAAAAAAAAAA
379 11               AAAAA         AAAAA
380 12               AAAAA         AAAAA
381 13              AAAAAA         AAAAAA
382 14______________AAAAA___________AAAAA__________________________________
383 15
384 16
385 17
386 18
387 19
388 20
389
390 This line is 20 pixels high, has FONT_ASCENT=14, FONT_DESCENT=6.  It's
391 "base" is at y=20.  Characters are drawn at y=14.  The LINE_START
392 macro returns the pixel height.  The LINE_CONTAINS macro is true if
393 the line contains a certain buffer index.  The LINE_STARTS_AT macro is
394 true if the line starts at a certain buffer index.  The
395 LINE_START_PIXEL is the pixel offset the line should be drawn at,
396 according the the tab continuation of the previous line.
397
398 Exposure and drawing:
399
400 Exposure is handled from the EXPOSE_TEXT function.  It assumes that
401 the LINE_START_CACHE and all its parameters are accurate and simply
402 exposes any line which is in the exposure region.  It calls the
403 CLEAR_AREA function to clear the background and/or lay down a pixmap
404 background.  The text widget has a scrollable pixmap background, which
405 is implemented in CLEAR_AREA.  CLEAR_AREA does the math to figure out
406 how to tile the pixmap itself so that it can scroll the text with a
407 copy area call.  If the CURSOR argument to EXPOSE_TEXT is true, it
408 also draws the cursor.
409
410 The function DRAW_LINE draws a single line, doing all the tab and
411 color computations neccesary.  The function DRAW_LINE_WRAP draws the
412 line wrap bitmap at the end of the line if it wraps.  TEXT_EXPOSE will
413 expand the cached line data list if it has to by calling
414 FETCH_LINES_FORWARD.  The functions DRAW_CURSOR and UNDRAW_CURSOR draw
415 and undraw the cursor.  They count the number of draws and undraws so
416 that the cursor may be undrawn even if the cursor is already undrawn
417 and the re-draw will not occur too early.  This is useful in handling
418 scrolling.
419
420 Handling of the cursor is a little messed up, I should add.  It has to
421 be undrawn and drawn at various places.  Something better needs to be
422 done about this, because it currently doesn't do the right thing in
423 certain places.  I can't remember where very well.  Look for the calls
424 to DRAW_CURSOR and UNDRAW_CURSOR.
425
426 RECOMPUTE_GEOMETRY is called when the geometry of the window changes
427 or when it is first drawn.  This is probably not done right.  My
428 biggest weakness in writing this code is that I've never written a
429 widget before so I got most of the event handling stuff wrong as far
430 as Gtk is concerned.  Fortunatly, most of the code is unrelated and
431 simply an exercise in data structure manipulation.
432
433 Scrolling:
434
435 Scrolling is fairly straighforward.  It looks at the top line, and
436 advances it pixel by pixel until the FIRST_CUT_PIXELS equals the line
437 height and then advances the LINE_START_CACHE.  When it runs out of
438 lines it fetches more.  The function SCROLL_INT is used to scroll from
439 inside the code, it calls the appropriate functions and handles
440 updating the scroll bars.  It dispatches a change event which causes
441 Gtk to call the correct scroll action, which then enters SCROLL_UP or
442 SCROLL_DOWN.  Careful with the cursor during these changes.
443
444 Insertion, deletion:
445
446 There's some confusion right now over what to do with the cursor when
447 it's offscreen due to scrolling.  This is a policy decision.  I don't
448 know what's best.  Spencer criticized me for forcing it to stay
449 onscreen.  It shouldn't be hard to make stuff work with the cursor
450 offscreen.
451
452 Currently I've got functions to do insertion and deletion of a single
453 character.  It's fairly complicated.  In order to do efficient pasting
454 into the buffer, or write code that modifies the buffer while the
455 buffer is drawn, it needs to do multiple characters at at time.  This
456 is the hardest part of what remains.  Currently, gtk_text_insert does
457 not reexpose the modified lines.  It needs to.  Pete did this wrong at
458 one point and I disabled modification completely, I don't know what
459 the current state of things are.  The functions
460 INSERT_CHAR_LINE_EXPOSE and DELETE_CHAR_LINE_EXPOSE do the work.
461 Here's pseudo code for insert.  Delete is quite similar.
462
463   insert character into the buffer
464   update the text property list
465   move the point
466   undraw the cursor
467   correct all LineParams cache entries after the insertion point
468   compute the new height of the modified line
469   compare with the old height of the modified line
470   remove the old LineParams from the cache
471   insert the new LineParams into the cache
472   if the lines are of different height, do a copy area to move the
473     area below the insertion down
474   expose the current line
475   update the cursor mark
476   redraw the cursor
477
478 What needs to be done:
479
480 Horizintal scrolling, robustness, testing, selection handling.  If you
481 want to work in the text widget pay attention to the debugging
482 facilities I've written at the end of gtktext.c.  I'm sorry I waited
483 so long to try and pass this off.  I'm super busy with school and
484 work, and when I have free time my highest priority is another version
485 of PRCS.
486
487 Feel free to ask me questions.