Salome HOME
Update copyrights
[modules/shaper.git] / doc / tui / first_feature_help.doc
1 /*!
2 \page first_feature_help How to create custom features or plugins
3
4 A SHAPER module consists of one or several plug-ins which provide implementation of Module features. To extend the application functionality, developers are able to add their own features into existent plugins. Also, it is possible to create a custom plugin, if necessary. 
5 \n
6 This document describes the basic principles of plugin/feature system and shows how to use the API for writing a feature or plugin. Currently, the API is available for C++ and Python languages. Plugin, written in C++ is a shared object (dll); For Python, it is regular python module, with *.py extension. 
7 \n
8 <h3>XML configuration of the module</h3>
9 By default, all application's plugins are stored in the `/plugins` folder. However, it is possible to change this path in preferences: "Preferences >> Module|Plugins >> Default Path".
10 \n
11 The plugins directory have to contain `plugins.xml` file, which declares plugins included in the module and describes their parameters, like this:
12 \code
13 <plugin library="FooPlugin" configuration="plugin-Foo.xml"/>
14 \endcode
15 or, for python plugin:
16 \code
17 <plugin script="PythonicPlugin" configuration="plugin-Pythonic.xml"/>
18 \endcode
19 First example declares FooPlugin, which is library (`FooPlugin.dll` or `FooPlugin.so`) with "plugin-Foo.xml" configuration file. The second - a Python module, `PythonicPlugin.py`. All the declared libraries, scripts and configuration files should be placed in the same directory as the `plugins.xml`. Note also, that `library` and `script` attributes should not contain any extensions (*.py, *.dll); However, the `configuration` attribute doesn't have any pattern and just have to match name of the plugin configuration file.
20 \n
21 <h3>XML configuration of a plugin</h3>
22 The plugin configuration files contains description of features: 
23 <ol>
24 <li>Position in the main menu: workbench and group.</li>
25 <li>Visual representation of feature's button: text, icon, tooltip</li>
26 <li>Representation feature's in the property panel (widgets)</li>
27 </ol>
28 Here is example configuration for a feature for storing and editing a double value:
29 \code
30 <plugin>
31   <workbench id="FooTab">
32     <group "Counters">
33       <feature id="CppDoubleCounter" title="Counter">
34         <doublevalue id="DoubleCounterData" label="Value" default="0"/>
35       </feature>
36     </group>
37   </workbench>
38 </plugin>
39 \endcode
40 Now we will show how to implement a plugin with this feature using the C++ and Python APIs.
41 \n
42 <h3>Creating a plugin</h3>
43 First of all, you should create/modify all configuration files (*.xml) in your `plugin` directory, as shown in examples before. For `plugin-Pythonic.xml` you may use the same content as in the `plugin-Foo.xml`, just change the name of the feature, in example, to `PythonDoubleCounter`. In this case you will get two features in one workbench and group, despite the fact that they are from different plugins.
44 \n
45 Secondly, you should create a subclass of ModelAPI_Plugin. Constructor on the subclass should register itself in the application. In C++ it is:
46 \code
47 #include <ModelAPI_Plugin.h>
48 //...
49 class FooPlugin : public ModelAPI_Plugin {
50   
51   FooPlugin() // Constructor
52   {
53     ModelAPI_Session::get()->registerPlugin(this);
54   }
55   //...
56 \endcode
57 And in Python:
58 \code
59 import ModelAPI
60 #...
61 class PythonicPlugin(ModelAPI.ModelAPI_Plugin)
62   
63   def __init__(self): #constructor
64     ModelAPI.ModelAPI_Plugin.__init__(self) # call ancestor's constructor
65     aSession = ModelAPI.ModelAPI_Session.get()
66     aSession.registerPlugin(self) # register itself
67   #...
68 \endcode
69 Furthermore, your class must have implementation of the `createFeature(...)` method, which should create corresponding feature object by the given id:
70 \code
71 FeaturePtr FooPlugin::createFeature(std::string theFeatureID)
72 {
73   if (theFeatureID == "CppDoubleCounter") {
74     return FeaturePtr(new CppDoubleCounter);
75   } else {
76     return FeaturePtr()
77   }
78 }
79 \endcode
80 It is a bit more tricky for Python - you should pass the ownership of created object from Python to the application by calling the \_\_disown\_\_() method:
81 \code
82     def createFeature(self, theFeatureID):
83       if theFeatureID == "PythonDoubleCounter":
84         return PythonDoubleCounter().__disown__() # passing the ownership
85       else:
86             return None
87 \endcode
88 Now your plugin is able to create features, declared in its configuration file. However, to register the plugin properly, its constructor must be called on loading of the library (script), like this:
89 \code
90 static FooPlugin* MY_FOOPLUGIN_INSTANCE = new FooPlugin();
91 \endcode
92 Please note, that some linux platforms required unique names for static variables.
93 \n
94 For Python, note that this code should be in the module's namespace (has no leading spaces):
95 \code
96 plugin = PythonicPlugin()
97 plugin.__disown__()
98 \endcode
99 Plugin is created, lets pass over to the feature's implementation.
100 \n
101 <h3>Creating a feature</h3>
102 Like a plugin, feature has its own base class - ModelAPI_Feature. 
103 \code
104 #include <ModelAPI_Feature.h>
105 //...
106 class CppDoubleCounter : public ModelAPI_Feature { //...
107 \endcode
108 Python:
109 \code
110 import ModelAPI
111 #...
112 class PythonDoubleCounter(ModelAPI.ModelAPI_Feature):
113
114   def __init__(self):
115     ModelAPI.ModelAPI_Feature.__init__(self) # default constructor;
116 \endcode
117 And like a plugin implements a functionality to create 'feature' objects by string id, feature defines which type of data should be stored in model and how this data should be processed. The `initAttributes(...)` method links feature's widget with data model:
118 \code
119 void ConstructionPlugin_Point::initAttributes()
120 {
121   data()->addAttribute("DoubleCounterData", ModelAPI_AttributeDouble::typeId());
122 }
123 \endcode
124 Python:
125 \code
126   def initAttributes(self):
127     self.data().addAttribute("DoubleCounterData", ModelAPI.ModelAPI_AttributeDouble.typeId())
128 \endcode
129 As you may notice, this method defines that feature has a widget with "DoubleCounterData" id, which has Double type. Therefore, if your feature uses, in example, three widgets, the `initAttributes()` method should 'init' three attributes.
130 \n
131 Sometimes, it is not enough just to get data (ModelAPI_Data) from user input, and an internal logic (how to process the data) is required. The `execute()` method gives ability to implement it. In our example, we will just print the data from the input to console:
132 \code
133 void CppDoubleCounter::execute()
134 {
135   double d = data()->real("DoubleCounterData")->value();
136   std::cout << "User input: " << d << std::endl;
137 }
138 \endcode
139 Python:
140 \code
141   def execute(self):
142     d = data().real("DoubleCounterData").value()
143         print "User input: ", d
144 \endcode
145
146 <h3>Creation of a Qt panel and custom controls in the plugin</h3>
147 Plugin allows creating of a specific property panel content and a custom widget. The panel content will be shown instead of controls container in the application property panel. The custom widget will be shown among other standard and custom widgets in the Property Panel.
148
149 To provide this SHAPER has a widget creator interface and a factory of the creators. Each plugin which creates panels or custom widgets should implement own creator and register it in this factory by some unique string-ID. This creator will be obtained by this name and create new controls.
150 Steps to create the Qt property panel content are the following:
151 <ol>
152   <li>append Qt library dependencies in plugin project</li>
153   <li>write the custom panel content. It should be a child of a QWidget. It is necessary to put it in the application Property Panel.</li>
154   <li>define a unique panel name. Put this name in the "property_panel_id" section of the feature. If this XML section is filled, XML sections of feature attributes should not be defined, they will be ignored. The following example creates a custom panel:</li>
155     \code
156     <plugin>
157       <workbench id="FooTab">
158         <group id="Counters">
159           <feature id="CppDoubleCounter" title="Counter" property_panel_id = "CountersPanel">
160           </feature>
161         </group>
162       </workbench>
163     </plugin>
164     \endcode
165     <li>write a widget creator, which will create an instance of a panel content by the unique name. This creator must inherit SHAPER widget creator interface. It will implement a virtual method of a panel creation.</li>
166     <li>create an instance of the widget creator and register it in the widget creator factory.</li>
167 </ol>
168 SamplePanelPlugin_Plugin is an example plugin to create a custom property panel.
169
170
171 Steps to create a custom widget are the following:
172 <ol>
173   <li>append Qt library dependencies in plugin project</li>
174   <li>write the custom widget. It should be a child of a SHAPER model widget. It provides a set of standard Qt controls and methods to apply these controls content into the model and back. So, the next virtual methods should be implemented:</li>
175   <ol>
176     <li>provides all internal Qt widgets (to let application to listen "value changed" Qt events, etc.)</li>
177     <li>store of the values from controls to data model by request</li>
178     <li>fill Qt widgets with the values from data model by request</li>
179   </ol>
180   <li>define a unique key value for the custom widget, add a section with this key in the XML, for an example:</li>
181     \code
182     <plugin>
183       <workbench id="FooTab">
184         <group id="Counters">
185           <feature id="CppDoubleCounter" title="Counter">
186             <sample_combo_box id="ComboValue" values="Value_1, Value_2"/>
187           </feature>
188         </group>
189       </workbench>
190     </plugin>
191     \endcode
192     <li>write a widget creator, which will create an instance of a custom widget by the unique name. This creator must inherit SHAPER widget creator interface. It will implement a virtual method of a widget creation. In this method some information is accessible, which can be useful in this widget. This is API of an XML configuration of this widget and a workshop instance to obtain some information about application state if needed.</li>
193     <li>create an instance of the widget creator and register it in the widget creator factory.</li>
194 </ol>
195
196 <h3>To sum everything up, we:</h3>
197 <ol>
198 <li>Declared two custom plugins in the plugins.xml</li>
199 <li>Declared a feature for each plugin</li>
200 <li>Created a custom plugin, by subclassing from ModelAPI_Plugin and implementation of `createFeature(...)` method</li>
201 <li>Created a custom feature, by subclassing from ModelAPI_Feature and implementation of `initAttributes()` and `execute()` methods</li>
202 </ol>
203 If you writing a C++ plugin you should compile and link all sources in a dynamic link library (shared object). In Windows, do not forget to <a href="https://msdn.microsoft.com/en-us/library/3y1sfaz2.aspx">export</a> symbols.
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233 */