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
      • Uses a lot of memory
    • 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.