]> Pileus Git - ~andy/gtk/blob - docs/text_widget.txt
83d3b31e5a5a14300c2e7bb0004bd3dda5df7dab
[~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 Tab stops are variable width.  A list of tab stops is contained in the
165 GtkText structure:
166
167   GList *tab_stops;
168   gint default_tab_width;
169
170 The elements of tab_stops are integers casted to gpointer.  This is a
171 little bogus, but works.  For example:
172
173   text->default_tab_width = 4;
174   text->tab_stops = NULL;
175   text->tab_stops = g_list_prepend (text->tab_stops, (void*)8);
176   text->tab_stops = g_list_prepend (text->tab_stops, (void*)8);
177
178 is how these fields are initialized, currently.  This means that the
179 first two tabs occur at 8 and 16, and every 4 characters thereafter.
180 Tab stops are used in the computation of line geometry (to fill in a
181 LineParams structure), and the width of the space character in the
182 current font is used.  The PrevTabCont structure, of which two are
183 stored per line, is used to compute the geometry of lines which may
184 have wrapped and carried part of a tab with them:
185
186   guint pixel_offset;
187   TabStopMark tab_start;
188
189 PIXEL_OFFSET is the number of pixels at which the line should start,
190 and tab_start is a tab stop mark, which is similar to a property mark,
191 only it keeps track of the mapping between line position (column) and
192 the next tab stop.  A TabStopMark contains:
193
194   GList* tab_stops;
195   gint to_next_tab;
196
197 TAB_STOPS is a pointer into the TAB_STOPS field of the GtkText
198 structure.  TO_NEXT_TAB is the number of characters before the next
199 tab.  The functions ADVANCE_TAB_MARK and ADVANCE_TAB_MARK_N advance
200 these marks.  The LineParams structure contains two PrevTabCont
201 structures, which each contain a tab stop.  The first (TAB_CONT) is
202 for computing the beginning pixel offset, as mentioned above.  The
203 second (TAB_CONT_NEXT) is used to initialize the TAB_CONT field of the
204 next line if it wraps.
205
206 Since computing the parameters of a line are fairly complicated, I
207 have one interface that should be all you ever need to figure out
208 something about a line.  The function FIND_LINE_PARAMS computes the
209 parameters of a single line.  The function LINE_PARAMS_ITERATE is used
210 for computing the properties of some number (> 0) of sequential lines.
211
212 void
213 line_params_iterate (GtkText* text,
214                      const GtkPropertyMark* mark0,
215                      const PrevTabCont* tab_mark0,
216                      gboolean alloc,
217                      gpointer data,
218                      LineIteratorFunction iter);
219
220 where LineIteratorFunction is:
221
222 typedef gint (*LineIteratorFunction) (GtkText* text,
223                                       LineParams* lp,
224                                       gpointer data);
225
226 The arguments are a text widget (TEXT), the property mark at the
227 beginning of the first line (MARK0), the tab stop mark at the
228 beginning of that line (TAB_MARK0), whether to heap-allocate the
229 LineParams structure (ALLOC), some client data (DATA), and a function
230 to call with the parameters of each line.  TAB_MARK0 may be NULL, but
231 if so MARK0 MUST BE A REAL LINE START (not a continued line start; it
232 is preceded by a newline).  If TAB_MARK0 is not NULL, MARK0 may be any
233 line start (continued or not).  See the code for examples.  The
234 function ITER is called with each LineParams computed.  If ALLOC was
235 true, LINE_PARAMS_ITERATE heap-allocates the LineParams and does not
236 free them.  Otherwise, no storage is permanently allocated.  ITER
237 should return TRUE when it wishes to continue no longer.
238
239 There are currently two uses of LINE_PARAMS_ITERATE:
240
241 * Compute the total buffer height for setting the parameters of the
242   scroll bars.  This is done in SET_VERTICAL_SCROLL each time the
243   window is resized.  When horizontal scrolling is added, depending on
244   the policy chosen, the max line width can be computed here as well.
245
246 * Computing geometry of some pixel height worth of lines.  This is
247   done in FETCH_LINES, FETCH_LINES_BACKWARD, FETCH_LINES_FORWARD, etc.
248
249 The GtkText structure contains a cache of the LineParams data for all
250 visible lines:
251
252   GList *current_line;
253   GList *line_start_cache;
254
255   guint first_line_start_index;
256   guint first_cut_pixels;
257   guint first_onscreen_hor_pixel;
258   guint first_onscreen_ver_pixel;
259
260 LINE_START_CACHE is a doubly linked list of LineParams.  CURRENT_LINE
261 is a transient piece of data which is set in varoius places such as
262 the mouse click code.  Generally, it is the line on which the cursor
263 property mark CURSOR_MARK is on.  LINE_START_CACHE points to the first
264 visible line and may contain PREV pointers if the cached data of
265 offscreen lines is kept around.  I haven't come up with a policy.  The
266 cache can keep more lines than are visible if desired, but the result
267 is that inserts and deletes will then become slower as the entire
268 cache has to be "corrected".  Right now it doesn't delete from the
269 cache (it should).  As a result, scrolling through the whole buffer
270 once will fill the cache with an entry for each line, and subsequent
271 modifications will be slower than they should
272 be. FIRST_LINE_START_INDEX is the index of the *REAL* line start of
273 the first line.  That is, if the first visible line is a continued
274 line, this is the index of the real line start (preceded by a
275 newline).  FIRST_CUT_PIXELS is the number of pixels which are not
276 drawn on the first visible line.  If FIRST_CUT_PIXELS is zero, the
277 whole line is visible.  FIRST_ONSCREEN_HOR_PIXEL is not used.
278 FIRST_ONSCREEN_VER_PIXEL is the absolute pixel which starts the
279 visible region.  This is used for setting the vertical scroll bar.
280
281 Other miscellaneous things in the GtkText structure:
282
283 Gtk specific things:
284
285   GtkWidget widget;
286
287   GdkWindow *text_area;
288
289   GtkAdjustment *hadj;
290   GtkAdjustment *vadj;
291
292   GdkGC *gc;
293
294   GdkPixmap* line_wrap_bitmap;
295   GdkPixmap* line_arrow_bitmap;
296
297 These are pretty self explanatory, especially if you know Gtk.
298 LINE_WRAP_BITMAP and LINE_ARROW_BITMAP are two bitmaps used to
299 indicate that a line wraps and is continued offscreen, respectively.
300
301 Some flags:
302
303   guint has_cursor : 1;
304   guint is_editable : 1;
305   guint line_wrap : 1;
306   guint freeze : 1;
307   guint has_selection : 1;
308   guint own_selection : 1;
309
310 HAS_CURSOR is true iff the cursor is visible.  IS_EDITABLE is true iff
311 the user is allowed to modify the buffer.  If IS_EDITABLE is false,
312 HAS_CURSOR is guaranteed to be false.  If IS_EDITABLE is true,
313 HAS_CURSOR starts out false and is set to true the first time the user
314 clicks in the window.  LINE_WRAP is where the line-wrap policy is
315 set.  True means wrap lines, false means continue lines offscreen,
316 horizontally.
317
318 The text properties list:
319
320   GList *text_properties;
321   GList *text_properties_end;
322
323 A scratch area used for constructing a contiguous piece of the buffer
324 which may otherwise span the gap.  It is not strictly neccesary
325 but simplifies the drawing code because it does not need to deal with
326 the gap.
327
328   guchar* scratch_buffer;
329   guint   scratch_buffer_len;
330
331 The last vertical scrollbar position.  Currently this looks the same
332 as FIRST_ONSCREEN_VER_PIXEL.  I can't remember why I have two values.
333 Perhaps someone should clean this up.
334
335   gint last_ver_value;
336
337 The cursor:
338
339   gint            cursor_pos_x;
340   gint            cursor_pos_y;
341   GtkPropertyMark cursor_mark;
342   gchar           cursor_char;
343   gchar           cursor_char_offset;
344   gint            cursor_virtual_x;
345   gint            cursor_drawn_level;
346
347 CURSOR_POS_X and CURSOR_POS_Y are the screen coordinates of the
348 cursor.  CURSOR_MARK is the buffer position.  CURSOR_CHAR is
349 TEXT_INDEX (TEXT, CURSOR_MARK.INDEX) if a drawable character, or 0 if
350 it is whitespace, which is treated specially.  CURSOR_CHAR_OFFSET is
351 the pixel offset above the base of the line at which it should be
352 drawn.  Note that the base of the line is not the "baseline" in the
353 traditional font metric sense.  A line (LineParams) is
354 FONT_ASCENT+FONT_DESCENT high (use the macro LINE_HEIGHT).  The
355 "baseline" is FONT_DESCENT below the base of the line.  I think this
356 requires a drawing.
357
358 0                      AAAAAAA
359 1                      AAAAAAA
360 2                     AAAAAAAAA
361 3                     AAAAAAAAA
362 4                    AAAAA AAAAA
363 5                    AAAAA AAAAA
364 6                   AAAAA   AAAAA
365 7                  AAAAA     AAAAA
366 8                  AAAAA     AAAAA
367 9                 AAAAAAAAAAAAAAAAA
368 10                AAAAAAAAAAAAAAAAA
369 11               AAAAA         AAAAA
370 12               AAAAA         AAAAA
371 13              AAAAAA         AAAAAA
372 14______________AAAAA___________AAAAA__________________________________
373 15
374 16
375 17
376 18
377 19
378 20
379
380 This line is 20 pixels high, has FONT_ASCENT=14, FONT_DESCENT=6.  It's
381 "base" is at y=20.  Characters are drawn at y=14.  The LINE_START
382 macro returns the pixel height.  The LINE_CONTAINS macro is true if
383 the line contains a certain buffer index.  The LINE_STARTS_AT macro is
384 true if the line starts at a certain buffer index.  The
385 LINE_START_PIXEL is the pixel offset the line should be drawn at,
386 according the the tab continuation of the previous line.
387
388 Exposure and drawing:
389
390 Exposure is handled from the EXPOSE_TEXT function.  It assumes that
391 the LINE_START_CACHE and all it's parameters are accurate and simply
392 exposes any line which is in the exposure region.  It calls the
393 CLEAR_AREA function to clear the background and/or lay down a pixmap
394 background.  The text widget has a scrollable pixmap background, which
395 is implemented in CLEAR_AREA.  CLEAR_AREA does the math to figure out
396 how to tile the pixmap itself so that it can scroll the text with a
397 copy area call.  If the CURSOR argument to EXPOSE_TEXT is true, it
398 also draws the cursor.
399
400 The function DRAW_LINE draws a single line, doing all the tab and
401 color computations neccesary.  The function DRAW_LINE_WRAP draws the
402 line wrap bitmap at the end of the line if it wraps.  TEXT_EXPOSE will
403 expand the cached line data list if it has to by calling
404 FETCH_LINES_FORWARD.  The functions DRAW_CURSOR and UNDRAW_CURSOR draw
405 and undraw the cursor.  They count the number of draws and undraws so
406 that the cursor may be undrawn even if the cursor is already undrawn
407 and the re-draw will not occur too early.  This is useful in handling
408 scrolling.
409
410 Handling of the cursor is a little messed up, I should add.  It has to
411 be undrawn and drawn at various places.  Something better needs to be
412 done about this, because it currently doesn't do the right thing in
413 certain places.  I can't remember where very well.  Look for the calls
414 to DRAW_CURSOR and UNDRAW_CURSOR.
415
416 RECOMPUTE_GEOMETRY is called when the geometry of the window changes
417 or when it is first drawn.  This is probably not done right.  My
418 biggest weakness in writing this code is that I've never written a
419 widget before so I got most of the event handling stuff wrong as far
420 as Gtk is concerned.  Fortunatly, most of the code is unrelated and
421 simply an exercise in data structure manipulation.
422
423 Scrolling:
424
425 Scrolling is fairly straighforward.  It looks at the top line, and
426 advances it pixel by pixel until the FIRST_CUT_PIXELS equals the line
427 height and then advances the LINE_START_CACHE.  When it runs out of
428 lines it fetches more.  The function SCROLL_INT is used to scroll from
429 inside the code, it calls the appropriate functions and handles
430 updating the scroll bars.  It dispatches a change event which causes
431 Gtk to call the correct scroll action, which then enters SCROLL_UP or
432 SCROLL_DOWN.  Careful with the cursor during these changes.
433
434 Insertion, deletion:
435
436 There's some confusion right now over what to do with the cursor when
437 it's offscreen due to scrolling.  This is a policy decision.  I don't
438 know what's best.  Spencer criticized me for forcing it to stay
439 onscreen.  It shouldn't be hard to make stuff work with the cursor
440 offscreen.
441
442 Currently I've got functions to do insertion and deletion of a single
443 character.  It's fairly complicated.  In order to do efficient pasting
444 into the buffer, or write code that modifies the buffer while the
445 buffer is drawn, it needs to do multiple characters at at time.  This
446 is the hardest part of what remains.  Currently, gtk_text_insert does
447 not reexpose the modified lines.  It needs to.  Pete did this wrong at
448 one point and I disabled modification completely, I don't know what
449 the current state of things are.  The functions
450 INSERT_CHAR_LINE_EXPOSE and DELETE_CHAR_LINE_EXPOSE do the work.
451 Here's pseudo code for insert.  Delete is quite similar.
452
453   insert character into the buffer
454   update the text property list
455   move the point
456   undraw the cursor
457   correct all LineParams cache entries after the insertion point
458   compute the new height of the modified line
459   compare with the old height of the modified line
460   remove the old LineParams from the cache
461   insert the new LineParams into the cache
462   if the lines are of different height, do a copy area to move the
463     area below the insertion down
464   expose the current line
465   update the cursor mark
466   redraw the cursor
467
468 What needs to be done:
469
470 Horizintal scrolling, robustness, testing, selection handling.  If you
471 want to work in the text widget pay attention to the debugging
472 facilities I've written at the end of gtktext.c.  I'm sorry I waited
473 so long to try and pass this off.  I'm super busy with school and
474 work, and when I have free time my highest priority is another version
475 of PRCS.
476
477 Feel free to ask me questions.