Blame docs/text_widget.txt

Packit 98cdb6
Date: Sun, 14 Sep 1997 20:17:06 -0700 (PDT)
Packit 98cdb6
From: Josh MacDonald <jmacd@CS.Berkeley.EDU>
Packit 98cdb6
To: gnome@athena.nuclecu.unam.mx, gtk-list@redhat.com
Packit 98cdb6
Subject: [gtk-list] gtktext widget internal documentation
Packit 98cdb6
Packit 98cdb6
Packit 98cdb6
Pete convinced me to just write up the text widget and let someone else
Packit 98cdb6
finish it.  I'm pretty busy and have other commitments now.  Sorry.  I think
Packit 98cdb6
I'm not the most qualified for some of the remaining work anyway, because I
Packit 98cdb6
don't really know Gtk and it's event model very well.  Most of the work so 
Packit 98cdb6
far was possible without knowing Gtk all that well, it was simply a data 
Packit 98cdb6
structure exercise (though after reading this you might say it was a fairly
Packit 98cdb6
complicated data structure exercise).  I'm happy to answer questions.
Packit 98cdb6
Packit 98cdb6
-josh
Packit 98cdb6
Packit 98cdb6
Packit 98cdb6
High level description:
Packit 98cdb6
Packit 98cdb6
There are several layers of data structure to the widget.  They are
Packit 98cdb6
separated from each other as much as possible.  The first is a gapped
Packit 98cdb6
text segment similar to the data structure Emacs uses for representing
Packit 98cdb6
text.  Then there is a property list, which stores text properties for
Packit 98cdb6
various ranges of text.  There is no direct relation between the text
Packit 98cdb6
property list and the gapped text segment.  Finally there is a drawn
Packit 98cdb6
line parameter cache to speed calculations when drawing and redrawing
Packit 98cdb6
lines on screen.  In addition to these data structures, there are
Packit 98cdb6
structures to help iterate over text in the buffer.
Packit 98cdb6
Packit 98cdb6
The gapped text segment is quite simple.  It's parameters are (all
Packit 98cdb6
parameters I mention here are in the structure GtkText):
Packit 98cdb6
Packit 98cdb6
  guchar* text;
Packit 98cdb6
  guint text_len;
Packit 98cdb6
  guint gap_position;
Packit 98cdb6
  guint gap_size;
Packit 98cdb6
  guint text_end;
Packit 98cdb6
Packit 98cdb6
TEXT is the buffer, TEXT_LEN is its allocated length.  TEXT_END is the
Packit 98cdb6
length of the text, including the gap.  GAP_POSITION is the start of
Packit 98cdb6
the gap, and GAP_SIZE is the gap's length.  Therefore, TEXT_END -
Packit 98cdb6
GAP_SIZE is the length of the text in the buffer.  The macro
Packit 98cdb6
TEXT_LENGTH returns this value.  To get the value of a character in
Packit 98cdb6
the buffer, use the macro TEXT_INDEX(TEXT,INDEX).  This macro tests
Packit 98cdb6
whether the index is less than the GAP_POSITION and returns
Packit 98cdb6
TEXT[INDEX] or returns TEXT[GAP_SIZE+INDEX].  The function
Packit 98cdb6
MOVE_GAP_TO_POINT positions the gap to a particular index.  The
Packit 98cdb6
function MAKE_FORWARD_SPACE lengthens the gap to provide room for a
Packit 98cdb6
certain number of characters.
Packit 98cdb6
Packit 98cdb6
The property list is a doubly linked list (GList) of text property
Packit 98cdb6
data for each contiguous set of characters with similar properties.
Packit 98cdb6
The data field of the GList points to a TextProperty structure, which
Packit 98cdb6
contains:
Packit 98cdb6
Packit 98cdb6
  TextFont* font;
Packit 98cdb6
  GdkColor* back_color;
Packit 98cdb6
  GdkColor* fore_color;
Packit 98cdb6
  guint length;
