# Part of the A-A-P GUI IDE: Tool class and associated stuff # Copyright (C) 2002-2003 Stichting NLnet Labs # Permission to copy and use this file is specified in the file COPYING. # If this file is missing you can find it here: http://www.a-a-p.org/COPYING import os.path from glob import glob import Util # TODO: do this properly with gettext(). def _(x): return x class Tool: """Generic Tool, to be derived by actual tools.""" def __init__(self, name, item, action, lnum = None, col = None, off = None): self.name = name # name of the tool self.topmodel = item.acty.topmodel # toplevel model: AIDE object self.itemlist = [ item ] # Items this tool is working on self.currentnode = None # Set after tool is editing the item. self.action = action # action we were started for def setName(self, name): """Set the name of the tool. Used for a tool that knows more about what it's doing after it started.""" self.name = name self.topmodel.toollist.updateTool(self) def addItem(self, item): """Add an ActyItem that this tool is working on. An item can be added multiple times.""" if not item in self.itemlist: self.itemlist.append(item) def setCurrentItem(self, item): """Set the item currently edited by this Tool. Normally called from foreground().""" if not item in self.itemlist: self.itemlist.append(item) self.currentnode = item.node def delItem(self, item): """Remove an Item from the list of items this tool is working on.""" if item in self.itemlist: self.itemlist.remove(item) def allClosed(self): """Return non-zero if the tool does not have items.""" return len(self.itemlist) == 0 def getProperties(self): """Properties that this tools supports. To be overruled by the derived class.""" return {} def close(self, shutdown): """Close the tool. Return non-zero if closing is OK. Should be overruled by the derived class.""" self.topmodel.toollist.delTool(self) return 1 # can close def foreground(self, item, node = None, lnum = None): """Move this activity to the foreground. Use the specified "item", unless it's None. Use the specified "node", unless it's None. Should be overruled by the derived class.""" if item: self.setCurrentItem(item) else: self.currentnode = node class ToolList: """List of Tool objects.""" def __init__(self): self.list = [] self.view = None # view for the ToolList, if any def __len__(self): return len(self.list) def addTool(self, tool): """Add a tool to the list of open tools.""" if not tool in self.list: # Add it only once. self.list.append(tool) if self.view: self.view.addTool(tool) def updateTool(self, tool): """Update the way "tool" is displayed. Used when its name was changed.""" if self.view: i = self.list.index(tool) self.view.setTool(i, tool) def getList(self): """Return the list of tools.""" return self.list def getTool(self, idx): return self.list[idx] def delTool(self, tool): """Remove a tool from the list.""" try: i = self.list.index(tool) except ValueError, e: # print "Tool does not exist in list: " + str(e) return self.list.remove(tool) if self.view: self.view.delTool(i) def availableTools(topmodel): """Return the list of available Tool class names.""" cwd = os.getcwd() os.chdir(topmodel.rootdir) l = map(lambda x: x[6:-3], glob("Tools/*.py")) l.sort() os.chdir(cwd) return l def availableActions(item): """Return a list of action names that are possible for "item". Used for the context menu in navigators.""" # Consult all available tools to find out which actions are possible # for this node. if item.node: type = item.node.getFiletype() else: type = '' alist = [] for toolname in availableTools(item.acty.topmodel): exec "import " + toolname dict = eval(toolname + ".canDoActions(item, type)") for a in dict.keys(): if not a in alist: alist.append(a) return alist def runTool(item, action = None): """Run the default tool for an item. Perform "action" if specified. Return the tool.""" node = item.node tool = None # Consult all available tools to find out which one is the best at # editing this node. if node: type = node.getFiletype() else: type = '' foundpri = 0 for toolname in availableTools(item.acty.topmodel): exec "import " + toolname dict = eval(toolname + ".canDoActions(item, type)") # Prefer a tool that can do debugging, editing or viewing. pri = 0 if action: if dict.has_key(action): pri = dict[action] act = action else: if dict.has_key("debug"): pri = dict["debug"] * 3 act = "debug" elif dict.has_key("edit"): pri = dict["edit"] * 2 act = "edit" elif dict.has_key("view"): pri = dict["view"] act = "view" if pri > foundpri: foundtoolname = toolname foundpri = pri foundaction = act if foundpri > 0: # Open the node in the found Tool. tool = runToolByName(foundtoolname, item, foundaction) else: print "Can't find a tool for this item" return tool def runToolByName(toolname, item, action): """Run a tool with name "toolname" for ActyItem "item".""" exec "import " + toolname props = eval(toolname + ".getProperties(item.acty.topmodel)") toollist = eval(toolname + ".toollist") if not toollist or not "mdi" in props.keys(): # Instantiate a new tool for this item. tool = eval(toolname + ".openItem(item, action)") if not tool: # Failed to run the tool or the user cancelled a dialog. return None toollist.append(tool) else: # Re-use an existing tool for this item. Just take the first one # (normally there is only one anyway). tool = toollist[0] tool.openItem(item, action) item.acty.topmodel.toollist.addTool(tool) node = item.node if node: node.topmodel.nodelist.addNode(node) item.addTool(tool, action) item.foreground() return tool # Activity for items that are not related to an activity that the user started. no_activity = None def set_no_activity(topmodel): """Make sure no_activity is set.""" global no_activity if not no_activity: from Activity import Activity from Navigator import ActyItem, Navigator no_activity = Activity("Hidden", topmodel) no_activity.addNav(Navigator("Files", ActyItem("top", ".", no_activity, None, addnode = 0))) def shutdown_no_acty(): """Shutdown, close no_activity. Return non-zero when closed OK.""" if no_activity: # Reset the modified flag, don't ask the user whether it should be # saved. no_activity.modified = 0 return no_activity.close(1) return 1 def gotoFile(topmodel, acty, fname, lnum = None, col = None, off = None): """Show file "fname" and position at the mentioned lnum/col or off. Finds an exisiting file in Activity "acty" or creates one. Finds an existing view or edit action or opens a new one.""" if not acty: # No specific activity given, use no_activity. set_no_activity(topmodel) acty = no_activity item = findOrCreateItem(topmodel, acty, fname) gotoItem(topmodel, item, lnum, col, off) def gotoItem(topmodel, item, lnum = None, col = None, off = None): """Show file "item" and position at the mentioned lnum/col or off. Finds an existing view or edit action or opens a new one.""" tool = findOrRunViewTool(item) if not tool: print "Cannot go to item." elif tool.getProperties().get("set_position"): tool.goto(item, lnum = lnum, col = col, off = off) def toolOpenedFile(tool, name): """Called when a tool opened a file. May add a node to the list of nodes that the tool is using. Returns the node.""" from Navigator import findNodeByName node = findNodeByName(name) if not node or not node.items: # Not an existing node or node is not used in an actyitem. # Create a node and/or actyitem now. set_no_activity(tool.topmodel) item = no_activity.getTopItem().addChildByName(name) node = item.node else: item = node.items[0] # XXX use another one? tool.addItem(item) item.addTool(tool, "edit") return node def showPCFile(topmodel, acty, fname, lnum = None, col = None, off = None, show = 1): """Show PC in file "fname" at the mentioned lnum/col or off. Finds an exisiting file in Activity "acty" or creates one. Finds an existing view or edit action or opens a new one.""" item = findOrCreateItem(topmodel, acty, fname) showPCItem(topmodel, item, lnum, col, off, show) def showPCItem(topmodel, item, lnum = None, col = None, off = None, show = 1): """Show PC in "item" at the mentioned lnum/col or off. Finds an existing view or edit action or opens a new one.""" tool = findOrRunViewTool(item) if not tool: print "Cannot go to item." elif tool.getProperties().get("show_pc"): tool.showPC(item, lnum = lnum, col = col, off = off, show = show) class Breakpoint: """Class used to store info about a breakpoint.""" def __init__(self, tool, ID, enable, dir, fname, lnum = None, col = None, off = None): self.ID = ID # breakpoint number or name self.enable = enable # true or false if dir and not os.path.isabs(fname): self.fname = os.path.join(dir, fname) # dir + file name else: self.fname = fname # file name self.lnum = lnum # line number in fname (one based) self.col = col # column number (zero based) self.off = off # character offset (zero based) self.item = findOrCreateItem(tool.topmodel, tool.itemlist[0].acty, self.fname) def displayBreakpoint(tool, what, bp): """Update breakpoint "bp" with tool "tool" with action "what". Check if the tool supports this.""" if tool.getProperties().get("display_breakpoint"): tool.displayBreakpoint(what, bp) def updateBreakpoints(tool, node): """Called by an editor that opened a node. Check what breakpoints exist for this node and call back the editor to set them.""" for bp in getBreakpoints(node): tool.displayBreakpoint("new", bp) def getBreakpoints(node): """Get a list of all breakpoints for "node". Goes through the list of running tools and gets breakpoints from the ones that have the "get_breakpoints" property.""" list = [] for tool in node.topmodel.toollist.getList(): if tool.getProperties().get("get_breakpoints"): list.extend(tool.getBreakpoints(node)) return list def setBreakpoint(what, node, enable, lnum = None, col = None, off = None): """Change a breakpoint (add/remove/enable/disable). Finds all tools that can set breakpoints and invokes them.""" for tool in node.topmodel.toollist.getList(): if tool.getProperties().get("set_breakpoint"): tool.setBreakpoint(what, node, enable, lnum, col, off) def debugCmd(what, node, enable, lnum = None, col = None, off = None): """Pass on a debugger command (run/cont/step/next/finish). Finds all debuggers that can take commands and invokes them.""" for tool in node.topmodel.toollist.getList(): if tool.getProperties().get("debug_command"): tool.debugCmd(what, node, enable, lnum, col, off) def debugEvalText(tool, text): """Evaluate "text" in a debugger and call back "tool" to display the result (if any). Used by an editor when the mouse pointer is resting on a word and can display the value in a balloon.""" for t in tool.topmodel.toollist.getList(): if t.getProperties().get("eval_text"): t.evalText(tool.showBalloon, text) break def findOrCreateItem(topmodel, acty, fname): """Find an item for "fname". If it doesn't exist yet, add it. To be used by a debugger. Return the item.""" item = acty.findItemByName(Util.full_fname(fname)) if not item: # Use a "Debug" Navigator. XXX: what about non-debuggers? mod = acty.modified nav = acty.findNavByName("Debug") if not nav: import Navigator item = Navigator.ActyItem("debug files", '.', acty, None) nav = Navigator.Navigator("Debug", item) acty.addNav(nav) if acty.name != "Hidden": acty.foreground() # will show the new Navigator item = nav.getTopItem().addChildByName(fname) # Restore the "modified" flag on the Activity, these items will not be # stored in the recipe. acty.modified = mod return item def findOrRunViewTool(item): """Find an existing tool for viewing "item". If it doesn't exist, start one.""" if not item.node: return None tool = item.findViewTool() if not tool: tool = runTool(item, "view") return tool # # Spawning a process for any OS (theoretically). # def spawn(cmd): """Execute "cmd" in a shell and return the process ID, which can be given to stillRunning() to find out the process is still running.""" # Use a dictionary for the result, so we can add an "exited" flag. res = {"pid" : 0, "exited" : 0} if os.name == "posix": # XXX Forking is quite expensive... pid = os.fork() if pid == 0: # child process os.system(cmd) os._exit(0) # parent process res["pid"] = pid elif os.name in [ 'dos', 'os2', 'nt', 'win32' ]: # Need to get cmd.exe or command.exe name. shell = Util.get_shell_name() # XXX this doesn't work for "sh", "bash", etc... # This actually gives us the process handle, not a process ID. # Can't use os.P_DETACH, this always returns zero. res["pid"] = os.spawnv(os.P_NOWAIT, shell, ["/c", cmd]) else: # TODO! os.system(cmd) return res def stillRunning(proc): """Check process "proc", returned by spawn() above, is still running.""" if proc["exited"]: return 0 if os.name == "posix": pid, status = os.waitpid(proc["pid"], os.WNOHANG) if pid != 0 and os.WIFEXITED(status): proc["exited"] = 1 return 0 elif os.name in [ 'dos', 'os2', 'nt', 'win32' ]: try: import win32event res = win32event.WaitForSingleObject(proc["pid"], 0) if res == win32event.WAIT_OBJECT_0: proc["exited"] = 1 return 0 except ImportError: # cannot wait for process because win32event is missing return 0 else: # TODO! return 0 return 1 # gettext: message translation gettextobj = None def init_gettext(topmodel): if gettextobj: gettextobj.bindtextdomain("agide", topmodel.rootdir + "/lang") gettextobj.textdomain("agide") def _null_gettext(x): """Dummy gettext() function.""" return x # Try importing the gettext library. If it works use its gettext() function. try: import gettext gettextobj = gettext _gettext = gettext.gettext except ImportError: _gettext = _null_gettext # vim: set sw=4 et sts=4 tw=79 fo+=l: