ArchitectureOverview - robmcmullen/peppy GitHub Wiki
Peppy is based on Python 2.4 or later, wxPython 2.8 or later, the wx.StyledTextCtrl, the python bindings to the AUI library, and Yapsy Plugins for plugins and dynamic extensibility. The user experience is designed to feel like XEmacs.
The basis of peppy is the Yapsy plugin system. This is a versioning plugin system that allows plugins to be loaded and updated at will.
By default, plugins are loaded from the peppy/plugins directory in the peppy source, and from the plugins directory within the peppy configuratoin directory (i.e. .peppy/plugins on unix, and /Document s and Settings/username/Application Data/Peppy on windows. ??? on Mac. If there are multiple plugins of the same name, only the most recent version is activated by default.
Plugins are classes that implement the IPeppyPlugin interface and are loaded through the PluginManager instance of the main application. Only the desired methods of IPeppyPlugin need to be implemented -- all others are ignored if not implemented by the plugin.
Buffers represent the data storage of the file that is being edited, and the storage container is based on the wx.StyledTextCtrl (STC) interface. If the file can be represented as text or a reasonable length string of bytes, then it is an actual STC instance or subclass. A limitation of the STC is that the entire file is read into memory, so if the file is too big or there is some other reason not to load it all into memory at once, a new class that implements the STCInterface object is required.
The Buffer's STC provides the data storage, but not the display of the data. Display is left to the major mode (see below), which refers to the Buffer's STC to get the data.
STC provides the idea of a Document and a DocPtr. These are opaque objects, as they're implemented in the Scintilla C++ source code, but it provides for us the concept of multiple views of the same data. For each file, the Buffer class maintains an STC instance that holds the primary copy of the data, and other views reference this primary Buffer STC by pointing their viewing STC to the Buffer's reference DocPtr.
Files are loaded through the various protocol handlers. All files are named by URL (defaulting to the file: protocol), where the protocol identifier specifies the handler to use. The basic protocols are specified in the iofilter.py module, and are implemented as Trac plugins.
Major modes are objects subclassed from wx.Panel that allow interaction with the Buffer's data. They are views of the Buffer, displayed in the notebook of the frame (see below for a description of Frames). Changing tabs in the notebook flips between different views so that multiple files can be open at the same time. Since peppy's focus is a text editor, most of the major modes (but not all) are implemented as STC instances.
If the major mode uses an STC to display the text, it will use its own instance of an STC. The reason this is done is because views can be created and destroyed at will, and it is much easier to just have a buffer STC that doesn't move rather than trying to reparent the last remaining view back to the buffer. Simplicity, not optimization. And all that.
In the case of a single view of a buffer that uses an STC to display the text, two STCs exist: one for the buffer that acts as the primary storage of the data, and one for the view for the user interaction. Each additional view of the buffer will use another STC, so there are always n+1 STCs for every n views of the data.
It is not required that a major mode use the STC to display data. An example is the HexEditMode -- it uses the wx.Grid widget to display data from the Buffer's STC as hexadecimal numbers. Changes in the hex numbers will be reflected back to other views of the data (even those that do use the STC to display data) because each view is linked to the Buffer's STC.
If the major mode doesn't use the STC for display, it can get information directly from the Buffer's STC; it does not have to create its own hidden STC that references the Buffer's STC.
The layout of the major mode within the notebook is controlled by an AuiManager. The main editing window is generated using the createEditWindow method of the major mode subclass, and it is placed in the center pane of the AuiManager. Minor modes (see below) can occupy the other panes.
Major modes are registered by implementing the IMajorModeMatcher trac interface. Since implementing a trac interface implies that you are creating a singleton, the class that implements this must be different than the major mode class. The methods provided by the IMajorModeMatcher allow the MajorModeLoader to determine which major mode should be assigned to view a file. The matcher provides several methods that try to examine the filename or the data in the file itself to determine if the major mode is relevant to the file.
Minor modes augment functionality of major modes. They can add to the user interface, but they aren't required to provide any user interface components. They are subclassed from wx.Window and have MinorMode as a mixin class.
As noted above, the major mode is a wx.Panel that is managed by an AuiManager, where the editing window occupies the center pane of the manager. The other panes are available for any user interface components that are created by the minor mode.
Sidebars are components that are associated with the frame, not the current major mode. Their user interface components (if any) don't change when the major mode changes. An example of this is the FileBrowser plugin that shows a tree view of the filesystem and allows the user to select files by navigating through this tree.
The BufferFrame is subclassed from wx.Frame, meaning that it is a toplevel window. It contains the menubar, toolbar, and the application area. The application area is governed by an AuiManager that places the notebook that holds the major modes in the center pane of the manager, and allows the other panes (top, bottom, left, and right) to hold global plugins.
Note that there are several AuiManagers involved here -- the AuiManager of the frame only manages the notebook in the center pane. Each notebook page is represented by the major mode, which is a wx.Panel subclass that itself has an AuiManager managing its contents. So, there's a hierarchy here, and at some point there will be a picture
here
that shows the relationships between all the components.
As I've mentioned elsewhere, multiple top-level frames are allowed.
The menubar is a work in progress. I always though the idea of hardcoding menu items to their callback functions was a bad idea, because it meant that your frame class got bogged down with a lot of extra stuff. Another drawback is in any reasonable sized application, there are so many menu items that the code that creates the menu item is far away from the code that actually performs the action, leading to less code cohesion.
So, menu items are reborn here as actions. An action is declared as a subclass of SelectAction, and the menu items are linked together by a constraint system that attempts to order the menu items on the fly.
One of the big problems I had in designing my precursor menu systems was the synchronization of the menu state and the menu item state. Finally, it dawned on me that the menu item should never store the state itself, rather, it should always refer back to the application to set and retrieve its state. This way, there are never two different variables that can get out of sync. So, actions override methods like isEnabled() and isChecked() that report state, and use the action() method to set the state.
Menu items are simply actions that have been told to present themselves in the menu bar. Every action has a class attribute called default_menu, which indicates the default menu and placement of the action. In the future, the menu will be completely customizable by a configuration file, but for now, the menu item is placed based on what is in default_menu.
default_menu holds either a tuple of information, or a single string. If it is a tuple, the first element contains the menu to which the action belongs, and the second contains the weight: a number between 0 and 1000 indicating the relative position within the menu.
Toolbars are quite similar in principle to menubars as described above. The same actions can be used in both places, and in fact this provides the bonus that the menu and toolbar will always remain in sync with each other. If the same action exists in both places, the status of the item (enabled, checked, radio state, etc.) will always correspond because they are pointing to the same extrinsic data to maintain state.
The class attribute icon must be present in order for an action to appear in the toolbar.
The AUI framework is very cool because it allows the custom placement of toolbars. They can be reordered, hidden, or even popped out of the framework to become floating toolbars. This is still a work in progress for me to figure out how to save the state of these things.