Packit 98cdb6
Packit 98cdb6
Currently, only font and color data are contained in the property
Packit 98cdb6
list, but it can be extended by modifying the INSERT_TEXT_PROPERTY,
Packit 98cdb6
TEXT_PROPERTIES_EQUAL, and a few other procedures.  The text property
Packit 98cdb6
structure does not contain an absolute offset, only a length.  As a
Packit 98cdb6
result, inserting a character into the buffer simply requires moving
Packit 98cdb6
the gap to the correct position, making room in the buffer, and either
Packit 98cdb6
inserting a new property or extending the old one.  This logic is done
Packit 98cdb6
by INSERT_TEXT_PROPERTY.  A similar procedure exists to delete from
Packit 98cdb6
the text property list, DELETE_TEXT_PROPERTY.  Since the property
Packit 98cdb6
structure doesn't contain an offset, insertion into the list is an
Packit 98cdb6
O(1) operation.  All such operations act on the insertion point, which
Packit 98cdb6
is the POINT field of the GtkText structure.
Packit 98cdb6
Packit 98cdb6
The GtkPropertyMark structure is used for keeping track of the mapping
Packit 98cdb6
between absolute buffer offsets and positions in the property list.
Packit 98cdb6
These will be referred to as property marks.  Generally, there are
Packit 98cdb6
four property marks the system keeps track of.  Two are trivial, the
Packit 98cdb6
beginning and the end of the buffer are easy to find.  The other two
Packit 98cdb6
are the insertion point (POINT) and the cursor point (CURSOR_MARK).
Packit 98cdb6
All operations on the text buffer are done using a property mark as a
Packit 98cdb6
sort of cursor to keep track of the alignment of the property list and
Packit 98cdb6
the absolute buffer offset.  The GtkPropertyMark structure contains:
Packit 98cdb6
Packit 98cdb6
  GList* property;
Packit 98cdb6
  guint offset;
Packit 98cdb6
  guint index;
Packit 98cdb6
Packit 98cdb6
PROPERTY is a pointer at the current property list element.  INDEX is
Packit 98cdb6
the absolute buffer index, and OFFSET is the offset of INDEX from the
Packit 98cdb6
beginning of PROPERTY.  It is essential to keep property marks valid,
Packit 98cdb6
or else you will have the wrong text properties at each property mark
Packit 98cdb6
transition.  An important point is that all property marks are invalid
Packit 98cdb6
after a buffer modification unless care is taken to keep them
Packit 98cdb6
accurate.  That is the difficulty of the insert and delete operations,
Packit 98cdb6
because as the next section describes, line data is cached and by
Packit 98cdb6
necessity contains text property marks.  The functions for operating
Packit 98cdb6
and computing property marks are:
Packit 98cdb6
Packit 98cdb6
 void advance_mark     (GtkPropertyMark* mark);
Packit 98cdb6
 void decrement_mark   (GtkPropertyMark* mark);
Packit 98cdb6
 void advance_mark_n   (GtkPropertyMark* mark, gint n);
Packit 98cdb6
 void decrement_mark_n (GtkPropertyMark* mark, gint n);
Packit 98cdb6
 void move_mark_n      (GtkPropertyMark* mark, gint n);
Packit 98cdb6
Packit 98cdb6
 GtkPropertyMark find_mark      (GtkText* text, guint mark_position);
Packit 98cdb6
 GtkPropertyMark find_mark_near (GtkText* text, guint mark_position,
Packit 98cdb6
                                 const GtkPropertyMark* near);
