Source code for gpi.library

#    Copyright (C) 2014  Dignity Health
#
#    This program is free software: you can redistribute it and/or modify
#    it under the terms of the GNU Lesser General Public License as published by
#    the Free Software Foundation, either version 3 of the License, or
#    (at your option) any later version.
#
#    This program is distributed in the hope that it will be useful,
#    but WITHOUT ANY WARRANTY; without even the implied warranty of
#    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
#    GNU Lesser General Public License for more details.
#
#    You should have received a copy of the GNU Lesser General Public License
#    along with this program.  If not, see <http://www.gnu.org/licenses/>.
#
#    NO CLINICAL USE.  THE SOFTWARE IS NOT INTENDED FOR COMMERCIAL PURPOSES
#    AND SHOULD BE USED ONLY FOR NON-COMMERCIAL RESEARCH PURPOSES.  THE
#    SOFTWARE MAY NOT IN ANY EVENT BE USED FOR ANY CLINICAL OR DIAGNOSTIC
#    PURPOSES.  YOU ACKNOWLEDGE AND AGREE THAT THE SOFTWARE IS NOT INTENDED FOR
#    USE IN ANY HIGH RISK OR STRICT LIABILITY ACTIVITY, INCLUDING BUT NOT
#    LIMITED TO LIFE SUPPORT OR EMERGENCY MEDICAL OPERATIONS OR USES.  LICENSOR
#    MAKES NO WARRANTY AND HAS NO LIABILITY ARISING FROM ANY USE OF THE
#    SOFTWARE IN ANY HIGH RISK OR STRICT LIABILITY ACTIVITIES.

# Logic for searching for nodes and networks within the library path and
# generating a mouse menu.
#
# TODO: possibly make the library a global for all canvases


import os
import shutil
import subprocess

# gpi
from gpi import QtCore, QtGui
from .config import Config
from .defaultTypes import GPITYPE_PASS
from .defines import isWidget, isGPIType, isExternalNode
from .catalog import Catalog, CatalogObj
from .defaultTypes import GPIDefaultType
from .defines import isGPIModFile, isGPITypeFile, isGPINetworkFile, GPI_PYMOD_PRE_EXT
from .loader import loadMod, PKGroot, appendSysPath
from .node import Node
from .sysspecs import Specs

from .logger import manager

# start logger for this module
log = manager.getLogger(__name__)

NOPATH_MESSAGE = "<em>No library selected...</em>"

