kogmbh/WebODF — pull request #112 by adityab (2013-10-09 14:11:09+00:00)
Title: Virtual selections

Finally, phew!

This patch introduces:

1. A `gui.SelectionView` class. This takes a `Cursor` in the constructor and draws 3 carefully sized divs to represent the selection range it stores, whenever the cursor moves.
2. A 'gui.SelectionViewManager` class, which manages `SelectionView` instances and provides methods to register a cursor with it to track it's selections, to rerender selections, etc.
3. A 'gui.ShadowCursor` class, which is a minimal `OdtCursor`-like class, which provides only the methods that are absolutely needed for maintaining a `selectedRange` object as done in `OdtCursor`. This is used because we do not want to destroy the browser's native selections by injecting anchor nodes.

The EditorSession instance feeds a `ShadowCursor` instance (with the id "") to the `SessionController` instance. The controller reads mouseup and mousemove events to constantly call `shadowCursor.setSelectedRange` and feed it the native browser selection's range. Since the `shadowCursor`, like all other normal cursors, is registered with the `SelectionViewManager` instance used in `SessionView`, the virtual selection will constantly update to match the `shadowCursor`'s internal range.
If, during `mousemove`, the mouse enters a region not allowed by the `RootFilter` (which has been pegged to the root of the `mousedown` position), `maintainShadowSelection` is called, which changes the browser's native selection to match the `shadowCursor`'s previous selection. Therefore, 'leakage' of selections (even the native selection) into other local roots is prevented.

The `SelectionView` calculates the first rect's dimensions by reading the client rect of the first position in the first textnode, that lies in the range; the last rect's dimensions are calculated by doing the same for the last position of the last textnode, that is within the range.
Now we have the top and bottom coordinates of the entire virtual selection, the left coordinate of the first div, and the right coordinate of the last div.
This brings us to the last bit of information required for drawing our 3-box selection: the width of the entire selection.
To compute that, we will need to compute the `fillerRect`. Unfortunately, because of chrome's `getBoundingClientRects` which is buggy when operated on *ranges*, we need to break the range down and compute the bounding client rects of all significant (and as few as possible) *elements* in between the range's endpoints.
This finally gives us a `fillerRect` of the correct width, but the wrong height (because the rects of the parent elements of the startContainer and endContainer will be included). But worry not! We already have the vertical coordinates of the `firstRect` and the `lastRect`, so we can ignore the height of the so-called `fillerRect`.
This is enough information to draw the 3 boxes, the rest is just minor details.

**Note**: Selections are a complex beast with many little details, so this is by no means perfect. But I think it is usable already (hey, look at chrome's buggy selections, I'm not alone! :stuck_out_tongue_closed_eyes: ) A couple of things are still missing, such as correcting for zoom levels. And selecting along a line which has words of multiple heights (we probably need to use SelectionMover code there) is not very good. And when the selection passes over a cursor, the rectangles are glitchy (hey, chrome does this too!). All should be fixable, but in later pull requests.