grail.widgets.gpanelapp module

Version: 16.2

Table of Contents

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 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 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 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 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 GPanelApp comes with a very ruff sketch of a default implementation, one that lacks any convincing menu structure or behavior. Except the GPanelApp knows how to exit the application really well.

An example of using the GPanelApp framework straight out of the box would be as follows,

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 GPanelApp requires at least these four steps to start execution.

  1. The initialization process, goes and starts up the Tkinter environment.
  2. The build process starts to construct the widgets. The build you can safely assume that all the application's widgets have been constructed.
  3. The show attribute tells the application to show itself to the user.
  4. The 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 definemenus function. For an example, lets define one that looks like the compass-centric GPanelApp,

# 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 self.menus, attribute needs to be defined at some point in the definemenus function. The self.menus, attribute is read to construct the menubar. In fact 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 onexit, onhelp, and onabout, although the latter two are essentially stubs, the 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,

# 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 onexit being called, then we would do something like this,

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 loadappspec and saveappspec.

Lets define the loadappspec for our custom application, assuming that we already have a WinRegistry (see grail.misc.winregistry) object called 'registry' pre-configured,

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 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 loadappspec attribute,

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 GPanelApp object's 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 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',

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',

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 build function,

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 GFolder(name, contents)

Defines the folder structure.

Note

Folders can be nested inside the contents supplied to a GFolder. In other words you can do the following,

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:
name : string
Name of the folder.
contents : list of GItem or GFolder
Folder listing, like the example shown above.
contents()
Returns the contents of the folder.

GItem

class 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 GPanelApp widget. It represents the panel's information (state, name, etc...).

Some further points,

  • Specifying a rtv with an IntegerRTV object overrides the state variable.

  • If you do not define a name, then there will be a search for the name attribute on the panelclass, specifically for NAME. This is also known as 'auto-naming'. For example,

    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,

    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 const.sigON_STATE_CHANGE whenever the internal state of the items goes from disabled to enabled.
Arguments:
name : string
Name to give the item.
state : boolean
State of the item (1 = enabled; 0 = disabled).
rtv : IntegerRTV
Can control the state of the item.
invertrtv : integer
If this is 1, then the GItem will invert the value inside the rtv object.
panelclass()
Returns a reference to the panelclass defined for this item.
getstate()
Returns the state of the folder/item (1 = enabled; 0 = disabled).
name()
Returns the name of the folder/item.
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 GOnlinePanelHelp([panelconfiguration])

Translates a panel configuration into user HTML documentation. Mainly for internal usage only.

Arguments:
panelconfiguration : list
A list structure containing the panel configuration typically used in a GPanelApp.
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:
title : string
Name of the HTML help document you want to show
showpanelhelp(title, panel)
Displays the help for a given panel.

GPanelApp

class GPanelApp([title, panelconfiguration])

Allows you to build and customize a 'folder/panel' type of application.

Arguments:
title : string
The name for your application.
panelconfiguration : GFolder
A GFolder object containing the layout of all the panels within the tree structure.
Methods to override:
definemenus
Specifies how the menu bar is going to be built.
loadappspec
Permits application persistence.
saveappspec
Permits application persistence.
onerror
Handles messages posted to sys.stderr
onabout
Default 'about' menu handler.
onexit
Default 'exit' menu handler (cleans up and shuts down application).
onhelp
Default 'help' menu handler generates online help.
onpanelhelp
Default panel help for active panel
onexpandfolders
Handles a request to expand all folders.
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.
currentpanel()
Returns the current active panel, or None if no panel is active.
definemenus()

Defines how the menu is going to be layed out. Part of 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,

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 AttributeError referring to there not being a 'menus' attribute.

destroy()
Cleans up the GPanelApp.
geometry()
Returns the geometry of the application. Basically a forward call to the root-window's geometry attribute.
getitem(panelname)
Returns the GItem corresponding to the panelname. Raises a KeyError exception if the panelname can not be found.
getpanel(panelname)
Returns a reference to a panel with the given panelname. Raises a GWidgetError if the panelname can not be found, because either build has not been executed yet, or the name is not in the list of panels used in the initial panelconfiguration.
getpanelconfiguration()
Returns the GFolder and GItem constructs used to create the GPanelApp. Treat this as a read-only value. Typically used with the grail.widgets.gtools walkpaneltree function.
getpanespecs()
Returns the size of tree pane window the at the time of calling this.
gettitle()
Returns the title of the application.
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,

def loadappsec(self, spec):
  
if spec='myspec': return 'parrots are smart'
  
else: return GPanelApp.loadspec(spec)   # rely on defaults

Calling the GPanelApp object's loadspec method will handle all the default conditions.

This function should always return a string.

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.

onabout([event])
Default event handler for the about. Does nothing, but you could start up an GAboutDialog from here.
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 statusbarwrite.

onexit([event])
Default event handler for the exiting the window.
onexpandfolders([event])
Default event handler for an expand all request. Informs the internal _panelnavigator object that it should expand all its folders.
onhelp([event])
Default event handler for the menu help. Currently spawns a browser with a listing of all the panel help.
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.
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 savedialog method.

savedialog()

Saves all pertinent values to via the saveappspec method. This method should be called prior to calling destroy to maintain dialog persistence.

Extend this method if you wish to save other values.

show()
Displays the application to the user. When the GPanelApp is initialized, it is initialized in an a 'invisible' iconfied state. Call 'show' to start show the window to the world.
showpanel(panelname)

Requests that a particular panel be shown.

Warning

This assumes that the build has occurred, otherwise the GPanelApp will fail horribly.

statusbarwrite(input)
Writes the input to the GPanelApp statusbar.
wm_state()
Returns the wm_state for the application.