Packit 98cdb6
Packit 98cdb6
ADVANCE_MARK and DECREMENT_MARK modify the mark by plus or minus one
Packit 98cdb6
buffer index.  ADVANCE_MARK_N and DECREMENT_MARK_N modify the mark by
Packit 98cdb6
plus or minus N indices.  MOVE_MARK_N accepts a positive or negative
Packit 98cdb6
argument.  FIND_MARK returns a mark at MARK_POSITION using a linear
Packit 98cdb6
search from the nearest known property mark (the beginning, the end,
Packit 98cdb6
the point, etc).  FIND_MARK_NEAR also does a linear search, but
Packit 98cdb6
searches from the NEAR argument.  A number of macros exist at the top
Packit 98cdb6
of the file for doing things like getting the current text property,
Packit 98cdb6
or some component of the current property.  See the MARK_* macros.
Packit 98cdb6
Packit 98cdb6
Next there is a LineParams structure which contains all the
Packit 98cdb6
information necessary to draw one line of text on screen.  When I say
Packit 98cdb6
"line" here, I do not mean one line of text separated by newlines,
Packit 98cdb6
rather I mean one row of text on screen.  It is a matter of policy how
Packit 98cdb6
visible lines are chosen and there are currently two policies,
Packit 98cdb6
line-wrap and no-line-wrap.  I suspect it would not be difficult to
Packit 98cdb6
implement new policies for doing such things as justification.  The
Packit 98cdb6
LineParams structure includes the following fields:
Packit 98cdb6
Packit 98cdb6
  guint font_ascent;
Packit 98cdb6
  guint font_descent;
Packit 98cdb6
  guint pixel_width;
Packit 98cdb6
  guint displayable_chars;
Packit 98cdb6
  guint wraps : 1;
Packit 98cdb6
Packit 98cdb6
  PrevTabCont tab_cont;
Packit 98cdb6
  PrevTabCont tab_cont_next;
Packit 98cdb6
Packit 98cdb6
  GtkPropertyMark start;
Packit 98cdb6
  GtkPropertyMark end;
Packit 98cdb6
Packit 98cdb6
FONT_ASCENT and FONT_DESCENT are the maximum ascent and descent of any
Packit 98cdb6
character in the line.  PIXEL_WIDTH is the number of pixels wide the
Packit 98cdb6
drawn region is, though I don't think it's actually being used
Packit 98cdb6
currently.  You may wish to remove this field, eventually, though I
Packit 98cdb6
suspect it will come in handy implementing horizontal scrolling.
Packit 98cdb6
DISPLAYABLE_CHARS is the number of characters in the line actually
Packit 98cdb6
drawn.  This may be less than the number of characters in the line
Packit 98cdb6
when line wrapping is off (see below).  The bitflag WRAPS tells
Packit 98cdb6
whether the next line is a continuation of this line.  START and END
Packit 98cdb6
are the marks at the beginning and end of the line.  Note that END is
Packit 98cdb6
the actual last character, not one past it, so the smallest line
Packit 98cdb6
(containing, for example, one newline) has START == END.  TAB_CONT and
Packit 98cdb6
TAB_CONT_NEXT are for computation of tab positions.  I will discuss
Packit 98cdb6
them later.
Packit 98cdb6
Packit 98cdb6
A point about the end of the buffer.  You may be tempted to consider
Packit 98cdb6
working with the buffer as an array of length TEXT_LENGTH(TEXT), but
Packit 98cdb6
you have to be careful that the editor allows you to position your
Packit 98cdb6
cursor at the last index of the buffer, one past the last character.
Packit 98cdb6
The macro LAST_INDEX(TEXT, MARK) returns true if MARK is positioned at
Packit 98cdb6
this index.  If you see or add a special case in the code for this
Packit 98cdb6
end-of-buffer case, make sure to use LAST_INDEX if you can.  Very
Packit 98cdb6
often, the last index is treated as a newline.
Packit 98cdb6
Packit 98cdb6
[ One way the last index is special is that, although it is always
Packit 98cdb6
  part of some property, it will never be part of a property of
Packit 98cdb6
  length 1 unless there are no other characters in the text. That
Packit 98cdb6
  is, its properties are always that of the preceding character,
Packit 98cdb6
  if any.
Packit 98cdb6
  
Packit 98cdb6
  There is a fair bit of special case code to maintain this condition -
Packit 98cdb6
  which is needed so that user has control over the properties of
Packit 98cdb6
  characters inserted at the last position. OWT 2/9/98 ]
Packit 98cdb6
Packit 98cdb6
Tab stops are variable width.  A list of tab stops is contained in the
Packit 98cdb6
GtkText structure:
Packit 98cdb6
Packit 98cdb6
  GList *tab_stops;
Packit 98cdb6
  gint default_tab_width;
