I have written two Qt-only plugins that any user of Leo-Editor might
well find useful. The "Clone Navigator" plugin allows you to move
between any two positions of a clone by typing just two keystrokes. The
"Node Visit History" plugin allows you to go to any recently visited
node position by typing just two keystrokes.
Attachment clone_nav.py is the Clone Navigator. Attachment
node_visit_history.py is the Node Visit Navigator.
Clone Navigator help:
The "Clone Navigator" plugin allows you to move between any two
positions of a clone by typing just two keystrokes.
The "Clone Navigator" plugin creates a button on the icon bar, installs
command clone-nav, and assigns Alt-Shift-c to command clone-nav.
Left clicking the cloneNav button, (or Alt-Shift-C, or command
clone-nav) pops up a menu listing the paths to root of all the parent
nodes of clones of the node at the current position.
If a clone is a root, then "Clone is a root" is displayed as its
path-to-root. The path-to-root for a non-root clone is the clone's
parent's headline, followed by "<-", followed by the clone's parent's
parent's headline, etc. -- till a root node is reached. The path-to-root
for the current position is marked with a right arrow icon.
Every path-to-root has an underline marking its shortcut character (if
any is possible). Typing a shortcut character selects the menu item.
Left-clicking a menu item also selects it.
Selecting a menu item positions to the clone corresponding to the menu
item and also destroys the pop-up menu.
Doing anything other than selecting a clone menu item, also destroys the
pop-up menu. Consequently, you should never see a pop-up messsage
telling you "Selected clone position no longer exists." but this
condition is tested and reported.
-----------
Node Visit History plugin help:
The "Node Visit History" plugin allows you to go to any recently visited
node position by typing just two keystrokes.
The "Node Visit History" plugin creates the nodeVisitHist button on the
Icon Bar, installs command node-visit-history, and assigns Alt-Shift-h
to command node-visit-history.
The path-to-root for a node is the node's headline, followed by "<-",
followed by the node's parent's headline, etc. -- till a root node is
reached. The path-to-root for the current position is marked with a
right arrow icon.
Every path-to-root has an underline marking its shortcut character (if
any is possible). Typing a shortcut character selects the menu item.
Left-clicking a menu item also selects it.
Selecting a menu item positions to that node in the outline and also
destroys the pop-up menu.
Doing anything other than selecting a pop-up menu item, also destroys
the pop-up menu.
If you select a position which no longer exists because you have changed
the outline, a message box pops up saying "Selected position no longer
exists.
----------------
clone_nav.py and node_visit_history.py were created using Leo-Editor.
You can look at them using Leo-editor by using the menu command File -->
Import File. This pops up a file selection dialog which lets you
choose the file to import. The imported file is then rooted by an @file
node following the current node position.
You can add these plugins to your Leo-Editor by placing them in the same
directory with the standard Leo-Editor plugins or by placing them in any
directory in the PYTHONPATH environment variable; and then enabling them
in your myLeoSettings.leo.
I'm confident that both of these plugins will behave as you expect and
will not cause you trouble, but you obviously try them at your own risk
and you should be careful.
------------
I have tested these plugins in the following environment:
Leo-Editor Revision: 4430
Python 2.7.1, qt version 4.7.2
linux2 -- Ubuntu Studio 11.04 (natty)
-----------
Help Please
I hope that someone, who knows more than I about PyQt4 and Leo-Editor,
will tell me if I'm misusing PyQt4 or Leo-Editor.
In particular, note that I create a new pop-up menu on each button click
and I never explicitly free the discarded menus. So far as I can tell,
this does not cause a "memory leak," but it worries me.
Is there some way to assign a shortcut keystroke to a command
implemented in a plugin that allows a user to change the shortcut
keystroke assigned to the command? I don't know any way to do this. My
testing indicates that a shortcut specifier in myLeoSettings is silently
ignored if the command it assigns is defined in a plugin or is not
defined anywhere.
--
You received this message because you are subscribed to the Google Groups
"leo-editor" group.
To post to this group, send email to [email protected].
To unsubscribe from this group, send email to
[email protected].
For more options, visit this group at
http://groups.google.com/group/leo-editor?hl=en.
#@+leo-ver=5-thin
#@+node:bob.20101130143836.1344: * @file clone_nav.py
#@+<< docstring >>
#@+node:bob.20101130143836.1345: ** << docstring >>
""" The "Clone Navigator" plugin allows you to move between any two positions of a clone by typing just two keystrokes.
The "Clone Navigator" plugin creates a button on the icon bar, installs command clone-nav, and assigns Alt-Shift-c to command clone-nav.
Left clicking the cloneNav button, (or Alt-Shift-c, or command clone-nav) pops up a menu listing
the paths to root of all the parent nodes of clones of the node at the current position.
If a clone is a root, then "Clone is a root" is displayed as its path-to-root.
The path-to-root for a non-root clone is the clone's parent's headline, followed by "<-", followed by the clone's parent's parent's headline, etc. -- till a root node
is reached. The path-to-root for the current position is marked with a right arrow icon.
Every path-to-root has an underline marking its shortcut character (if any is possible). Typing a shortcut character selects the menu item. Left-clicking
a menu item also selects it.
Selecting a menu item positions to the clone corresponding to the menu item and also
destroys the pop-up menu.
Doing anything other than selecting a clone menu item, also destroys the pop-up menu. Consequently, you should never see a pop-up messsage telling you "Selected clone position no longer exists." but this condition is tested and reported."""
#@-<< docstring >>
__version__ = '2.0'
#@+<< version_history >>
#@+node:bob.20101204110352.1341: ** << version_history >>
# 2011-07-14 - 2011-07-16 SegundoBob (Bob Hossley)
# Change GUI from log pane to button on Icon Bar. Change version to 2.0.
# 2010-12-11 - 2010-12-11 SegundoBob (Bob Hossley)
# Display the full path to root, not just the parent headline.
# 2010-11-30 - 2010-12-07 SegundoBob (Bob Hossley)
# First released version
#@-<< version_history >>
#@+<< Copyright >>
#@+node:bob.20110716090050.1255: ** << Copyright >>
# Copyright (c) 2011 Robert F. Hossley
# This code is distributed under the MIT license:
# Permission is hereby granted, free of charge, to any person obtaining
# a copy of this software and associated documentation files (the
# "Software"), to deal in the Software without restriction, including
# without limitation the rights to use, copy, modify, merge, publish,
# distribute, sublicense, and/or sell copies of the Software, and to
# permit persons to whom the Software is furnished to do so, subject to
# the following conditions:
# The above copyright notice and this permission notice shall be
# included in all copies or substantial portions of the Software.
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
#@-<< Copyright >>
#@+<< imports >>
#@+node:bob.20101130143836.1347: ** << imports >>
import leo.core.leoGlobals as g
g.assertUi('qt')
import PyQt4.QtGui as QtGui
import PyQt4.QtCore as QtCore
#@-<< imports >>
#@+others
#@+node:bob.20101130143836.1348: ** init
def init ():
ok = g.app.gui.guiName() == "qt"
if ok:
g.registerHandler('after-create-leo-frame',onCreate)
g.plugin_signon(__name__)
return ok
#@+node:bob.20101130143836.1349: ** onCreate
def onCreate (tag, keys):
c = keys.get('c')
if not c: return
CloneNav(c)
#@+node:bob.20110715123442.1322: ** _pathToRoot()
def _pathToRoot(pos):
"""
Generate a string representing the path to the root from the
parent of the specified position.
@param pos: Position pointer to the target node
@param return: String representing the path to the root
from the parent of the target node
"""
if pos.parent():
pathToRootList = []
pos = pos.parent()
while (pos):
pathToRootList.append(pos.h)
pos = pos.parent()
return '<-'.join(pathToRootList)
else:
return 'Clone is a root'
#@+node:bob.20101130190826.1406: ** class CloneNav
class CloneNav(object):
"""
Class representing and controlling the cloneNav button.
This class is only instantiated once per Leo-editor application instance.
"""
#@+others
#@+node:bob.20101204113945.1346: *3* __init__()
def __init__(self, c, parent=None):
"""
@param c: Leo-editor "commander" for the current .leo file
"""
w = c.frame.iconBar.w
if not w: return # EKR: can be None when unit testing.
self._c = c
button1 = QtGui.QPushButton("cloneNav", w)
self._button1 = button1
self._icon_r = w.style().standardIcon(QtGui.QStyle.SP_ArrowRight)
w.connect(button1, QtCore.SIGNAL('clicked()'), lambda arg2=None: self._buttonClicked(arg2))
c.k.registerCommand('clone-nav','Alt-Shift-c', self._buttonClicked)
self._c.frame.iconBar.addWidget(button1)
#@+node:bob.20101211105548.1352: *3* _findClones()
def _findClones(self):
c = self._c
p = c.p
return [z.copy() for z in c.all_positions() if z.v == p.v]
#@+node:bob.20110714142409.1251: *3* _buttonClicked()
def _buttonClicked(self, evnt):
"""
cloneNav button clicked or clone-nav command executed.
@param self: Unique instance of the class controlling the cloneNav button
@param evnt: Leo-Editor event instance. This is not used.
"""
c = self._c
menu1 = QtGui.QMenu('CloneNav', self._button1)
self._clonesList = self._findClones()
exclude = ['<', '-', '>', '_', ' ']
for idx2, pClone in enumerate(self._clonesList):
pathToRoot = _pathToRoot(pClone)
for idx3, scc in enumerate(pathToRoot):
if scc.lower() in exclude: continue
pathToRoot = pathToRoot[:idx3] + '&' + pathToRoot[idx3:]
exclude.append(scc.lower())
break
if pClone == c.p:
# Current position
menu1.addAction(self._icon_r, pathToRoot, lambda idxP=idx2: self._menuItemTriggered(idxP))
else:
menu1.addAction(pathToRoot, lambda idxP=idx2: self._menuItemTriggered(idxP))
QtGui.QPushButton.setMenu (self._button1, menu1)
self._button1.showMenu()
QtGui.QPushButton.setMenu(self._button1, None)
#@+node:bob.20110714142409.1252: *3* _menuItemTriggered()
def _menuItemTriggered(self, idx):
c = self._c
tarPtr = self._clonesList[idx]
if not c.positionExists(tarPtr):
QtGui.QMessageBox.warning(c.frame.iconBar.w, 'Clone Navigator Warning', 'Selected clone position no longer exists.')
else:
c.selectPosition(tarPtr)
c.redraw_after_select(tarPtr)
#@-others
#@-others
#@-leo
#@+leo-ver=5-thin
#@+node:bob.20101130143836.1344: * @file node_visit_history.py
#@+<< docstring >>
#@+node:bob.20101130143836.1345: ** << docstring >>
""" The "Node Visit History" plugin allows you to go to any recently visited node position by typing just two keystrokes.
The "Node Visit History" plugin creates the nodeVisitHist button on the Icon Bar,
installs command node-visit-history, and assigns Alt-Shift-h to command node-visit-history.
Left clicking the nodeVisitHist button, (or Alt-Shift-h, or command node-visit-history) pops up a menu listing the path to the root of each node that has
been visited in this .leo file since it was last opened.
The path-to-root for a node is the node's headline, followed by "<-", followed by the node's parent's headline, etc. -- till a root node is reached. The path-to-root for the current position is marked with a right arrow icon.
Every path-to-root has an underline marking its shortcut character (if any is possible). Typing a shortcut character selects the menu item. Left-clicking a menu item also selects it.
Selecting a menu item positions to that node in the outline and also destroys the pop-up menu.
Doing anything other than selecting a pop-up menu item, also destroys the pop-up menu.
If you select a position which no longer exists because you have changed the outline, a message box pops up saying "Selected position no longer exists." """
#@-<< docstring >>
__version__ = '1.0'
#@+<< version_history >>
#@+node:bob.20101204110352.1341: ** << version_history >>
# 2011-06-01 - 2011-07-16 SegundoBob (Bob Hossley)
# First released version
#@-<< version_history >>
#@+<< Copyright >>
#@+node:bob.20110716093827.1207: ** << Copyright >>
# Copyright (c) 2011 Robert F. Hossley
# This code is distributed under the MIT license:
# Permission is hereby granted, free of charge, to any person obtaining
# a copy of this software and associated documentation files (the
# "Software"), to deal in the Software without restriction, including
# without limitation the rights to use, copy, modify, merge, publish,
# distribute, sublicense, and/or sell copies of the Software, and to
# permit persons to whom the Software is furnished to do so, subject to
# the following conditions:
# The above copyright notice and this permission notice shall be
# included in all copies or substantial portions of the Software.
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
#@-<< Copyright >>
#@+<< imports >>
#@+node:bob.20101130143836.1347: ** << imports >>
import leo.core.leoGlobals as g
g.assertUi('qt')
import PyQt4.QtGui as QtGui
import PyQt4.QtCore as QtCore
#@-<< imports >>
#@+others
#@+node:bob.20101130143836.1348: ** init
def init ():
ok = g.app.gui.guiName() == "qt"
if ok:
g.registerHandler('after-create-leo-frame',onCreate)
g.plugin_signon(__name__)
return ok
#@+node:bob.20101130143836.1349: ** onCreate
def onCreate (tag, keys):
c = keys.get('c')
if not c: return
NodeVisitHistory(c)
#@+node:bob.20110715125923.1208: ** _pathToRoot()
def _pathToRoot(pos):
"""
Generate a string representing the path to the root from the
the specified position.
@param pos: Position pointer to the target node
@param return: String representing the path to the root
from the target node
"""
pathToRootList = []
while (pos):
pathToRootList.append(pos.h)
pos = pos.parent()
return '<-'.join(pathToRootList)
#@+node:bob.20101130190826.1406: ** class NodeVisitHistory
class NodeVisitHistory(object):
"""
Class representing and controlling the nodeVisitHist button
This class is only instantiated once per Leo-editor application instance.
"""
#@+others
#@+node:bob.20101204113945.1346: *3* __init__()
def __init__(self, c, parent=None):
"""
@param c: Leo-editor "commander" for the current .leo file
"""
w = c.frame.iconBar.w
if not w: return # EKR: can be None when unit testing.
self._c = c
button1 = QtGui.QPushButton("nodeVisitHist", w)
self._button1 = button1
self._icon_r = w.style().standardIcon(QtGui.QStyle.SP_ArrowRight)
w.connect(button1, QtCore.SIGNAL('clicked()'), lambda arg2=None: self._buttonClicked(arg2))
c.k.registerCommand('node-visit-history','Alt-Shift-h', self._buttonClicked)
self._c.frame.iconBar.addWidget(button1)
#@+node:bob.20110715123442.1316: *3* _buttonClicked()
def _buttonClicked(self, evnt):
"""
nodeVisitHist button clicked or node-visit-history command executed.
@param self: Unique instance of the class controlling the nodeVisitHist button
@param evnt: Leo-Editor event instance. This is not used.
"""
c = self._c
menu1 = QtGui.QMenu('NodeVisitHistory', self._button1)
exclude = ['<', '-', '>', '_', ' ']
for idx2, pVisited in enumerate(reversed(c.nodeHistory.visitedPositions())):
pathToRoot = _pathToRoot(pVisited)
for idx3, scc in enumerate(pathToRoot):
if scc.lower() in exclude: continue
pathToRoot = pathToRoot[:idx3] + '&' + pathToRoot[idx3:]
exclude.append(scc.lower())
break
if pVisited == c.p:
# Current position
menu1.addAction(self._icon_r, pathToRoot, lambda idxP=idx2: self._menuItemTriggered(idxP))
else:
menu1.addAction(pathToRoot, lambda idxP=idx2: self._menuItemTriggered(idxP))
QtGui.QPushButton.setMenu (self._button1, menu1)
self._button1.showMenu()
QtGui.QPushButton.setMenu(self._button1, None)
#@+node:bob.20110715123442.1320: *3* _menuItemTriggered()
def _menuItemTriggered(self, idx):
c = self._c
reversedHist = c.nodeHistory.visitedPositions()
reversedHist.reverse()
tarPtr = reversedHist[idx]
if not c.positionExists(tarPtr):
QtGui.QMessageBox.warning(c.frame.iconBar.w, 'Node Visit History Warning', 'Selected position no longer exists.')
else:
c.selectPosition(tarPtr)
c.redraw_after_select(tarPtr)
#@-others
#@-others
#@-leo