#@+leo-ver=4-thin
#@+node:tbrown.20081223111325.3:@thin backlink.py
#@@language python
#@@tabwidth -4
#@+others
#@+node:ekr.20090616105756.3939:backlink declarations
'''Backlink - allow arbitrary links between nodes
'''
# Notes
#
# gnxs won't work, because they belong to tnodes, not vnodes
#
# Backlink will store all its stuff in v.unknownAttributes['_bklnk']
#
# The vnode's id will be v.unknownAttributes['_bklnk']['id']
#
# Unless Edward decides otherwise, backlink will use
# leo.core.leoNodes.nodeIndices.getNewIndex() to make these ids
#
# When nodes are copied and pasted unknownAttributes are duplicated.
# during load, backlink will create a dict. of vnode ids. Duplicates
# will be split, so that a node linking to a node which is copied and
# pasted will link to both nodes after the paste, *after* a save and
# load cycle. Before a save and load cycle it will link to whichever
# vnode originally held the id
# TODO
#
# - provide API
#
# - mark Src / Dst - vnodes more robust that positions?
#
# - store attributes for link start/whole-link/end (name, weight)
#
# - restore dropped links (cut / paste or undo)?
__version__ = '0.1.1'
#
# 0.1 - initial release - TNB
# 0.1.1 - both UIs work - TNB
import leo.core.leoGlobals as g
import leo.core.leoPlugins as leoPlugins
# can this happen?
# if g.app.gui is None:
# g.app.createTkGui(__file__)
Tk = None
Qt = None
# UI class signatures, main class signature
#
# UI classes must have (i.e. owner will call):
# - __init__(owner)
# - showMessage(txt, color=['black'|'red'])
# - enableDelete(bool) (also unchecks delete checkbox)
# - loadList(listOfStrings)
# UI classes should call the following on owner:
# - markSrc()
# - markDst()
# - linkSrc()
# - linkDst()
# - linkUnd()
# - loadLinksInt()
# - deleteSet(bool)
# - linkClicked(n) (zero based)
if g.app.gui.guiName() == "tkinter":
Tk = g.importExtension('Tkinter',pluginName=__name__,verbose=True,required=True)
class backlinkTkUI(object):
def __init__(self, owner):
self.owner = owner
c = self.owner.c
c.frame.log.createTab('Links', createText=False)
w = c.frame.log.frameDict['Links']
f = Tk.Frame(w)
scrollbar = Tk.Scrollbar(f, orient=Tk.VERTICAL)
self.listbox = Tk.Listbox(f, height=4, yscrollcommand=scrollbar.set)
scrollbar.config(command=self.listbox.yview)
scrollbar.pack(side=Tk.RIGHT, fill=Tk.Y)
self.listbox.pack(side=Tk.RIGHT, fill=Tk.BOTH, expand=True)
f.pack(side=Tk.TOP, fill=Tk.BOTH, expand=True)
self.listbox.bind("<ButtonRelease-1>", self.tkListClicked)
commands = [
('Mark source', self.owner.markSrc),
('Mark dest.', self.owner.markDst),
('Link source', self.owner.linkSrc),
('Link dest.', self.owner.linkDst),
('Undirected link', self.owner.linkUnd),
('Rescan links', self.owner.loadLinksInt),
]
comms = iter(commands)
for i in range(3):
f = Tk.Frame(w)
for j in range(2):
txt, com = comms.next()
b = Tk.Button(f, text=txt, width=10, height=1, command=com)
b.pack(side=Tk.LEFT, fill=Tk.BOTH)
f.pack(side=Tk.TOP, fill=Tk.BOTH)
f = Tk.Frame(w)
self.message = Tk.Label(f, text='no msg.')
self.message.pack(side=Tk.LEFT)
self.delete = Tk.IntVar()
self.deleteButton = Tk.Checkbutton(f, text='Delete link', variable=self.delete,
command=self.tkDeleteClicked)
self.deleteButton.pack(side=Tk.RIGHT)
f.pack(side=Tk.TOP, fill=Tk.BOTH)
def loadList(self, lst):
self.listbox.delete(0, Tk.END)
for i in lst:
self.listbox.insert(Tk.END, i)
def enableDelete(self, enable):
self.delete.set(0)
if enable:
self.deleteButton.configure(state=Tk.NORMAL)
else:
self.deleteButton.configure(state=Tk.DISABLED)
def showMessage(self, msg, color='black'):
"""Show the message using whatever u.i. is available"""
self.message.configure(text = msg, fg=color)
def tkListClicked(self, event):
selected = self.listbox.curselection() # list of selected indexes
if not selected:
return # click on empty list of unlinked node
selected = int(selected[0]) # not some fancy smancy Tk value
self.owner.linkClicked(selected)
def tkDeleteClicked(self):
self.owner.deleteSet(self.delete.get())
def updateTkTab(self,tag,k):
# deprecated
if k['c'] != self.c: return # not our problem
self.updateTkTabInt()
def updateTkTabInt(self):
# deprecated
c = self.c
p = c.p
v = p.v
self.listbox.delete(0,Tk.END)
self.messageUsed = False
self.delete.set(0)
self.deleteButton.configure(state=Tk.DISABLED)
self.showMessage('', optional=True)
if hasattr(v, 'unknownAttributes') and '_bklnk' in v.unknownAttributes:
i = 0
links = v.unknownAttributes['_bklnk']['links']
dests = []
while i < len(links):
linkType, other = links[i]
otherV = self.vnode[other]
otherP = self.vnodePosition(otherV)
if not self.c.positionExists(otherP):
self.showMessage('Lost link(s) deleted', color='red')
del links[i]
else:
i += 1
dests.append((linkType, otherP))
if dests:
self.deleteButton.configure(state=Tk.NORMAL)
self.showMessage('Click a link to follow it', optional=True)
for i in dests:
def goThere(where = i[1]): c.selectPosition(where)
txt = {'S':'->','D':'<-','U':'--'}[i[0]] + ' ' + i[1].h
self.listbox.insert(Tk.END, txt)
def delLink(on=v,
to=i[1].v.gnx,
type_=i[0]): self.deleteLink(on,to,type_)
self.dests = dests
elif g.app.gui.guiName() == "qt":
from PyQt4 import QtCore,QtGui,uic
Qt = QtCore.Qt
class backlinkQtUI(QtGui.QWidget):
def __init__(self, owner):
self.owner = owner
QtGui.QWidget.__init__(self)
uiPath = g.os_path_join(g.app.leoDir, 'plugins', 'Backlink.ui')
form_class, base_class = uic.loadUiType(uiPath)
self.owner.c.frame.log.createTab('Links', widget = self)
self.UI = form_class()
self.UI.setupUi(self)
u = self.UI
o = self.owner
self.connect(u.markBtn,
QtCore.SIGNAL("clicked()"), o.mark)
self.connect(u.swapBtn,
QtCore.SIGNAL("clicked()"), o.swap)
self.connect(u.linkBtn,
QtCore.SIGNAL("clicked()"), self.linkClicked)
self.connect(u.rescanBtn,
QtCore.SIGNAL("clicked()"), o.loadLinksInt)
self.connect(u.dirLeftBtn,
QtCore.SIGNAL("clicked()"), self.dirClicked)
self.connect(u.dirRightBtn,
QtCore.SIGNAL("clicked()"), self.dirClicked)
self.connect(u.linkList,
QtCore.SIGNAL("itemClicked(QListWidgetItem*)"), self.listClicked)
self.connect(u.deleteBtn,
QtCore.SIGNAL("stateChanged(int)"), o.deleteSet)
def dirClicked(self):
if self.UI.dirLeftBtn.text() == "from":
self.UI.dirLeftBtn.setText("to")
self.UI.dirRightBtn.setText("from")
else:
self.UI.dirLeftBtn.setText("from")
self.UI.dirRightBtn.setText("to")
def listClicked(self):
self.owner.linkClicked(self.UI.linkList.currentRow())
def linkClicked(self):
if self.UI.whatSel.currentText() == "mark, undirected":
self.owner.linkAction('undirected')
return
newChild = self.UI.whatSel.currentText() == "new child of mark"
if self.UI.dirLeftBtn.text() == "from":
self.owner.linkAction('from', newChild=newChild)
else:
self.owner.linkAction('to', newChild=newChild)
def loadList(self, lst):
self.UI.linkList.clear()
self.UI.linkList.addItems(lst)
def showMessage(self, msg, color='black'):
fg = Qt.black
if hasattr(Qt, color):
fg = getattr(Qt, color)
pal = QtGui.QPalette(self.UI.label.palette())
pal.setColor(QtGui.QPalette.WindowText, fg)
self.UI.label.setPalette(pal)
self.UI.label.setText(msg)
def enableDelete(self, enable):
self.UI.deleteBtn.setChecked(False)
self.UI.deleteBtn.setEnabled(enable)
#@-node:ekr.20090616105756.3939:backlink declarations
#@+node:ekr.20090616105756.3940:init
def init ():
leoPlugins.registerHandler('after-create-leo-frame',onCreate)
# can't use before-create-leo-frame because Qt dock's not ready
g.plugin_signon(__name__)
return True
#@-node:ekr.20090616105756.3940:init
#@+node:ekr.20090616105756.3941:onCreate
def onCreate (tag, keys):
c = keys.get('c')
if not c: return
backlinkController(c)
#@-node:ekr.20090616105756.3941:onCreate
#@+node:ekr.20090616105756.3942:class backlinkController
class backlinkController(object):
"""Display and edit links in leo trees"""
#@ @+others
#@+node:ekr.20090616105756.3943:__init__
def __init__ (self,c):
self.c = c
self.c.backlinkController = self
self.initIvars()
self.fixIDs(c)
if Tk:
self.ui = backlinkTkUI(self)
elif Qt:
self.ui = backlinkQtUI(self)
leoPlugins.registerHandler('select3', self.updateTab)
leoPlugins.registerHandler('open2', self.loadLinks)
# already missed initial 'open2' because of after-create-leo-frame, so
self.loadLinksInt()
#@-node:ekr.20090616105756.3943:__init__
#@+node:tbrown.20091005145931.5227:fixIDs
def fixIDs(self, c):
update = {}
for v in c.all_unique_nodes():
# collect old -> new ID mapping
if (hasattr(v, 'unknownAttributes') and
'_bklnk' in v.u and
'id' in v.u['_bklnk']):
update[v.u['_bklnk']['id']] = v.gnx
for v in c.all_unique_nodes():
if (hasattr(v, 'unknownAttributes') and
'_bklnk' in v.u):
if 'id' in v.u['_bklnk']:
# remove old id
del v.u['_bklnk']['id']
if 'links' in v.u['_bklnk']:
v.u['_bklnk']['links'] = [
(i[0], update[i[1]]) for i in v.u['_bklnk']['links']
if i[1] in update ]
#@-node:tbrown.20091005145931.5227:fixIDs
#@+node:ekr.20090616105756.3944:deleteLink
def deleteLink(self, on, to, type_):
"""delete a link from 'on' to 'to' of type 'type_'"""
vid = on.gnx #X unknownAttributes['_bklnk']['id']
links = on.unknownAttributes['_bklnk']['links']
for n,link in enumerate(links):
if type_ == link[0] and to == link[1]:
del links[n]
v = self.vnode[to]
links = v.unknownAttributes['_bklnk']['links']
if type_ == 'S':
type_ = 'D'
elif type_ == 'D':
type_ = 'S'
for n,link in enumerate(links):
if type_ == link[0] and link[1] == vid:
del links[n]
break
else:
self.showMessage("Couldn't find other side of link")
break
else:
self.showMessage("Error: no such link")
gcc = getattr(self.c, 'graphcanvasController')
if gcc:
gcc.update()
#@-node:ekr.20090616105756.3944:deleteLink
#@+node:ekr.20090616105756.3945:deleteSet
def deleteSet(self, enabled):
"""UI informing us that delete mode has been set to value of 'enabled'"""
self.deleteMode = enabled
if enabled:
self.showMessage('Click a link to DELETE it', color='red')
else:
self.showMessage('Click a link to follow it')
#@-node:ekr.20090616105756.3945:deleteSet
#@+node:ekr.20090616105756.3946:initBacklink
def initBacklink(self, v):
"""set up a vnode to support links"""
if '_bklnk' not in v.u:
v.u['_bklnk'] = {}
if 'links' not in v.u['_bklnk']:
v.u['_bklnk'].update({'links':[]})
self.vnode[v.gnx] = v
#@-node:ekr.20090616105756.3946:initBacklink
#@+node:ekr.20090616105756.3947:initIvars
def initIvars(self):
"""initialize, called by __init__ and loadLinks(Int)"""
self.linkDestination = None
self.linkSource = None
self.linkMark = None
self.vnode = {}
#X self.positions = {}
self.messageUsed = False
#@-node:ekr.20090616105756.3947:initIvars
#@+node:ekr.20090616105756.3948:linkAction
def linkAction(self, dir_, newChild=False):
"""link to/from current position from/to mark node"""
if not self.linkMark or not self.c.positionExists(self.linkMark):
self.showMessage('Link mark not specified or no longer valid', color='red')
return
if dir_ == "from":
p = self.linkMark
if newChild:
p = self.linkMark.insertAsLastChild()
p.h = self.c.p.h
self.link(self.c.p, p)
else:
p = self.linkMark
if newChild:
p = self.linkMark.insertAsLastChild()
p.h = self.c.p.h
self.link(p, self.c.p)
self.updateTabInt()
self.c.redraw()
#@-node:ekr.20090616105756.3948:linkAction
#@+node:ekr.20090616105756.3949:link
def link(self, from_, to, type_='directed'):
"""make a link"""
self.vlink(from_.v, to.v, type_=type_)
#@-node:ekr.20090616105756.3949:link
#@+node:ekr.20090616105756.3950:vlink
def vlink(self, v0, v1, type_='directed'):
self.initBacklink(v0)
self.initBacklink(v1)
linkType = 'U'
if type_ == 'directed':
linkType = 'S'
v0.u['_bklnk']['links'].append( (linkType, v1.gnx) )
if type_ == 'directed':
linkType = 'D'
v1.u['_bklnk']['links'].append( (linkType, v0.gnx) )
gcc = getattr(self.c, 'graphcanvasController')
if gcc:
gcc.update()
#@-node:ekr.20090616105756.3950:vlink
#@+node:ekr.20090616105756.3951:linkClicked
def linkClicked(self, selected):
"""UI informs us that link number 'selected' (zero based) was clicked"""
if not self.deleteMode:
assert self.c.positionExists(self.dests[selected][1])
self.c.selectPosition(self.dests[selected][1])
return
elif self.deleteMode:
self.deleteLink(
self.c.p.v,
self.dests[selected][1].v.gnx,
self.dests[selected][0]
)
self.updateTabInt()
#@-node:ekr.20090616105756.3951:linkClicked
#@+node:ekr.20090616105756.3952:linkDst
def linkDst(self):
"""link from current position to dest. node"""
if not self.linkDestination or not self.c.positionExists(self.linkDestination):
self.showMessage('Link destination not specified or no longer valid', color='red')
return
self.link(self.c.p, self.linkDestination)
self.updateTabInt()
#@-node:ekr.20090616105756.3952:linkDst
#@+node:ekr.20090616105756.3953:linksFrom
def linksFrom(self, v, type_='S'):
ans = []
if not (v.u and '_bklnk' in v.u and 'links' in v.u['_bklnk']):
return ans
for i in v.u['_bklnk']['links']:
linkType, other = i
if linkType == type_:
ans.append(self.vnode[other])
return ans
#@-node:ekr.20090616105756.3953:linksFrom
#@+node:ekr.20090616105756.3954:linksTo
def linksTo(self, v):
return self.linksFrom(v, type_='D')
#@-node:ekr.20090616105756.3954:linksTo
#@+node:ekr.20090616105756.3955:linkSrc
def linkSrc(self):
"""link from current position to source node"""
if not self.linkSource or not self.c.positionExists(self.linkSource):
self.showMessage('Link source not specified or no longer valid', color='red')
return
self.link(self.linkSource, self.c.p)
self.updateTabInt()
#@-node:ekr.20090616105756.3955:linkSrc
#@+node:ekr.20090616105756.3956:linkUnd
def linkUnd(self):
"""undirected link from current position to source node, use dest.
if source not set."""
source = None
if self.linkSource and self.c.positionExists(self.linkSource):
source = self.linkSource
elif not self.linkDestination or not self.c.positionExists(self.linkDestination):
self.showMessage('Link source/dest. not specified or no longer valid', color='red')
return
else:
source = self.linkDestination
self.link(source, self.c.p, type_='undirected')
self.updateTabInt()
#@-node:ekr.20090616105756.3956:linkUnd
#@+node:ekr.20090616105756.3957:loadLinks
def loadLinks(self, tag, keywords):
"""load links after file opened"""
if self.c != keywords['c']:
return # not our problem
self.loadLinksInt()
#@-node:ekr.20090616105756.3957:loadLinks
#@+node:ekr.20090616105756.3958:loadLinksInt
def loadLinksInt(self):
"""load links after file opened or reload on request from UI"""
c = self.c # checked in loadLinks()
self.initIvars() # clears self.vnode
idsSeen = set() # just the vnodes with link info.
# make map from linked node's ids to their vnodes
for p in c.all_positions():
v = p.v
self.vnode[v.gnx] = v
if v.u and '_bklnk' in v.u:
idsSeen.add(v.gnx)
for vnode in idsSeen: # just the vnodes with link info.
if 'links' not in self.vnode[vnode].u['_bklnk']:
# graphcanvas.py will only init x and y keys
self.vnode[vnode].u['_bklnk']['links'] = []
links = self.vnode[vnode].u['_bklnk']['links']
newlinks = [] # start with empty list and include only good links
for link in links:
if link[1] not in self.vnode:
# other end if missing
lt = ('to', 'from')
if link[0] == 'S':
lt = ('from', 'to')
# use g.es rather than showMessage here
g.es('backlink: link %s %s %s ??? lost' % (
lt[0], self.vnode[vnode].h, lt[1]), color='red')
continue
# check other end has link
other = self.vnode[link[1]]
if '_bklnk' not in other.u or 'links' not in other.u['_bklnk']:
self.initBacklink(other)
if not [i for i in other.u['_bklnk']['links']
if i[1] == vnode]:
# we are not in the other's list
direc = {'U':'U', 'S':'D', 'D':'S'}[link[0]]
other.u['_bklnk']['links'].append((direc, vnode))
newlinks.append((link[0], link[1]))
self.vnode[vnode].u['_bklnk']['links'] = newlinks
self.showMessage('Link info. loaded on %d nodes' % len(idsSeen))
#@-node:ekr.20090616105756.3958:loadLinksInt
#@+node:ekr.20090616105756.3959:mark
def mark(self):
"""Mark current position as 'mark' (called by UI)"""
self.linkMark = self.c.p.copy()
self.showMessage('Marked')
#@-node:ekr.20090616105756.3959:mark
#@+node:ekr.20090616105756.3960:markDst
def markDst(self):
"""Mark current position as 'destination' (called by UI)"""
self.linkDestination = self.c.p.copy()
self.showMessage('Dest. marked')
#@-node:ekr.20090616105756.3960:markDst
#@+node:ekr.20090616105756.3961:markSrc
def markSrc(self):
"""Mark current position as 'source' (called by UI)"""
self.linkSource = self.c.p.copy()
self.showMessage('Source marked')
#@-node:ekr.20090616105756.3961:markSrc
#@+node:ekr.20090616105756.3962:positionExistsSomewhere
def positionExistsSomewhere(self,p,root=None):
"""A local copy of c.positionExists so that when the
failure to check p._childIndex bug is fixed, that fixing
doesn't make backlink.py search more of the tree than it
needs to"""
# used by vnodePosition, not needed node there's c.vnode2position
c = self.c ; p = p.copy()
# This code must be fast.
if not root:
root = c.rootPosition()
while p:
if p == root:
return True
if p.hasParent():
v = p.v
p.moveToParent()
# Major bug fix: 2009/1/2
if v not in p.v.children:
return False
else:
p.moveToBack() # ???
return False
#@-node:ekr.20090616105756.3962:positionExistsSomewhere
#@+node:ekr.20090616105756.3963:showLinksLog
def showLinksLog(self,tag,k):
# deprecated
if k['c'] != self.c: return # not our problem
p = k['new_p']
v = p.v
if hasattr(v, 'unknownAttributes') and '_bklnk' in v.unknownAttributes:
i = 0
links = v.unknownAttributes['_bklnk']['links']
dests = []
while i < len(links):
linkType, other = links[i]
if other not in self.vnode:
return
# called before load hook?
otherV = self.vnode[other]
otherP = self.vnodePosition(otherV)
if not self.c.positionExists(otherP):
g.es('Deleting lost link')
del links[i]
else:
i += 1
dests.append((linkType, otherP))
if dests:
g.es("- link info -")
for i in dests:
g.es("%s %s" %({'S':'->','D':'<-','U':'--'}[i[0]],
i[1].h))
#@-node:ekr.20090616105756.3963:showLinksLog
#@+node:ekr.20090616105756.3964:showMenu
def showMenu(self,tag,k):
# deprecated
g.app.gui.killPopupMenu()
if k['c'] != self.c: return # not our problem
p = k['p']
self.c.selectPosition(p)
v = p.v
c = self.c
# Create the menu.
menu = Tk.Menu(None,tearoff=0,takefocus=0)
commands = [
(True, 'Mark as link source', self.markSrc),
(True, 'Mark as link dest.', self.markDst),
(True, 'Link to source', self.linkSrc),
(True, 'Link to dest.', self.linkDst),
(True, 'Undirected link', self.linkUnd),
(True, 'Rescan links', self.loadLinksInt),
]
if hasattr(v, 'unknownAttributes') and '_bklnk' in v.unknownAttributes:
i = 0
links = v.unknownAttributes['_bklnk']['links']
dests = []
while i < len(links):
linkType, other = links[i]
otherV = self.vnode[other]
otherP = self.vnodePosition(otherV)
if not self.c.positionExists(otherP):
g.es('Deleting lost link')
del links[i]
else:
i += 1
dests.append((linkType, otherP))
if dests:
smenu = Tk.Menu(menu,tearoff=0,takefocus=1)
for i in dests:
def goThere(where = i[1]): c.selectPosition(where)
c.add_command(menu,label={'S':'->','D':'<-','U':'--'}[i[0]]
+ i[1].h,
underline=0,command=goThere)
def delLink(on=v,
to=i[1].v.unknownAttributes['_bklnk']['id'],
type_=i[0]): self.deleteLink(on,to,type_)
c.add_command(smenu,label={'S':'->','D':'<-','U':'--'}[i[0]]
+ i[1].h,
underline=0,command=delLink)
menu.add_cascade(label='Delete link', menu=smenu,underline=1)
menu.add_separator()
for command in commands:
available, text, com = command
if not available:
continue
c.add_command(menu,label=text,
underline=0,command=com)
# Show the menu.
event = k['event']
g.app.gui.postPopupMenu(self.c, menu, event.x_root,event.y_root)
return None # 'break' # EKR: Prevent other right clicks.
#@-node:ekr.20090616105756.3964:showMenu
#@+node:ekr.20090616105756.3965:showMessage
def showMessage(self, msg, optional=False, color='black'):
"""Show the message, but don't overwrite earlier important
message if this message is optional"""
if self.messageUsed and optional:
return
if not self.messageUsed and not optional:
self.messageUsed = True
self.ui.showMessage(msg, color=color)
#@-node:ekr.20090616105756.3965:showMessage
#@+node:ekr.20090616105756.3966:swap
def swap(self):
"""Swap current pos. w. mark"""
if not self.linkMark or not self.c.positionExists(self.linkMark):
self.showMessage('Link mark not specified or no longer valid', color='red')
return
p = self.linkMark
self.linkMark = self.c.p.copy()
self.c.selectPosition(p)
#@-node:ekr.20090616105756.3966:swap
#@+node:ekr.20090616105756.3967:updateTab
def updateTab(self,tag,k):
"""called by leo select position hook"""
if k['c'] != self.c: return # not our problem
self.updateTabInt()
#@-node:ekr.20090616105756.3967:updateTab
#@+node:ekr.20090616105756.3968:updateTabInt
def updateTabInt(self):
"""called on new position (leo hook) and when links added / deleted"""
c = self.c
p = c.p
v = p.v
self.messageUsed = False
self.ui.enableDelete(False)
self.deleteMode = False
self.showMessage('', optional=True)
texts = []
if (v.u and '_bklnk' in v.u and 'links' in v.u['_bklnk']):
i = 0
links = v.u['_bklnk']['links']
dests = []
self.dests = dests
while i < len(links):
linkType, other = links[i]
otherV = self.vnode[other]
otherP = self.vnodePosition(otherV)
if not self.c.positionExists(otherP):
self.showMessage('Lost link(s) deleted', color='red')
del links[i]
else:
i += 1
dests.append((linkType, otherP))
if dests:
self.ui.enableDelete(True)
self.showMessage('Click a link to follow it', optional=True)
for i in dests:
def goThere(where = i[1]): c.selectPosition(where)
txt = {'S':'->','D':'<-','U':'--'}[i[0]] + ' ' + i[1].h
texts.append(txt)
self.ui.loadList(texts)
#@-node:ekr.20090616105756.3968:updateTabInt
#@+node:ekr.20090616105756.3969:vnodePosition
def vnodePosition(self, v):
"""Return a position for vnode v, if there is one"""
return self.c.vnode2position(v)
# rest for historical interest
search_from = self.c.all_positions
# first check the cache
if v in self.positions:
p = self.positions[v]
if p.v is v and self.positionExistsSomewhere(p):
search_from = p.self_and_siblings
# p._childIndex may be out of date, the above is equivalent to but
# less ugly than the below
# index = [x.v for x in p.self_and_siblings()].index(v)
# # assumes this iter runs in child order, not self first then others
# if p._childIndex != index:
# p._childIndex = index
# g.es('updating index')
# return p.copy()
# else search for one, abort search as soon as it's found
for p in search_from():
self.positions[v] = p.copy() # update cache
if p.v is v:
return p.copy()
return None
#@-node:ekr.20090616105756.3969:vnodePosition
#@-others
#@-node:ekr.20090616105756.3942:class backlinkController
#@-others
#@-node:tbrown.20081223111325.3:@thin backlink.py
#@-leo
|