Packit 98cdb6
Packit 98cdb6
The elements of tab_stops are integers casted to gpointer.  This is a
Packit 98cdb6
little bogus, but works.  For example:
Packit 98cdb6
Packit 98cdb6
  text->default_tab_width = 4;
Packit 98cdb6
  text->tab_stops = NULL;
Packit 98cdb6
  text->tab_stops = g_list_prepend (text->tab_stops, (void*)8);
Packit 98cdb6
  text->tab_stops = g_list_prepend (text->tab_stops, (void*)8);
Packit 98cdb6
Packit 98cdb6
is how these fields are initialized, currently.  This means that the
Packit 98cdb6
first two tabs occur at 8 and 16, and every 4 characters thereafter.
Packit 98cdb6
Tab stops are used in the computation of line geometry (to fill in a
Packit 98cdb6
LineParams structure), and the width of the space character in the
Packit 98cdb6
current font is used.  The PrevTabCont structure, of which two are
Packit 98cdb6
stored per line, is used to compute the geometry of lines which may
Packit 98cdb6
have wrapped and carried part of a tab with them:
Packit 98cdb6
Packit 98cdb6
  guint pixel_offset;
Packit 98cdb6
  TabStopMark tab_start;
Packit 98cdb6
Packit 98cdb6
PIXEL_OFFSET is the number of pixels at which the line should start,
Packit 98cdb6
and tab_start is a tab stop mark, which is similar to a property mark,
Packit 98cdb6
only it keeps track of the mapping between line position (column) and
Packit 98cdb6
the next tab stop.  A TabStopMark contains:
Packit 98cdb6
Packit 98cdb6
  GList* tab_stops;
Packit 98cdb6
  gint to_next_tab;
Packit 98cdb6
Packit 98cdb6
TAB_STOPS is a pointer into the TAB_STOPS field of the GtkText
Packit 98cdb6
structure.  TO_NEXT_TAB is the number of characters before the next
Packit 98cdb6
tab.  The functions ADVANCE_TAB_MARK and ADVANCE_TAB_MARK_N advance
Packit 98cdb6
these marks.  The LineParams structure contains two PrevTabCont
Packit 98cdb6
structures, which each contain a tab stop.  The first (TAB_CONT) is
Packit 98cdb6
for computing the beginning pixel offset, as mentioned above.  The
Packit 98cdb6
second (TAB_CONT_NEXT) is used to initialize the TAB_CONT field of the
Packit 98cdb6
next line if it wraps.
Packit 98cdb6
Packit 98cdb6
Since computing the parameters of a line are fairly complicated, I
Packit 98cdb6
have one interface that should be all you ever need to figure out
Packit 98cdb6
something about a line.  The function FIND_LINE_PARAMS computes the
Packit 98cdb6
parameters of a single line.  The function LINE_PARAMS_ITERATE is used
Packit 98cdb6
for computing the properties of some number (> 0) of sequential lines.
Packit 98cdb6
Packit 98cdb6
void
Packit 98cdb6
line_params_iterate (GtkText* text,
Packit 98cdb6
		     const GtkPropertyMark* mark0,
Packit 98cdb6
		     const PrevTabCont* tab_mark0,
Packit 98cdb6
		     gboolean alloc,
Packit 98cdb6
		     gpointer data,
Packit 98cdb6
		     LineIteratorFunction iter);
Packit 98cdb6
Packit 98cdb6
where LineIteratorFunction is:
Packit 98cdb6
Packit 98cdb6
typedef gint (*LineIteratorFunction) (GtkText* text,
Packit 98cdb6
                                      LineParams* lp,
Packit 98cdb6
                                      gpointer data);
