Code:
/ Dotnetfx_Win7_3.5.1 / Dotnetfx_Win7_3.5.1 / 3.5.1 / DEVDIV / depot / DevDiv / releases / Orcas / NetFXw7 / wpf / src / Framework / System / Windows / Documents / TextRangeEditLists.cs / 1 / TextRangeEditLists.cs
//---------------------------------------------------------------------------- // // File TextRangeEditLists.cs // // Copyright (C) Microsoft Corporation. All rights reserved. // // Description: Internal static class representing a group of methods // for list editing // //--------------------------------------------------------------------------- namespace System.Windows.Documents { using System; using MS.Internal; using System.Windows.Controls; ////// The TextRange class represents a pair of TextPositions, with many /// rich text editing operations exposed. /// internal static class TextRangeEditLists { // ------------------------------------------------------------------- // // Internal Methods // // ------------------------------------------------------------------- #region Internal Methods ////// Merges two paragraphs followinng one another. /// The content of a second paragraph is moved into the end /// of the first one. /// /// /// First of two merged paragraphs or BlockUIContainer. /// /// /// Second of two mered paragraphs or BlockUIContainer. /// ////// true if paragraphs have been merged; false if no actions where made. /// internal static bool MergeParagraphs(Block firstParagraphOrBlockUIContainer, Block secondParagraphOrBlockUIContainer) { if (!ParagraphsAreMergeable(firstParagraphOrBlockUIContainer, secondParagraphOrBlockUIContainer)) { return false; // Cannot mearge these paragraphs. } // Store parent list item of a second paragraph - // to correct its structure after the merge ListItem secondListItem = secondParagraphOrBlockUIContainer.PreviousBlock == null ? secondParagraphOrBlockUIContainer.Parent as ListItem : null; if (secondListItem != null && secondListItem.PreviousListItem == null && secondParagraphOrBlockUIContainer.NextBlock is List) { // The second paragraph is a first list item in some list. // It has a sublists in it, so this sublist must be unindented // to avoid double bulleted line. List sublistOfSecondParagraph = (List)secondParagraphOrBlockUIContainer.NextBlock; if (sublistOfSecondParagraph.ElementEnd.CompareTo(secondListItem.ContentEnd) == 0) { secondListItem.Reposition(null, null); } else { secondListItem.Reposition(sublistOfSecondParagraph.ElementEnd, secondListItem.ContentEnd); } // At this point the schema is temporaty broken: the secondParagraph and the sublistOfSecondParagraph have List as a parent sublistOfSecondParagraph.Reposition(null, null); // The schema is repared as to sublistOfSecondParagraph concern, but still broken for secondParagraph - must be corrected in the following code } // Move the second paragraph out of its wrappers separating from the first paragraph (if any). // We can not use RepositionWithContent because it would destroy // all pointers and ranges within a moved paragraph. // Instead we reposition elements around the two paragraphs. while (secondParagraphOrBlockUIContainer.ElementStart.GetPointerContext(LogicalDirection.Backward) == TextPointerContext.ElementStart) { TextElement parentBlock = (TextElement)secondParagraphOrBlockUIContainer.Parent; Invariant.Assert(parentBlock != null); Invariant.Assert(TextSchema.AllowsParagraphMerging(parentBlock.GetType())); if (secondParagraphOrBlockUIContainer.ElementEnd.CompareTo(parentBlock.ContentEnd) == 0) { // Remove ancestor block if it becomes empty parentBlock.Reposition(null, null); } else { // Move ancestor's Start after the end of our paragraph parentBlock.Reposition(secondParagraphOrBlockUIContainer.ElementEnd, parentBlock.ContentEnd); } } // Store a position after the second paragraph where list merging may be needed TextPointer positionAfterSecondParagraph = secondParagraphOrBlockUIContainer.ElementEnd.GetFrozenPointer(LogicalDirection.Forward); // Move the second paragraph to become an immediate following sibling of the first paragraph while (true) { TextElement previousBlock = secondParagraphOrBlockUIContainer.ElementStart.GetAdjacentElement(LogicalDirection.Backward) as TextElement; // Note: We cannot use Block.NextSibling property, because the structure is invalid during this process Invariant.Assert(previousBlock != null); if (previousBlock is Paragraph || previousBlock is BlockUIContainer) { break; } Invariant.Assert(TextSchema.AllowsParagraphMerging(previousBlock.GetType())); previousBlock.Reposition(previousBlock.ContentStart, secondParagraphOrBlockUIContainer.ElementEnd); } // Now that paragraphs are next to each other merge them. // If one of paragraphs is empty we will apply special logic - to preserve a formatting from a non-empty one if (secondParagraphOrBlockUIContainer.TextRange.IsEmpty) { secondParagraphOrBlockUIContainer.RepositionWithContent(null); } else if (firstParagraphOrBlockUIContainer.TextRange.IsEmpty) { firstParagraphOrBlockUIContainer.RepositionWithContent(null); } else if (firstParagraphOrBlockUIContainer is Paragraph && secondParagraphOrBlockUIContainer is Paragraph) { // Do reposition magic for merging paragraph content // without destroying any pointers positioned in them. // Pull the second paragraph into the first one Invariant.Assert(firstParagraphOrBlockUIContainer.ElementEnd.CompareTo(secondParagraphOrBlockUIContainer.ElementStart) == 0); firstParagraphOrBlockUIContainer.Reposition(firstParagraphOrBlockUIContainer.ContentStart, secondParagraphOrBlockUIContainer.ElementEnd); // Store inline merging position TextPointer inlineMergingPosition = secondParagraphOrBlockUIContainer.ElementStart; // Now we can delete the second paragraph secondParagraphOrBlockUIContainer.Reposition(null, null); // Merge formatting elements at the point of paragraphs merging TextRangeEdit.MergeFormattingInlines(inlineMergingPosition); } // Merge ListItems wrapping first and second paragraphs. ListItem followingListItem = positionAfterSecondParagraph.GetPointerContext(LogicalDirection.Forward) == TextPointerContext.ElementStart ? positionAfterSecondParagraph.GetAdjacentElement(LogicalDirection.Forward) as ListItem : null; if (followingListItem != null && followingListItem == secondListItem) { ListItem precedingListItem = positionAfterSecondParagraph.GetAdjacentElement(LogicalDirection.Backward) as ListItem; if (precedingListItem != null) { // Merge the second list item with the preceding one Invariant.Assert(positionAfterSecondParagraph.GetPointerContext(LogicalDirection.Forward) == TextPointerContext.ElementStart); Invariant.Assert(positionAfterSecondParagraph.GetPointerContext(LogicalDirection.Backward) == TextPointerContext.ElementEnd); precedingListItem.Reposition(precedingListItem.ContentStart, followingListItem.ElementEnd); followingListItem.Reposition(null, null); } } // Merge lists at merge position MergeLists(positionAfterSecondParagraph); return true; } ////// Like MergeLists, but will search over formatting elements when /// looking for Lists to merge. /// internal static bool MergeListsAroundNormalizedPosition(TextPointer mergePosition) { // Search forward for a List to merge with. TextPointer navigator = mergePosition.CreatePointer(); while (navigator.GetPointerContext(LogicalDirection.Forward) == TextPointerContext.ElementEnd) { navigator.MoveToNextContextPosition(LogicalDirection.Forward); } bool merged = MergeLists(navigator); // Search backward for a List to merge with. if (!merged) { navigator.MoveToPosition(mergePosition); while (navigator.GetPointerContext(LogicalDirection.Backward) == TextPointerContext.ElementStart) { navigator.MoveToNextContextPosition(LogicalDirection.Backward); } merged = MergeLists(navigator); } return merged; } ////// Merges two naighboring lists ending and starting at position mergePosition /// /// /// Position at with two List elements are expected to appear next to each other /// ////// true if there were two mergeable List elements and merge happened. /// false if there is no pair of List elements at the mergePosition. /// internal static bool MergeLists(TextPointer mergePosition) { if (mergePosition.GetPointerContext(LogicalDirection.Backward) != TextPointerContext.ElementEnd || mergePosition.GetPointerContext(LogicalDirection.Forward) != TextPointerContext.ElementStart) { return false; } List precedingList = mergePosition.GetAdjacentElement(LogicalDirection.Backward) as List; List followingList = mergePosition.GetAdjacentElement(LogicalDirection.Forward) as List; if (precedingList == null || followingList == null) { return false; } precedingList.Reposition(precedingList.ContentStart, followingList.ElementEnd); followingList.Reposition(null, null); // We need to set appropriate FlowDirection property on the new List and its paragraph children. // We take the FlowDirection value from the preceding list. TextRangeEdit.SetParagraphProperty(precedingList.ElementStart, precedingList.ElementEnd, Paragraph.FlowDirectionProperty, precedingList.GetValue(Paragraph.FlowDirectionProperty)); return true; } // Returns true if all paragraphs in a range belong to the same block, // so they can be easily grouped and ungrouped. internal static bool IsListOperationApplicable(TextRange range) { // First check, if range start/end are parented by ListItems within the same parent list. if (IsRangeWithinSingleList(range)) { return true; } // Adjust range end so that it does not affect a following paragraph. TextPointer end = (TextPointer)TextRangeEdit.GetAdjustedRangeEnd(range.Start, range.End); // Now try plain Paragraphs Block firstBlock = range.Start.ParagraphOrBlockUIContainer; Block lastBlock = end.ParagraphOrBlockUIContainer; if (firstBlock != null && lastBlock != null && firstBlock.Parent == lastBlock.Parent) { return true; } // Allow list editing at potential paragraph positions, this includes // positions in initial RichTextBox, empty TableCell, empty ListItem where paragraphs were not yet created. if (range.IsEmpty && TextPointerBase.IsAtPotentialParagraphPosition(range.Start)) { return true; } return false; } internal static bool ConvertParagraphsToListItems(TextRange range, TextMarkerStyle markerStyle) { if (range.IsEmpty && TextPointerBase.IsAtPotentialParagraphPosition(range.Start)) { TextPointer insertionPosition = TextRangeEditTables.EnsureInsertionPosition(range.Start); ((ITextRange)range).Select(insertionPosition, insertionPosition); } Block firstBlock = range.Start.ParagraphOrBlockUIContainer; TextPointer end = (TextPointer)TextRangeEdit.GetAdjustedRangeEnd(range.Start, range.End); Block lastBlock = end.ParagraphOrBlockUIContainer; // We assume that a range contains a sequence of one-level paragraphs. // Otherwise the operation is disabled. if (firstBlock == null || lastBlock == null || firstBlock.Parent != lastBlock.Parent || firstBlock.Parent is ListItem && firstBlock.PreviousBlock == null) { // Either the paragraphs belong to different scopes or first of them has a bullet already. // We cannot convert them into bulleted lists. return false; } // Check that all top-level elements of selection are Paragraphs. // We do not apply the command to Tables or Sections. for (Block block = firstBlock; block != lastBlock && block != null; block = block.NextBlock) { if (block is Table || block is Section) { return false; } } ListItem parentListItem = firstBlock.Parent as ListItem; if (parentListItem != null) { // Paragraphs are inside of ListItem already. // Split a current ListItem before each of selected blocks Block block = firstBlock; while (block != null) { Block nextBlock = block == lastBlock ? null : block.ElementEnd.GetAdjacentElement(LogicalDirection.Forward) as Block; Invariant.Assert(block.Parent is ListItem); TextRangeEdit.SplitElement(block.ElementStart); block = nextBlock; } } else { // Create a list around all paragraphs List list = new List(); list.MarkerStyle = markerStyle; list.Apply(firstBlock, lastBlock); // Merge with neighboring lists //MergeLists(list.ElementEnd); // start with End to not loose an instance of "list" during merging with the following list //MergeLists(list.ElementStart); } return true; } // Assumes that a range contains a sequence of same-level ListItems. // Converts all these ListItems into Paragraphs and // either adds them to preceding ListItem (as non-bulleted continuation) // or pulls them out of a List if they start in the beginning of a List internal static void ConvertListItemsToParagraphs(TextRange range) { ListItem firstListItem = TextPointerBase.GetListItem(range.Start); ListItem lastListItem = TextPointerBase.GetListItem((TextPointer)TextRangeEdit.GetAdjustedRangeEnd(range.Start, range.End)); // The range must be in a sequence of ListItems belonging to one List wrapper if (firstListItem == null || lastListItem == null || firstListItem.Parent != lastListItem.Parent || !(firstListItem.Parent is List)) { return; } List listToRemove = null; ListItem leadingListItem = firstListItem.PreviousListItem; if (leadingListItem != null) { // We have a leading ListItem, so pull selected items into it leadingListItem.Reposition(leadingListItem.ContentStart, lastListItem.ElementEnd); } else { // We do not have a leading ListItem. So pull selected items out of a list // Cut wrapping list after endListItem if (lastListItem.NextListItem != null) { TextRangeEdit.SplitElement(lastListItem.ElementEnd); } // Set list to remove listToRemove = firstListItem.List; } // Remove ListItems from all selected blocks ListItem listItem = firstListItem; while (listItem != null) { ListItem nextListItem = listItem.ElementEnd.GetAdjacentElement(LogicalDirection.Forward) as ListItem; // If this is an empty, insert an explicit paragraph in it before deleting the list item. if (listItem.ContentStart.CompareTo(listItem.ContentEnd) == 0) { TextRangeEditTables.EnsureInsertionPosition(listItem.ContentStart); } listItem.Reposition(null, null); listItem = listItem == lastListItem ? null : nextListItem; } // If we have a list to remove, remove it and set its FlowDirection to its children if (listToRemove != null) { FlowDirection flowDirection = (FlowDirection)listToRemove.GetValue(Paragraph.FlowDirectionProperty); listToRemove.Reposition(null, null); TextRangeEdit.SetParagraphProperty(range.Start, range.End, Paragraph.FlowDirectionProperty, flowDirection); } } internal static void IndentListItems(TextRange range) { ListItem firstListItem = TextPointerBase.GetImmediateListItem(range.Start); ListItem lastListItem = TextPointerBase.GetImmediateListItem((TextPointer)TextRangeEdit.GetAdjustedRangeEnd(range.Start, range.End)); // The range must be in a sequence of ListItems belonging to one List wrapper if (firstListItem == null || lastListItem == null || firstListItem.Parent != lastListItem.Parent || !(firstListItem.Parent is List)) { return; } // Identify a ListItem which will become a leading item for this potential sublist ListItem leadingListItem = firstListItem.PreviousListItem; if (leadingListItem == null) { // There is no leading list item for this group. Indentation is impossible return; } // Get current List List list = (List)firstListItem.Parent; // Wrap these items into a List - inheriting all properties from our current list List indentedList = (List)TextRangeEdit.InsertElementClone(firstListItem.ElementStart, lastListItem.ElementEnd, list); // Wrap the leading ListItem to include the sublist leadingListItem.Reposition(leadingListItem.ContentStart, indentedList.ElementEnd); // Unwrap sublist from the last selected list item (to keep it on its level) Paragraph leadingParagraphOfLastItem = lastListItem.Blocks.FirstBlock as Paragraph; if (leadingParagraphOfLastItem != null) { // Unindenting all items of a sublist - if it is the only following element of a list List nestedListOfLastItem = leadingParagraphOfLastItem.NextBlock as List; if (nestedListOfLastItem != null && nestedListOfLastItem.NextBlock == null) { lastListItem.Reposition(lastListItem.ContentStart, nestedListOfLastItem.ElementStart); nestedListOfLastItem.Reposition(null, null); } } // Merge with neighboring lists MergeLists(indentedList.ElementStart); // No need in merging at nestedList.ElementEnd as ListItem ends there. } internal static bool UnindentListItems(TextRange range) { // If listitems in this range cross a list boundary, we cannot unindent them. if (!IsRangeWithinSingleList(range)) { return false; } ListItem firstListItem = TextPointerBase.GetListItem(range.Start); ListItem lastListItem = TextPointerBase.GetListItem((TextPointer)TextRangeEdit.GetAdjustedRangeEnd(range.Start, range.End)); // At this point it is possible that lastListItem is a child of // firstListItem. // // This is due to a special case in IsRangeWithinSingleList // which allows the input TextRange to cross List boundaries only // in the case where the TextRange ends adjacent to an insertion // position at the same level as the start, e.g.: // // - parent item // - start item (range starts here) // - child item (range ends here) // // // Here we check for that special case and ensure that // lastListItem is at the same level as firstListItem. TextElement parent = (TextElement)lastListItem.Parent; while (parent != firstListItem.Parent) { lastListItem = parent as ListItem; parent = (TextElement)parent.Parent; } if (lastListItem == null) { // This can happen if the input is a fragment, a collection // of ListItems not parented by an outer List. return false; } // Cut wrapping list before startListItem if (firstListItem.PreviousListItem != null) { TextRangeEdit.SplitElement(firstListItem.ElementStart); } // Cut wrapping list after endListItem if (lastListItem.NextListItem != null) { TextRangeEdit.SplitElement(lastListItem.ElementEnd); } // Remove List wrapper from selected items List unindentedList = (List)firstListItem.Parent; // Check whether we have outer ListItem ListItem outerListItem = unindentedList.Parent as ListItem; if (outerListItem != null) { // Selected items belong to a nested list. // So we need to pull them to the level of enclosing ListItem, i.e. cut this ListItem. // In this case we also need to include trailing list into the last of selected items // Remove a wrapping List from selected items unindentedList.Reposition(null, null); // Remember end position of outerListItem to pull any trailing list or other blocks into the last of selected listitems TextPointer outerListItemEnd = outerListItem.ContentEnd; if (outerListItem.ContentStart.CompareTo(firstListItem.ElementStart) == 0) { // There is nothing before first list item; so outer list item would be empty - just delete it outerListItem.Reposition(null, null); } else { // Wrap all stuff preceding firstListItem in outerListItem outerListItem.Reposition(outerListItem.ContentStart, firstListItem.ElementStart); } if (outerListItemEnd.CompareTo(lastListItem.ElementEnd) == 0) { // There are no following siblings to pull into last selected item; do nothing. } else { // Pull trailing items (following siblings to the selected ones) into the last selected item // Remember a position to merge any trailing list after lastListItem TextPointer mergePosition = lastListItem.ContentEnd; // Reposition last selectd ListItem so that it includes trailing list (or other block) as its children lastListItem.Reposition(lastListItem.ContentStart, outerListItemEnd); // Merge any trailing list with a sublist outdented with our listitem MergeLists(mergePosition); } } else { // Selected items are not in nested list. // We need to simply unwrap them and convert to paragraphs TextPointer start = unindentedList.ElementStart; TextPointer end = unindentedList.ElementEnd; // Save the list's FlowDirection value, to apply later to its children. object listFlowDirectionValue = unindentedList.GetValue(Paragraph.FlowDirectionProperty); // Remove a wrapping List from selected items unindentedList.Reposition(null, null); // Remove ListItems from all selected items ListItem listItem = firstListItem; while (listItem != null) { ListItem nextListItem = listItem.ElementEnd.GetAdjacentElement(LogicalDirection.Forward) as ListItem; // If this is an empty , insert an explicit paragraph in it before deleting the list item. if (listItem.ContentStart.CompareTo(listItem.ContentEnd) == 0) { TextRangeEditTables.EnsureInsertionPosition(listItem.ContentStart); } listItem.Reposition(null, null); listItem = listItem == lastListItem ? null : nextListItem; } // Apply FlowDirection of the list just deleted to all its children. TextRangeEdit.SetParagraphProperty(start, end, Paragraph.FlowDirectionProperty, listFlowDirectionValue); // Merge lists on boundaries MergeLists(start); MergeLists(end); } return true; } // Predicate which returns true if list items in this range are within the scope of the same parent list. private static bool IsRangeWithinSingleList(TextRange range) { ListItem startListItem = TextPointerBase.GetListItem(range.Start); // Adjust range end so that it does not affect a following paragraph. TextPointer end = (TextPointer)TextRangeEdit.GetAdjustedRangeEnd(range.Start, range.End); ListItem endListItem = TextPointerBase.GetListItem(end); // Check if the ListItems belong to one List wrapper. if (startListItem != null && endListItem != null && startListItem.Parent == endListItem.Parent) { return true; } // In case of nested lists, it may be the case that start and end list item do not belong to one list wrapper, // yet no visual list boundary is crossed. // e.g. // * aa // * bb // * cc // Special case so that list operations are applicable in this scenario. if (startListItem != null && endListItem != null) { while (end.GetPointerContext(LogicalDirection.Forward) == TextPointerContext.ElementEnd) { if (end.Parent == startListItem.Parent) { return true; } end = end.GetNextContextPosition(LogicalDirection.Forward); } } return false; } // Checks whether two paragraphs are meargeable. // To be meargeable they need to be separated by s sequence of closing, then opening // tags only. // And all tags must be Sections/Lists/ListItems only. internal static bool ParagraphsAreMergeable(Block firstParagraphOrBlockUIContainer, Block secondParagraphOrBlockUIContainer) { if (firstParagraphOrBlockUIContainer == null || secondParagraphOrBlockUIContainer == null || firstParagraphOrBlockUIContainer == secondParagraphOrBlockUIContainer) { return false; // nothing to merge } TextPointer position = firstParagraphOrBlockUIContainer.ElementEnd; TextPointer startOfSecondParagraph = secondParagraphOrBlockUIContainer.ElementStart; // Skip and check all closing tags (if any) while (position.GetPointerContext(LogicalDirection.Forward) == TextPointerContext.ElementEnd) { if (!TextSchema.AllowsParagraphMerging(position.Parent.GetType())) { return false; // Crossing hard-structured element. Paragraphs are not meargeable. } position = position.GetNextContextPosition(LogicalDirection.Forward); } // Skip and check all opening tags (if any) while (position.GetPointerContext(LogicalDirection.Forward) == TextPointerContext.ElementStart) { if (position.CompareTo(startOfSecondParagraph) == 0) { // Successfully skipped all tags, and reached the seconfParagraph. // The paragraphs are meargeable. return true; } position = position.GetNextContextPosition(LogicalDirection.Forward); if (!TextSchema.AllowsParagraphMerging(position.Parent.GetType())) { return false; // Crossing hardd-structured element. Paragraphs are not meargeable. } } // Non-tag run found. Paragraphs are not meargeable. return false; } // Checks if start and end positions are parented by a List. // If so, unindents list items between (start - start's list end) or (end's list start - end) // until they are parented by a top level list. // Then, if needed, splits the list(s) at start and/or end positions. // Returns false if splitting is not successful due to a failing unindent operation on any nested lists. internal static bool SplitListsForFlowDirectionChange(TextPointer start, TextPointer end, object newFlowDirectionValue) { ListItem startListItem = start.GetListAncestor(); // Unindent startListItem's list to prepare for a split, if the List's FlowDirection value is different. if (startListItem != null && startListItem.List != null && // Check for unparented list items !TextSchema.ValuesAreEqual(/*newValue*/newFlowDirectionValue, /*currentValue*/startListItem.List.GetValue(Paragraph.FlowDirectionProperty))) { while (startListItem != null && startListItem.List != null && startListItem.List.Parent is ListItem) { // startListItem is within a nested List. if (!UnindentListItems(new TextRange(start, GetPositionAfterList(startListItem.List)))) { return false; } startListItem = start.GetListAncestor(); } } ListItem endListItem = end.GetListAncestor(); // Unindent endListItem's list to prepare for a split, if the List's FlowDirection value is different. if (endListItem != null && endListItem.List != null && !TextSchema.ValuesAreEqual(/*newValue*/newFlowDirectionValue, /*currentValue*/endListItem.List.GetValue(Paragraph.FlowDirectionProperty))) { if (startListItem != null && startListItem.List != null && endListItem.List.ElementEnd.CompareTo(startListItem.List.ElementEnd) < 0) { // endListItem's List is contained within startListItem's List. // No need to unindent endListItem. } else { while (endListItem != null && endListItem.List != null && endListItem.List.Parent is ListItem) { // endListItem is within a nested List. if (!UnindentListItems(new TextRange(endListItem.List.ContentStart, GetPositionAfterList(endListItem.List)))) { return false; } endListItem = end.GetListAncestor(); } } } // Split list(s) at boundary position(s) if // 1. startListItem is not the first list item within its list (or endListItem is not the last one) // and // 2. start/end's parent List's flow direction value is different than the new value being set if ((startListItem = start.GetListAncestor()) != null && startListItem.PreviousListItem != null && startListItem.List != null && // Check for unparented list items (!TextSchema.ValuesAreEqual(/*newValue*/newFlowDirectionValue, /*currentValue*/startListItem.List.GetValue(Paragraph.FlowDirectionProperty)))) { Invariant.Assert(!(startListItem.List.Parent is ListItem), "startListItem's list must not be nested!"); TextRangeEdit.SplitElement(startListItem.ElementStart); } if ((endListItem = end.GetListAncestor()) != null && endListItem.List != null && // Check for unparented list items (!TextSchema.ValuesAreEqual(/*newValue*/newFlowDirectionValue, /*currentValue*/endListItem.List.GetValue(Paragraph.FlowDirectionProperty)))) { // Walk up from endListItem to find the topmost listitem that contains it. if (endListItem.List.Parent is ListItem) { while (endListItem.List != null && endListItem.List.Parent is ListItem) { endListItem = (ListItem)endListItem.List.Parent; } } if (endListItem.List != null && endListItem.NextListItem != null) { Invariant.Assert(!(endListItem.List.Parent is ListItem), "endListItem's list must not be nested!"); TextRangeEdit.SplitElement(endListItem.ElementEnd); } } return true; } // Finds an insertion position after the list private static TextPointer GetPositionAfterList(List list) { Invariant.Assert(list != null, "list cannot be null"); TextPointer adjustedEnd = list.ElementEnd.GetInsertionPosition(LogicalDirection.Backward); if (adjustedEnd != null) { adjustedEnd = adjustedEnd.GetNextInsertionPosition(LogicalDirection.Forward); } if (adjustedEnd == null) { adjustedEnd = list.ElementEnd.TextContainer.End; } if (TextRangeEditTables.IsTableStructureCrossed(list.ElementEnd, adjustedEnd)) { adjustedEnd = list.ContentEnd; } return adjustedEnd; } #endregion Internal Methods } } // File provided for Reference Use Only by Microsoft Corporation (c) 2007. // Copyright (c) Microsoft Corporation. All rights reserved. //---------------------------------------------------------------------------- // // File TextRangeEditLists.cs // // Copyright (C) Microsoft Corporation. All rights reserved. // // Description: Internal static class representing a group of methods // for list editing // //--------------------------------------------------------------------------- namespace System.Windows.Documents { using System; using MS.Internal; using System.Windows.Controls; /// /// The TextRange class represents a pair of TextPositions, with many /// rich text editing operations exposed. /// internal static class TextRangeEditLists { // ------------------------------------------------------------------- // // Internal Methods // // ------------------------------------------------------------------- #region Internal Methods ////// Merges two paragraphs followinng one another. /// The content of a second paragraph is moved into the end /// of the first one. /// /// /// First of two merged paragraphs or BlockUIContainer. /// /// /// Second of two mered paragraphs or BlockUIContainer. /// ////// true if paragraphs have been merged; false if no actions where made. /// internal static bool MergeParagraphs(Block firstParagraphOrBlockUIContainer, Block secondParagraphOrBlockUIContainer) { if (!ParagraphsAreMergeable(firstParagraphOrBlockUIContainer, secondParagraphOrBlockUIContainer)) { return false; // Cannot mearge these paragraphs. } // Store parent list item of a second paragraph - // to correct its structure after the merge ListItem secondListItem = secondParagraphOrBlockUIContainer.PreviousBlock == null ? secondParagraphOrBlockUIContainer.Parent as ListItem : null; if (secondListItem != null && secondListItem.PreviousListItem == null && secondParagraphOrBlockUIContainer.NextBlock is List) { // The second paragraph is a first list item in some list. // It has a sublists in it, so this sublist must be unindented // to avoid double bulleted line. List sublistOfSecondParagraph = (List)secondParagraphOrBlockUIContainer.NextBlock; if (sublistOfSecondParagraph.ElementEnd.CompareTo(secondListItem.ContentEnd) == 0) { secondListItem.Reposition(null, null); } else { secondListItem.Reposition(sublistOfSecondParagraph.ElementEnd, secondListItem.ContentEnd); } // At this point the schema is temporaty broken: the secondParagraph and the sublistOfSecondParagraph have List as a parent sublistOfSecondParagraph.Reposition(null, null); // The schema is repared as to sublistOfSecondParagraph concern, but still broken for secondParagraph - must be corrected in the following code } // Move the second paragraph out of its wrappers separating from the first paragraph (if any). // We can not use RepositionWithContent because it would destroy // all pointers and ranges within a moved paragraph. // Instead we reposition elements around the two paragraphs. while (secondParagraphOrBlockUIContainer.ElementStart.GetPointerContext(LogicalDirection.Backward) == TextPointerContext.ElementStart) { TextElement parentBlock = (TextElement)secondParagraphOrBlockUIContainer.Parent; Invariant.Assert(parentBlock != null); Invariant.Assert(TextSchema.AllowsParagraphMerging(parentBlock.GetType())); if (secondParagraphOrBlockUIContainer.ElementEnd.CompareTo(parentBlock.ContentEnd) == 0) { // Remove ancestor block if it becomes empty parentBlock.Reposition(null, null); } else { // Move ancestor's Start after the end of our paragraph parentBlock.Reposition(secondParagraphOrBlockUIContainer.ElementEnd, parentBlock.ContentEnd); } } // Store a position after the second paragraph where list merging may be needed TextPointer positionAfterSecondParagraph = secondParagraphOrBlockUIContainer.ElementEnd.GetFrozenPointer(LogicalDirection.Forward); // Move the second paragraph to become an immediate following sibling of the first paragraph while (true) { TextElement previousBlock = secondParagraphOrBlockUIContainer.ElementStart.GetAdjacentElement(LogicalDirection.Backward) as TextElement; // Note: We cannot use Block.NextSibling property, because the structure is invalid during this process Invariant.Assert(previousBlock != null); if (previousBlock is Paragraph || previousBlock is BlockUIContainer) { break; } Invariant.Assert(TextSchema.AllowsParagraphMerging(previousBlock.GetType())); previousBlock.Reposition(previousBlock.ContentStart, secondParagraphOrBlockUIContainer.ElementEnd); } // Now that paragraphs are next to each other merge them. // If one of paragraphs is empty we will apply special logic - to preserve a formatting from a non-empty one if (secondParagraphOrBlockUIContainer.TextRange.IsEmpty) { secondParagraphOrBlockUIContainer.RepositionWithContent(null); } else if (firstParagraphOrBlockUIContainer.TextRange.IsEmpty) { firstParagraphOrBlockUIContainer.RepositionWithContent(null); } else if (firstParagraphOrBlockUIContainer is Paragraph && secondParagraphOrBlockUIContainer is Paragraph) { // Do reposition magic for merging paragraph content // without destroying any pointers positioned in them. // Pull the second paragraph into the first one Invariant.Assert(firstParagraphOrBlockUIContainer.ElementEnd.CompareTo(secondParagraphOrBlockUIContainer.ElementStart) == 0); firstParagraphOrBlockUIContainer.Reposition(firstParagraphOrBlockUIContainer.ContentStart, secondParagraphOrBlockUIContainer.ElementEnd); // Store inline merging position TextPointer inlineMergingPosition = secondParagraphOrBlockUIContainer.ElementStart; // Now we can delete the second paragraph secondParagraphOrBlockUIContainer.Reposition(null, null); // Merge formatting elements at the point of paragraphs merging TextRangeEdit.MergeFormattingInlines(inlineMergingPosition); } // Merge ListItems wrapping first and second paragraphs. ListItem followingListItem = positionAfterSecondParagraph.GetPointerContext(LogicalDirection.Forward) == TextPointerContext.ElementStart ? positionAfterSecondParagraph.GetAdjacentElement(LogicalDirection.Forward) as ListItem : null; if (followingListItem != null && followingListItem == secondListItem) { ListItem precedingListItem = positionAfterSecondParagraph.GetAdjacentElement(LogicalDirection.Backward) as ListItem; if (precedingListItem != null) { // Merge the second list item with the preceding one Invariant.Assert(positionAfterSecondParagraph.GetPointerContext(LogicalDirection.Forward) == TextPointerContext.ElementStart); Invariant.Assert(positionAfterSecondParagraph.GetPointerContext(LogicalDirection.Backward) == TextPointerContext.ElementEnd); precedingListItem.Reposition(precedingListItem.ContentStart, followingListItem.ElementEnd); followingListItem.Reposition(null, null); } } // Merge lists at merge position MergeLists(positionAfterSecondParagraph); return true; } ////// Like MergeLists, but will search over formatting elements when /// looking for Lists to merge. /// internal static bool MergeListsAroundNormalizedPosition(TextPointer mergePosition) { // Search forward for a List to merge with. TextPointer navigator = mergePosition.CreatePointer(); while (navigator.GetPointerContext(LogicalDirection.Forward) == TextPointerContext.ElementEnd) { navigator.MoveToNextContextPosition(LogicalDirection.Forward); } bool merged = MergeLists(navigator); // Search backward for a List to merge with. if (!merged) { navigator.MoveToPosition(mergePosition); while (navigator.GetPointerContext(LogicalDirection.Backward) == TextPointerContext.ElementStart) { navigator.MoveToNextContextPosition(LogicalDirection.Backward); } merged = MergeLists(navigator); } return merged; } ////// Merges two naighboring lists ending and starting at position mergePosition /// /// /// Position at with two List elements are expected to appear next to each other /// ////// true if there were two mergeable List elements and merge happened. /// false if there is no pair of List elements at the mergePosition. /// internal static bool MergeLists(TextPointer mergePosition) { if (mergePosition.GetPointerContext(LogicalDirection.Backward) != TextPointerContext.ElementEnd || mergePosition.GetPointerContext(LogicalDirection.Forward) != TextPointerContext.ElementStart) { return false; } List precedingList = mergePosition.GetAdjacentElement(LogicalDirection.Backward) as List; List followingList = mergePosition.GetAdjacentElement(LogicalDirection.Forward) as List; if (precedingList == null || followingList == null) { return false; } precedingList.Reposition(precedingList.ContentStart, followingList.ElementEnd); followingList.Reposition(null, null); // We need to set appropriate FlowDirection property on the new List and its paragraph children. // We take the FlowDirection value from the preceding list. TextRangeEdit.SetParagraphProperty(precedingList.ElementStart, precedingList.ElementEnd, Paragraph.FlowDirectionProperty, precedingList.GetValue(Paragraph.FlowDirectionProperty)); return true; } // Returns true if all paragraphs in a range belong to the same block, // so they can be easily grouped and ungrouped. internal static bool IsListOperationApplicable(TextRange range) { // First check, if range start/end are parented by ListItems within the same parent list. if (IsRangeWithinSingleList(range)) { return true; } // Adjust range end so that it does not affect a following paragraph. TextPointer end = (TextPointer)TextRangeEdit.GetAdjustedRangeEnd(range.Start, range.End); // Now try plain Paragraphs Block firstBlock = range.Start.ParagraphOrBlockUIContainer; Block lastBlock = end.ParagraphOrBlockUIContainer; if (firstBlock != null && lastBlock != null && firstBlock.Parent == lastBlock.Parent) { return true; } // Allow list editing at potential paragraph positions, this includes // positions in initial RichTextBox, empty TableCell, empty ListItem where paragraphs were not yet created. if (range.IsEmpty && TextPointerBase.IsAtPotentialParagraphPosition(range.Start)) { return true; } return false; } internal static bool ConvertParagraphsToListItems(TextRange range, TextMarkerStyle markerStyle) { if (range.IsEmpty && TextPointerBase.IsAtPotentialParagraphPosition(range.Start)) { TextPointer insertionPosition = TextRangeEditTables.EnsureInsertionPosition(range.Start); ((ITextRange)range).Select(insertionPosition, insertionPosition); } Block firstBlock = range.Start.ParagraphOrBlockUIContainer; TextPointer end = (TextPointer)TextRangeEdit.GetAdjustedRangeEnd(range.Start, range.End); Block lastBlock = end.ParagraphOrBlockUIContainer; // We assume that a range contains a sequence of one-level paragraphs. // Otherwise the operation is disabled. if (firstBlock == null || lastBlock == null || firstBlock.Parent != lastBlock.Parent || firstBlock.Parent is ListItem && firstBlock.PreviousBlock == null) { // Either the paragraphs belong to different scopes or first of them has a bullet already. // We cannot convert them into bulleted lists. return false; } // Check that all top-level elements of selection are Paragraphs. // We do not apply the command to Tables or Sections. for (Block block = firstBlock; block != lastBlock && block != null; block = block.NextBlock) { if (block is Table || block is Section) { return false; } } ListItem parentListItem = firstBlock.Parent as ListItem; if (parentListItem != null) { // Paragraphs are inside of ListItem already. // Split a current ListItem before each of selected blocks Block block = firstBlock; while (block != null) { Block nextBlock = block == lastBlock ? null : block.ElementEnd.GetAdjacentElement(LogicalDirection.Forward) as Block; Invariant.Assert(block.Parent is ListItem); TextRangeEdit.SplitElement(block.ElementStart); block = nextBlock; } } else { // Create a list around all paragraphs List list = new List(); list.MarkerStyle = markerStyle; list.Apply(firstBlock, lastBlock); // Merge with neighboring lists //MergeLists(list.ElementEnd); // start with End to not loose an instance of "list" during merging with the following list //MergeLists(list.ElementStart); } return true; } // Assumes that a range contains a sequence of same-level ListItems. // Converts all these ListItems into Paragraphs and // either adds them to preceding ListItem (as non-bulleted continuation) // or pulls them out of a List if they start in the beginning of a List internal static void ConvertListItemsToParagraphs(TextRange range) { ListItem firstListItem = TextPointerBase.GetListItem(range.Start); ListItem lastListItem = TextPointerBase.GetListItem((TextPointer)TextRangeEdit.GetAdjustedRangeEnd(range.Start, range.End)); // The range must be in a sequence of ListItems belonging to one List wrapper if (firstListItem == null || lastListItem == null || firstListItem.Parent != lastListItem.Parent || !(firstListItem.Parent is List)) { return; } List listToRemove = null; ListItem leadingListItem = firstListItem.PreviousListItem; if (leadingListItem != null) { // We have a leading ListItem, so pull selected items into it leadingListItem.Reposition(leadingListItem.ContentStart, lastListItem.ElementEnd); } else { // We do not have a leading ListItem. So pull selected items out of a list // Cut wrapping list after endListItem if (lastListItem.NextListItem != null) { TextRangeEdit.SplitElement(lastListItem.ElementEnd); } // Set list to remove listToRemove = firstListItem.List; } // Remove ListItems from all selected blocks ListItem listItem = firstListItem; while (listItem != null) { ListItem nextListItem = listItem.ElementEnd.GetAdjacentElement(LogicalDirection.Forward) as ListItem; // If this is an empty, insert an explicit paragraph in it before deleting the list item. if (listItem.ContentStart.CompareTo(listItem.ContentEnd) == 0) { TextRangeEditTables.EnsureInsertionPosition(listItem.ContentStart); } listItem.Reposition(null, null); listItem = listItem == lastListItem ? null : nextListItem; } // If we have a list to remove, remove it and set its FlowDirection to its children if (listToRemove != null) { FlowDirection flowDirection = (FlowDirection)listToRemove.GetValue(Paragraph.FlowDirectionProperty); listToRemove.Reposition(null, null); TextRangeEdit.SetParagraphProperty(range.Start, range.End, Paragraph.FlowDirectionProperty, flowDirection); } } internal static void IndentListItems(TextRange range) { ListItem firstListItem = TextPointerBase.GetImmediateListItem(range.Start); ListItem lastListItem = TextPointerBase.GetImmediateListItem((TextPointer)TextRangeEdit.GetAdjustedRangeEnd(range.Start, range.End)); // The range must be in a sequence of ListItems belonging to one List wrapper if (firstListItem == null || lastListItem == null || firstListItem.Parent != lastListItem.Parent || !(firstListItem.Parent is List)) { return; } // Identify a ListItem which will become a leading item for this potential sublist ListItem leadingListItem = firstListItem.PreviousListItem; if (leadingListItem == null) { // There is no leading list item for this group. Indentation is impossible return; } // Get current List List list = (List)firstListItem.Parent; // Wrap these items into a List - inheriting all properties from our current list List indentedList = (List)TextRangeEdit.InsertElementClone(firstListItem.ElementStart, lastListItem.ElementEnd, list); // Wrap the leading ListItem to include the sublist leadingListItem.Reposition(leadingListItem.ContentStart, indentedList.ElementEnd); // Unwrap sublist from the last selected list item (to keep it on its level) Paragraph leadingParagraphOfLastItem = lastListItem.Blocks.FirstBlock as Paragraph; if (leadingParagraphOfLastItem != null) { // Unindenting all items of a sublist - if it is the only following element of a list List nestedListOfLastItem = leadingParagraphOfLastItem.NextBlock as List; if (nestedListOfLastItem != null && nestedListOfLastItem.NextBlock == null) { lastListItem.Reposition(lastListItem.ContentStart, nestedListOfLastItem.ElementStart); nestedListOfLastItem.Reposition(null, null); } } // Merge with neighboring lists MergeLists(indentedList.ElementStart); // No need in merging at nestedList.ElementEnd as ListItem ends there. } internal static bool UnindentListItems(TextRange range) { // If listitems in this range cross a list boundary, we cannot unindent them. if (!IsRangeWithinSingleList(range)) { return false; } ListItem firstListItem = TextPointerBase.GetListItem(range.Start); ListItem lastListItem = TextPointerBase.GetListItem((TextPointer)TextRangeEdit.GetAdjustedRangeEnd(range.Start, range.End)); // At this point it is possible that lastListItem is a child of // firstListItem. // // This is due to a special case in IsRangeWithinSingleList // which allows the input TextRange to cross List boundaries only // in the case where the TextRange ends adjacent to an insertion // position at the same level as the start, e.g.: // // - parent item // - start item (range starts here) // - child item (range ends here) // // // Here we check for that special case and ensure that // lastListItem is at the same level as firstListItem. TextElement parent = (TextElement)lastListItem.Parent; while (parent != firstListItem.Parent) { lastListItem = parent as ListItem; parent = (TextElement)parent.Parent; } if (lastListItem == null) { // This can happen if the input is a fragment, a collection // of ListItems not parented by an outer List. return false; } // Cut wrapping list before startListItem if (firstListItem.PreviousListItem != null) { TextRangeEdit.SplitElement(firstListItem.ElementStart); } // Cut wrapping list after endListItem if (lastListItem.NextListItem != null) { TextRangeEdit.SplitElement(lastListItem.ElementEnd); } // Remove List wrapper from selected items List unindentedList = (List)firstListItem.Parent; // Check whether we have outer ListItem ListItem outerListItem = unindentedList.Parent as ListItem; if (outerListItem != null) { // Selected items belong to a nested list. // So we need to pull them to the level of enclosing ListItem, i.e. cut this ListItem. // In this case we also need to include trailing list into the last of selected items // Remove a wrapping List from selected items unindentedList.Reposition(null, null); // Remember end position of outerListItem to pull any trailing list or other blocks into the last of selected listitems TextPointer outerListItemEnd = outerListItem.ContentEnd; if (outerListItem.ContentStart.CompareTo(firstListItem.ElementStart) == 0) { // There is nothing before first list item; so outer list item would be empty - just delete it outerListItem.Reposition(null, null); } else { // Wrap all stuff preceding firstListItem in outerListItem outerListItem.Reposition(outerListItem.ContentStart, firstListItem.ElementStart); } if (outerListItemEnd.CompareTo(lastListItem.ElementEnd) == 0) { // There are no following siblings to pull into last selected item; do nothing. } else { // Pull trailing items (following siblings to the selected ones) into the last selected item // Remember a position to merge any trailing list after lastListItem TextPointer mergePosition = lastListItem.ContentEnd; // Reposition last selectd ListItem so that it includes trailing list (or other block) as its children lastListItem.Reposition(lastListItem.ContentStart, outerListItemEnd); // Merge any trailing list with a sublist outdented with our listitem MergeLists(mergePosition); } } else { // Selected items are not in nested list. // We need to simply unwrap them and convert to paragraphs TextPointer start = unindentedList.ElementStart; TextPointer end = unindentedList.ElementEnd; // Save the list's FlowDirection value, to apply later to its children. object listFlowDirectionValue = unindentedList.GetValue(Paragraph.FlowDirectionProperty); // Remove a wrapping List from selected items unindentedList.Reposition(null, null); // Remove ListItems from all selected items ListItem listItem = firstListItem; while (listItem != null) { ListItem nextListItem = listItem.ElementEnd.GetAdjacentElement(LogicalDirection.Forward) as ListItem; // If this is an empty , insert an explicit paragraph in it before deleting the list item. if (listItem.ContentStart.CompareTo(listItem.ContentEnd) == 0) { TextRangeEditTables.EnsureInsertionPosition(listItem.ContentStart); } listItem.Reposition(null, null); listItem = listItem == lastListItem ? null : nextListItem; } // Apply FlowDirection of the list just deleted to all its children. TextRangeEdit.SetParagraphProperty(start, end, Paragraph.FlowDirectionProperty, listFlowDirectionValue); // Merge lists on boundaries MergeLists(start); MergeLists(end); } return true; } // Predicate which returns true if list items in this range are within the scope of the same parent list. private static bool IsRangeWithinSingleList(TextRange range) { ListItem startListItem = TextPointerBase.GetListItem(range.Start); // Adjust range end so that it does not affect a following paragraph. TextPointer end = (TextPointer)TextRangeEdit.GetAdjustedRangeEnd(range.Start, range.End); ListItem endListItem = TextPointerBase.GetListItem(end); // Check if the ListItems belong to one List wrapper. if (startListItem != null && endListItem != null && startListItem.Parent == endListItem.Parent) { return true; } // In case of nested lists, it may be the case that start and end list item do not belong to one list wrapper, // yet no visual list boundary is crossed. // e.g. // * aa // * bb // * cc // Special case so that list operations are applicable in this scenario. if (startListItem != null && endListItem != null) { while (end.GetPointerContext(LogicalDirection.Forward) == TextPointerContext.ElementEnd) { if (end.Parent == startListItem.Parent) { return true; } end = end.GetNextContextPosition(LogicalDirection.Forward); } } return false; } // Checks whether two paragraphs are meargeable. // To be meargeable they need to be separated by s sequence of closing, then opening // tags only. // And all tags must be Sections/Lists/ListItems only. internal static bool ParagraphsAreMergeable(Block firstParagraphOrBlockUIContainer, Block secondParagraphOrBlockUIContainer) { if (firstParagraphOrBlockUIContainer == null || secondParagraphOrBlockUIContainer == null || firstParagraphOrBlockUIContainer == secondParagraphOrBlockUIContainer) { return false; // nothing to merge } TextPointer position = firstParagraphOrBlockUIContainer.ElementEnd; TextPointer startOfSecondParagraph = secondParagraphOrBlockUIContainer.ElementStart; // Skip and check all closing tags (if any) while (position.GetPointerContext(LogicalDirection.Forward) == TextPointerContext.ElementEnd) { if (!TextSchema.AllowsParagraphMerging(position.Parent.GetType())) { return false; // Crossing hard-structured element. Paragraphs are not meargeable. } position = position.GetNextContextPosition(LogicalDirection.Forward); } // Skip and check all opening tags (if any) while (position.GetPointerContext(LogicalDirection.Forward) == TextPointerContext.ElementStart) { if (position.CompareTo(startOfSecondParagraph) == 0) { // Successfully skipped all tags, and reached the seconfParagraph. // The paragraphs are meargeable. return true; } position = position.GetNextContextPosition(LogicalDirection.Forward); if (!TextSchema.AllowsParagraphMerging(position.Parent.GetType())) { return false; // Crossing hardd-structured element. Paragraphs are not meargeable. } } // Non-tag run found. Paragraphs are not meargeable. return false; } // Checks if start and end positions are parented by a List. // If so, unindents list items between (start - start's list end) or (end's list start - end) // until they are parented by a top level list. // Then, if needed, splits the list(s) at start and/or end positions. // Returns false if splitting is not successful due to a failing unindent operation on any nested lists. internal static bool SplitListsForFlowDirectionChange(TextPointer start, TextPointer end, object newFlowDirectionValue) { ListItem startListItem = start.GetListAncestor(); // Unindent startListItem's list to prepare for a split, if the List's FlowDirection value is different. if (startListItem != null && startListItem.List != null && // Check for unparented list items !TextSchema.ValuesAreEqual(/*newValue*/newFlowDirectionValue, /*currentValue*/startListItem.List.GetValue(Paragraph.FlowDirectionProperty))) { while (startListItem != null && startListItem.List != null && startListItem.List.Parent is ListItem) { // startListItem is within a nested List. if (!UnindentListItems(new TextRange(start, GetPositionAfterList(startListItem.List)))) { return false; } startListItem = start.GetListAncestor(); } } ListItem endListItem = end.GetListAncestor(); // Unindent endListItem's list to prepare for a split, if the List's FlowDirection value is different. if (endListItem != null && endListItem.List != null && !TextSchema.ValuesAreEqual(/*newValue*/newFlowDirectionValue, /*currentValue*/endListItem.List.GetValue(Paragraph.FlowDirectionProperty))) { if (startListItem != null && startListItem.List != null && endListItem.List.ElementEnd.CompareTo(startListItem.List.ElementEnd) < 0) { // endListItem's List is contained within startListItem's List. // No need to unindent endListItem. } else { while (endListItem != null && endListItem.List != null && endListItem.List.Parent is ListItem) { // endListItem is within a nested List. if (!UnindentListItems(new TextRange(endListItem.List.ContentStart, GetPositionAfterList(endListItem.List)))) { return false; } endListItem = end.GetListAncestor(); } } } // Split list(s) at boundary position(s) if // 1. startListItem is not the first list item within its list (or endListItem is not the last one) // and // 2. start/end's parent List's flow direction value is different than the new value being set if ((startListItem = start.GetListAncestor()) != null && startListItem.PreviousListItem != null && startListItem.List != null && // Check for unparented list items (!TextSchema.ValuesAreEqual(/*newValue*/newFlowDirectionValue, /*currentValue*/startListItem.List.GetValue(Paragraph.FlowDirectionProperty)))) { Invariant.Assert(!(startListItem.List.Parent is ListItem), "startListItem's list must not be nested!"); TextRangeEdit.SplitElement(startListItem.ElementStart); } if ((endListItem = end.GetListAncestor()) != null && endListItem.List != null && // Check for unparented list items (!TextSchema.ValuesAreEqual(/*newValue*/newFlowDirectionValue, /*currentValue*/endListItem.List.GetValue(Paragraph.FlowDirectionProperty)))) { // Walk up from endListItem to find the topmost listitem that contains it. if (endListItem.List.Parent is ListItem) { while (endListItem.List != null && endListItem.List.Parent is ListItem) { endListItem = (ListItem)endListItem.List.Parent; } } if (endListItem.List != null && endListItem.NextListItem != null) { Invariant.Assert(!(endListItem.List.Parent is ListItem), "endListItem's list must not be nested!"); TextRangeEdit.SplitElement(endListItem.ElementEnd); } } return true; } // Finds an insertion position after the list private static TextPointer GetPositionAfterList(List list) { Invariant.Assert(list != null, "list cannot be null"); TextPointer adjustedEnd = list.ElementEnd.GetInsertionPosition(LogicalDirection.Backward); if (adjustedEnd != null) { adjustedEnd = adjustedEnd.GetNextInsertionPosition(LogicalDirection.Forward); } if (adjustedEnd == null) { adjustedEnd = list.ElementEnd.TextContainer.End; } if (TextRangeEditTables.IsTableStructureCrossed(list.ElementEnd, adjustedEnd)) { adjustedEnd = list.ContentEnd; } return adjustedEnd; } #endregion Internal Methods } } // File provided for Reference Use Only by Microsoft Corporation (c) 2007. // Copyright (c) Microsoft Corporation. All rights reserved.
Link Menu
This book is available now!
Buy at Amazon US or
Buy at Amazon UK
- PublisherIdentityPermission.cs
- DataSetSchema.cs
- ArrayWithOffset.cs
- SelectedGridItemChangedEvent.cs
- StatusBarAutomationPeer.cs
- QueryContinueDragEvent.cs
- Line.cs
- wmiprovider.cs
- Condition.cs
- XsltQilFactory.cs
- UIElementParaClient.cs
- WebCategoryAttribute.cs
- ValueExpressions.cs
- Main.cs
- SecurityCriticalDataForSet.cs
- XmlProcessingInstruction.cs
- CommonXSendMessage.cs
- UInt16.cs
- CatalogZone.cs
- MulticastDelegate.cs
- XmlElementCollection.cs
- Vector3dCollection.cs
- InfoCardServiceInstallComponent.cs
- SimpleMailWebEventProvider.cs
- TaskExceptionHolder.cs
- UserNameSecurityTokenAuthenticator.cs
- TextServicesContext.cs
- MultiBindingExpression.cs
- TraceFilter.cs
- Pick.cs
- ValidationVisibilityAttribute.cs
- PenContexts.cs
- InputLangChangeEvent.cs
- ProfileSection.cs
- BitStack.cs
- xdrvalidator.cs
- IisTraceWebEventProvider.cs
- ObjectConverter.cs
- HtmlInputSubmit.cs
- SortableBindingList.cs
- ContentElement.cs
- SignatureHelper.cs
- IIS7WorkerRequest.cs
- MemberInitExpression.cs
- OrderedDictionary.cs
- UserPreferenceChangingEventArgs.cs
- DataGridRow.cs
- MetadataProperty.cs
- SurrogateEncoder.cs
- IEnumerable.cs
- TextSelectionHelper.cs
- TypeResolver.cs
- StrokeCollection.cs
- PathSegmentCollection.cs
- RandomNumberGenerator.cs
- RoutingSection.cs
- ConsumerConnectionPointCollection.cs
- ConfigurationSectionCollection.cs
- HostProtectionException.cs
- PenCursorManager.cs
- PagerSettings.cs
- EndEvent.cs
- ClosableStream.cs
- OleDbConnectionPoolGroupProviderInfo.cs
- SqlXml.cs
- ClassicBorderDecorator.cs
- XPathNavigatorKeyComparer.cs
- NamedPipeProcessProtocolHandler.cs
- CategoryGridEntry.cs
- AssertSection.cs
- OdbcConnectionString.cs
- FormsIdentity.cs
- ConnectionPointCookie.cs
- Camera.cs
- CollectionConverter.cs
- TextRangeEditTables.cs
- SerializationInfo.cs
- BooleanStorage.cs
- MethodAccessException.cs
- MessageQueue.cs
- PrinterResolution.cs
- ConfigurationProperty.cs
- AppearanceEditorPart.cs
- ApplicationCommands.cs
- RegexRunnerFactory.cs
- SqlError.cs
- DecoderFallback.cs
- GridViewRowPresenter.cs
- PeerCollaborationPermission.cs
- ReadOnlyCollection.cs
- Helpers.cs
- PrintDialog.cs
- UnsafeNativeMethods.cs
- mactripleDES.cs
- Vector3DValueSerializer.cs
- XmlEnumAttribute.cs
- XmlWriterTraceListener.cs
- LinearQuaternionKeyFrame.cs
- XmlArrayAttribute.cs
- Interlocked.cs