/// <summary> /// Inserts the specified text at the current cursor position, if possible /// </summary> private async Task InsertText(XmlCursor cursor, string text, XmlRules xmlRules) { XmlCursorPos insertPos; // If something is selected, then delete it first, because it will be replaced by the new text XmlCursor deleteArea = cursor.Clone(); await deleteArea.OptimizeSelection(); var deleteResult = await XmlCursorSelectionHelper.DeleteSelection(deleteArea); if (deleteResult.Success) { insertPos = deleteResult.NewCursorPosAfterDelete; } else { insertPos = cursor.StartPos.Clone(); } // insert the specified text at the cursor position var replacementNode = InsertAtCursorPosHelper.InsertText(insertPos, text, xmlRules).ReplaceNode; if (replacementNode != null) { // Text could not be inserted because a node input was converted from text input. // Example: In the AIML template, * is pressed, and a <star> is inserted there instead InsertAtCursorPosHelper.InsertXmlNode(insertPos, replacementNode, xmlRules, false); } // then the cursor is only one line behind the inserted text await cursor.SetPositions(insertPos.ActualNode, insertPos.PosOnNode, insertPos.PosInTextNode, throwChangedEventWhenValuesChanged : false); }
/// <summary> /// deletes the actual cursor selection /// </summary> /// <returns>true, if deleted successfully</returns> public virtual async Task <bool> ActionDelete(SetUndoSnapshotOptions setUnDoSnapshot) { if (!this.ActionsAllowed) { return(false); } if (this.editorState.IsRootNodeSelected) { return(false); // The root node is to be deleted: Not allowed } if (setUnDoSnapshot == SetUndoSnapshotOptions.Yes) { this.editorState.UndoHandler.SetSnapshot("delete", this.editorState.CursorRaw); } var optimized = this.editorState.CursorRaw; await optimized.OptimizeSelection(); var deleteResult = await XmlCursorSelectionHelper.DeleteSelection(optimized); if (deleteResult.Success) { await this.editorState.CursorRaw.SetPositions(deleteResult.NewCursorPosAfterDelete.ActualNode, deleteResult.NewCursorPosAfterDelete.PosOnNode, deleteResult.NewCursorPosAfterDelete.PosInTextNode, throwChangedEventWhenValuesChanged : false); await this.editorState.FireContentChangedEvent(needToSetFocusOnEditorWhenLost : false, forceFullRepaint : false); return(true); } else { return(false); } }
/// <summary> /// Copies the current selection to the clipboard /// </summary> public virtual async Task <bool> ActionCopyToClipboard() { if (!this.ActionsAllowed) { return(false); } var content = await XmlCursorSelectionHelper.GetSelectionAsString(this.editorState.CursorRaw); if (string.IsNullOrEmpty(content)) { return(false); //Nothing selected } try { await this.nativePlatform.Clipboard.Clear(); await this.nativePlatform.Clipboard.SetText(content); // Copy selection as text to clipboard } catch (Exception) { return(false); } return(true); }
/// <summary> /// FInserts the specified node at the current cursor position, if possible /// </summary> private async Task XMLNodeEinfuegen(XmlCursor cursor, XmlNode node, XmlRules xmlRules, bool setNewCursorPosBehindNewInsertedNode) { // If something is selected, then delete it first, because it will be replaced by the new text XmlCursor deleteArea = cursor.Clone(); await deleteArea.OptimizeSelection(); var deleteResult = await XmlCursorSelectionHelper.DeleteSelection(deleteArea); if (deleteResult.Success) { await cursor.SetPositions(deleteResult.NewCursorPosAfterDelete.ActualNode, deleteResult.NewCursorPosAfterDelete.PosOnNode, deleteResult.NewCursorPosAfterDelete.PosInTextNode, throwChangedEventWhenValuesChanged : false); } // insert the specified node at the cursor position if (InsertAtCursorPosHelper.InsertXmlNode(cursor.StartPos, node, xmlRules, setNewCursorPosBehindNewInsertedNode)) { // then the cursor is only one line behind the inserted cursor.EndPos.SetPos(cursor.StartPos.ActualNode, cursor.StartPos.PosOnNode, cursor.StartPos.PosInTextNode); } }
/// <summary> /// Delete the node or the character behind the cursor /// </summary> public async Task <bool> ActionDeleteNodeOrSignBehindCursorPos(XmlCursorPos position, SetUndoSnapshotOptions setUnDoSnapshot) { if (!this.ActionsAllowed) { return(false); } if (setUnDoSnapshot == SetUndoSnapshotOptions.Yes) { this.editorState.UndoHandler.SetSnapshot("delete", this.editorState.CursorRaw); } var deleteArea = new XmlCursor(); deleteArea.StartPos.SetPos(position.ActualNode, position.PosOnNode, position.PosInTextNode); var endPos = deleteArea.StartPos.Clone(); await CursorPosMoveHelper.MoveRight(endPos, this.editorState.RootNode, this.xmlRules); deleteArea.EndPos.SetPos(endPos.ActualNode, endPos.PosOnNode, endPos.PosInTextNode); await deleteArea.OptimizeSelection(); if (deleteArea.StartPos.ActualNode == this.editorState.RootNode) { return(false); // You must not delete the rootnot } var deleteResult = await XmlCursorSelectionHelper.DeleteSelection(deleteArea); if (deleteResult.Success) { // After successful deletion the new CursorPos is retrieved here await this.editorState.CursorRaw.SetPositions(deleteResult.NewCursorPosAfterDelete.ActualNode, deleteResult.NewCursorPosAfterDelete.PosOnNode, deleteResult.NewCursorPosAfterDelete.PosInTextNode, throwChangedEventWhenValuesChanged : false); await this.editorState.FireContentChangedEvent(needToSetFocusOnEditorWhenLost : false, forceFullRepaint : false); return(true); } return(false); }
/// <summary> /// Optimizes the selected area /// </summary> public async Task OptimizeSelection() { // Define exchange buffer variables XmlCursorPositions dummyPos; int dummyTextPos; if (StartPos.ActualNode == null) { return; } // 1. if the start pos is behind the end pos, then swap both if (StartPos.ActualNode == EndPos.ActualNode) // Both nodes are equal { if (StartPos.PosOnNode > EndPos.PosOnNode) // If StartPos is within a node behind EndPos { // exchange both positions at the same node dummyPos = StartPos.PosOnNode; dummyTextPos = StartPos.PosInTextNode; StartPos.SetPos(EndPos.ActualNode, EndPos.PosOnNode, EndPos.PosInTextNode); EndPos.SetPos(EndPos.ActualNode, dummyPos, dummyTextPos); } else // StartPos was not behind Endpos { // Is a text part within a text node selected ? if ((StartPos.PosOnNode == XmlCursorPositions.CursorInsideTextNode) && (EndPos.PosOnNode == XmlCursorPositions.CursorInsideTextNode)) { // A part of a text node is selected if (StartPos.PosInTextNode > EndPos.PosInTextNode) // If the TextStartpos is behind the TextEndpos, then change { // Exchange text selection dummyTextPos = StartPos.PosInTextNode; StartPos.SetPos(StartPos.ActualNode, XmlCursorPositions.CursorInsideTextNode, EndPos.PosInTextNode); EndPos.SetPos(StartPos.ActualNode, XmlCursorPositions.CursorInsideTextNode, dummyTextPos); } } } } else // Both nodes are not equal { // If the nodes are wrong in the order, then swap both if (ToolboxXml.Node1LaisBeforeNode2(EndPos.ActualNode, StartPos.ActualNode)) { var tempPos = this.StartPos.Clone(); this.StartPos.SetPos(this.EndPos.ActualNode, this.EndPos.PosOnNode, this.EndPos.PosInTextNode); this.EndPos.SetPos(tempPos.ActualNode, tempPos.PosOnNode, tempPos.PosInTextNode); } // If the EndNode is in the StartNode, select the entire surrounding StartNode if (ToolboxXml.IsChild(EndPos.ActualNode, StartPos.ActualNode)) { await SetPositions(StartPos.ActualNode, XmlCursorPositions.CursorOnNodeStartTag, 0, throwChangedEventWhenValuesChanged : false); } // Find the first common parent of start and end and select the nodes in this height. // This leads to the fact that e.g. with LI elements and UL only the whole LI // is selected when dragging the selection over several LI and not only parts of it if (StartPos.ActualNode.ParentNode != EndPos.ActualNode.ParentNode) // if start and end are not directly in the same parent { // - first find out which is the deepest common parent of start and end node XmlNode commonParent = XmlCursorSelectionHelper.DeepestCommonParent(StartPos.ActualNode, EndPos.ActualNode); // - then upscale start- and end-node to before the parent XmlNode nodeStart = StartPos.ActualNode; while (nodeStart.ParentNode != commonParent) { nodeStart = nodeStart.ParentNode; } XmlNode nodeEnde = EndPos.ActualNode; while (nodeEnde.ParentNode != commonParent) { nodeEnde = nodeEnde.ParentNode; } // - finally show the new start and end nodes StartPos.SetPos(nodeStart, XmlCursorPositions.CursorOnNodeStartTag); EndPos.SetPos(nodeEnde, XmlCursorPositions.CursorOnNodeStartTag); } } }
private Selection CalculateStartAndEndOfSelection(string actualText, XmlCursor cursor) { var result = new Selection { Start = -1, Length = 0 }; if (cursor.StartPos.ActualNode == this.XmlNode) // The start of the selection is on this node { switch (cursor.StartPos.PosOnNode) { case XmlCursorPositions.CursorOnNodeStartTag: // The node itself is selected as start node case XmlCursorPositions.CursorOnNodeEndTag: throw new ArgumentOutOfRangeException($"{nameof(cursor.StartPos.PosOnNode)}:{cursor.StartPos.PosOnNode.ToString()} not possible on a text node"); // result.Start = 0; // result.Length = actualText.Length; // break; case XmlCursorPositions.CursorBehindTheNode: case XmlCursorPositions.CursorInsideTheEmptyNode: // Since the cursor position arrives sorted, the EndPos can only lie behind the node result.Start = -1; result.Length = 0; break; case XmlCursorPositions.CursorInFrontOfNode: case XmlCursorPositions.CursorInsideTextNode: if (cursor.StartPos.PosOnNode == XmlCursorPositions.CursorInsideTextNode) { result.Start = Math.Max(0, cursor.StartPos.PosInTextNode); // inside text node } else { result.Start = 0; // in front of the node } if (cursor.EndPos.ActualNode == this.XmlNode) // If the end of the selection also is inside this node { switch (cursor.EndPos.PosOnNode) { case XmlCursorPositions.CursorOnNodeStartTag: // start node is in front of the node, end node behind: everything is selected case XmlCursorPositions.CursorOnNodeEndTag: case XmlCursorPositions.CursorBehindTheNode: result.Length = Math.Max(0, actualText.Length - result.Start); break; case XmlCursorPositions.CursorInsideTheEmptyNode: result.Start = -1; result.Length = 0; break; case XmlCursorPositions.CursorInsideTextNode: // till the marker in the text result.Length = Math.Max(0, cursor.EndPos.PosInTextNode - result.Start); break; case XmlCursorPositions.CursorInFrontOfNode: result.Length = 0; break; default: throw new ApplicationException("unknown cursor.EndPos.PosOnNode '" + cursor.EndPos.PosOnNode + "'B"); } } else // The end of the selection is not inside this node { if (cursor.EndPos.ActualNode.ParentNode == cursor.StartPos.ActualNode.ParentNode) // If start and end are different, but directly in the same parent { result.Length = Math.Max(0, actualText.Length - result.Start); // Select only the selected part } else // Start and end are different and have different parents { result.Start = 0; result.Length = actualText.Length; // select whole text node } } break; default: throw new ApplicationException("unknown cursor.StartPos.PosOnNode '" + cursor.StartPos.PosOnNode + "'A"); } } else // The start of the selection is not on this node { if (cursor.EndPos.ActualNode == this.XmlNode) // But the end of the selection is { switch (cursor.EndPos.PosOnNode) { case XmlCursorPositions.CursorOnNodeStartTag: // The node itself is selected as End-Node case XmlCursorPositions.CursorOnNodeEndTag: // start node is in front of the node, end node behind: everything is selected case XmlCursorPositions.CursorBehindTheNode: result.Start = 0; result.Length = actualText.Length; break; case XmlCursorPositions.CursorInsideTheEmptyNode: result.Start = -1; result.Length = 0; break; case XmlCursorPositions.CursorInsideTextNode: // Start node is in front of the node, end node in the middle, so select from front to middle if (cursor.EndPos.ActualNode.ParentNode == cursor.StartPos.ActualNode.ParentNode) // If start and end are different, but directly in the same parent { result.Start = 0; result.Length = Math.Max(0, cursor.EndPos.PosInTextNode); // Select only the selected front part } else // Start and end different and different parents { result.Start = 0; result.Length = actualText.Length; // select whole text node } break; case XmlCursorPositions.CursorInFrontOfNode: // Start node is in front of the node, end node also result.Start = -1; result.Length = 0; break; default: throw new ApplicationException("unknown cursor.EndPos.PosOnNode '" + cursor.EndPos.PosOnNode + "'X"); } } else // Neither the start nor the end of the selection lies exactly on this node { if (XmlCursorSelectionHelper.IsThisNodeInsideSelection(EditorState.CursorOptimized, this.XmlNode)) { result.Start = 0; result.Length = actualText.Length; // Select entire text node } } } return(result); }
protected override async Task <PaintContext> PaintInternal(PaintContext paintContext, bool cursorBlinkOn, XmlCursor cursor, IGraphics gfx, PaintModes paintMode, int depth) { this.nodeDimensions.Update(); var isSelected = cursor?.StartPos != null && cursor?.EndPos != null && XmlCursorSelectionHelper.IsThisNodeInsideSelection(cursor, this.XmlNode); this.CreateChildElementsIfNeeded(gfx); Point newCursorPaintPos = null; bool alreadyUnpainted = false; switch (paintMode) { case PaintModes.ForcePaintNoUnPaintNeeded: alreadyUnpainted = true; break; case PaintModes.ForcePaintAndUnpaintBefore: this.UnPaint(gfx); alreadyUnpainted = true; break; case PaintModes.OnlyPaintWhenChanged: break; } // If the cursor is inside the empty node, then draw the cursor there if (cursor.StartPos.ActualNode == this.XmlNode) { if (cursor.StartPos.PosOnNode == XmlCursorPositions.CursorInFrontOfNode) { // remember position for cursor line newCursorPaintPos = new Point(paintContext.PaintPosX, paintContext.PaintPosY); } } var cursorIsOnThisNode = cursor.StartPos.ActualNode == this.XmlNode || cursor.EndPos.ActualNode == this.XmlNode; paintContext = await this.startTag.Paint(paintContext, cursorIsOnThisNode, cursorBlinkOn, alreadyUnpainted, isSelected, gfx); // If the cursor is inside the empty node, then draw the cursor there if (cursor.StartPos.ActualNode == this.XmlNode) { if (cursor.StartPos.PosOnNode == XmlCursorPositions.CursorInsideTheEmptyNode) { // set position for cursor line newCursorPaintPos = new Point(paintContext.PaintPosX - 1, paintContext.PaintPosY); } } paintContext = await this.PaintSubNodes(paintContext, cursorBlinkOn, cursor, gfx, paintMode, depth); if (this.endTag != null) { paintContext = await this.endTag.Paint(paintContext, cursorIsOnThisNode, cursorBlinkOn, alreadyUnpainted, isSelected, gfx); } // If the cursor is behind the node, then also draw the cursor there if (cursor.StartPos.ActualNode == this.XmlNode) { if (cursor.StartPos.PosOnNode == XmlCursorPositions.CursorBehindTheNode) { newCursorPaintPos = new Point(paintContext.PaintPosX - 1, paintContext.PaintPosY); } } this.cursorPaintPos = newCursorPaintPos; return(paintContext.Clone()); }