Packit 98cdb6
Packit 98cdb6
The arguments are a text widget (TEXT), the property mark at the
Packit 98cdb6
beginning of the first line (MARK0), the tab stop mark at the
Packit 98cdb6
beginning of that line (TAB_MARK0), whether to heap-allocate the
Packit 98cdb6
LineParams structure (ALLOC), some client data (DATA), and a function
Packit 98cdb6
to call with the parameters of each line.  TAB_MARK0 may be NULL, but
Packit 98cdb6
if so MARK0 MUST BE A REAL LINE START (not a continued line start; it
Packit 98cdb6
is preceded by a newline).  If TAB_MARK0 is not NULL, MARK0 may be any
Packit 98cdb6
line start (continued or not).  See the code for examples.  The
Packit 98cdb6
function ITER is called with each LineParams computed.  If ALLOC was
Packit 98cdb6
true, LINE_PARAMS_ITERATE heap-allocates the LineParams and does not
Packit 98cdb6
free them.  Otherwise, no storage is permanently allocated.  ITER
Packit 98cdb6
should return TRUE when it wishes to continue no longer.
Packit 98cdb6
Packit 98cdb6
There are currently two uses of LINE_PARAMS_ITERATE:
Packit 98cdb6
Packit 98cdb6
* Compute the total buffer height for setting the parameters of the
Packit 98cdb6
  scroll bars.  This is done in SET_VERTICAL_SCROLL each time the
Packit 98cdb6
  window is resized.  When horizontal scrolling is added, depending on
Packit 98cdb6
  the policy chosen, the max line width can be computed here as well.
Packit 98cdb6
Packit 98cdb6
* Computing geometry of some pixel height worth of lines.  This is
Packit 98cdb6
  done in FETCH_LINES, FETCH_LINES_BACKWARD, FETCH_LINES_FORWARD, etc.
Packit 98cdb6
Packit 98cdb6
The GtkText structure contains a cache of the LineParams data for all
Packit 98cdb6
visible lines:
Packit 98cdb6
Packit 98cdb6
  GList *current_line;
Packit 98cdb6
  GList *line_start_cache;
Packit 98cdb6
Packit 98cdb6
  guint first_line_start_index;
Packit 98cdb6
  guint first_cut_pixels;
Packit 98cdb6
  guint first_onscreen_hor_pixel;
Packit 98cdb6
  guint first_onscreen_ver_pixel;
Packit 98cdb6
Packit 98cdb6
LINE_START_CACHE is a doubly linked list of LineParams.  CURRENT_LINE
Packit 98cdb6
is a transient piece of data which is set in various places such as
Packit 98cdb6
the mouse click code.  Generally, it is the line on which the cursor
Packit 98cdb6
property mark CURSOR_MARK is on.  LINE_START_CACHE points to the first
Packit 98cdb6
visible line and may contain PREV pointers if the cached data of
Packit 98cdb6
offscreen lines is kept around.  I haven't come up with a policy.  The
Packit 98cdb6
cache can keep more lines than are visible if desired, but the result
Packit 98cdb6
is that inserts and deletes will then become slower as the entire
Packit 98cdb6
cache has to be "corrected".  Right now it doesn't delete from the
Packit 98cdb6
cache (it should).  As a result, scrolling through the whole buffer
Packit 98cdb6
once will fill the cache with an entry for each line, and subsequent
Packit 98cdb6
modifications will be slower than they should
Packit 98cdb6
be. FIRST_LINE_START_INDEX is the index of the *REAL* line start of
Packit 98cdb6
the first line.  That is, if the first visible line is a continued
Packit 98cdb6
line, this is the index of the real line start (preceded by a
Packit 98cdb6
newline).  FIRST_CUT_PIXELS is the number of pixels which are not
Packit 98cdb6
drawn on the first visible line.  If FIRST_CUT_PIXELS is zero, the
Packit 98cdb6
whole line is visible.  FIRST_ONSCREEN_HOR_PIXEL is not used.
Packit 98cdb6
FIRST_ONSCREEN_VER_PIXEL is the absolute pixel which starts the
Packit 98cdb6
visible region.  This is used for setting the vertical scroll bar.
Packit 98cdb6
Packit 98cdb6
Other miscellaneous things in the GtkText structure:
Packit 98cdb6
Packit 98cdb6
Gtk specific things:
Packit 98cdb6
Packit 98cdb6
  GtkWidget widget;
Packit 98cdb6
Packit 98cdb6
  GdkWindow *text_area;
Packit 98cdb6
Packit 98cdb6
  GtkAdjustment *hadj;
