Tutorial: Writing your own Plugin¶
This tutorial presents information for any one wanting to create a custom plug-in for ARTview. Plug-ins are just a special form of a Component. ARTview is all based on Components, so this information is important to anyone (user or developer) wanting to understand ARTview.
Plug-ins can be added by anyone and are encouraged in this open source environment. There are a few rules the developers have established in the course of setting up the ARTview infastructure (see below). There are no limits on how to program your plug-in, for that you can use any of the tools available in the Python programming language.
That said, we do suggest that you start a Github Issue as the intended tool may be in development or we could provide some hard-learned advice or ideas on how to solve the problem.
The Basics¶
To allow the integration of plug-ins in ARTview the developers have had to make some rules to follow. Otherwise you risk the operability of your plug-in or in worse ARTview. Here are the requirements:
Plug-ins must be located in one single file in artview/plugins ending in .py.
The plug-in file must contain a variable
_plugins
, this is a list of plug-ins, normally just one.Plug-ins are always a class, moreover they are always child classes of
Component
. Like this:class MyPlugin(core.Component):
Plug-ins have no mandatory argument and can be started like this:
myplugin = MyPlugin()
If plug-ins must interact with other ARTview components they use
Variable
, not direct call.Plug-ins must have a GUIstart class method, like this:
@classmethod def guiStart(self, parent=None): ################################ # Define Call Parameters # ################################ return self(...), True/FalseLast in order to avoid future problems get
QtCore
andQtGui
modules fromartview.core
and not directly forPyQt4
, even if this is still only an alias.Next we’ll discuss how these are used in creating a plug-in.
The Plug-in File¶
ARTview expects all plug-in files to be present in artview/plugins and with a .py extension (e.g my_plugin.py) and to be importable into Python. There must also exist a (possibly empty) list of plug-ins in the attribute
_plugins
(e.g_plugins = [MyPlugin]
. Only plug-ins present in such list are added toartview.plugins
. Files starting with an underscore(_) are ignored. This allows the separation of a plug-in into multiple file or even folders if needed.As the file my_plugin.py is imported inside ARTview you should not import it in an absolute sense, but rather make this a relative import. An example is instead of
from artview import core, components
dofrom .. import core, components
.
The Component Class¶
Plug-ins are a special case of Components, therefore it must work just like one. The first requirement being that it is a class derived from
Component
. This class in turn inheritsPyQt4.QtGui.QMainWindow
, so you can use any PyQt method of a QMainWindow while building your component. Accordingly, there is just one difference fromQMainWindow
andComponent
:
Component
passes keyPressEvents to its parent, whileQMainWindow
mostly ignores them.Another aspect of
Component
is that it always has a string name. This has two functions: First, it will define the window title; and Second, ARTview may use it to identify different instances of the same component. Therefore it is important for the user to have the potential to define the name at initialization. But there is a helpful standard to follow, the common practice of capitilization relatively common in Python programming, along with no underscores. For instancedef __init__(..., name="MyPlugin", ...):
.Further important points are:
- As of now ARTview keeps a list of initialized components in
artview.core.core.componentsList
.Component
has the methodsconnectSharedVariable
anddisconnectSharedVariable
, which will be explained in the next section.Finally it is our policy that all components are able to stand on their own. One must be able to execute it as the only ARTview component, even if it depends of other ones to work properly. Parallel to that, starting a component from another component is not prohibited, but it’s strongly discouraged. Component iteration shall be performed mainly using shared variables.
Graphical Start¶
A graphical start is mandatory for plug-ins. A class method called
GUIstart
that receives an optional parent argument and returns two values: an initialized instance of the the plug-in and a boolean value. The boolean value will be used byMenu
. If False, the menu instance will executeaddLayoutWidget
, otherwise the plug-in will be an independent window. The main difficulty in writing a method is defining the arguments needed for initializing your plug-in.Here are some tools in ARTview to hopefully help:
artview.core.common._SimplePluginStart
will ask the user for a name and if the plug-in should be an independent window. Use like this:def guiStart(self, parent=None): kwargs, independent = core.common._SimplePluginStart( "CalculateAttenuation").startDisplay() kwargs['parent'] = parent return self(**kwargs), independent
artview.core.choose_variable.VariableChoose
will present the user a tree view of the current components and its shared variables, allowing the selection of one instance.This is a more historical request, but as for now it is still useful and therefore still mandatory.
Example¶
Combining the above tutorial, here is a skeleton outline for your Plug-in:
# Load the needed packages from .. import core, components class MyPlugin(core.Component): @classmethod def guiStart(self, parent=None): kwargs, independent = core.common._SimplePluginStart( "MyPlugin").startDisplay() kwargs['parent'] = parent return self(**kwargs), independent def __init__(self, VmyVar=None, name="MyPlugin", parent=None): if VmyVar is None: valid_value = "something" self.VmyVar = core.Variable(valid_value) else: self.VmyVar = VmyVar self.sharedVariables = {"VmyVar": self.NewMyVar} self.connectAllVariables() ################################ # Build Plug-in # ################################ # don`t do: self.VmyVar.change(value, True) # but rather: self.VmyVar.change(value, False) # don`t do: self.VmyVar.emit(...) # but rather: self.NewMyVar(...) # show plugin self.show() ################################ # Other Methods # ################################ def NewMyVar(self, variable, strong): print self.VmyVar.value # => "something else" _plugins=[MyPlugin]