char* TextSelection::GetXaml () { if (anchor.GetLocation () == moving.GetLocation()) return g_strdup (""); // I'm guessing this should only include ancestors (up to the // root) that are required to serialize the actual selection, // but for now let's just serialize the entire contents of the // RTB. DependencyObject *el = anchor.GetParent(); while (el) { if (el->Is (Type::RICHTEXTBOX)) break; el = el->GetParent() ? el->GetParent()->GetParent() : NULL; if (!el) break; } if (el == NULL) { g_warning ("this shouldn't happen..."); return NULL; } // el should be the RichTextBox now. GString *str = g_string_new (""); IDocumentNode *node = IDocumentNode::CastToIDocumentNode (el); node->SerializeXaml(str); return g_string_free (str, FALSE); }
Value * TextSelection::GetPropertyValue (DependencyProperty *formatting) const { Value *value = start.GetParent()->GetValue (formatting); Value *current_value; DocumentWalker walker (start.GetParentNode(), DocumentWalker::Forward); walker.Step ();// skip the ::Leave for the start node while (true) { IDocumentNode *node; DocumentWalker::StepType step = walker.Step (&node); switch (step) { case DocumentWalker::Enter: current_value = node->AsDependencyObject()->GetValue (formatting); if (!Value::AreEqual (value, current_value)) return NULL; if (node == end.GetParentNode()) return value; case DocumentWalker::Leave: break; case DocumentWalker::Done: // this shouldn't happen, but do we care? g_warning ("TextSelecion::GetPropertyValue document walker found the end of the document before finding the selection end."); break; return NULL; } } }
void TextSelection::Insert (TextElement *element) { if (anchor.GetParent() == NULL || moving.GetParent() == NULL) // if either are null we're going nowhere fast... return; // refactor out the "clear out selection" from SetText ClearSelection (); // at this point both anchor and moving are the same location // depending on what the anchor's parent is, // and what @element is, we might need to // split the tree higher up DependencyObject *el = anchor.GetParent (); int loc = anchor.ResolveLocation(); while (el) { DependencyObject *el_parent = el->GetParent(); if (el_parent) el_parent = el_parent->GetParent(); IDocumentNode *node = IDocumentNode::CastToIDocumentNode (el); DependencyObjectCollection *children = node->GetDocumentChildren (); if (children && element->Is (children->GetElementType())) { // we can insert the element here, so let's do it and be done with things children->Insert (loc, element); return; } if (!el_parent) { g_warning ("new element cannot fit inside element, and we can't split it"); return; } // we need to split the current element at // @loc, add the new element to el's parent // after el, and walk back up to el's parent // with new_el's index as @loc. IDocumentNode *parent_node = IDocumentNode::CastToIDocumentNode (el_parent); DependencyObjectCollection *parents_children = parent_node ? parent_node->GetDocumentChildren () : NULL; DependencyObject *new_el = node ? node->Split (loc) : NULL; if (!new_el) { g_warning ("split failed"); return; } int new_el_loc = parents_children->IndexOf (el) + 1; parents_children->Insert (new_el_loc, new_el); el = el_parent; loc = new_el_loc; new_el->unref (); } printf ("TextSelection::Insert\n"); }
void TextSelection::ClearSelection () { // clear out the selection if (anchor.GetParent() == moving.GetParent()) { bool remove_element = false; if (anchor.GetLocation () != moving.GetLocation()) { if (anchor.GetParent()->Is (Type::RUN)) { Run *run = (Run*)anchor.GetParent(); char *run_text = g_strdup (run->GetText()); if (run_text) { int length_to_remove = moving.ResolveLocation() - anchor.ResolveLocation(); memmove (run_text + anchor.ResolveLocation(), run_text + moving.ResolveLocation (), strlen (run_text + moving.ResolveLocation())); *(run_text + strlen(run_text) - length_to_remove) = 0; run->SetText (run_text); // we need to remove the run if we've cleared all the text in it remove_element = !*run_text; } else { remove_element = true; } g_free (run_text); } else { IDocumentNode *node = anchor.GetParentNode(); int length_to_remove = moving.ResolveLocation() - anchor.ResolveLocation(); for (int i = 0; i < length_to_remove; i ++) node->GetDocumentChildren()->RemoveAt (anchor.ResolveLocation()); // we need to remove the element if we've removed all the children remove_element = node->GetDocumentChildren()->GetCount() == 0; } } DependencyObject *el = anchor.GetParent(); while (remove_element) { if (el->Is (Type::RICHTEXTBOX)) break; DependencyObject* parent = el->GetParent() ? el->GetParent()->GetParent() : NULL; if (!parent) { g_warning ("shouldn't happen"); return; } IDocumentNode *parent_node = IDocumentNode::CastToIDocumentNode (parent); parent_node->GetDocumentChildren()->Remove (Value(el)); el = parent; remove_element = parent_node->GetDocumentChildren()->GetCount() == 0; } } else { // harder case, anchor/moving are in different elements printf ("NIEX hard case TextSelection::ClearSelection\n"); } g_free (text); text = NULL; g_free (xaml); xaml = NULL; }
char* TextSelection::GetXaml () { const char *header = "<Section xml:space=\"preserve\" HasTrailingParagraphBreakOnPaste=\"False\" xmlns=\"http://schemas.microsoft.com/winfx/2006/xaml/presentation\">"; const char *trailer = "</Section>"; GString *str; ArrayList ancestors; DependencyObject *el; if (xaml) goto done; if (!anchor.GetParent() || !moving.GetParent()) { xaml = g_strdup (""); goto done; } if (anchor.GetParent() == moving.GetParent() && anchor.GetLocation () == moving.GetLocation()) { xaml = g_strdup (""); goto done; } str = g_string_new (header); // first we serialize the xaml start elements for all // TextElements that contain the selection start (but not the // most deeply nested element itself) el = anchor.GetParent(); if (el && !el->Is (Type::RICHTEXTBOX)) { // skip anchor.GetParent() here. el = el->GetParent() ? el->GetParent()->GetParent() : NULL; while (el) { if (el->Is (Type::RICHTEXTBOX)) break; ancestors.Add (el); el = el->GetParent() ? el->GetParent()->GetParent() : NULL; if (!el) break; } } for (int i = ancestors.GetCount() - 1; i >= 0; i --) { TextElement *te = (TextElement*)ancestors[i]; IDocumentNode *node = IDocumentNode::CastToIDocumentNode (te); node->SerializeXamlStartElement (str); } // now we output the start element (and deal with the case where start_element == end_element) el = anchor.GetParent (); if (el->Is (Type::RUN)) { if (el == moving.GetParent()) { // if both textpointers are in the same // element, we need to use start+length form ((Run*)el)->SerializeXaml (str, anchor.ResolveLocation (), moving.ResolveLocation () - anchor.ResolveLocation()); } else { // since the moving textpointer is outside // this run, we just use the start form. ((Run*)el)->SerializeXaml (str, anchor.ResolveLocation ()); } } else { ((TextElement*)el)->SerializeXaml (str); } if (anchor.GetParent() != moving.GetParent()) { // now walk the document from start element to end element, outputting everything manually along the way. DocumentWalker walker (anchor.GetParentNode(), DocumentWalker::Forward); IDocumentNode *node; DocumentWalker::StepType stepType; stepType = walker.Step (); // step out of the start element while (stepType != DocumentWalker::Done) { stepType = walker.Step (&node); if (node == moving.GetParentNode()) break; if (stepType == DocumentWalker::Enter) node->SerializeXamlStartElement(str); else node->SerializeXamlEndElement(str); } // now we output the end element el = moving.GetParent (); if (el->Is (Type::RUN)) { ((Run*)el)->SerializeXaml (str, 0, moving.ResolveLocation ()); } else { ((TextElement*)el)->SerializeXaml (str); } } // now serialize the xaml end elements for all TextElements // that contain the selection end (but not the most deeply // nested element itself) if (anchor.GetParent() == moving.GetParent ()) { // this case is trivial, we just output the same list // of end elements that we outputted the start // elements before for (int i = ancestors.GetCount() - 1; i >= 0; i --) { TextElement *te = (TextElement*)ancestors[i]; IDocumentNode *node = IDocumentNode::CastToIDocumentNode (te); node->SerializeXamlEndElement (str); } } else { ancestors.SetCount (0); DependencyObject *el = moving.GetParent(); if (el && !el->Is (Type::RICHTEXTBOX)) { // skip moving.GetParent() here. el = el->GetParent() ? el->GetParent()->GetParent() : NULL; while (el) { if (el->Is (Type::RICHTEXTBOX)) break; ancestors.Add (el); el = el->GetParent() ? el->GetParent()->GetParent() : NULL; if (!el) break; } } for (int i = ancestors.GetCount() - 1; i >= 0; i --) { TextElement *te = (TextElement*)ancestors[i]; IDocumentNode *node = IDocumentNode::CastToIDocumentNode (te); node->SerializeXamlEndElement (str); } } g_string_append (str, trailer); xaml = g_string_free (str, FALSE); done: return g_strdup (xaml); }
void TextSelection::SetText (const char *text) { #define CONTENT_START (0) #define CONTENT_END ((guint32)-1) if (anchor.GetParent() == NULL || moving.GetParent() == NULL) // if either are null we're going nowhere fast... return; ClearSelection (); // at this point the selection is empty and anchor == moving if (text && *text) { if (anchor.GetParent()->Is (Type::RUN)) { const char *run_text = ((Run*)anchor.GetParent())->GetText(); if (run_text == NULL) run_text = "\0"; char *new_text = (char*)g_malloc0 (strlen (run_text) + strlen (text) + 1); if (strlen (text) < (size_t) anchor.ResolveLocation ()){ // #339RT enters here g_free (new_text); new_text = g_strdup ("BUGBUGBUG"); } else { strncpy (new_text, run_text, anchor.ResolveLocation()); strcpy (new_text + anchor.ResolveLocation(), text); strncpy (new_text + anchor.ResolveLocation() + strlen(text), run_text + anchor.ResolveLocation(), strlen (run_text) - anchor.ResolveLocation()); } ((Run*)anchor.GetParent())->SetText (new_text); if (moving.GetLocation() > strlen (new_text)) { anchor = TextPointer (anchor.GetParent(), CONTENT_END, anchor.GetLogicalDirection()); moving = TextPointer (anchor.GetParent(), CONTENT_END, anchor.GetLogicalDirection()); } else { TextPointer new_anchor (anchor.GetParent(), anchor.ResolveLocation () + strlen (text), anchor.GetLogicalDirection()); moving = TextPointer (anchor.GetParent(), anchor.ResolveLocation () + strlen (text), anchor.GetLogicalDirection()); anchor = new_anchor; } g_free (new_text); if (anchor.CompareTo_np (moving) < 0) { start = anchor; end = moving; } else { start = moving; end = anchor; } } else { IDocumentNode *node = anchor.GetParentNode(); DependencyObjectCollection *children = node->GetDocumentChildren(); if (!children) { // this can happen when anchor is in an InlineUIContainer. printf ("NIEX TextSelection.SetText for anchor == InlineUIContainer.\n"); return; } Run *r = MoonUnmanagedFactory::CreateRun (); r->SetText (text); if (children->Is(Type::BLOCK_COLLECTION)) { // the node takes Blocks as children, so we need to create a Paragraph first. Paragraph *p = MoonUnmanagedFactory::CreateParagraph(); children->Insert (anchor.GetLocation(), Value (p)); children = p->GetInlines(); children->Add (Value (r)); p->unref (); } else { children->Insert (anchor.GetLocation(), Value (r)); } anchor = TextPointer (r, CONTENT_END, anchor.GetLogicalDirection()); moving = TextPointer (r, CONTENT_END, anchor.GetLogicalDirection()); r->unref (); if (anchor.CompareTo_np (moving) < 0) { start = anchor; end = moving; } else { start = moving; end = anchor; } } } g_free (this->text); this->text = NULL; }
void TextSelection::Insert (TextElement *element) { if (anchor.GetParent() == NULL || moving.GetParent() == NULL) // if either are null we're going nowhere fast... return; ClearSelection (); // at this point both anchor and moving are the same location // depending on what the anchor's parent is, // and what @element is, we might need to // split the tree higher up DependencyObject *el = anchor.GetParent (); int loc = anchor.ResolveLocation(); while (el) { DependencyObject *el_parent = el->GetParent(); if (el_parent) el_parent = el_parent->GetParent(); IDocumentNode *node = IDocumentNode::CastToIDocumentNode (el); DependencyObjectCollection *children = node->GetDocumentChildren (); if (children && element->Is (children->GetElementType())) { // we can insert the element here, so let's do it and be done with things children->Insert (loc, element); return; } if (!el_parent) { g_warning ("new element cannot fit inside element, and we can't split it"); return; } // we need to split the current element at // @loc, add the new element to el's parent // after el, and walk back up to el's parent // with new_el's index as @loc. IDocumentNode *parent_node = IDocumentNode::CastToIDocumentNode (el_parent); DependencyObjectCollection *parents_children = parent_node ? parent_node->GetDocumentChildren () : NULL; if (parents_children == NULL) return; /* #rtx24 */ DependencyObject *new_el; if (element->Is (el->GetObjectType()) /* a more precise check perhaps? instead of subclass? */) { // we don't need to split the node. we just // need to reach in and reparent children // after @loc into element. new_el = node ? node->Split (loc, element) : NULL; } else { new_el = node ? node->Split (loc) : NULL; } int new_el_loc = parents_children->IndexOf (el) + 1; if (new_el) parents_children->Insert (new_el_loc, new_el); el = el_parent; loc = new_el_loc; if (new_el && new_el != element) new_el->unref (); if (new_el == element) { // we've already inserted the element as part of the split. return; } } printf ("NIEX TextSelection::Insert\n"); }
TextPointer TextPointer::GetPositionInsideRun (int offset) const { DependencyObject *parent = GetParent (); if (parent == NULL) return *this; if (offset > 0) { if (parent->Is(Type::RUN)) { int location = ResolveLocation (); const char *text = ((Run*)parent)->GetText(); if ((guint)location < strlen (text)) return GetPositionAtOffset_np (1, LogicalDirectionForward); } // we're at the end of the run (or not in a run at all). we need to walk the document until we hit another run. DocumentWalker walker (IDocumentNode::CastToIDocumentNode (parent), DocumentWalker::Forward); walker.Step (); while (true) { IDocumentNode *node; DocumentWalker::StepType type = walker.Step (&node); switch (type) { case DocumentWalker::Done: // there is no position beyond this return *this; case DocumentWalker::Enter: if (node->AsDependencyObject()->Is(Type::RUN)) return ((Run*)node->AsDependencyObject())->GetContentStart_np(); break; case DocumentWalker::Leave: // do nothing here break; } } } else { if (parent->Is(Type::RUN)) { int location = ResolveLocation (); if (location > 0) return GetPositionAtOffset_np (-1, LogicalDirectionForward); } // we're at the start of the run (or not in a run at all). we need to walk the document until we hit another run. DocumentWalker walker (IDocumentNode::CastToIDocumentNode (parent), DocumentWalker::Backward); walker.Step (); while (true) { IDocumentNode *node; DocumentWalker::StepType type = walker.Step (&node); switch (type) { case DocumentWalker::Done: // there is no position before this return *this; case DocumentWalker::Enter: if (node->AsDependencyObject()->Is(Type::RUN)) return ((Run*)node->AsDependencyObject())->GetContentEnd_np(); break; case DocumentWalker::Leave: // do nothing here break; } } } return *this; }
bool PositionAtOffsetIterator::Step (int *offset) { if (element == NULL) return false; if (*offset == 0) { // we're done stepping. element/location should be valid. return false; } if (*offset < 0) { // we're stepping backward if (location == CONTENT_START) { // we're stepping backward but we're currently // at element's ContentStart. we need to step // up into our parent DependencyObject *parent = GetElementParent(); if (!parent) { // we can't back up anymore. result is NULL. element = NULL; return true; } IDocumentNode *parent_node = IDocumentNode::CastToIDocumentNode (parent); (*offset) ++; location = parent_node->GetDocumentChildren()->IndexOf (Value (element)); element = parent; return true; } if (element->Is (Type::RUN)) { const char *text = ((Run*)element)->GetText(); guint32 textlen = strlen (text); if (location == CONTENT_END) location = textlen; if (location + *offset < CONTENT_START) { // bump up to the parent after adding the location to our offset *offset += location; location = CONTENT_START; return true; } else { // the operation can be satisfied completely inside this run location += *offset; *offset = 0; return false; } } else { IDocumentNode *node = IDocumentNode::CastToIDocumentNode (element); DependencyObjectCollection *doc_children = node->GetDocumentChildren(); int children_count = doc_children ? doc_children->GetCount() : 0; if (children_count) { if (location == CONTENT_END) location = children_count; element = node->GetDocumentChildren()->GetValueAt(location-1)->AsTextElement(); (*offset) ++; location = CONTENT_END; return true; } else { // not a Run, but also doesn't have children DependencyObject *parent = GetElementParent(); if (!parent) { // we can't back up anymore. result is NULL. element = NULL; return true; } IDocumentNode *parent_node = IDocumentNode::CastToIDocumentNode (parent); (*offset) ++; location = parent_node->GetDocumentChildren()->IndexOf (Value (element)); element = parent; return true; } } } else /* if offset > 0 */ { if (location == CONTENT_END) { // we're stepping forward but we're currently // at element's ContentEnd. we need to step // up into our parent DependencyObject *parent = GetElementParent(); if (!parent) { // we can't step forward anymore. result is NULL. element = NULL; return true; } IDocumentNode *parent_node = IDocumentNode::CastToIDocumentNode (parent); (*offset) --; location = parent_node->GetDocumentChildren()->IndexOf (Value (element)); if ((int)location == parent_node->GetDocumentChildren()->GetCount() - 1) location = CONTENT_END; else location ++; element = parent; return true; } if (element->Is (Type::RUN)) { const char *text = ((Run*)element)->GetText(); if (text == NULL) { (*offset) --; location = CONTENT_END; return true; } guint32 textlen = strlen (text); if (location + *offset >= textlen) { // bump up to the parent after subtracting the remaining length of the run from offset *offset = location + *offset - textlen; location = CONTENT_END; return true; } else { // the operation can be satisfied completely inside this run location += *offset; *offset = 0; return false; } } else { IDocumentNode *node = IDocumentNode::CastToIDocumentNode (element); DependencyObjectCollection *doc_children = node->GetDocumentChildren(); int children_count = doc_children ? doc_children->GetCount() : 0; if (children_count > 0) { if ((int)location > children_count - 1) { DependencyObject *parent = GetElementParent(); if (!parent) { // we can't step forward anymore. result is NULL. element = NULL; return true; } IDocumentNode *parent_node = IDocumentNode::CastToIDocumentNode (parent); (*offset) --; location = parent_node->GetDocumentChildren()->IndexOf (Value (element)) + 1; if ((int)location == children_count - 1) location = CONTENT_END; element = parent; return true; } else { element = node->GetDocumentChildren()->GetValueAt(location)->AsTextElement(); (*offset) --; location = CONTENT_START; return true; } } else { // not a Run, but also doesn't have children DependencyObject *parent = GetElementParent(); if (!parent) { // we can't step forward anymore. result is NULL. element = NULL; return true; } IDocumentNode *parent_node = IDocumentNode::CastToIDocumentNode (parent); (*offset) --; location = parent_node->GetDocumentChildren()->IndexOf (Value (element)) + 1; if ((int)location == children_count - 1) location = CONTENT_END; element = parent; return true; } } } }