Packit 98cdb6
  GtkAdjustment *vadj;
Packit 98cdb6
Packit 98cdb6
  GdkGC *gc;
Packit 98cdb6
Packit 98cdb6
  GdkPixmap* line_wrap_bitmap;
Packit 98cdb6
  GdkPixmap* line_arrow_bitmap;
Packit 98cdb6
Packit 98cdb6
These are pretty self explanatory, especially if you know Gtk.
Packit 98cdb6
LINE_WRAP_BITMAP and LINE_ARROW_BITMAP are two bitmaps used to
Packit 98cdb6
indicate that a line wraps and is continued offscreen, respectively.
Packit 98cdb6
Packit 98cdb6
Some flags:
Packit 98cdb6
Packit 98cdb6
  guint has_cursor : 1;
Packit 98cdb6
  guint is_editable : 1;
Packit 98cdb6
  guint line_wrap : 1;
Packit 98cdb6
  guint freeze : 1;
Packit 98cdb6
  guint has_selection : 1;
Packit 98cdb6
  guint own_selection : 1;
Packit 98cdb6
Packit 98cdb6
HAS_CURSOR is true iff the cursor is visible.  IS_EDITABLE is true iff
Packit 98cdb6
the user is allowed to modify the buffer.  If IS_EDITABLE is false,
Packit 98cdb6
HAS_CURSOR is guaranteed to be false.  If IS_EDITABLE is true,
Packit 98cdb6
HAS_CURSOR starts out false and is set to true the first time the user
Packit 98cdb6
clicks in the window.  LINE_WRAP is where the line-wrap policy is
Packit 98cdb6
set.  True means wrap lines, false means continue lines offscreen,
Packit 98cdb6
horizontally.
Packit 98cdb6
Packit 98cdb6
The text properties list:
Packit 98cdb6
Packit 98cdb6
  GList *text_properties;
Packit 98cdb6
  GList *text_properties_end;
Packit 98cdb6
Packit 98cdb6
A scratch area used for constructing a contiguous piece of the buffer
Packit 98cdb6
which may otherwise span the gap.  It is not strictly necessary
Packit 98cdb6
but simplifies the drawing code because it does not need to deal with
Packit 98cdb6
the gap.
Packit 98cdb6
Packit 98cdb6
  guchar* scratch_buffer;
Packit 98cdb6
  guint   scratch_buffer_len;
Packit 98cdb6
Packit 98cdb6
The last vertical scrollbar position.  Currently this looks the same
Packit 98cdb6
as FIRST_ONSCREEN_VER_PIXEL.  I can't remember why I have two values.
Packit 98cdb6
Perhaps someone should clean this up.
Packit 98cdb6
Packit 98cdb6
  gint last_ver_value;
Packit 98cdb6
Packit 98cdb6
The cursor:
Packit 98cdb6
Packit 98cdb6
  gint            cursor_pos_x;
Packit 98cdb6
  gint            cursor_pos_y;
Packit 98cdb6
  GtkPropertyMark cursor_mark;
Packit 98cdb6
  gchar           cursor_char;
Packit 98cdb6
  gchar           cursor_char_offset;
Packit 98cdb6
  gint            cursor_virtual_x;
Packit 98cdb6
  gint            cursor_drawn_level;
