# Part of the A-A-P GUI IDE: The toplevel of the GUI # 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 # Agide uses a GUI for the IDE. The GUI code is called the "view" and the data # structures that are GUI-independent are called the "model". There are links # between the model and the view in nearly all items. from wxPython.wx import * import os import string import sys import types import ActyListView import ToolListView import NodeListView import Activity from NavTree import NavTree import Xterm import Tool import gettext # message translation def _(x): return Tool._gettext(x) # # LAYOUT items # TODO: move this to a configuration file # toplevel_xpos = 1 # X position of toplevel window toplevel_ypos = 1 # Y position of toplevel window toplevel_width = 310 # width of the toplevel window toplevel_height = 500 # height of the toplevel window (inside) topwin_height = 120 # height of the top half, the listbox border_width = 10 # assumed witdh of window decoration border border_height = 30 # assumed height of the window decoration shell_width = 600 shell_height = 600 grep_width = 600 grep_height = 220 debug_width = 600 debug_height = 300 console_width = 600 console_height = 300 # Define the IDs used below. [ # Frame and window IDs wxID_TOPFRAME, wxID_SPLITTER, wxID_NOTEBOOK1, wxID_NOTEBOOK2, wxID_TOPWINDOW, wxID_STATUSBAR, ] = map(lambda x: wxNewId(), range(6)) [ # Menu IDs ID_MENU_OPEN, ID_MENU_NEW, ID_MENU_RECENT, ID_MENU_SAVE, ID_MENU_CLOSE, ID_MENU_EXIT, ID_MENU_ABOUT, ] = map(lambda x: wxNewId(), range(7)) class AgideApp(wxApp): """The application, root for all the topframes.""" def __init__(self, x, topmodel): self.topmodel = topmodel wxApp.__init__(self, x) def OnInit(self): wxInitAllImageHandlers() wxToolTip_Enable(true) try: self.SetAssertMode(wxPYAPP_ASSERT_SUPPRESS) except AttributeError: pass # < 2.3.4 self.topmodel.view = NavFrame(None, self.topmodel) #workaround for running in wxProcess self.topmodel.view.Show() self.SetTopWindow(self.topmodel.view) return true # Display all files in the file dialog, use "*.*" on MS-Windows? wildallfiles = '*' class NavFrame(wxFrame): """The topframe that contains the navigation stuff.""" def __init__(self, parent, topmodel): # The AIDE object is the topmodel. self.topmodel = topmodel self.treecontrols = [] wxFrame.__init__(self, id = wxID_TOPFRAME, parent = parent, pos = wxPoint(toplevel_xpos, toplevel_ypos), size = wxSize(toplevel_width, toplevel_height), style = wxDEFAULT_FRAME_STYLE, title = 'Agide') EVT_CLOSE(self, self.OnFrameClose) # Set up for handling asynchronous events in the main thread. EVT_POSTCALL(self, self.OnPostCall) # Menu bar at the top. self._initMenu() # Status bar at the bottom. self.CreateStatusBar() # Splitter window in the main area, containing two windows. self.splitterwin = wxSplitterWindow(parent = self, id = wxID_SPLITTER, name = 'splitterwin', point = wxPoint(0, 0), style = wxSP_3D) # Create a notebook in each splitterwin self.topnotebook = wxNotebook(parent = self.splitterwin, id = wxID_NOTEBOOK1, name = 'notebook1', style = 0) self.botnotebook = wxNotebook(parent = self.splitterwin, id = wxID_NOTEBOOK2, name = 'notebook2', style = 0) self.splitterwin.SplitHorizontally(self.topnotebook, self.botnotebook, topwin_height) # Activity Listbox in the top window. self.topmodel.actylist.view = ActyListView.ActyListView(self, self.topnotebook, self.topmodel.actylist) # Tool Listbox in the top window. self.topmodel.toollist.view = ToolListView.ToolListView(self, self.topnotebook, self.topmodel.toollist) # Activity Item Listbox in the top window. self.topmodel.nodelist.view = NodeListView.NodeListView(self, self.topnotebook, self.topmodel.nodelist) # Redirect stdout and stderr to use the console window. # The console window is not opened until there is a message. import Console self.consout = Console.ConsoleOut(self, self.topmodel) sys.stdout = self.consout self.stderr = Console.ConsoleErr(self.consout) # Xterms used to execute a program in (if possible). self.xterm_list = [] def _initMenu(self): """Setup the menu for the main Agide window.""" self.menubar = wxMenuBar() self.menu_file = wxMenu() # File.Open self.menubar.Append(self.menu_file, _("&File")) self.menu_file.Append(ID_MENU_OPEN, _("&Open activity..."), "Open an existing project or file") EVT_MENU(self, ID_MENU_OPEN, self.OnMenuOpen) # File.New Activity submenu self.menu_new = wxMenu() self.newmenuitems = {} # class names for new activities, key is menu ID for cl in Activity.classlist(self.topmodel): exec "import " + cl menustring, text = eval(cl + ".getMenuText()") if menustring: id = wxNewId() self.newmenuitems[id] = cl self.menu_new.Append(id, menustring, text) EVT_MENU(self, id, self.OnNewMenu) self.menu_file.AppendMenu(ID_MENU_NEW, _("&New activity"), self.menu_new, "Create a new project or file") # File.Open Recent Activity submenu self.menu_recent = wxMenu() self.recentmenuitems = {} self.menu_file.AppendMenu(ID_MENU_RECENT, _("Open &Recent activity"), self.menu_recent, "Open a recently used project or file") self.updateRecent() # File.Save self.menu_file.Append(ID_MENU_SAVE, _("&Save activity"), "Save the current project") EVT_MENU(self, ID_MENU_SAVE, self.OnMenuSave) # File.Close self.menu_file.Append(ID_MENU_CLOSE, _("&Close activity"), "Close the current project") EVT_MENU(self, ID_MENU_CLOSE, self.OnMenuClose) # File.Exit self.menu_file.Append(ID_MENU_EXIT, _("E&xit"), "Terminate the program") EVT_MENU(self, ID_MENU_EXIT, self.OnMenuExit) self.menu_help = wxMenu() self.menubar.Append(self.menu_help, _("&Help")) self.menu_help.Append(ID_MENU_ABOUT, _("&About"), "Info about this program") EVT_MENU(self, ID_MENU_ABOUT, self.OnMenuAbout) self.SetMenuBar(self.menubar) def raiseConsole(self): """Raise the console window.""" self.consout.doRaise() def doDialog(self, msg, choices, default): """Show a dialog. "msg" is the text for the dialog. "choices" is the list of alternatives. "default" is de index in choices[] for the default choice. Returns the entry in "choices" that was selected or "Cancel" when the dialog was cancelled.""" # There must be a simpler way to do this... dlg = wxDialog(self, -1, _("Agide dialog"), wxDefaultPosition, wxSize(350, 150), style = wxDEFAULT_DIALOG_STYLE | wxRESIZE_BORDER) self.dialog = dlg # Put all the buttons in a horizontal sizer. hbox = wxBoxSizer(wxHORIZONTAL) choiceIDs = {} def_but = None for i in range(len(choices)): id = wxNewId() choiceIDs[id] = i b = wxButton(dlg, id, choices[i]) hbox.Add(b, 0, wxALIGN_CENTER) if i == default: def_but = b EVT_BUTTON(dlg, id, self.OnDialogButton) # Add a spacer between buttons. if i + 1 < len(choices): if wxVERSION_STRING < "2.5": hbox.Add(20, 20, 0, wxEXPAND) else: hbox.Add((20, 20), 0, wxEXPAND) # Put the message and the row of buttons in a vertical sizer. vbox = wxBoxSizer(wxVERTICAL) if wxVERSION_STRING < "2.5": vbox.Add(0, 0, 1) else: vbox.Add((0, 0), 1) vbox.Add(wxStaticText(dlg, 1, msg), 1, wxALIGN_CENTER) if wxVERSION_STRING < "2.5": vbox.Add(0, 0, 1) else: vbox.Add((0, 0), 1) vbox.Add(hbox, 0, wxALIGN_CENTER) if wxVERSION_STRING < "2.5": vbox.Add(0, 0, 1) else: vbox.Add((0, 0), 1) if def_but: # Apprently this doesn't work, hitting return always uses the first # button... def_but.SetDefault() def_but.SetFocus() dlg.SetAutoLayout(true) dlg.SetSizer(vbox) dlg.CentreOnParent(wxBOTH) i = dlg.ShowModal() dlg.Destroy() if choiceIDs.has_key(i): return choices[choiceIDs[i]] return "Cancel" def OnDialogButton(self, event): """Button of the dialog clicked. End the dialog and return the button ID.""" button = event.GetEventObject() dlg = button.GetParent() dlg.EndModal(button.GetId()) def clearNavs(self): """Clear the navigators.""" while self.botnotebook.GetPageCount() > 0: self.botnotebook.RemovePage(0) def showNavs(self, acty): """Show Navigators for Activity "acty" in the bottom notebook.""" # First remove all existing pages. Don't delete them, the contents # would be deleted as well. self.clearNavs() for nav in acty.navlist: if not nav.view: # Special case for GTK to fix redraw problems in a tree. if wxPlatform == '__WXGTK__': nav.prxy, nav.view = wxProxyPanel(self.botnotebook, NavTree, self.treecontrols, nav.topitem) else: nav.view = NavTree(self.botnotebook, self.treecontrols, nav.topitem) nav.prxy = nav.view self.treecontrols.append(nav.view) self.botnotebook.AddPage(nav.prxy, nav.name, nav == acty.currentnav) dirname = "" def updateRecent(self): """Called when the list of recent activities has changed. Updates the submenu with recent activities.""" # Delete all the existing entries for id in self.recentmenuitems.keys(): self.menu_recent.Delete(id) self.recentmenuitems = {} # Add an item for each recent activity, upto 10 items. count = 0 for a in self.topmodel.actylist.getRecentList(): id = wxNewId() self.recentmenuitems[id] = a self.menu_recent.Append(id, a, "Open " + a) EVT_MENU(self, id, self.OnRecentMenu) count = count + 1 if count == 10: break def fileDialog(self, title): """Show a normal file dialog. Used to obtain the name of a new Activity.""" dlg = wxFileDialog(self, title, self.dirname, "", wildallfiles, wxOPEN) if dlg.ShowModal() == wxID_OK: name = dlg.GetPath() # Remember dir for next time. self.dirname = dlg.GetDirectory() else: name = None dlg.Destroy() return name def OnFrameClose(self, e): """The frame is being closed.""" if not self.Shutdown(not e.CanVeto()): e.Veto() def Shutdown(self, forced): """Shutdown every activity; cancel shutdown if one of them does not want to shutdown.""" res = self.topmodel.shutdown(forced) if res: self.consout.shutdown() sys.stdout = sys.__stdout__ sys.stderr = sys.__stderr__ for x in self.xterm_list: x.close() self.xterm_list = [] self.Destroy() return res def postCall(self, function, *args, **kwargs): """Invoked from a different thread: Call "function(args)" from the main thread. Used to handle events synchronously.""" wxPostEvent(self, PostCallEvent(function, args, kwargs)) def OnPostCall(self, event): """Handle a PostCallEvent in the main thread.""" event.doCall() # # Menu event handlers. # def OnMenuOpen(self, e): """File.Open menu event.""" dlg = wxFileDialog(self, _("Choose an activity"), self.dirname, "", wildallfiles, wxOPEN) if dlg.ShowModal() == wxID_OK: name = dlg.GetPath() self.openActy(name) # Remember dir for next time. self.dirname = dlg.GetDirectory() dlg.Destroy() def OnRecentMenu(self, e): """Handles an item in the "Open Recent Activity" menu.""" self.openActy(self.recentmenuitems[e.GetId()]) def openActy(self, name): """Open an activity, for the "Open" and "Open recent" menus.""" i = self.topmodel.actylist.name2index(name) if i >= 0: # This activity already exists, bring it to the foreground. self.topmodel.actylist.view.SetSelection(i) self.topmodel.actylist[i].foreground() else: # Create an activity and add it to the actylistbox. self.topmodel.addActyByName(name) def OnNewMenu(self, e): """Handles an item in the "New Activity" menu.""" self.topmodel.newActy(self.newmenuitems[e.GetId()]) def OnMenuSave(self, e): """Handles an item in the "Save Activity" menu.""" if self.topmodel.actylist.currentacty: self.topmodel.actylist.currentacty.save() def OnMenuClose(self, e): """Handles an item in the "Close Activity" menu.""" cur = self.topmodel.actylist.currentacty if cur: if cur.close(0): self.topmodel.delActy(cur) def OnMenuExit(self, e): """File.Exit menu event.""" self.Shutdown(0) def OnMenuAbout(self, e): """Help.About menu event.""" import AgideVersion import AapVersion dlg = wxMessageDialog(self, _("Agide - The A-A-P GUI IDE\n") + (_("version %s\n") % AgideVersion.version_string) + (_("released %s\n") % AgideVersion.version_date) + "\n" + (_("using Aap version %s\n") % AapVersion.version_string), _("About Agide"), wxOK) dlg.ShowModal() dlg.Destroy() def execute(self, actyitem): """Execute an actyitem.""" print _("Executing %s...") % actyitem.listName() # TODO: allow the user to specify arguments cmd = actyitem.node.name if os.name == "posix": # On Unix we can start a separate xterm to run the program in. # Find an xterm with nothing to do. xterm = None for x in self.xterm_list: if not Tool.stillRunning(x.pid): xterm = x break if not xterm: # Need to start another xterm. xterm = Xterm.Xterm(self.topmodel) self.xterm_list.append(xterm) # Always open the xterm, it may have been closed. tty = xterm.open('Agide program') cmd = cmd + (" < %s > %s 2>&1" % (tty, tty)) xterm.pid = Tool.spawn(cmd) print _("Started %s.") % actyitem.listName() elif os.name in [ 'dos', 'os2', 'nt', 'win32' ]: Tool.spawn('"' + string.replace(cmd, '/', '\\') + '"') print _("Started %s.") % actyitem.listName() else: # TODO: run it in the background! os.system(cmd) print _("Finished executing %s.") % actyitem.listName() EVT_POSTCALL_ID = wxNewEventType() class PostCallEvent(wxPyEvent): """Event to pass the received text from gdb to the main thread.""" def __init__(self, function, args, kwargs): wxPyEvent.__init__(self) self.SetEventType(EVT_POSTCALL_ID) self.function = function self.args = args self.kwargs = kwargs def doCall(self): """Call the function specified in the event.""" apply(self.function, self.args, self.kwargs) def EVT_POSTCALL(win, func): win.Connect(-1, -1, EVT_POSTCALL_ID, func) # From Boa "Utils.py" def wxProxyPanel(parent, Win, *args, **kwargs): """ Function which put's a panel in between two controls. Mainly for better repainting under GTK. Based on a pattern by Kevin Gill. """ panel = wxPanel(parent, -1, style=wxTAB_TRAVERSAL | wxCLIP_CHILDREN) if type(Win) is types.ClassType or type(Win) is types.TypeType: win = apply(Win, (panel,) + args, kwargs) elif type(Win) is types.InstanceType: win = Win win.Reparent(panel) else: raise 'Unhandled type for Win' def OnWinSize(evt, win=win): win.SetSize(evt.GetSize()) EVT_SIZE(panel, OnWinSize) return panel, win # vim: set sw=4 et sts=4 tw=79 fo+=l: