Editor Project 3 Design Discussion
Lecture Notes for CS 190
Spring 2016
John Ousterhout
- How do you get started on a design like this? Think about what pieces
of knowledge need to be encapsulated:
- Management of display styles
- Word wrapping
- How do you know if you have achieved good encapsulation? Can make
modifications and improvements in one place (e.g. add a new style
attribute such as background color).
- Must externalize style information in two ways:
- Saving in files
- Cut and paste
Try to find one approach that works for both
- Dependencies: what aspects of the editor will depend on the fact that
text is styled? This determines what features must be provided by the
style class(es).
- Text representation: must associate styles with text
- Store styling information on disk
- Displaying text
- Computing screen layout
- Mapping between screen coordinates and document positions
- Scrolling:
- Total height of document depends on formatting and line wrapping
- When scrolling to a particular document location (e.g. after
undo, to make undone info visible), need to compute new value
for scrollbar
- Formatting panel:
- Elements reflect the features of styling
- Need to display styles of selected text
- Text insertion: use style of previous character
- Cut and paste: must include styling information
- Ideally, have one class with a minimal core of all the above features;
it knows everything about styles, but as little as possible about
anything else
- How to associate styles with text?
- Each character has a style
- Styles are associated with ranges of characters
- Requires splitting and merging ranges as formatting changes
- Parallel data structure, or combined with text?
- Parallel is too hard to maintain
- Styles in text, or reference to style object?
- Full styles will be too expensive, at least for per-character
- Better to separate style information from text storage
- Text representation knows how to associate a style with each character,
but it knows nothing about how the styles work.
- I would use an atom-like approach:
- Keep a hash table with all unique styles currently in use
- All Style pointers refer to entries in this hash table
- Use WeakReferences for garbage collection
- Template the text with the style type, or use a common interface?
Probably template: allows totally different implementations of
Styles.
- StyledString class encapsulates how to associate styles with
text, provides string manipulation, but knows almost nothing about the
contents of styles (e.g. no idea that there is a "bold" attribute).
- Many methods from String: substr, concat, etc.
- String rawText(int start, int end): retrieve text without
style information
- int numBlocks(): total number of blocks, each with uniform styling
- StyledString.Block getBlock(int index): retrieve block
- StyledString insert(int start, String newText, Style style):
create a new StyledString formed by inserting text with a given style
at a given location.
- StyledString delete(int start, int end):
create a new StyledString formed by removing a given range from an existing
StyledString.
- setStyle(Style, style, int start, int end): split and merge
blocks if necessary
- int draw(Graphics g, int x, int y, int start, int end):
returns the total display width of all the characters that were drawn.
- static <ascent, descent> getHeights(Graphics g, int start, int end)
- Pair<int charsThatFit, int width> measureXDistance(Graphics g, int start, int x):
measure x units across the string, starting at a given character location;
return information about how many characters completely fit in the given
distance (possibly the entire StylesString) and the combined width of all
the characters that fit. Useful for line wrapping, screen-to-document
calculations.
- static int getWidth(Graphics g, int start, int end); may not be
needed, given measureXDistance?
- Style class encapsulates style information, knows how to measure
and paint styles.
- toString(): for writing text to files
- static Style getStyle(String s): find Style "atom" using result
of previous toString call; "" for empty (totally default) style
- int drawText(Graphics g, String s, int x, int y, int start, int end):
returns the screen width of the rendered text.
- Font getFont(): used for getting font metrics, etc.
- Pair<int charsThatFit, int width> measureXDistance(Graphics g, String s, int start, int x):
same interface as StylesString.measureXDistance. For line wrapping,
screen-to-document calculations
- int getWidth(Graphics g, int start, int end): is this needed,
given measureXDistance?
- How to handle style changes, such as adding boldface? Could potentially
modify any combination of the style attributes.
- Introduce an additional class StyleChanges? Would duplicate
Style in many ways.
- Allow Styles to be incomplete (only some attributes specified).
- Additional Style methods:
- Style(): create empty style (everything defaults)
- Style setFontFamily(String familyName)
- Style clearFontFamily()
- boolean hasFontFamily()
- Font getFontFamily()
- Style setBold(bool bold)
- Style clearBold()
- boolean hasBold()
- boolean getBold()
- 4 more methods for each supported attribute: italics, size, etc.
- Style setAttributes(Style other): used to modify selected
attributes. A new Style is created by copying the original style
and, for each attribute that is specified in other, copying that
attribute value to the new Style.
- static setDefault(Style default): the attributes of this style
will be used for attributes not specified in other Styles for purposes
such as dispalying, measuring, etc.
- Style intersectStyle(Style other): used for finding common
styles across a range of text (e.g., is boldface specified explicitly
with the same value in each of a collection of Styles?). Returns
a new Style created by copying the original Style and then modifying it
based on other: if other does not specify a value for
a given attribute, or if it specifies a value that conflicts with
the original Style, then clear corresponding attribute in the new Style.
- Additional StyledString methods:
- Style changeAttributes(int start, int end, Style other):
invokes StyledString.setAttributes for each character in the given range.
- Line wrapping:
- Most students "worked forwards":
- Measure some characters
- If they don't fit, pick a different set of characters, measure them, etc.
- Instead, work backwards:
- First, measure how many characters fit completely.
- If not all fit, then back off to find break point.
- Managing screen layout: must separate screen lines from document lines
(potentially multiple screen lines per document line)
- TextPanel manages ScreenLine objects, each of which corresponds to
one line on the screen of a particular window:
- Position of the first (leftmost) character displayed on that line.
- Number of characters to display on that line
- Y-location of the topmost pixel in the line
- Height in pixels of that line
- Ascent in pixels for that line (# pixels above the baseline)
- Maintain an array of ScreenLines corresponding to what is currently
visible in the window.
- Most student projects used a 2-level structure consisting of document
lines that were further broken into screen lines. However, there's
no advantage to this: once the screen lines have been computed,
document lines just add complexity (every traversal has to be a
doubly nested loop).
- For scrolling, need to keep overall information about vertical distances
in the document (and this is different in every window, since lines
may be wrapped differently).
- Keep an ArrayList in each TextPanel: one entry per line in the
document, whose value is the screen height of that document line
(i.e. could involve multiple screen lines). -1 for a value means
"needs to be recomputed".
- When the document is modified, add and remove entries in the
ArrayList, and change existing entries to -1 as needed.
- Keep track of the indexes of the -1 values, recompute these values
the next time paintComponent is called (could be slightly tricky,
since a series of updates could change the line numbers that need to
be recomputed).
- Maintain an overallHeight variable. Whenever an entry is removed from
the ArrayList or set to -1, subtract the old value from overallHeight.
Whenever a new non-negative value is set in ArrayList, add the new
value to overallHeight.