import time def cloneToSiblingTodayNode(p): """ Clone the node identified by p and move that clone to a child of a sibling "@day %Y-%m-%d" node. If the @day node doesn't exist yet, create it. If p already has a clone as a child of @day, then do nothing. The position pointing at the original node is returned as output (warning: actually the first child of the parent of p is returned...the assumption is that will be the same node as pointed to by the original p). """ today = "@day " + time.strftime("%Y-%m-%d", time.gmtime()) def findTodayNode(p): day = ( p1 for p1 in p.self_and_siblings() if p1.h == today ) return next(day, None) day = findTodayNode(p) if day: # The @day node for today already exists cloneAlreadyUnderDay = ( p1 for p1 in day.children() if p1.v == p.v ) if next(cloneAlreadyUnderDay, None): # A clone is already child of @day, leave it where it is (should it also move to the top?) p2 = p else: # p doesn't have a clone in @day, so create one p2 = p.clone() # Outline modification! # With the outline modification, the day position can't be trusted, so query it again day = findTodayNode(p2) p2.moveToFirstChildOf(day) # Outline modification! p2 = p2.parent().parent().moveToFirstChild() else: p2 = p.clone() # Outline modification! day = p2.insertAfter() # Outline modification! day.h = today # With the outline modification, the p2 position can't be trusted, so query it again p2 = day.copy().moveToBack() p2.moveToFirstChildOf(day) p2 = p2.parent().parent().moveToFirstChild() return p2 def findFtlistAncestor(p): ftlist = ( p1 for p1 in p.parents() if p1.h.startswith("@ftlist") ) return next(ftlist, None) def moveOrCloneToTop(p, ftlist): """ If p or a clone of p is already a direct child of ftlist, then move the node to the first child of ftlist. Otherwise, create a clone of p and move the clone """ if ftlist.v != p.parent().v: # Node is not a child of ftlist...it is a deeper descendant, so clone instead of move # But only add the clone if the node doesn't already have a direct clone child in @ftlist cloneIsAlreadyFtlistChild = ( p1 for p1 in ftlist.children() if p1.v == p.v ) child = next(cloneIsAlreadyFtlistChild, None) if not child: p = p.clone() # Outline modification! # Since the outline changed, the ftlist position can't be trusted, so query it again ftlist = findFtlistAncestor(p) else: p = child return p, ftlist # TODO: what about undo? def moveToTopAndCloneToAtDay(c, p): """ Move the node identified by position p to the first child of an ancestor @ftlist node and also clone it to a sibling @day node with date matching today's date """ ftlist = findFtlistAncestor(p) if not ftlist: g.es("Not in ftlist tree") return p, ftlist = moveOrCloneToTop(p, ftlist) # Outline modification! p.moveToFirstChildOf(ftlist) # Outline modification! p2 = cloneToSiblingTodayNode(p) # Outline modification! c.redraw(p2) def insertToTopAndCloneToAtDay(c, p): """ Insert a new node as the first child of an ancestor @ftlist node and also clone it to a sibling @day node with date matching today's date Place the headline of the new node in edit mode """ ftlist = findFtlistAncestor(p) if not ftlist: if p.h.startswith("@ftlist"): ftlist = p else: g.es("Not in ftlist tree") return p = ftlist.insertAsNthChild(0) # Outline modification! p2 = cloneToSiblingTodayNode(p) # Outline modification! c.redraw(p2) c.editHeadline() @g.command('ftlist-insert-node') def ftlistInsertNodeCommand(event): c = event['c'] insertToTopAndCloneToAtDay(c,c.p) @g.command('ftlist-move-to-top') def ftlistMoveToTopCommand(event): c = event['c'] moveToTopAndCloneToAtDay(c, c.p)
p1 = p.insertAsLastChild()
# now p is still valid
import time
def cloneToSiblingTodayNode(p):
"""
Clone the node identified by p and move that clone to a child of a
sibling "@day %Y-%m-%d" node. If the @day node doesn't exist yet,
create it. If p already has a clone as a child of @day, then do nothing.
The position pointing at the original node is returned as output
(warning: actually the first child of the parent of p is returned...the
assumption is that will be the same node as pointed to by the original p).
"""
today = "@day " + time.strftime("%Y-%m-%d", time.gmtime())
day = next(iter(c.find_h(today)), None)
if not day:
day = p.parent().insertAsLastChild()
day.h = today
vday = day.v
if p.v not in vday.children:
vday.children.insert(0, p.v)
p.v.parents.append(vday)
# position p should be valid as is
pc.redraw()def moveOrCloneToTop(p, ftlist):
"""
If p or a clone of p is already a direct child of ftlist, then move the node
to the first child of ftlist. Otherwise, create a clone of p and move the clone
"""
if ftlist.v not in p.v.parents:
# neither p nor clone of p is a direct child of ftlist
ftlist.v.children.insert(0, p.v)
p.v.parents.append(ftlist.v)
else:
# ftlist contains clone of p as a direct child
i = ftlist.v.children.index(p.v)
if i > 0:
# clone is not at the top
# moving it to the top
del ftlist.v.children[i]
ftlist.v.children.insert(0, p.v)
return ftlist, ftlist.firstChild()
def moveToTopAndCloneToAtDay(c, p):
"""
Move the node identified by position p to the first child of
an ancestor @ftlist node and also clone it to a sibling @day
node with date matching today's date
"""
ftlist = findFtlistAncestor(p)
if not ftlist:
g.es("Not in ftlist tree")
return
ftlist, p = moveOrCloneToTop(p, ftlist)
p2 = cloneToSiblingTodayNode(p)
c.redraw(p2)
What are the rules which can be followed in order to safely deal with positions during outline modification. Edward or Vitalije or anyone else, do you know?
Discard all positions other than the one used to modify the outline for any outline modification other than insertAsLastChild and deleting the last child
When making multiple outline modifications, it might be best to learn and use vnodes instead of positions
--
You received this message because you are subscribed to the Google Groups "leo-editor" group.
To unsubscribe from this group and stop receiving emails from it, send an email to leo-editor+...@googlegroups.com.
To view this discussion on the web visit https://groups.google.com/d/msgid/leo-editor/a7749d44-abb7-4e62-9bb0-746c27f91fdc%40googlegroups.com.
As Vitalije says"- Adding (or deleting) the last sibling will not change the position of any existing node. This includes adding/deleting the last top-level node.- If you can recast your algorithm in terms of vnodes, it will be impervious to changes in the outline.
Another possible pattern:1. Create, say, an @dates node (somewhere) first, if it doesn't already exist. If you do create an @dates node, then call c.redraw, which will, in effect, recompute all positions.
2. Now that you have an @dates node, create your @date nodes as children of the @dates node. This will preserve all positions.
As Vitalije says"- Adding (or deleting) the last sibling will not change the position of any existing node. This includes adding/deleting the last top-level node.- If you can recast your algorithm in terms of vnodes, it will be impervious to changes in the outline.The way I read Vitalije's explanation, it sounded like the last child of a subtree. But are you saying it is only the last node in the entire outline?
Maybe I should start my journey on position enlightenment with c.redraw(). If I see how positions are recomputed, maybe I will better understand the behavior.
2. Now that you have an @dates node, create your @date nodes as children of the @dates node. This will preserve all positions.Hmm, are you saying no matter where I create the @dates node all previously create positions will still be valid? Even after making more modifications like inserting @date nodes?
No. See the emphasis above. Adding after any last sibling will leave all positions unchanged.
Maybe I should start my journey on position enlightenment with c.redraw(). If I see how positions are recomputed, maybe I will better understand the behavior.I wouldn't advise that. Instead, read << about the position class >>. If you want more detail, look at Position.__init__().A position consists of a stack of ancestor vnodes, and a child index.
If you move a node, both the stack and child index will change for descendant nodes.If you insert or delete a node, the child index of sibling nodes may change, and that will also affect the children of those siblings. But no positions will change if you only add/delete the last sibling. Clear?
Important: Usually scripts don't care much about changing positions. Changing positions only bite you if you save positions. Instead, it may be simpler to recompute positions of interest.
import time def moveOrCloneToTop(p, parent): """ If p or a clone of p is already a direct child of parent, then move the node to the first child of parent. Otherwise, create a clone of p and move the clone """ clone = next((p1 for p1 in parent.children() if p1.v == p.v), None) p = clone if clone else p.clone() p.moveToFirstChildOf(parent) return p
def cloneToSiblingTodayNode(p): """ Clone the node identified by p and move that clone to a child of a sibling "@day %Y-%m-%d" node. If the @day node doesn't exist yet, create it. If p already has a clone as a child of @day, then do nothing. The position pointing at the original node is returned as output """
today = "@day " + time.strftime("%Y-%m-%d", time.gmtime())
day = (
p1
for p1 in p.self_and_siblings() if p1.h ==
today
)
day = next(day, None)
if not day:
# Create today's @day node
day = p.insertAfter()
day.h = today
p1 = moveOrCloneToTop(p, day)
# Since day is a sibling of p, modifying day's children don't affect
# p so it is still valid
return p
def findFtlistAncestor(p): ftlist = ( p1 for p1 in p.parents() if p1.h.startswith("@ftlist") ) return next(ftlist, None)
def moveToTopAndCloneToAtDay(c, p): """ Move the node identified by position p to the first child of an ancestor @ftlist node and also clone it to a sibling @day node with date matching today's date """ ftlist = findFtlistAncestor(p) if not ftlist: g.es("Not in ftlist tree") return
p = moveOrCloneToTop(p, ftlist)
p2 = cloneToSiblingTodayNode(p)
c.redraw(p2)
def insertToTopAndCloneToAtDay(c, p): """ Insert a new node as the first child of an ancestor @ftlist node and also clone it to a sibling @day node with date matching today's date Place the headline of the new node in edit mode """ ftlist = findFtlistAncestor(p) if not ftlist: if p.h.startswith("@ftlist"): ftlist = p else: g.es("Not in ftlist tree") return p = ftlist.insertAsNthChild(0
)
p2 = cloneToSiblingTodayNode(p)
c.redraw(p2)
c.editHeadline()
A position consists of a stack of ancestor vnodes, and a child index.Excellent, this is a huge help. I had it in my head before that there was some kind of linked list and C-style pointer type of thing going on.
What I didn't realize is that just because those are the only operations in which all saved positions are safe, doesn't mean there aren't some saved positions which will still be safe after other operations.
Armed with the knowledge of what a position is, I came up with these examples:
- Position p gains a new following sibling (i.e. p2 = p.insertAfter()). In this case both p2 and p are safe. With the more restrictive rules, I thought only p2 would be safe, but p is still ok because its child index can only go wrong if previous siblings are inserted or modified
- Position p gains a new niece/nephew from either previous or following siblings. Again in this case the position p remains valid
- When changing a following sibling of position p to be a niece/nephew of any sibling, the position p remains valid
So armed with this knowledge and the code Vitalije share, I was able to re-write my code to be much simpler.