[docs]class FauxMenu(QtGui.QLabel): '''For the node search in the right-button mouse menu. This label is what is rendered during the intermediate searching. ''' def __init__(self, realMenu, menuPos, parent=None): super(FauxMenu, self).__init__(parent=parent) self._menu = realMenu self._menuPos = menuPos def enterEvent(self, event): # once the mouse enters the faux menu, initiate the real menu self._menu.popup(self._menuPos)
#self._menu.exec_(self._menuPos) #self.setParent(None)
[docs]class NodeCatalogItem(CatalogObj): '''A single entry to a Node database. For information such as library, path, type, etc... ''' def __init__(self, fullpath): dn = os.path.dirname bn = os.path.basename self.fullpath = fullpath # determine if there is an editable code file for direct node edit access self.editable_path = None epath, ext = os.path.splitext(fullpath) epath += '.py' if os.path.isfile(epath): self.editable_path = epath # 'SpiralCoords_GPI.py' and path fil = bn(fullpath) path = dn(fullpath) # get module name and lib name name, ext = os.path.splitext(fil) # 'SpiralCoords_GPI' '.py' # verify the '_GPI' before the file extension self.isNodeFile = False if name.endswith(GPI_PYMOD_PRE_EXT): self.isNodeFile = True name = name.split(GPI_PYMOD_PRE_EXT)[0] # get node's display name 'SpiralCoords' second = bn(path) # 'GPI' if the GPI dir is used third = bn(dn(path)) # 'core' if GPI-dir is NOT used lib_path = dn(path) # skip the GPI directory name by removing it from path if second == 'GPI': # 'spiral' second = bn(dn(path)) # 'core' third = bn(dn(dn(path))) lib_path = dn(dn(path)) # save node info self.name = name self.second = second self.third = third self._lib_path = lib_path self.path = path self.ext = [ext] self.pkg_root = None if self.path: self.pkg_root = PKGroot(self.path) # This is the only element that cannot be duplicated across nodes self._id = self.third+'.'+self.second+'.'+self.name self.thrd_sec = self.third+'.'+self.second # add to sys path in class node module has pkg.module refs if self.pkg_root: appendSysPath(self.pkg_root) # try loading the node module self.mod = None self.widgetNames = [] #self.load() def key(self): return self._id def lib(self): return self.third def lib_path(self): return self._lib_path def sameLib(self, item): return item.lib_path() == self.lib_path() def libWarn(self, item): msg = 'Multiple library paths with the name: \''+str(self.lib())+'\'\n' \ + 'Paths:\n\t* ' + str(self.lib_path()) +'\n\t '+ str(item.lib_path()) \ + '\nNode:\n\t ' + str(self.key())+' '+str(item.ext)+'\n' \ + 'Skipping duplicate library...' log.warn(msg) def find(self, key): return (key in self.types) def reload(self): log.info("reload: "+str(self.fullpath)) self.load() def load(self): # clear previous instance self.mod = None # enforce filename law if not self.isNodeFile: log.error('Not a valid GPI node filename, skipping. ('+str(self.fullpath)+')') return # verify that there is a node description mod = loadMod(self.fullpath) if mod: if hasattr(mod, 'ExternalNode'): if isExternalNode(getattr(mod, 'ExternalNode')): self.mod = mod else: log.error('ExternalNode object found, but it is not a GPI node def. ('+str(self.fullpath)+')') return else: log.error('No ExternalNode definition found, skipping. ('+str(self.fullpath)+')') return else: log.error('Module not loadable, skipping. ('+str(self.fullpath)+')') return if self.mod: # check if it has widget definitions self.widgetNames = [] # re-init list in case of reload for name in dir(self.mod): if isWidget(getattr(self.mod, name)): self.widgetNames.append(name) if len(self.widgetNames): log.info(str(self.fullpath)+' contains widget definitions: '+str(self.widgetNames)) def valid(self): return self.isLoaded() # and self.isNodeFile def validExt(self): return self.isNodeFile def isLoaded(self): if self.mod: return True else: return False def hasWidgets(self): return len(self.widgetNames) > 0 def getWidget(self, name): if name in self.widgetNames: return getattr(self.mod, name) def appendExt(self, ext): self.ext.append(ext) self.ext = list(set(self.ext)) def merge(self, m): if not self.sameLib(m): self.libWarn(m) return # this handles collisions between nodes found with the same id self.ext += m.ext self.ext = list(set(self.ext)) def description(self): # return the node description class return self.mod.ExternalNode def __str__(self): return '< '+self._id+', '+str(self.path)+', '+str(self.ext) +' >'
[docs]class NetworkCatalogItem(CatalogObj): '''A single database entry to a GPI Network database. ''' def __init__(self, fullpath): # TODO: more rigorous file checking (perhaps a magic number). dn = os.path.dirname bn = os.path.basename self.fullpath = fullpath # 'SpiralCoords_GPI.net' and path fil = bn(fullpath) path = dn(fullpath) # get module name and lib name name, ext = os.path.splitext(fil) # 'SpiralCoords_GPI' '.net' # verify the '_GPI' before the file extension # determine whether to install net in the library menu self.isNetLibFile = False if name.endswith(GPI_PYMOD_PRE_EXT): self.isNetLibFile = True name = name.split(GPI_PYMOD_PRE_EXT)[0] # get node's display name 'SpiralCoords' second = bn(path) # 'GPI' if the GPI dir is used third = bn(dn(path)) # 'core' if GPI-dir is NOT used # skip the GPI directory name by removing it from path if second == 'GPI': # 'spiral' second = bn(dn(path)) # 'core' third = bn(dn(dn(path))) # save net info self.name = name self.second = second self.third = third self.path = path self.ext = ext # This is the only element that cannot be duplicated across nodes self._id = self.third+'.'+self.second+'.'+self.name self.thrd_sec = self.third+'.'+self.second def valid(self): return isGPINetworkFile(self.fullpath) and self.isNetLibFile def key(self): return self._id def appendExt(self, ext): self.ext.append(ext) self.ext = list(set(self.ext)) def merge(self, m): # this handles collisions between nodes found with the same id self.ext += m.ext self.ext = list(set(self.ext)) def __str__(self): return self._id+', '+str(self.path)+', '+str(self.ext)
class GPITYPECatalogItem(CatalogObj): '''Object specific to GPITYPE plugins. 1) input the full path: /path-to-plugin-dir/mytype_GPITYPE.py or .pyc 2) this will try to verify the path 3) try to load the python module 4) failures will be flagged ''' def __init__(self, fullpath): if type(fullpath) is not str: log.error('GPITYPECatalogItem requires a string argument.') self.fullpath = fullpath self.name, ext = os.path.splitext(fullpath) self.path, self.name = os.path.split(self.name) self.ext = [ext] # unique name self._id = self.path+'/'+self.name # try to have python load the module self.mod = loadMod(fullpath) # Extract the GPITYPES from the module for checking and keep a list of names. self.types = [] for cn in dir(self.mod): cls = getattr(self.mod, cn) if isGPIType(cls): log.info('\t'+fullpath+' has GPITYPE: '+cn) self.types.append(cn) def valid(self): return self.isLoaded() def isLoaded(self): if self.mod: return True else: return False def key(self): return self._id def appendExt(self, ext): self.ext.append(ext) self.ext = list(set(self.ext)) def merge(self, m): # this handles collisions between nodes found with the same id self.ext += m.ext self.ext = list(set(self.ext)) def __str__(self): return self._id+', '+str(self.path)+', '+str(self.ext) def hasType(self, key): # GPITYPE libraries can hold multiple types. Usually, the type is what # is searched for. return (key in self.types) def type(self, key): # return the type object associated with the key name. if self.hasType(key): return getattr(self.mod, key)()
[docs]class Library(object): '''Contains all the Node, Network, and GPIType path searching, mouse menu generation and indexing for the node library. The contents of the library are loaded at startup (each time) and when the user adds Nodes via drag'n drop or menu contexts. ''' def __init__(self, parent): self._parent = parent # must be a Qt parent for signalling self._known_GPI_nodes = Catalog() # list of all modules within each lib self._known_GPI_networks = Catalog() # all networks in each lib self._known_GPI_types = Catalog() # all GPI types found in init search self.extTypes = dict() self._listwdg = None # for searching node list self.generateNewNodeListWindow() self._lib_menus = {} # third level menu (holds second lev list) self._lib_second = {} # second level menu (holds node list) self._lib_menu = [] # third level menu list self.scanGPIModulesIn_LibraryPath(recursion_depth=3) self.generateLibMenus() self.generateNewNodeList() def showNewNodeListWindow(self): self._list_win.show() def _get_new_node_name(self): fname = self._new_node_name_field.text() if fname == "": fname = self._new_node_name_field.placeholderText() if not fname.endswith(GPI_PYMOD_PRE_EXT + '.py'): fname += GPI_PYMOD_PRE_EXT + '.py' return fname def _createNewNode(self): # copy node template to this library, and open it up fullpath = self._new_node_path new_node_created = False if os.path.exists(fullpath): log.warn("Didn't create new node at path: " + fullpath + " (file already exists)") else: try: shutil.copyfile(Config.GPI_NEW_NODE_TEMPLATE_FILE, fullpath) except OSError as e: print(e) log.warn("Didn't create new node at path: " + fullpath) else: log.dialog("New node created at path: " + fullpath) new_node_created = True self.rescan() self._list_win.hide() if new_node_created: # instantiate our new node on the canvas canvas = self._parent pos = QtCore.QPoint(0, 0) node = self.findNode_byPath(fullpath) sig = {'sig': 'load', 'subsig': node, 'pos': pos} canvas.addNodeRun(sig) # now open the file for editing (stolen from node.py) if Specs.inOSX(): # OSX users set their launchctl associated file prefs command = "open \"" + fullpath + "\"" subprocess.Popen(command, shell=True) # Linux users set their editor choice # TODO: this should be moved to config elif Specs.inLinux(): editor = 'gedit' if os.environ.has_key("EDITOR"): editor = os.environ["EDITOR"] command = editor + " \"" + fullpath + "\"" subprocess.Popen(command, shell=True) else: log.warn("Quick-Edit unavailable for this OS, aborting...") def _setQTLabelElided(self, label, text): fm = QtGui.QFontMetrics(label.font()) width = label.width() elided_text = fm.elidedText(text, QtCore.Qt.ElideMiddle, width) label.setText(elided_text) def _newNodeNameEdited(self): new_name = self._get_new_node_name() current_path = self._new_node_path if current_path != '': path, old_name = os.path.split(current_path) fullpath = os.path.join(path, new_name) self._new_node_path = fullpath self._setQTLabelElided(self._new_node_path_field, fullpath) # This slot is called whenever a list item is clicked. This is used to # update the path and set the enabled/disabled state of the create node # button. def _listItemClicked(self, item): idx, label = self._new_node_list_index if idx == 0: self._create_button.setDisabled(True) self._new_node_path_field.setText(NOPATH_MESSAGE) self._new_node_path = '' elif idx == 1: if item.text() == '..': self._create_button.setDisabled(True) self._new_node_path_field.setText(NOPATH_MESSAGE) self._new_node_path = '' else: for k in self._known_GPI_nodes.keys(): node = self._known_GPI_nodes.get(k) if node.thrd_sec == '.'.join((label, item.text())): fullpath = os.path.join(node.path, self._get_new_node_name()) self._new_node_path = fullpath self._setQTLabelElided(self._new_node_path_field, fullpath) self._create_button.setEnabled(True) break elif idx == 2: self._create_button.setEnabled(True) # This slot is called whenever a list item is double-clicked. This is used # for navigation of the library lists when creating a new node. def _listItemDoubleClicked(self, item): new_node_created = False idx, label = self._new_node_list_index if idx == 0: self.generateNewNodeList(item.text()) elif item.text() == '..': new_index = label.split('.') if idx == 1: self.generateNewNodeList() else: self.generateNewNodeList('.'.join(new_index[:idx-1])) elif idx < 2: new_index = '.'.join((label, item.text())) self.generateNewNodeList(new_index) def scanForNewNodes(self): log.dialog("Scanning for newly created modules and libraries...") self.scanGPIModulesIn_LibraryPath(recursion_depth=3) self.regenerateLibMenus() log.dialog("Finished rescanning.") def rescan(self): # remove all libs for a fresh rescan self._known_GPI_nodes = Catalog() # list of all modules within each lib self._known_GPI_networks = Catalog() # all networks in each lib self._known_GPI_types = Catalog() # all GPI types found in init search self.extTypes = dict() log.dialog("Rescanning for newly created modules and libraries...") self.scanGPIModulesIn_LibraryPath(recursion_depth=3) self.regenerateLibMenus() self.generateNewNodeList() log.dialog("Finished rescanning.") def getUserLibsWithPaths(self): return None def getType(self, key): # GPITYPE # if requested type was returned, then include a True, else False. if key == GPITYPE_PASS: return (GPIDefaultType(), True) typObj = self._known_GPI_types.intrafind('hasType', key) if typObj is None: log.info('Requested port-type: \''+str(key)+'\' not found. Using \'' + str(GPITYPE_PASS) + '\' instead.') return (GPIDefaultType(), False) else: return (typObj.type(key), True) def findNode_byName(self, name): # expose the catalog's find function return self._known_GPI_nodes.find('name', name) def findNode_byPath(self, path): return self._known_GPI_nodes.find('fullpath', path) def findNode_byLibrary(self, name, second, third): key = third+'.'+second+'.'+name if key in list(self._known_GPI_nodes.keys()): return self._known_GPI_nodes.get(key) def findNode_byKey(self, key): if key in list(self._known_GPI_nodes.keys()): return self._known_GPI_nodes.get(key) def findNode_byClosestMatch(self, name, wdg_port_names): # find all nodes with the same name, then try to match as many widget # and port names from the list. log.debug('Find node by closest match: '+str(name)) byname = self._known_GPI_nodes.list('name', name) if byname is None: log.warn('No node found with name \''+str(name)+'\'') return None if len(byname) == 0: log.warn(str(name) + ' node not found.') return None if len(byname) == 1: log.info('Found one match for '+str(name)) return byname[0] any_cnt = [] exact_cnt = [] for item in byname: # need an instance to check the node's widget and port names cur_names = Node(None, nodeCatItem=item).getWidgetAndPortNames() # try checking for any match type cnt = 0 for wpn in wdg_port_names: match = False for cn in cur_names: # if one is in the other or the other is in the one if cn.lower().count(wpn.lower()) or wpn.lower().count(cn.lower()): match = True if match: cnt += 1 any_cnt.append(cnt*100.0/len(wdg_port_names)) # try checking for exact match type cnt = 0 for wpn in wdg_port_names: if wpn in cur_names: cnt += 1 exact_cnt.append(cnt*100.0/len(wdg_port_names)) msg = 'Find Node: '+str(name)+'\n' msg += 'Candidates:\n' for item, cpc, epc in zip(byname, any_cnt, exact_cnt): msg += '\t'+str(item.fullpath) + ' any: '+str(cpc)+'%, exact: '+str(epc)+'%\n' if len(set(exact_cnt)) > 1: msg += 'Exact Name Match:\n' chosen_item = byname[0] chosen_score = exact_cnt[0] for item, epc in zip(byname, exact_cnt): if chosen_score < epc: chosen_score = epc chosen_item = item msg +='\tChosen: ' + str(item.fullpath) + ', MP: ' + str(chosen_score) + '%' log.warn(msg) return chosen_item if len(set(any_cnt)) > 1: msg += 'Any Name Match:\n' chosen_item = byname[0] chosen_score = any_cnt[0] for item, epc in zip(byname, any_cnt): if chosen_score < epc: chosen_score = epc chosen_item = item msg += '\tChosen: ' + str(item.fullpath) + ', MP: ' +str(chosen_score)+'%' log.warn(msg) return chosen_item log.warn(msg) log.warn('No node distinction found, choosing first match: '+str(byname[0].fullpath)) return byname[0] def addNode(self, item, check=True): if check: item.load() if not item.valid(): return -1 existing = self.findNode_byKey(item.key()) if not existing: self._known_GPI_nodes.append(item) return 1 elif not existing.sameLib(item): existing.libWarn(item) return -1 return 0 # already in database def listWdg(self): return self._listwdg def libMenus(self): return self._lib_menus def libMenu(self): return self._lib_menu def searchMenu(self, txt, parent, mousemenu): # this menu needs to be rebuilt everytime a character changes # don't bother if the user just cleared the search if len(txt) == 0: return pos = self._parent.mapToGlobal(self._parent._event_pos + QtCore.QPoint(mousemenu.sizeHint().width(), 0)) # generate the menu menu = QtGui.QMenu(self._parent) self.generateNodeSearchActions(str(txt), menu, mousemenu) # render the menu without executing it menupixmap = QtGui.QPixmap().grabWidget(menu) # display the menu image (as a dummy menu as its being built) # TODO: this could probably be moved to the FauxMenu self._listwdg = FauxMenu(menu, pos) self._listwdg.setWindowFlags(QtCore.Qt.Tool | QtCore.Qt.FramelessWindowHint) self._listwdg.move(pos) self._listwdg.setPixmap(menupixmap) self._listwdg.show() self._listwdg.raise_() def removeSearchPopup(self): self._listwdg = None def addNodeAndCloseMouseMenu(self, s, searchmenu, mousemenu): '''Close all menus and related objects. NOTE: THIS ORDER MUST BE PRESERVED!!! ''' self.removeSearchPopup() searchmenu.close() mousemenu.close() self._parent.activateWindow() self._parent.update() self._parent.addNodeRun(s) def scanGPIModulesIn_LibraryPath(self, recursion_depth=1): new_sys_paths = [] types_path = os.path.join(os.path.dirname(os.path.realpath(__file__)), 'types') for spath in Config.GPI_LIBRARY_PATH + [types_path]: path = os.path.realpath(spath) # remove excess '/' if os.path.isdir(path): self.scanGPIModules(path, recursion_depth) log.info("GPI mods/nets/types found:") log.info(str(self._known_GPI_nodes)) log.info(str(self._known_GPI_networks)) log.info(str(self._known_GPI_types)) log.info('number of nodes: '+str(len(self._known_GPI_nodes))) log.info('number of networks: '+str(len(self._known_GPI_networks))) log.info('number of types: '+str(len(self._known_GPI_types))) # TODO: move this and others like it to a common help-object that can errorcheck. def openGPIRCHelp(self): if not QtGui.QDesktopServices.openUrl(QtCore.QUrl('http://docs.gpilab.com')): QtGui.QMessageBox.information(self, 'Documentation',"Documentation can be found at\nhttp://docs.gpilab.com", QtGui.QMessageBox.Close) # http://docs.gpilab.com/Configuration/#configuration-library-directories def openLIBDIRSHelp(self): if not QtGui.QDesktopServices.openUrl(QtCore.QUrl('http://docs.gpilab.com/Configuration/#configuration-library-directories')): QtGui.QMessageBox.information(self, 'Documentation',"Documentation can be found at\nhttp://docs.gpilab.com", QtGui.QMessageBox.Close) def regenerateLibMenus(self): self._lib_menus = {} # third level menu (holds second lev list) self._lib_second = {} # second level menu (holds node list) self._lib_menu = [] # third level menu list self.generateLibMenus() self.generateNewNodeList() def generateLibMenus(self): # default menu if no libraries are found numnodes = len(list(self._known_GPI_nodes.keys())) if numnodes == 0: self._lib_menus['No Nodes Found'] = QtGui.QMenu('No Nodes Found') buf = 'Check your ~/.gpirc for the correct LIB_DIRS.' act = QtGui.QAction(buf, self._parent, triggered = self.openLIBDIRSHelp) self._lib_menus['No Nodes Found'].addAction(act) for m in sorted(list(self._lib_menus.keys()), key=lambda x: x.lower()): mm = self._lib_menus[m] mm.setTearOffEnabled(False) self._lib_menu.append(mm) return # NODE MENU # setup libs using node id. ex: core.mathematics.sum # the ids of for k in sorted(self._known_GPI_nodes.keys(), key=lambda x: x.lower()): node = self._known_GPI_nodes.get(k) if node.third not in self._lib_menus: #self._lib_menus[node.third] = QtGui.QMenu(node.third.capitalize()) self._lib_menus[node.third] = QtGui.QMenu(node.third) self._lib_menus[node.third].setTearOffEnabled(True) if node.thrd_sec not in self._lib_second: self._lib_second[node.thrd_sec] = QtGui.QMenu(node.second) self._lib_second[node.thrd_sec].setTearOffEnabled(True) ma = self._lib_menus[node.third].addMenu(self._lib_second[node.thrd_sec]) sm = self._lib_second[node.thrd_sec] # TODO: try setting up hotkeys/shortcuts for specific nodes a = QtGui.QAction(node.name, self._parent, statusTip="Click to instantiate the \'"+str(node.name)+"\' node.") s = {'subsig': node} self._parent.connect(a, QtCore.SIGNAL("triggered()"), lambda who=s: self._parent.addNodeRun(who)) sm.addAction(a) # NETWORK MENU for sm in list(self._lib_second.values()): sm.addSeparator() for k in sorted(list(self._known_GPI_networks.keys()), key=lambda x: x.lower()): net = self._known_GPI_networks.get(k) if net.third not in self._lib_menus: #self._lib_menus[net.third] = QtGui.QMenu(net.third.capitalize()) self._lib_menus[net.third] = QtGui.QMenu(net.third) self._lib_menus[net.third].setTearOffEnabled(True) if net.thrd_sec not in self._lib_second: self._lib_second[net.thrd_sec] = QtGui.QMenu(net.second) self._lib_second[net.thrd_sec].setTearOffEnabled(True) self._lib_menus[net.third].addMenu(self._lib_second[node.thrd_sec]) sm = self._lib_second[net.thrd_sec] a = QtGui.QAction(net.name + ' (net)', self._parent, statusTip="Click to instantiate the \'"+str(net.name)+"\' network.") s = {'sig': 'load', 'subsig': 'net', 'path': net.fullpath} self._parent.connect(a, QtCore.SIGNAL("triggered()"), lambda who=s: self._parent.addNodeRun(who)) sm.addAction(a) for m in sorted(list(self._lib_menus.keys()), key=lambda x: x.lower()): mm = self._lib_menus[m] mm.setTearOffEnabled(True) self._lib_menu.append(mm) # each time this is called it goes through all the nodes to populate the # list def generateNewNodeList(self, top_lib=None): list_items = set() new_node_path = '' for k in self._known_GPI_nodes.keys(): node = self._known_GPI_nodes.get(k) if top_lib is None: list_items.add(node.third) elif node.third == top_lib: list_items.add(node.second) elif node.thrd_sec == top_lib: list_items.add(node.name) if new_node_path == '': new_node_path = os.path.join(node.path, self._get_new_node_name()) self._new_node_path = new_node_path if self._new_node_path == '': self._new_node_path_field.setText(NOPATH_MESSAGE) else: self._setQTLabelElided(self._new_node_path_field, new_node_path) self._new_node_list.clear() if top_lib is not None: self._new_node_list.addItem("..") [self._new_node_list.addItem(item) for item in list_items] if top_lib is None: idx = 0 list_label = "GPI Libraries" else: new_label = top_lib.split('.') idx = len(new_label) list_label = u' \u2799 '.join(["GPI Libraries"] + new_label) self._list_label.setText(list_label) self._new_node_list_index = (idx, top_lib) if idx > 1: self._create_button.setEnabled(True) else: self._create_button.setDisabled(True) # generate the new node list window def generateNewNodeListWindow(self): # the New Node window self._list_win = QtGui.QWidget() self._list_win.setFixedWidth(500) self._new_node_list = QtGui.QListWidget(self._list_win) self._create_button = QtGui.QPushButton("Create Node", self._list_win) self._create_button.setDisabled(True) self._create_button.clicked.connect(self._createNewNode) button_layout = QtGui.QHBoxLayout() button_layout.addStretch(1) button_layout.addWidget(self._create_button) self._new_node_list.itemDoubleClicked.connect(self._listItemDoubleClicked) self._new_node_list.itemClicked.connect(self._listItemClicked) self._list_label = QtGui.QLabel("GPI Libraries", self._list_win) node_name_layout = QtGui.QHBoxLayout() new_node_name_label = QtGui.QLabel("Name:", self._list_win) self._new_node_name_field = QtGui.QLineEdit(self._list_win) self._new_node_name_field.setPlaceholderText("NewNodeName_GPI.py") self._new_node_name_field.textChanged.connect(self._newNodeNameEdited) node_name_layout.addWidget(new_node_name_label) node_name_layout.addWidget(self._new_node_name_field) new_node_path_label = QtGui.QLabel("Path:", self._list_win) self._new_node_path = '' self._new_node_path_field = QtGui.QLabel(NOPATH_MESSAGE, self._list_win) self._new_node_path_field.setSizePolicy(QtGui.QSizePolicy(QtGui.QSizePolicy.Expanding, QtGui.QSizePolicy.Fixed)) path_layout = QtGui.QHBoxLayout() path_layout.addWidget(new_node_path_label) path_layout.addWidget(self._new_node_path_field) list_layout = QtGui.QVBoxLayout() list_layout.addWidget(self._list_label) list_layout.addWidget(self._new_node_list) list_layout.addLayout(node_name_layout) list_layout.addLayout(path_layout) list_layout.addLayout(button_layout) self._list_win.setLayout(list_layout) def scanGPIModules(self, ipath, recursion_depth=1): ocnt = ipath.count('/') for path, dn, fn in os.walk(ipath): # TODO: instead of checking for hidden svn dirs, just choose any hidden dir if (path.count('/') - ocnt <= recursion_depth) and not path.count('/.svn'): for fil in os.listdir(path): fullpath = path+'/'+fil if isGPIModFile(fullpath): item = NodeCatalogItem(fullpath) if Config.IMPORT_CHECK: item.load() # load check if item.valid(): self._known_GPI_nodes.append(item) else: self._known_GPI_nodes.append(item) elif isGPITypeFile(fullpath): item = GPITYPECatalogItem(fullpath) if item.valid(): self._known_GPI_types.append(item) elif isGPINetworkFile(fullpath): item = NetworkCatalogItem(fullpath) if item.valid(): self._known_GPI_networks.append(item) def generateNodeSearchActions(self, txt, menu, mousemenu): # user query txt = txt.lower() # NODE SEARCH # search using txt string sortedMods = [] if len(txt) > 2: # match anywhere in name for node in list(self._known_GPI_nodes.values()): if node.name.lower().find(txt) > -1: sortedMods.append(node) else: # only match from start of name for node in list(self._known_GPI_nodes.values()): if node.name.lower().startswith(txt): sortedMods.append(node) sortedMods = sorted(sortedMods, key=lambda x: x.name.lower()) # create actions and add them to the menu for node in sortedMods: a = QtGui.QAction(node.name+" (" + node.thrd_sec + ")", self._parent, statusTip="Click to instantiate the \'"+str(node.name)+"\' node.") s = {'subsig': node} # Somehow this lambda or the way this signal is connected, the # s-dict is fully copied which is required to pass the correct # mod name. self._parent.connect(a, QtCore.SIGNAL("triggered()"), lambda who=s: self.addNodeAndCloseMouseMenu(who, menu, mousemenu)) menu.addAction(a) # NETWORK SEARCH # search using txt string if True: sortedMods= [] if len(txt) > 2: for net in list(self._known_GPI_networks.values()): if net.name.lower().find(txt) > -1: sortedMods.append(net) else: for net in list(self._known_GPI_networks.values()): if net.name.lower().startswith(txt): sortedMods.append(net) sortedMods = sorted(sortedMods, key=lambda x: x.name.lower()) if len(sortedMods): menu.addSeparator() # create actions and add them to the menu for net in sortedMods: a = QtGui.QAction(net.name+" (net) (" + net.thrd_sec + ")", self._parent, statusTip="Click to instantiate the \'"+str(net.name)+"\' network.") s = {'sig': 'load', 'subsig': 'net', 'path': net.fullpath} self._parent.connect(a, QtCore.SIGNAL("triggered()"), lambda who=s: self.addNodeAndCloseMouseMenu(who, menu, mousemenu)) menu.addAction(a)