/// <summary> /// Select all the words that are between <paramref name="selectionStart"/> word and <paramref name="selectionEnd"/> word in the DOM hierarchy. /// </summary> /// <param name="box">the current traversal node</param> /// <param name="selectionStart">selection start word limit</param> /// <param name="selectionEnd">selection end word limit</param> /// <param name="inSelection">used to know the traversal is currently in selected range</param> /// <returns></returns> private bool SelectWordsInRange(CssBox box, CssBoxWord selectionStart, CssBoxWord selectionEnd, ref bool inSelection) { foreach (var boxWord in box.Words) { if (!inSelection && boxWord == selectionStart) { inSelection = true; } if (inSelection) { boxWord.Selection = this; if (selectionStart == selectionEnd || boxWord == selectionEnd) { return(true); } } } foreach (var childBox in box.Boxes) { if (SelectWordsInRange(childBox, selectionStart, selectionEnd, ref inSelection)) { return(true); } } return(false); }
/// <summary> /// Find the css line box that the given word is in. /// </summary> /// <param name="word">the word to search for it's line box</param> /// <returns>line box that the word is in</returns> public static CssLineBox GetCssLineBoxByWord(CssBoxWord word) { var box = word.OwnerBox; while (box != null && box.LineBoxes.Count == 0) { box = box.ParentBox; } if (box == null) { return(null); } foreach (var lineBox in box.LineBoxes) { foreach (var lineWord in lineBox.Words) { if (lineWord == word) { return(lineBox); } } } return(box.LineBoxes[0]); }
/// <summary> /// Select the word at the given location if found. /// </summary> /// <param name="control">the control hosting the html to invalidate</param> /// <param name="loc">the location to select word at</param> public void SelectWord(Control control, Point loc) { var word = DomUtils.GetCssBoxWord(_root, loc); if (word != null) { word.Selection = this; _selectionStartPoint = loc; _selectionStart = _selectionEnd = word; control.Invalidate(); } }
/// <summary> /// Calculate the charecter index and offset by charecters for the given word and given offset. /// If the location is below the word line then set the selection to the end.<br/> /// If the location is to the right of the word then set the selection to the end.<br/> /// If the offset is to the left of the word set the selection to the begining.<br/> /// Otherwise calculate the width of each substring to find the char the location is on. /// </summary> /// <param name="control">used to create graphics to measure string</param> /// <param name="word">the word to calculate its index and offset</param> /// <param name="loc">the location to calculate for</param> /// <param name="selectionIndex">return the index of the char under the location</param> /// <param name="selectionOffset">return the offset of the char under the location</param> /// <param name="inclusive">is to include the first charecter in the calculation</param> private static void CalculateWordCharIndexAndOffset(Control control, CssBoxWord word, Point loc, bool inclusive, out int selectionIndex, out float selectionOffset) { selectionIndex = 0; selectionOffset = 0.1f; var offset = loc.X - word.Left; if (word.Text == null) { // not a text word - set full selection selectionIndex = -1; selectionOffset = -1; } else if (offset > word.Width - word.OwnerBox.ActualWordSpacing || loc.Y > word.LineBox.LineBottom) { // mouse under the line, to the right of the word - set to the end of the word selectionIndex = word.Text.Length; selectionOffset = word.Width; } else if (offset > 0) { // calculate partial word selection selectionIndex = word.Text.Length; selectionOffset = word.OwnerBox.ActualWordSpacing / 2f; using (var g = control.CreateGraphics()) { for (int i = 1; i <= word.Text.Length; i++) { var sf = new StringFormat(); sf.SetMeasurableCharacterRanges(new[] { new CharacterRange(0, i) }); var regions = g.MeasureCharacterRanges(word.Text, word.OwnerBox.ActualFont, new RectangleF(0, 0, float.MaxValue, float.MaxValue), sf); SizeF s = regions[0].GetBounds(g).Size; if (!inclusive) { selectionOffset = s.Width + (s.Width / i / 15f); } if (s.Width > offset) { selectionIndex = i - (inclusive ? 1 : 0); break; } selectionOffset = s.Width + (s.Width / i / 15f); } } } }
/// <summary> /// Clear the current selection. /// </summary> private void ClearSelection() { // clear drag and drop _dragDropData = null; ClearSelection(_root); _selectionStartOffset = -1; _selectionStartIndex = -1; _selectionEndOffset = -1; _selectionEndIndex = -1; _selectionStartPoint = Point.Empty; _selectionStart = null; _selectionEnd = null; }
/// <summary> /// Calculate the charecter index and offset by charecters for the given word and given offset.<br/> /// <seealso cref="CalculateWordCharIndexAndOffset(Control, CssBoxWord, Point, bool, out int, out float)"/>. /// </summary> /// <param name="control">used to create graphics to measure string</param> /// <param name="word">the word to calculate its index and offset</param> /// <param name="loc">the location to calculate for</param> /// <param name="selectionStart">to set the starting or ending char and offset data</param> private void CalculateWordCharIndexAndOffset(Control control, CssBoxWord word, Point loc, bool selectionStart) { int selectionIndex; float selectionOffset; CalculateWordCharIndexAndOffset(control, word, loc, selectionStart, out selectionIndex, out selectionOffset); if (selectionStart) { _selectionStartIndex = selectionIndex; _selectionStartOffset = selectionOffset; } else { _selectionEndIndex = selectionIndex; _selectionEndOffset = selectionOffset; } }
/// <summary> /// Get the selected word with respect to partial selected words. /// </summary> /// <param name="boxWord">the word to append</param> /// <param name="selectedText">is to get selected text or all the text in the word</param> private static string GetSelectedWord(CssBoxWord boxWord, bool selectedText) { if (selectedText && boxWord.SelectedStartIndex > -1 && boxWord.SelectedEndIndexOffset > -1) { return(boxWord.Text.Substring(boxWord.SelectedStartIndex, boxWord.SelectedEndIndexOffset - boxWord.SelectedStartIndex)); } else if (selectedText && boxWord.SelectedStartIndex > -1) { return(boxWord.Text.Substring(boxWord.SelectedStartIndex) + " "); } else if (selectedText && boxWord.SelectedEndIndexOffset > -1) { return(boxWord.Text.Substring(0, boxWord.SelectedEndIndexOffset)); } else { return(boxWord.Text + " "); } }
/// <summary> /// Select all the words that are between <paramref name="selectionStart"/> word and <paramref name="selectionEnd"/> word in the DOM hierarchy.<br/> /// </summary> /// <param name="root">the root of the DOM sub-tree the selection is in</param> /// <param name="selectionStart">selection start word limit</param> /// <param name="selectionEnd">selection end word limit</param> private void SelectWordsInRange(CssBox root, CssBoxWord selectionStart, CssBoxWord selectionEnd) { bool inSelection = false; SelectWordsInRange(root, selectionStart, selectionEnd, ref inSelection); }
/// <summary> /// Handle html text selection by mouse move over the html with left mouse button pressed.<br/> /// Calculate the words in the selected range and set their selected property. /// </summary> /// <param name="control">the control hosting the html to invalidate</param> /// <param name="loc">the mouse location</param> /// <param name="allowPartialSelect">true - partial word selection allowed, false - only full words selection</param> private void HandleSelection(Control control, Point loc, bool allowPartialSelect) { // get the line under the mouse or nearest from the top var lineBox = DomUtils.GetCssLineBox(_root, loc); if (lineBox != null) { // get the word under the mouse var word = DomUtils.GetCssBoxWord(lineBox, loc); // if no word found uner the mouse use the last or the first word in the line if (word == null && lineBox.Words.Count > 0) { if (loc.Y > lineBox.LineBottom) { // under the line word = lineBox.Words[lineBox.Words.Count - 1]; } else if (loc.X < lineBox.Words[0].Left) { // before the line word = lineBox.Words[0]; } else if (loc.X > lineBox.Words[lineBox.Words.Count - 1].Right) { // at the end of the line word = lineBox.Words[lineBox.Words.Count - 1]; } } // if there is matching word if (word != null) { if (_selectionStart == null) { // on start set the selection start word _selectionStartPoint = loc; _selectionStart = word; if (allowPartialSelect) { CalculateWordCharIndexAndOffset(control, word, loc, true); } } // always set selection end word _selectionEnd = word; if (allowPartialSelect) { CalculateWordCharIndexAndOffset(control, word, loc, false); } ClearSelection(_root); if (CheckNonEmptySelection(loc, allowPartialSelect)) { CheckSelectionDirection(); SelectWordsInRange(_root, _backwardSelection ? _selectionEnd : _selectionStart, _backwardSelection ? _selectionStart : _selectionEnd); } else { _selectionEnd = null; } control.Cursor = Cursors.IBeam; control.Invalidate(); } } }
/// <summary> /// The selection end offset if the last selected word is partially selected (-1 if not selected or fully selected)<br/> /// if the given word is not starting or ending selection word -1 is returned as full word selection is in place. /// </summary> /// <remarks> /// Handles backward selecting by returning the selection end data instead of start. /// </remarks> /// <param name="word">the word to return the selection end offset for</param> public float GetSelectedEndOffset(CssBoxWord word) { return(word == (_backwardSelection ? _selectionStart : _selectionEnd) ? (_backwardSelection ? _selectionStartOffset : _selectionEndOffset) : -1); }
/// <summary> /// The selection end index if the last selected word is partially selected (-1 if not selected or fully selected)<br/> /// if the given word is not starting or ending selection word -1 is returned as full word selection is in place. /// </summary> /// <remarks> /// Handles backward selecting by returning the selection end data instead of start. /// </remarks> /// <param name="word">the word to return the selection end index for</param> public int GetSelectedEndIndexOffset(CssBoxWord word) { return(word == (_backwardSelection ? _selectionStart : _selectionEnd) ? (_backwardSelection ? _selectionStartIndex : _selectionEndIndex) : -1); }
/// <summary> /// The selection start index if the first selected word is partially selected (-1 if not selected or fully selected)<br/> /// if the given word is not starting or ending selection word -1 is returned as full word selection is in place. /// </summary> /// <remarks> /// Handles backward selecting by returning the selection end data instead of start. /// </remarks> /// <param name="word">the word to return the selection start index for</param> /// <returns>data value or -1 if not aplicable</returns> public int GetSelectingStartIndex(CssBoxWord word) { return(word == (_backwardSelection ? _selectionEnd : _selectionStart) ? (_backwardSelection ? _selectionEndIndex : _selectionStartIndex) : -1); }