FYI: GtkText design progress



     All,
	I am one of the people working on a replacement GtkText widget.  

	I have started a document, appended below, that describes my new
design.  Most of it will simply explain the current design of the GtkText,
since I am using most of the structs designed by Josh MacDonald, but there
are a couple of changes.  Most notably, the linked list of TextPropertys
and the gapped text buffer will become members of a "data buffer" struct,
and all of the drawing code will be part of an API for a "renderer"
struct.

	This is all just in preparation for the designing of the APIs, and
really reflect what I've learned about text editors more than anything
else.  But if you are interested, please take the time to read it and
offer suggestions for an improved design.

Thanks,
Derek
---------------------------------------------------------------------------

New textwidget API and design:
------------------------------

	There will be two modules (i.e., components) to the text
widget.

	Normally, these would be modules in their own .h and .c files, but
since this is a GTK+ widget, everything goes into the same .h and .c
files.  The APIs are treated separately, so don't add any functions that
access private members of structs that should be opaque.  If you need
access to something that can't be obtained via that module's API, write a
new function for it.  Clarity and maintainability are paramount.

	'Component' is probably a better word to use than 'module', but
think of them as functional black boxes.


	The first component is the data buffer.  In short, this is an
emacs-like gapped text buffer plus font information.  Josh MacDonald, the
author of the GtkText, gives the following short description of a gapped
text buffer:

-------------------------------------------------------------------------
Here's a picture of a buffer.

        ABCDEFGHIJ      KLMNOPQRSTUVWXYZ

        0         1         2         3
        0123456789012345678901234567890123456789

The allocation is 40 bytes, the gap is positioned at offset 10 and
has a width of 6 bytes.  To insert a character into the buffer at
the gap position (after J, before K), you just move the gap forward
and shrink it.  Eventually you will either run out of room in the
gap, in which case you must shift everything after the gap over.  To
move the insertion point, you shift the gap.  To insert after 'F' I
would move "GHIJ" forward to the end of the gap.  To insert after 'N'
I would move "KLMN" backward to before the gap.
-------------------------------------------------------------------------

	A more comprehensive description of a gapped text buffer can be
found at:

http://www.web.us.uu.net/staff/djm/lore/buffer-gap

	...which has three sections, so be sure to read them all (number
two especially).

	Another individual, sub-component of the data buffer is the
property list.  One design requirement of the GtkText is to be able to
display different fonts in different colors simultaneously.  That means
that, for each character, we need to store extra information about what
properties that character has (i.e., what font and what color that
character should be displayed in).

	One way to store that information would be to maintain a second 
buffer, parallel to the gapped text buffer, that contained the font/color
information for each character in the text buffer.  Nedit uses this
technique.

	Unfortunately, a second buffer is rather wasteful of memory.  For
example, if you have a text file in which all the characters are the same
font and color (except, say, the heading at the beginning is in bold), you
are using twice as much RAM as necessary to store the characters.

	Instead, we use a "property list".  The property list is a
doubly-linked list of TextProperty structs.  A TextProperty struct is
defined as:

struct _TextProperty
{
  /* Font. */
  GtkSCTextFont* font;

  /* Background Color. */
  GdkColor back_color;
  
  /* Foreground Color. */
  GdkColor fore_color;

  /* Show which properties are set */
  TextPropertyFlags flags;

  /* Length of this property. */
  guint length;

  /* user data */
  gpointer user_data;
};

	[...and just FYI:]

typedef enum {
  PROPERTY_FONT =       1 << 0,
  PROPERTY_FOREGROUND = 1 << 1,
  PROPERTY_BACKGROUND = 1 << 2,
  PROPERTY_DATA =       1 << 3
} TextPropertyFlags;



FIXME: Should some of those members be labelled as 'private'?

	Note that the TextProperty stores font and color information for a
certain number of bytes ('length'), but does not contain an absolute
offset from the beginning of the buffer.  I.e., there's no index for each
TextProperty, because if I added a single character at the beginning of
the displayed text file, I'd have to correct every TextPropery in the
list, and that could become a major performance problem.

	The disadvantage of using a linked list over the parallel buffer
method used by Nedit is that it must be maintained, and you need to track
which TextPropery you are currently in so you know how to draw the text.
This can get a little expensive when you jump from one location in the
text file to another, because you must iterate over the list and add all
the 'length' elements until you get to the correct TextProperty for your
current position.  In practice, though, this shouldn't be noticable to the
user.  (FIXME: Need to verify that this property list is not a performance
bottleneck.)  Also, the code for dealing with things like deletions can
get a little complicated (because you need to splice out, and free, the
affected TextPropery list elements), but it's not too bad.

	The user_data struct member is there for extensibility.  With it,
you could specify things like whether or not the text should be blinking,
or translucent, or saved with <emphasis> tags, or who-knows-what.  
(FIXME: Need to describe the API for that here).  The TextProperty also
has its own API, in order to maintain clarity of code, but it is
definately a sub-componenent of the data buffer.

	The data buffer should be thought of as the raw data.  You should
be able to grab the buffer component and reuse its API with any
interface--a terminal screen, a scripted text-editor, or whatever.  No
function in the data buffer component's API should depend on (or even know
about) how the text is actually drawn to screen.

	That is why the TextProperty list is a sub-component of the data
buffer; the application using the GtkText may wish to save the displayed
file, with its bold and colored fonts, as an HTML file (or, say, in
WordPerfect format) instead of plaintext. 

	The second component is the renderer.  The renderer translates the
stuff from the data buffer into pixels on the screen.  It uses GDK to draw
fonts, the scrollable background pixmap, etcetera.  It is also in charge
of handling scrolling, since scrolling is a purely visual phenomena, and
of drawing (and undrawing) the cursor.

	Of course, the renderer does depend on the data buffer (what good
is a renderer without data which needs to be rendered).  However, it will
access the data buffer only through the data buffer's API and public
struct members.  So one member of the renderer stuct needs to be a pointer
to a data buffer struct.  (FIXME:Describe that here when the API/structs
are designed.)


	Be aware that there is much code in the GtkText widget which is
not part of either the data buffer or the renderer.  The GtkText is built
on top of those componenents, but some code (such as handling the
GtkBindings, clipboard stuff, the GTK+ signals, etc.) is purely widget
code which does not belong in either the buffer or the renderer.


	TODO: Write the structs and APIs and list them here.



EOF



[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]