# tktext-port **Repository Path**: mirrors_chromium_gitlab_gnome/tktext-port ## Basic Information - **Project Name**: tktext-port - **Description**: No description available - **Primary Language**: Unknown - **License**: Not specified - **Default Branch**: master - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 0 - **Forks**: 0 - **Created**: 2021-08-09 - **Last Updated**: 2025-12-03 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README This is a port of the TkText widget from a Tk8.3 beta to GTK+. It is now reasonably complete and should be basically usable. For now it is code-named "FrooTkxt". The reason it's called FrooTkxt is that that's a unique string I can run a sed/perl script over when I decide what to call it. :-) If you want to use the widget go ahead, I'll make the script available to fix your program when I rename the widget. Compiling === To compile, you need "libunicode", in CVS module "libunicode" Features === - horizontal scrolling (what a concept) - tags specify text properties. tags can also be used to mark text semantically, or to handle events on regions of text. the properties of a tag can be changed after applying it, so for example you can change all text with the "foo" tag to be blue by just modifying the "foo" tag object. - marks can be set in the text; the insertion point is an example of a mark. But you can set your own custom marks to keep track of a place in the text - text has a lot of properties, like stipple, overstrike, justification, margins, wrap mode, spacing between lines, etc.; all properties are per-tag rather than global to the widget. - Flexible means of referring to locations in the buffer; character index (like GtkText), line index with character-on-line index, fast iterator object, mark names - Embeds images (and eventually widgets) - Model-view architecture allows multiple widgets displaying the same text buffer - UTF8 storage (but has display problems for non-Latin-1 chars right now) - Fast, and you should almost never have to understand its performance characteristics because almost any operation is reasonably fast. No need for freeze/thaw. - Pretty clean interface for the public stuff (frootkxt.h, frootkxtbuffer.h, frootkxtiter.h, frootkxttag.h, frootkxttagtable.h) - Pixel-based, flicker-free scrolling Non-Features === - Uses a reasonable-given-the-functionality, but fairly large, amount of memory. - Private implementation files are somewhat crufty, and the BTree data structure can probably be described as scary. It is a sane structure, it is supposed to work in a certain way and does work that way, and there are a million assertions and invariants - but it's still Extreme Complexity. Description of the original Tk widget === To see the Tk widget in action, on my Debian system you go to /usr/doc/tk8.2-dev/examples and run the script "widget", on my Red Hat system you install the SRPM for tcl/tk, rpm -bp, and then look at /usr/src/redhat/BUILD/tcltk-8.0.5/tk8.0.5/library/demos/widget. On both systems "man text" should give you the man page as well. Tags === The basic idea of the Tk widget is that you create named tags to apply to your text. These are conceptually similar to XML/HTML and stylesheets. A tag is simply a named collection of properties; you apply a tag to a region of text. For example you might make a tag called "blue" that just colors some text blue. You might have another tag "bold". If you apply both "blue" and "bold" to some text it will be both blue and bold. The tag properties are things like: - foreground color - background color - margins - font - spacing between lines and wrapped lines - super/subscript offset - 3D border - tab stops Tags have priorities. No two tags have the same priority. If two tags both specify a value for the same attribute (say the foreground color), the higher priority tag wins. The tags are kept in a tag table, that provides quick lookup by name, and also enforces the invariant that no two tags have the same priority (i.e. if you change the priority of a tag it goes through the table and fixes the other tags). You can bind tcl commands to tags, which are invoked when events occur on tagged text. For example you could have a "hotlink" tag and whenever you get a button click on text tagged with "hotlink" your callback would be invoked. Marks === A mark is like a bookmark in the text. An example of a mark is the insertion point. Marks can have right gravity (like the insertion point), so they move right if you insert at the mark, or they can have left gravity. The insertion point is implemented as a mark, but you can also add your own marks; they are referred to by name just as tags are. Indexes === Points or regions of text in the widget are specified with indexes. An index is just a string with a special mini-language; some examples are: @10,10 location at pixel 10,10 end end of text markname location of a named mark tagname.first location of first char tagged with tagname tagname.last one past the last char tagged with tagname @10,10 + 15 lines 15 lines past the location at 10,10 markname linestart start of line containing markname @10,10 wordend end of word at 10,10 @10,10 wordstart + 1 chars one char into the word at 10,10 The GTK port maintains a function to parse this but obviously it's a bit awful from C, it's for language bindings. Images/widgets === You can embed images and widgets in the Tk widget; they are placed in the buffer like large characters (as with HTML inline images, no text flow or anything like that). Scrolling === Scrolling is based on the number of unwrapped lines. This means that lines are either fully on-screen or fully off-screen; which produces a bad effect with lines that contain images. Also the scrollbars change size as you scroll as the actually-on-screen lines are wrapped, which looks strange. i18n === The widget stores text in UTF8 and distinguishes number of characters from number of bytes. However it doesn't support right-to-left or bidirectional text or anything like that. Basically it's as good as current GTK. Selection === The selection is a "magic tag" called "sel" - if you change what "sel" is applied to, the widget claims that region as the X selection, if the selection changes then "sel" is moved to cover the new selection, if the X selection is lost "sel" is removed from all text in the btree. Text data structure === The basic data structure of the widget is a btree. The tree leaves are paragraphs (unwrapped, unformatted lines that end with a newline). The tree is designed to allow you to find a particular line number quickly. Each node of the tree stores the number of lines below that node, etc. The nodes also track the number of transitions in or out of a given tag below each tree node. So, the tree gives you two things you can locate quickly: line numbers, and ranges of text a given tag is applied to. There's a hash from mark names to locations in the tree, so marks can be located in O(1) time. Each line (remember lines are stored at the tree leaves) is a list of "segments." There are many segment types: - character data - toggle on tag - means that a tag goes into effect at this point - toggle off tag - means a tag goes out of effect at this point - mark with left gravity - mark with right gravity - embedded image - embedded widget Text display === Remember that the scroll region is specified in terms of lines. To display, the visible scroll region is extracted from the BTree (a fast operation since the BTree is keyed by line number). Each line is wrapped into one or more "display lines" according to its wrap settings (none, character, or word). A display line is a list of display chunks; a display chunk can be text with a uniform style, the cursor, an image, etc. Only display lines for the current visible region are kept around. If the visible region changes the display lines are thrown out. Display lines are calculated in an idle function, similar to the way GnomeCanvas and presumably TkCanvas work. To find the character corresponding to a given pixel coordinate, you just linear search the on-screen display lines. Actually drawing the display is trivial, you iterate over the display lines and display chunks, draw them to a backing pixmap, then plop the pixmap on the screen. Port to GTK ================================= The GTK port makes a number of changes. It still uses the same basic BTree, and it still uses the concept of display lines and display chunks for display. It also maintains the same basic architecture: tags, marks, indexes, etc. Changes: - The BTree is wrapped with a GtkObject called FrooTkxtBuffer - The BTree is now keyed by character count and height in addition to line count, so there are four ways to search: tag, char number, line number, and Y pixel - The BTree now stores per-view information about wrapped lines, so this information can be rapidly located. - The BTree in effect maintains the damage region of lines that need to be rewrapped - you can have multiple text widgets displaying a given buffer - the line-wrap and display code is moved to a GtkObject called FrooTkxtLayout; this object is responsible for determining the size of the entire text widget in pixels, and generating display lines/chunks - the FrooTkxt widget itself subclasses GtkLayout; its expose/draw methods are just trivial calls into FrooTkxtLayout, the main job of FrooTkxt is to modify FrooTkxtBuffer in response to mouse and key events - FrooTkxtLayout implements pixel-based scrolling - I broke tab stop handling and word wrap (character-based wrap works), these need restoring - I took out the 3D border stuff because it is complicated to display and the port wasn't straightforward, and I don't consider it very important - all the key bindings were written in Tcl, so need to be redone with the GTK binding system (mostly done now) - I haven't ported widget embedding yet, but it should be trivial - GDK doesn't have a way to draw a UTF8 string, so right now it gets converted to Latin-1 and passed to gdk_draw_text() - The selection isn't a magic tag; instead it is the area between two magic marks, "selection_bound" and "insert" - "insert" is of course the insertion point My current method of doing pixel-based scrolling is to generate _all_ the display lines, instead of just the ones that are on-screen, as the Tk widget does. This means that _inevitably_ you have to wrap all the lines in the buffer to do your scrollbars, and rewrap them whenever they change. This is inherently somewhat slow. I have it pretty fast now, should be Good Enough (tm), but it is inherently slower than Emacs or the Tk widget. Keeping all the wrapped lines around uses significant RAM. I've made this a lot better through various optimizations; the main one is throwing out the display chunks (you have to generate the chunks in order to get the y coordinates of the lines, but then you can free them and later regenerate only the ones you need for the on-screen lines). I am wrapping lines "on demand", where demand is "when we need to draw a widget or find out its size" - this means I'm using the gtk_widget_queue_draw() drawing task in effect to queue up the line wrap. The other time wrapping is required is in response to size_allocate. There are two reasons for separating FrooTkxtLayout from the FrooTkxtWidget: - it keeps the code organized - FrooTkxtLayout can be re-used in an editable text canvas item, or other kinds of view Memory analysis (based on an old version, but the latest is similar) === All these memory figures INCLUDE the basic GTK overhead, i.e. they are based on the total bytes allocated reported by memprof for the test app. The widget uses perhaps more memory than I'd like; it is almost all in the BTree, and it can be reduced but only with a speed penalty (calculating some things every time instead of storing them). We already have a good idea how and where you'd go about reducing memory use, it's just a matter of whether you are willing to put up with the speed hit. Sample memory usage for a test program displaying ASCII buffer with significant tags, some short lines, and some long wrapped lines (wrapped lines use extra display lines, ergo more RAM): 263 bytes/unwrapped line 7.25 bytes/character This is a pretty bad case, because the lines are only 36 characters on average, except for the one long line using more display lines, and the lines are broken up by tags. Same text, but without styles and the window wide enough to avoid line wrap: 159 bytes/line 4.4 bytes/character Same number of lines, without styles, wide window, and about 80 chars per line: 200 bytes/line (note increase of 40 chars, we only paid for the text itself) 2.59 bytes/character Anyway what this tells you is that each blank line of text costs about 120 bytes of total overhead, including GTK runtime, the text widget display, the BTree, everything. Adding 80 characters to the line costs only the size of those characters. Adding line wrap increases the memory usage a little but not a lot. Adding styled text can sharply drive up your memory usage. Files === Public headers: frootkxt.h - the widget frootkxtbuffer.h - the GtkObject representing the text buffer frootkxttag.h - tags frootkxttagtable.h - tag table frootkxtiter.h - iterators Private headers: frootkxtlayout.h - GtkObject used to implement layout frootkxtbtree.h - the BTree data structure and associated scariness frootkxtiterprivate.h - internal iterator operations frootkxtmark.h - implementation of mark segment types for btree frootkxtdisplay.h - code to render the display lines from FrooTkxtLayout Unfortunately if you wanted to reuse FrooTkxtLayout, etc. for an editable canvas item or other alternative view of the FrooTkxtBuffer, you would need access to the private headers so they might have to be installed. Of course the good thing is that an editable text canvas item based on this buffer/layout engine is very easy to write. All code except for frootkxt.[hc] could be re-used in a canvas item or other display. TODO ================================= - Finish all the keybindings; see KEYS for a list - Embeddable widgets - The widget needs get/set functions and object arguments for all its settings (basically the default style should be configurable) - Eventually the widget could be Pango-ized; the BTree should be fine as is, Pango needs to be used to wrap the lines and then display them. Pango will slow down the line wrap most likely. - Need to restore word wrap and tab stops - Not all the tag attributes get displayed; for example stippling isn't yet implemented I don't think. Edit frootkxtdisplay.c - The first line ignores the pixels_above_line tag attribute - probably want to draw a frame around the text - Assorted cleanup, grep for FIXME - Squiggly underline - drag-and-drop the selection - anonymous marks