Packit 98cdb6
Packit 98cdb6
CURSOR_POS_X and CURSOR_POS_Y are the screen coordinates of the
Packit 98cdb6
cursor.  CURSOR_MARK is the buffer position.  CURSOR_CHAR is
Packit 98cdb6
TEXT_INDEX (TEXT, CURSOR_MARK.INDEX) if a drawable character, or 0 if
Packit 98cdb6
it is whitespace, which is treated specially.  CURSOR_CHAR_OFFSET is
Packit 98cdb6
the pixel offset above the base of the line at which it should be
Packit 98cdb6
drawn.  Note that the base of the line is not the "baseline" in the
Packit 98cdb6
traditional font metric sense.  A line (LineParams) is
Packit 98cdb6
FONT_ASCENT+FONT_DESCENT high (use the macro LINE_HEIGHT).  The
Packit 98cdb6
"baseline" is FONT_DESCENT below the base of the line.  I think this
Packit 98cdb6
requires a drawing.
Packit 98cdb6
Packit 98cdb6
0                      AAAAAAA
Packit 98cdb6
1                      AAAAAAA
Packit 98cdb6
2                     AAAAAAAAA
Packit 98cdb6
3                     AAAAAAAAA
Packit 98cdb6
4                    AAAAA AAAAA
Packit 98cdb6
5                    AAAAA AAAAA
Packit 98cdb6
6                   AAAAA   AAAAA
Packit 98cdb6
7                  AAAAA     AAAAA
Packit 98cdb6
8                  AAAAA     AAAAA
Packit 98cdb6
9                 AAAAAAAAAAAAAAAAA
Packit 98cdb6
10                AAAAAAAAAAAAAAAAA
Packit 98cdb6
11               AAAAA         AAAAA
Packit 98cdb6
12               AAAAA         AAAAA
Packit 98cdb6
13              AAAAAA         AAAAAA
Packit 98cdb6
14______________AAAAA___________AAAAA__________________________________
Packit 98cdb6
15
Packit 98cdb6
16
Packit 98cdb6
17
Packit 98cdb6
18
Packit 98cdb6
19
Packit 98cdb6
20
Packit 98cdb6
Packit 98cdb6
This line is 20 pixels high, has FONT_ASCENT=14, FONT_DESCENT=6.  It's
Packit 98cdb6
"base" is at y=20.  Characters are drawn at y=14.  The LINE_START
Packit 98cdb6
macro returns the pixel height.  The LINE_CONTAINS macro is true if
Packit 98cdb6
the line contains a certain buffer index.  The LINE_STARTS_AT macro is
Packit 98cdb6
true if the line starts at a certain buffer index.  The
Packit 98cdb6
LINE_START_PIXEL is the pixel offset the line should be drawn at,
Packit 98cdb6
according the the tab continuation of the previous line.
Packit 98cdb6
Packit 98cdb6
Exposure and drawing:
Packit 98cdb6
Packit 98cdb6
Exposure is handled from the EXPOSE_TEXT function.  It assumes that
Packit 98cdb6
the LINE_START_CACHE and all its parameters are accurate and simply
Packit 98cdb6
exposes any line which is in the exposure region.  It calls the
Packit 98cdb6
CLEAR_AREA function to clear the background and/or lay down a pixmap
Packit 98cdb6
background.  The text widget has a scrollable pixmap background, which
Packit 98cdb6
is implemented in CLEAR_AREA.  CLEAR_AREA does the math to figure out
Packit 98cdb6
how to tile the pixmap itself so that it can scroll the text with a
Packit 98cdb6
copy area call.  If the CURSOR argument to EXPOSE_TEXT is true, it
Packit 98cdb6
also draws the cursor.
Packit 98cdb6
Packit 98cdb6
The function DRAW_LINE draws a single line, doing all the tab and
Packit 98cdb6
color computations necessary.  The function DRAW_LINE_WRAP draws the
Packit 98cdb6
line wrap bitmap at the end of the line if it wraps.  TEXT_EXPOSE will
Packit 98cdb6
expand the cached line data list if it has to by calling
Packit 98cdb6
FETCH_LINES_FORWARD.  The functions DRAW_CURSOR and UNDRAW_CURSOR draw
Packit 98cdb6
and undraw the cursor.  They count the number of draws and undraws so
Packit 98cdb6
that the cursor may be undrawn even if the cursor is already undrawn
Packit 98cdb6
and the re-draw will not occur too early.  This is useful in handling
Packit 98cdb6
scrolling.
Packit 98cdb6
Packit 98cdb6
Handling of the cursor is a little messed up, I should add.  It has to
Packit 98cdb6
be undrawn and drawn at various places.  Something better needs to be
Packit 98cdb6
done about this, because it currently doesn't do the right thing in
Packit 98cdb6
certain places.  I can't remember where very well.  Look for the calls
Packit 98cdb6
to DRAW_CURSOR and UNDRAW_CURSOR.
Packit 98cdb6
Packit 98cdb6
RECOMPUTE_GEOMETRY is called when the geometry of the window changes
Packit 98cdb6
or when it is first drawn.  This is probably not done right.  My
Packit 98cdb6
biggest weakness in writing this code is that I've never written a
Packit 98cdb6
widget before so I got most of the event handling stuff wrong as far
Packit 98cdb6
as Gtk is concerned.  Fortunately, most of the code is unrelated and
Packit 98cdb6
simply an exercise in data structure manipulation.
Packit 98cdb6
Packit 98cdb6
Scrolling:
Packit 98cdb6
Packit 98cdb6
Scrolling is fairly straightforward.  It looks at the top line, and
Packit 98cdb6
advances it pixel by pixel until the FIRST_CUT_PIXELS equals the line
Packit 98cdb6
height and then advances the LINE_START_CACHE.  When it runs out of
Packit 98cdb6
lines it fetches more.  The function SCROLL_INT is used to scroll from
Packit 98cdb6
inside the code, it calls the appropriate functions and handles
Packit 98cdb6
updating the scroll bars.  It dispatches a change event which causes
Packit 98cdb6
Gtk to call the correct scroll action, which then enters SCROLL_UP or
Packit 98cdb6
SCROLL_DOWN.  Careful with the cursor during these changes.
Packit 98cdb6
Packit 98cdb6
Insertion, deletion:
Packit 98cdb6
Packit 98cdb6
There's some confusion right now over what to do with the cursor when
Packit 98cdb6
it's offscreen due to scrolling.  This is a policy decision.  I don't
Packit 98cdb6
know what's best.  Spencer criticized me for forcing it to stay
Packit 98cdb6
onscreen.  It shouldn't be hard to make stuff work with the cursor
Packit 98cdb6
offscreen.
Packit 98cdb6
Packit 98cdb6
Currently I've got functions to do insertion and deletion of a single
Packit 98cdb6
character.  It's fairly complicated.  In order to do efficient pasting
Packit 98cdb6
into the buffer, or write code that modifies the buffer while the
Packit 98cdb6
buffer is drawn, it needs to do multiple characters at at time.  This
Packit 98cdb6
is the hardest part of what remains.  Currently, gtk_text_insert does
Packit 98cdb6
not re-expose the modified lines.  It needs to.  Pete did this wrong at
Packit 98cdb6
one point and I disabled modification completely, I don't know what
Packit 98cdb6
the current state of things are.  The functions
Packit 98cdb6
INSERT_CHAR_LINE_EXPOSE and DELETE_CHAR_LINE_EXPOSE do the work.
Packit 98cdb6
Here's pseudo code for insert.  Delete is quite similar.
Packit 98cdb6
Packit 98cdb6
  insert character into the buffer
