e79fc3774c7f16fb2381815321aa3ce228a05f77
[bertos.git] / wizard / BModulePage.py
1 #!/usr/bin/env python
2 # encoding: utf-8
3 #
4 # This file is part of BeRTOS.
5 #
6 # Bertos is free software; you can redistribute it and/or modify
7 # it under the terms of the GNU General Public License as published by
8 # the Free Software Foundation; either version 2 of the License, or
9 # (at your option) any later version.
10 #
11 # This program is distributed in the hope that it will be useful,
12 # but WITHOUT ANY WARRANTY; without even the implied warranty of
13 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14 # GNU General Public License for more details.
15 #
16 # You should have received a copy of the GNU General Public License
17 # along with this program; if not, write to the Free Software
18 # Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
19 #
20 # As a special exception, you may use this file as part of a free software
21 # library without restriction.  Specifically, if other files instantiate
22 # templates or use macros or inline functions from this file, or you compile
23 # this file and link it with other files to produce an executable, this
24 # file does not by itself cause the resulting executable to be covered by
25 # the GNU General Public License.  This exception does not however
26 # invalidate any other reasons why the executable file might be covered by
27 # the GNU General Public License.
28 #
29 # Copyright 2008 Develer S.r.l. (http://www.develer.com/)
30 #
31 # $Id$
32 #
33 # Author: Lorenzo Berni <duplo@develer.com>
34 #
35
36 import os
37
38 from PyQt4.QtGui import *
39 from BWizardPage import *
40 import bertos_utils
41
42 from bertos_utils import SupportedException
43 from DefineException import *
44 from const import *
45
46 class BModulePage(BWizardPage):
47     """
48     Page of the wizard that permits to select and configurate the BeRTOS modules.
49     """
50     
51     def __init__(self):
52         BWizardPage.__init__(self, UI_LOCATION + "/module_select.ui")
53         self.setTitle(self.tr("Configure the BeRTOS modules"))
54         self._control_group = QControlGroup()
55         ## special connection needed for the QControlGroup
56         self.connect(self._control_group, SIGNAL("stateChanged"), self.saveValue)
57     
58     ## Overloaded BWizardPage methods ##
59
60     def setupUi(self):
61         """
62         Overload of BWizardPage setupUi method.
63         """
64         self.pageContent.moduleTree.clear()
65         self.pageContent.moduleTree.setHeaderHidden(True)
66         self.pageContent.propertyTable.horizontalHeader().setResizeMode(QHeaderView.Stretch)
67         self.pageContent.propertyTable.horizontalHeader().setVisible(False)
68         self.pageContent.propertyTable.verticalHeader().setResizeMode(QHeaderView.ResizeToContents)
69         self.pageContent.propertyTable.verticalHeader().setVisible(False)
70         self.pageContent.propertyTable.setColumnCount(2)
71         self.pageContent.propertyTable.setRowCount(0)
72         self.pageContent.moduleLabel.setVisible(False)
73         self.pageContent.warningLabel.setVisible(False)
74     
75     def connectSignals(self):
76         """
77         Overload of the BWizardPage connectSignals method.
78         """
79         self.connect(self.pageContent.moduleTree, SIGNAL("itemPressed(QTreeWidgetItem*, int)"), self.fillPropertyTable)
80         self.connect(self.pageContent.moduleTree, SIGNAL("itemPressed(QTreeWidgetItem*, int)"), self.moduleClicked)
81         self.connect(self.pageContent.moduleTree, SIGNAL("itemChanged(QTreeWidgetItem*, int)"), self.dependencyCheck)
82         self.connect(self.pageContent.propertyTable, SIGNAL("itemSelectionChanged()"), self.showPropertyDescription)
83
84     def reloadData(self):
85         """
86         Overload of the BWizardPage reloadData method.
87         """
88         QApplication.instance().setOverrideCursor(Qt.WaitCursor)
89         self.setupUi()
90         self.loadModuleData()
91         self.fillModuleTree()
92         QApplication.instance().restoreOverrideCursor()
93     
94     ####
95     
96     ## Slots ##
97
98     def moduleClicked(self, item, column):
99         self.setBold(item, False)
100
101     def fillPropertyTable(self):
102         """
103         Slot called when the user selects a module from the module tree.
104         Fills the property table using the configuration parameters defined in
105         the source tree.
106         """
107         module = self.currentModule()
108         if module:
109             try:
110                 supported = bertos_utils.isSupported(self.project(), module=module)
111             except SupportedException, e:
112                 self.exceptionOccurred(self.tr("Error evaluating \"%1\" for module %2").arg(e.support_string).arg(module))
113                 supported = True
114             self._control_group.clear()
115             configuration = self.projectInfo("MODULES")[module]["configuration"]
116             module_description = self.projectInfo("MODULES")[module]["description"]
117             self.pageContent.moduleLabel.setText(module_description)
118             self.pageContent.moduleLabel.setVisible(True)
119             if not supported:
120                 self.pageContent.warningLabel.setVisible(True)
121                 selected_cpu = self.projectInfo("CPU_NAME")
122                 self.pageContent.warningLabel.setText(self.tr("<font color='#FF0000'>Warning: the selected module, \
123                     is not completely supported by the %1.</font>").arg(selected_cpu))
124             else:
125                 self.pageContent.warningLabel.setVisible(False)
126             self.pageContent.propertyTable.clear()
127             self.pageContent.propertyTable.setRowCount(0)
128             if configuration != "":
129                 configurations = self.projectInfo("CONFIGURATIONS")[configuration]
130                 param_list = sorted(configurations["paramlist"])
131                 index = 0
132                 for i, property in param_list:
133                     if "type" in configurations[property]["informations"] and configurations[property]["informations"]["type"] == "autoenabled":
134                         # Doesn't show the hidden fields
135                         continue
136                     try:
137                         param_supported = bertos_utils.isSupported(self.project(), property_id=(configuration, property))
138                     except SupportedException, e:
139                         self.exceptionOccurred(self.tr("Error evaluating \"%1\" for parameter %2").arg(e.support_string).arg(property))
140                         param_supported = True
141                     if not param_supported:
142                         # Doesn't show the unsupported parameters
143                         continue
144                     # Set the row count to the current index + 1
145                     self.pageContent.propertyTable.setRowCount(index + 1)
146                     item = QTableWidgetItem(configurations[property]["brief"])
147                     item.setData(Qt.UserRole, qvariant_converter.convertString(property))
148                     self.pageContent.propertyTable.setItem(index, 0, item)
149                     if "type" in configurations[property]["informations"] and configurations[property]["informations"]["type"] == "boolean":
150                         self.insertCheckBox(index, configurations[property]["value"])
151                     elif "type" in configurations[property]["informations"] and configurations[property]["informations"]["type"] == "enum":
152                         self.insertComboBox(index, configurations[property]["value"], configurations[property]["informations"]["value_list"])
153                     elif "type" in configurations[property]["informations"] and configurations[property]["informations"]["type"] == "int":
154                         self.insertSpinBox(index, configurations[property]["value"], configurations[property]["informations"])
155                     else:
156                         # Not defined type, rendered as a text field
157                         self.pageContent.propertyTable.setItem(index, 1, QTableWidgetItem(configurations[property]["value"]))
158                     index += 1
159             if self.pageContent.propertyTable.rowCount() == 0:
160                 module_label = self.pageContent.moduleLabel.text()
161                 module_label += "\n\nNo configuration needed."
162                 self.pageContent.moduleLabel.setText(module_label) 
163         else:
164             self.pageContent.moduleLabel.setText("")
165             self.pageContent.moduleLabel.setVisible(False)
166             self.pageContent.propertyTable.clear()
167             self.pageContent.propertyTable.setRowCount(0)
168
169     def dependencyCheck(self, item):
170         """
171         Checks the dependencies of the module associated with the given item.
172         """
173         checked = False
174         module = unicode(item.text(0))
175         if item.checkState(0) == Qt.Checked:
176             self.moduleSelected(module)
177         else:
178             self.moduleUnselected(module)
179             self.removeFileDependencies(module)
180
181     def showPropertyDescription(self):
182         """
183         Slot called when the property selection changes. Shows the description
184         of the selected property.
185         """
186         self.resetPropertyDescription()
187         configurations = self.currentModuleConfigurations()
188         if self.currentProperty() in configurations:
189             description = configurations[self.currentProperty()]["brief"]
190             name = self.currentProperty()
191             self.currentPropertyItem().setText(description + "\n" + name)
192
193     def saveValue(self, index):
194         """
195         Slot called when the user modifies one of the configuration parameters.
196         It stores the new value."""
197         property = qvariant_converter.getString(self.pageContent.propertyTable.item(index, 0).data(Qt.UserRole))
198         configuration = self.projectInfo("MODULES")[self.currentModule()]["configuration"]
199         configurations = self.projectInfo("CONFIGURATIONS")
200         if "type" not in configurations[configuration][property]["informations"] or configurations[configuration][property]["informations"]["type"] == "int":
201             configurations[configuration][property]["value"] = unicode(int(self.pageContent.propertyTable.cellWidget(index, 1).value()))
202         elif configurations[configuration][property]["informations"]["type"] == "enum":
203             configurations[configuration][property]["value"] = unicode(self.pageContent.propertyTable.cellWidget(index, 1).currentText())
204         elif configurations[configuration][property]["informations"]["type"] == "boolean":
205             if self.pageContent.propertyTable.cellWidget(index, 1).isChecked():
206                 configurations[configuration][property]["value"] = "1"
207             else:
208                 configurations[configuration][property]["value"] = "0"
209         self.setProjectInfo("CONFIGURATIONS", configurations)
210
211     ####
212     
213     def loadModuleData(self):
214         """
215         Loads the module data.
216         """
217         # Load the module data only if it isn't already loaded
218         if not self.projectInfo("MODULES") \
219                 and not self.projectInfo("LISTS") \
220                 and not self.projectInfo("CONFIGURATIONS"):
221             try:
222                 bertos_utils.loadModuleData(self.project())
223             except ModuleDefineException, e:
224                 self.exceptionOccurred(self.tr("Error parsing line '%2' in file %1").arg(e.path).arg(e.line))
225             except EnumDefineException, e:
226                 self.exceptionOccurred(self.tr("Error parsing line '%2' in file %1").arg(e.path).arg(e.line))
227             except ConfigurationDefineException, e:
228                 self.exceptionOccurred(self.tr("Error parsing line '%2' in file %1").arg(e.path).arg(e.line))
229     
230     def fillModuleTree(self):
231         """
232         Fills the module tree with the module entries separated in categories.
233         """
234         self.pageContent.moduleTree.clear()
235         modules = self.projectInfo("MODULES")
236         if not modules:
237             return
238         categories = {}
239         for module, information in modules.items():
240             if information["category"] not in categories:
241                 categories[information["category"]] = []
242             categories[information["category"]].append(module)
243         for category, module_list in categories.items():
244             item = QTreeWidgetItem(QStringList([category]))
245             for module in module_list:
246                 enabled = modules[module]["enabled"]
247                 module_item = QTreeWidgetItem(item, QStringList([module]))
248                 try:
249                     supported = bertos_utils.isSupported(self.project(), module=module)
250                 except SupportedException, e:
251                     self.exceptionOccurred(self.tr("Error evaluating \"%1\" for module %2").arg(e.support_string).arg(module))
252                     supported = True
253                 if not supported:
254                     module_item.setForeground(0, QBrush(QColor(Qt.red)))
255                 if enabled:
256                     module_item.setCheckState(0, Qt.Checked)
257                 else:
258                     module_item.setCheckState(0, Qt.Unchecked)
259             self.pageContent.moduleTree.addTopLevelItem(item)
260         self.pageContent.moduleTree.sortItems(0, Qt.AscendingOrder)
261         self.fillPropertyTable()
262             
263     def insertCheckBox(self, index, value):
264         """
265         Inserts in the table at index a checkbox for a boolean property setted
266         to value.
267         """
268         check_box = QCheckBox()
269         self.pageContent.propertyTable.setCellWidget(index, 1, check_box)
270         if value == "1":
271             check_box.setChecked(True)
272         else:
273             check_box.setChecked(False)
274         self._control_group.addControl(index, check_box)
275     
276     def insertComboBox(self, index, value, value_list):
277         """
278         Inserts in the table at index a combobox for an enum property setted
279         to value.
280         """
281         try:
282             enum = self.projectInfo("LISTS")[value_list]
283             combo_box = QComboBox()
284             self.pageContent.propertyTable.setCellWidget(index, 1, combo_box)
285             for i, element in enumerate(enum):
286                 combo_box.addItem(element)
287                 if element == value:
288                     combo_box.setCurrentIndex(i)
289             self._control_group.addControl(index, combo_box)
290         except KeyError:
291             self.exceptionOccurred(self.tr("Define list \"%1\" not found. Check definition files.").arg(value_list))
292             self.pageContent.propertyTable.setItem(index, 1, QTableWidgetItem(value))
293     
294     def insertSpinBox(self, index, value, informations):
295         """
296         Inserts in the table at index a spinbox for an int, a long or an unsigned
297         long property setted to value.
298         """
299         # int, long or undefined type property
300         spin_box = None
301         if bertos_utils.isLong(informations) or bertos_utils.isUnsignedLong(informations):
302             spin_box = QDoubleSpinBox()
303             spin_box.setDecimals(0)
304         else:
305             spin_box = QSpinBox()
306         self.pageContent.propertyTable.setCellWidget(index, 1, spin_box)
307         minimum = -32768
308         maximum = 32767
309         suff = ""
310         if bertos_utils.isLong(informations):
311             minimum = -2147483648
312             maximum = 2147483647
313             suff = "L"
314         elif bertos_utils.isUnsigned(informations):
315             minimum = 0
316             maximum = 65535
317             suff = "U"
318         elif bertos_utils.isUnsignedLong(informations):
319             minimum = 0
320             maximum = 4294967295
321             suff = "UL"
322         if "min" in informations:
323             minimum = int(informations["min"])
324         if "max" in informations:
325             maximum = int(informations["max"])
326         spin_box.setRange(minimum, maximum)
327         spin_box.setSuffix(suff)
328         spin_box.setValue(int(value.replace("L", "").replace("U", "")))
329         self._control_group.addControl(index, spin_box)
330         
331     
332     def currentModule(self):
333         """
334         Retuns the current module name.
335         """
336         current_module = self.pageContent.moduleTree.currentItem()
337         # return only the child items
338         if current_module and current_module.parent():
339             return unicode(current_module.text(0))
340         else:
341             return None
342     
343     def currentModuleConfigurations(self):
344         """
345         Returns the current module configuration.
346         """
347         return self.configurations(self.currentModule())
348     
349     def currentProperty(self):
350         """
351         Rerturns the current property from the property table.
352         """
353         return qvariant_converter.getString(self.pageContent.propertyTable.item(self.pageContent.propertyTable.currentRow(), 0).data(Qt.UserRole))
354     
355     def currentPropertyItem(self):
356         """
357         Returns the QTableWidgetItem of the current property.
358         """
359         return self.pageContent.propertyTable.item(self.pageContent.propertyTable.currentRow(), 0)
360     
361     def configurations(self, module):
362         """
363         Returns the configuration for the selected module.
364         """
365         configuration = self.projectInfo("MODULES")[module]["configuration"]
366         if len(configuration) > 0:
367             return self.projectInfo("CONFIGURATIONS")[configuration]
368         else:
369             return {}
370     
371     def resetPropertyDescription(self):
372         """
373         Resets the label for each property table entry.
374         """
375         for index in range(self.pageContent.propertyTable.rowCount()):
376             property_name = qvariant_converter.getString(self.pageContent.propertyTable.item(index, 0).data(Qt.UserRole))
377             # Awful solution! Needed because if the user change the module, the selection changed...
378             if property_name not in self.currentModuleConfigurations():
379                 break
380             self.pageContent.propertyTable.item(index, 0).setText(self.currentModuleConfigurations()[property_name]['brief'])
381     
382     def setBold(self, item, bold):
383         self.pageContent.moduleTree.blockSignals(True)
384         font = item.font(0)
385         font.setBold(bold)
386         item.setFont(0, font)
387         self.pageContent.moduleTree.blockSignals(False)
388
389     def moduleSelected(self, selectedModule):
390         """
391         Resolves the selection dependencies.
392         """
393         modules = self.projectInfo("MODULES")
394         modules[selectedModule]["enabled"] = True
395         self.setProjectInfo("MODULES", modules)
396         depends = self.projectInfo("MODULES")[selectedModule]["depends"]
397         unsatisfied = []
398         if self.pageContent.automaticFix.isChecked():
399             unsatisfied = self.selectDependencyCheck(selectedModule)
400         if len(unsatisfied) > 0:
401             for module in unsatisfied:
402                 modules = self.projectInfo("MODULES")
403                 modules[module]["enabled"] = True
404             for category in range(self.pageContent.moduleTree.topLevelItemCount()):
405                 item = self.pageContent.moduleTree.topLevelItem(category)
406                 for child in range(item.childCount()):
407                     if unicode(item.child(child).text(0)) in unsatisfied:
408                         self.setBold(item.child(child), True)
409                         self.setBold(item, True)
410                         item.child(child).setCheckState(0, Qt.Checked)
411     
412     def moduleUnselected(self, unselectedModule):
413         """
414         Resolves the unselection dependencies.
415         """
416         modules = self.projectInfo("MODULES")
417         modules[unselectedModule]["enabled"] = False
418         self.setProjectInfo("MODULES", modules)
419         unsatisfied = []
420         if self.pageContent.automaticFix.isChecked():
421             unsatisfied = self.unselectDependencyCheck(unselectedModule)
422         if len(unsatisfied) > 0:
423             message = self.tr("The module %1 is needed by the following modules:\n%2.\n\nDo you want to remove these modules too?")
424             message = message.arg(unselectedModule).arg(", ".join(unsatisfied))
425             choice = QMessageBox.warning(self, self.tr("Dependency error"), message, QMessageBox.Yes | QMessageBox.No, QMessageBox.Yes)
426             if choice == QMessageBox.Yes:
427                 for module in unsatisfied:
428                     modules = self.projectInfo("MODULES")
429                     modules[module]["enabled"] = False
430                 for category in range(self.pageContent.moduleTree.topLevelItemCount()):
431                     item = self.pageContent.moduleTree.topLevelItem(category)
432                     for child in range(item.childCount()):
433                         if unicode(item.child(child).text(0)) in unsatisfied:
434                             item.child(child).setCheckState(0, Qt.Unchecked)
435     
436     def selectDependencyCheck(self, module):
437         """
438         Returns the list of unsatisfied dependencies after a selection.
439         """
440         unsatisfied = set()
441         modules = self.projectInfo("MODULES")
442         files = self.projectInfo("FILES")
443         for dependency in modules[module]["depends"]:
444             if dependency in modules and not modules[dependency]["enabled"]:
445                 unsatisfied |= set([dependency])
446                 if dependency not in unsatisfied:
447                     unsatisfied |= self.selectDependencyCheck(dependency)
448             if dependency not in modules:
449                 if dependency in files:
450                     files[dependency] += 1
451                 else:
452                     files[dependency] = 1
453         self.setProjectInfo("FILES", files)
454         return unsatisfied
455     
456     def unselectDependencyCheck(self, dependency):
457         """
458         Returns the list of unsatisfied dependencies after an unselection.
459         """
460         unsatisfied = set()
461         modules = self.projectInfo("MODULES")
462         for module, informations in modules.items():
463             if dependency in informations["depends"] and informations["enabled"]:
464                 unsatisfied |= set([module])
465                 if dependency not in unsatisfied:
466                     unsatisfied |= self.unselectDependencyCheck(module)
467         return unsatisfied
468     
469     def removeFileDependencies(self, module):
470         """
471         Removes the files dependencies of the given module.
472         """
473         modules = self.projectInfo("MODULES")
474         files = self.projectInfo("FILES")
475         dependencies = modules[module]["depends"]
476         for dependency in dependencies:
477             if dependency in files:
478                 files[dependency] -= 1
479                 if files[dependency] == 0:
480                     del files[dependency]
481         self.setProjectInfo("FILES", files)
482
483 class QControlGroup(QObject):
484     """
485     Simple class that permit to connect different signals of different widgets
486     with a slot that emit a signal. Permits to group widget and to understand which of
487     them has sent the signal.
488     """
489     
490     def __init__(self):
491         QObject.__init__(self)
492         self._controls = {}
493     
494     def addControl(self, id, control):
495         """
496         Add a control.
497         """
498         self._controls[id] = control
499         if type(control) == QCheckBox:
500             self.connect(control, SIGNAL("stateChanged(int)"), lambda: self.stateChanged(id))
501         elif type(control) == QSpinBox:
502             self.connect(control, SIGNAL("valueChanged(int)"), lambda: self.stateChanged(id))
503         elif type(control) == QComboBox:
504             self.connect(control, SIGNAL("currentIndexChanged(int)"), lambda: self.stateChanged(id))
505         elif type(control) == QDoubleSpinBox:
506             self.connect(control, SIGNAL("valueChanged(double)"), lambda: self.stateChanged(id))
507     
508     def clear(self):
509         """
510         Remove all the controls.
511         """
512         self._controls = {}
513     
514     def stateChanged(self, id):
515         """
516         Slot called when the value of one of the stored widget changes. It emits
517         another signal.
518         """
519         self.emit(SIGNAL("stateChanged"), id)