================================== ``grail.widgets.gpanelapp`` module ================================== .. include:: ../../version.h .. include:: ref.h .. contents:: Table of Contents :backlinks: top ----------- Description ----------- A skeleton framework for generating the basic MineSightŪ Grail Panel Application (GPA). The framework is an abstraction of all the mechanics of an application that uses the panels/tree idiom. Currently it allows you to customize the menu behavior, event handling, and loading and saving of application specifications. The details for performing these extensions/overrides are provided in this document. In the future, as the :c:`GPanelApp` gains more features, there will be more items that will be exposed for customization. --------------------------- Why the GPanelApp Framework --------------------------- The idea was to construct a somewhat flexible framework for working with the 'panel/tree' concept. Initially the panel/tree concept was designed for the medstyle-to-python-style procedures within the compass package. However, as the panel/tree concept was being stretched to other domains (for example, IP), we found that there were serious coupling between logic that was compass specific and logic that was GUI specific. The :c:`GPanelApp` is really only the basic GUI specifications that lacks problem-domain specific logic. The frameworks goals are, - Flexible enough to allow one to customize as much as one wanted. - Abstract enough to hide as much of the magic-low-level window logic as possible. Now to create your own version, which is no longer dependent on compass-centric concepts--you would just inherit and extend/override the components that you want in your application. By "extend/override" I mean that you really have two ways to customize your own :c:`GPanelApp`. Either you can extend the existing behavior to define some added features, or you can override the default behavior as you please to define your application better. See the following sections for more information on extending/overriding the :c:`GPanelApp` class. ------------------------- Basic GPanelApp Execution ------------------------- For starters lets assume that you don't really care about what the framework does, how it operates, etc. You just want to stuff a bunch of panels into the framework, and start prototyping, and you'll work out the finer details of your application later. The basic :c:`GPanelApp` comes with a very ruff sketch of a default implementation, one that lacks any convincing menu structure or behavior. Except the :c:`GPanelApp` knows how to exit the application really well. An example of using the :c:`GPanelApp` framework straight out of the box would be as follows, .. Python:: class Panel1(GPanel): ... class Panel2(GPanel): ... PANEL_LAYOUT = GFolder("Demo", [Panel1, Panel2]) panelapp = GPanelApp(title="My demo app", panelconfiguration=PANEL_LAYOUT) panelapp.build() panelapp.show() # start the event handler panelapp.mainloop() The :c:`GPanelApp` requires at least these four steps to start execution. 1. The initialization process, goes and starts up the Tkinter environment. 2. The :f:`build` process starts to construct the widgets. The :f:`build` you can safely assume that all the application's widgets have been constructed. 3. The :f:`show` attribute tells the application to show itself to the user. 4. The :f:`mainloop` call starts the event handler. That is all there is to it if you don't want to bother defining menu structures, or any of your own fancy event handling. --------------- Defining a Menu --------------- One of the first customization things you may want to do is define your own menu structure, along with your own event handlers. To define your own menu you would override the :f:`definemenus` function. For an example, lets define one that looks like the compass-centric :c:`GPanelApp`, .. Python:: # inherit basic attributes from GPanelApp class CustomApp(GPanelApp): def definemenu(self): """define my own menu""" self.filemenuitems = [GMenuItem("Run procedure", self.onrun), GMenuSeparator(), GMenuItem("Save variables", self.onsavevars), GMenuItem("Load variables", self.onloadvars), GMenuSeparator(), GMenuItem("Exit", self.onexit)] self.helpmenuitems = [GMenuItem("Help", self.onhelp), GMenuSeparator(), GMenuItem("About",self.onabout)] # now define the menus themselves, they're based on the items... self.filemenu = GMenu("File", self.filemenuitems) self.helpmenu = GMenu("Help", self.helpmenuitems) # now for the menu bar structure # (IMPORTANT! must define self.menus) self.menus = [self.filename, self.helpmenu] Like the example mentions, the :d:`self.menus`, attribute needs to be defined at some point in the :f:`definemenus` function. The :d:`self.menus`, attribute is read to construct the menubar. In fact :d:`self.menus` is just the 'menus' parameter for the GMenuBar class. Back to the example, now we need to define the event handlers that we specified in the menu structure, that's not to hard. First, notice that the GPanelApp already defines an :f:`onexit`, :f:`onhelp`, and :f:`onabout`, although the latter two are essentially stubs, the :f:`onexit` function does provide the basic 'exit' steps. So taking into account that we already have some handlers defined, we can quickly define the others like so, .. Python:: # continued from last example... def onrun(self): self.isrun = 1 # flag run condition self.destroy() # use the destroy method to kill ourselves def onsavevars(self): goandsavevars() # my own method... def onloadvars(self): goandloadvars() # my own method again... There, we are done the basic structure. If we wanted to hook into to an already pre-existing event. Say, for example, we wanted to perform one step prior to the default :f:`onexit` being called, then we would do something like this, .. Python:: def onexit(self): """EXTEND the default onexit()""" do_something_before_exiting() GPanelApp.onexit(self) # call the default exit ------------------------------------------------ Loading and Saving of Application Specifications ------------------------------------------------ By application specifications, we are referring to all that information that you would like to be persistent from run-to-run, regardless if the user decided to save it or not. Anything from, last files used, to last window position, to where the pane separator is specified. There are two functions that you have to override/extend to take advantage of this behavior. They are :f:`loadappspec` and :f:`saveappspec`. Lets define the :f:`loadappspec` for our custom application, assuming that we already have a :c:`WinRegistry` (see grail.misc.winregistry_) object called 'registry' pre-configured, .. Python:: def loadappspec(self, spec): # the standard three specs--see below... if spec is 'wm_state': return self.registry.get('wm_state', 'normal') if spec is 'geometry': return self.registry.get('geometry', '400x400+150+150') if spec is 'navigatorwidth': return self.registry.get('navigatorwidth', '250') if spec is 'myspec': return self.registry.get('myspec', 'dude! who stole my car?') Its important to note that you *have to* define at least three specifications specs, meaning, that if you decide to override the :f:`loadappspec`, you should be prepared to handle the following specs, - 'wm_state' This is fed into the root windows wm_state() method, it indicates if the window should be minimized, maximized, etc... Need to return a string value. - 'geometry' This is the standard Tkinter windows geometry and is sent to the root's geometry() method. Need to return a string value. - 'navigatorwidth' This is the width of the navigator window, and is used to configure where the middle window separator goes. Needs to also be a string value. If you don't really care about these specs, and you just want to charge ahead and define your own, then you could do something like this when overriding the :f:`loadappspec` attribute, .. Python:: def loadappspec(self, spec): if spec is 'myspec': return getmyspec() else: # could care less...let the default loadappspec() handle it return GPanelApp.loadappspec(self, spec) The :c:`GPanelApp` object's :f:`loadappspec` will provide reasonable default values when requested. This would be an example of *extending* the default implementation, you just hook in and do what you like, then let the default behavior take over for everything else. Now for saving specs, you would override the :f:`saveappspec` function. The default is just an empty function, so essentially, the default doesn't save anything. If we want to save specs we need to do it ourselves. As an example, lets consider saving of the three mandatory specs, again assuming that we have a WinRegistry object already pre-configured and called 'registry', .. Python:: def saveappspec(self, spec, value): self.registry[spec] = value # toss them _all_ into the registry. That is all there is to it. With the registry object, the saving becomes rather trivial: the spec becomes the key, and the value is what is tossed under that key. Off course, you may have your own means of saving this specification information. Maybe you want it to go to a file or to go across the internet. It doesn't really matter, you just need to define it in the saveappspec() function. -------------------------------------- Extending the Initialization Arguments -------------------------------------- So your application wants to take a few more arguments upon initialization? How to do this is best explained with an example, lets say we want our app to take one additional variable, for worldwide consistency, we will call it 'foo', .. Python:: class MyApp(GPanelApp): def __init__(self, foo=None, *args, **kws): # toss the other stuff up to the GPanelApp, could care less about what # they are. GPanelApp.__init__(self, *args, **kws) # save our foo self.foo = foo There that's all there is to it. Now the same methodology can be applied to any of the methods, where you just want to *extend* the behavior, but still execute the default code provided. For example, consider the :f:`build` function, .. Python:: def build(self, bars=None, *args, **kws): ilikechocolate(bars) GPanelApp.build(self, *args, **kws) ------- Classes ------- The following classes are defined within this module. GFolder ------- class :dc:`GFolder(name, contents)` Defines the folder structure. .. Note:: Folders can be nested inside the contents supplied to a :c:`GFolder`. In other words you can do the following, .. Python:: FOLDER1 = GFolder("My folder1", [GItem(panel=MyPanel1)]) FOLDER2 = GFolder("My folder2", []) ROOT = GFolder("Root", [FOLDER1, FOLDER2]) And you would get the following 'tree' structure, :: [Root] + [FOLDER1] + MyPanel1 + [FOLDER2] Arguments: :d:`name` : string Name of the folder. :d:`contents` : list of :c:`GItem` or :c:`GFolder` Folder listing, like the example shown above. :f:`contents()` Returns the contents of the folder. GItem ----- class :dc:`GItem(name, state, rtv, invertrtv)` An item, that contains a panel and a name. This is a basic part of the panel/folder configuration scheme used in the :c:`GPanelApp` widget. It represents the panel's information (state, name, etc...). Some further points, - Specifying a :a:`rtv` with an :c:`IntegerRTV` object overrides the state variable. - If you *do not* define a :a:`name`, then there will be a search for the name attribute on the panelclass, specifically for :d:`NAME`. This is also known as 'auto-naming'. For example, .. Python:: class MyPanel(GPanel): NAME = "My sample panel" mypanelitem = GItem(panel=MyPanel) Will set the mypanelitem name to be "My sample panel". The above is equivalent to, .. Python:: class MyPanel(GPanel): pass mypanelitem = GPanel(panel=MyPanel, name="My sample panel.") Sometimes its better to have the name of the panel, and the panel definition lie closer together as in the first example. Signals: This object will emit a :d:`const.sigON_STATE_CHANGE` whenever the internal state of the items goes from disabled to enabled. Arguments: :a:`name` : string Name to give the item. :a:`state` : boolean State of the item (1 = enabled; 0 = disabled). :a:`rtv` : :c:`IntegerRTV` Can control the state of the item. :a:`invertrtv` : integer If this is 1, then the :c:`GItem` will invert the value inside the :a:`rtv` object. :df:`panelclass()` Returns a reference to the panelclass defined for this item. :df:`getstate()` Returns the state of the folder/item (1 = enabled; 0 = disabled). :df:`name()` Returns the name of the folder/item. :df:`setstate(newstate)` Sets the state on the folder (1 = enabled; 0 = disabled). If the newstate is different from the current state, then a sigON_STATE_CHANGE signal is emitted. GOnlinePanelHelp ---------------- class :dc:`GOnlinePanelHelp([panelconfiguration])` Translates a panel configuration into user HTML documentation. Mainly *for internal usage only*. Arguments: :a:`panelconfiguration` : list A list structure containing the panel configuration typically used in a :c:`GPanelApp`. :df:`showhelp(title)` Show help for all the panels. This generates two distinct sections, one is a table of contents that mimics the panelconfiguration and links to the further documentation within the second part of the help. Argument: :a:`title` : string Name of the HTML help document you want to show :df:`showpanelhelp(title, panel)` Displays the help for a given panel. GPanelApp --------- class :dc:`GPanelApp([title, panelconfiguration])` Allows you to build and customize a 'folder/panel' type of application. Arguments: :a:`title` : string The name for your application. :a:`panelconfiguration` : :c:`GFolder` A :c:`GFolder` object containing the layout of all the panels within the tree structure. Methods to override: :f:`definemenus` Specifies how the menu bar is going to be built. :f:`loadappspec` Permits application persistence. :f:`saveappspec` Permits application persistence. :f:`onerror` Handles messages posted to sys.stderr :f:`onabout` Default 'about' menu handler. :f:`onexit` Default 'exit' menu handler (cleans up and shuts down application). :f:`onhelp` Default 'help' menu handler generates online help. :f:`onpanelhelp` Default panel help for active panel :f:`onexpandfolders` Handles a request to expand all folders. :df:`build()` Constructs the widgets; hooks up the relevant event handlers. After this call has been made, you can safely assume that all the widgets have been built. :df:`currentpanel()` Returns the current active panel, or :d:`None` if no panel is active. :df:`definemenus()` Defines how the menu is going to be layed out. Part of :f:`build`. To generate your own menu structure, override this method, and make sure a GMenuBar menus keyword is stored into 'self.menus'. This is the default implementation used for an example, .. Python:: self.filemenuitems = [GMenuItem("Exit", self.onexit)] self.optionsmenuitems = [GMenuItem("Expand All Folders", self.onexpandfolders)] self.helpmenuitems = [GMenuItem("Help", self.onhelp), GMenuItem("Panel Help", self.onpanelhelp), GMenuSeparator(), GMenuItem("About", self.onabout)] self.filemenu = GMenu("File", self.filemenuitems) self.helpmenu = GMenu("Help", self.helpmenuitems) # all the menus self.menus = [self.filemenu, self.helpmenu] .. Note:: Although this may seem odd, the 'self.menus' attribute must be defined with the menubar information if you decide to override this method. If you do not, you will most likely get an :e:`AttributeError` referring to there not being a 'menus' attribute. :df:`destroy()` Cleans up the GPanelApp. :df:`geometry()` Returns the geometry of the application. Basically a forward call to the root-window's geometry attribute. :df:`getitem(panelname)` Returns the :c:`GItem` corresponding to the :a:`panelname`. Raises a :e:`KeyError` exception if the :a:`panelname` can not be found. :df:`getpanel(panelname)` Returns a reference to a panel with the given :a:`panelname`. Raises a :e:`GWidgetError` if the :a:`panelname` can not be found, because either :f:`build` has not been executed yet, or the name is not in the list of panels used in the initial :a:`panelconfiguration`. :df:`getpanelconfiguration()` Returns the :c:`GFolder` and :c:`GItem` constructs used to create the :c:`GPanelApp`. Treat this as a read-only value. Typically used with the grail.widgets.gtools_ :f:`walkpaneltree` function. :df:`getpanespecs()` Returns the size of tree pane window the at the time of calling this. :df:`gettitle()` Returns the title of the application. :df:`loadappspec(spec)` Returns window specification information. This provide a means to perform persistence on what ever you want for your application window. When this was first built, there were three specs that must be satisfied if you decide to override this method. They are, - 'geometry' (can default to: GPanelApp.DEFAULT_GEOMETRY) - 'wm_state' (can default to: GPanelApp.DEFAULT_WM_STATE) - 'navigatorwidth' (can default to: GPanelApp.DEFAULT_NAVIGATOR_WIDTH) If you don't want to define these, and are comfortable with the defaults, just try extending this method like so, .. Python:: def loadappsec(self, spec): if spec='myspec': return 'parrots are smart' else: return GPanelApp.loadspec(spec) # rely on defaults Calling the :c:`GPanelApp` object's :f:`loadspec` method will handle all the default conditions. This function should always return a string. :df:`mainloop()` Forwards to the root window (the application) starts event loop. Your execution thread will return from this function call when the user destroys the window (via some 'exit' event). Or if the destroy method is called. :df:`onabout([event])` Default event handler for the about. Does nothing, but you could start up an GAboutDialog from here. :df:`onerror(errormessage)` Generated when an error is written sys.stderr. This gets called when an error is sent to sys.stderr. Default behavior is to write the message to the statusbar via :f:`statusbarwrite`. :df:`onexit([event])` Default event handler for the exiting the window. :df:`onexpandfolders([event])` Default event handler for an expand all request. Informs the internal :d:`_panelnavigator` object that it should expand all its folders. :df:`onhelp([event])` Default event handler for the menu help. Currently spawns a browser with a listing of all the panel help. :df:`onpanelhelp([event])` Default handler for requests for help on active panels. If no active panel is available, then a little message is shown to the user. :df:`saveappspec(spec, value)` Opposite of loading! This saves the 'spec' with the given value. In this case don't have to handle the 'geometry', 'wm_state', and 'navigatorwidth' spec's, because the default implementation is to completely ignore them (no saving). Typically this method is called during the :f:`savedialog` method. :df:`savedialog()` Saves all pertinent values to via the :f:`saveappspec` method. This method should be called prior to calling :f:`destroy` to maintain dialog persistence. Extend this method if you wish to save other values. :df:`show()` Displays the application to the user. When the :c:`GPanelApp` is initialized, it is initialized in an a 'invisible' iconfied state. Call 'show' to start show the window to the world. :df:`showpanel(panelname)` Requests that a particular panel be shown. .. Warning:: This assumes that the :f:`build` has occurred, otherwise the :c:`GPanelApp` will fail horribly. :df:`statusbarwrite(input)` Writes the input to the :c:`GPanelApp` statusbar. :df:`wm_state()` Returns the wm_state for the application.