Packit 98cdb6
  update the text property list
Packit 98cdb6
  move the point
Packit 98cdb6
  undraw the cursor
Packit 98cdb6
  correct all LineParams cache entries after the insertion point
Packit 98cdb6
  compute the new height of the modified line
Packit 98cdb6
  compare with the old height of the modified line
Packit 98cdb6
  remove the old LineParams from the cache
Packit 98cdb6
  insert the new LineParams into the cache
Packit 98cdb6
  if the lines are of different height, do a copy area to move the
Packit 98cdb6
    area below the insertion down
Packit 98cdb6
  expose the current line
Packit 98cdb6
  update the cursor mark
Packit 98cdb6
  redraw the cursor
Packit 98cdb6
Packit 98cdb6
What needs to be done:
Packit 98cdb6
Packit 98cdb6
Horizontal scrolling, robustness, testing, selection handling.  If you
Packit 98cdb6
want to work in the text widget pay attention to the debugging
Packit 98cdb6
facilities I've written at the end of gtktext.c.  I'm sorry I waited
Packit 98cdb6
so long to try and pass this off.  I'm super busy with school and
Packit 98cdb6
work, and when I have free time my highest priority is another version
Packit 98cdb6
of PRCS.
Packit 98cdb6
Packit 98cdb6
Feel free to ask me questions.