Further Cortex-M3 core related macros.
[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.QtCore import *
39 from PyQt4.QtGui import *
40 from BWizardPage import *
41 import bertos_utils
42
43 from bertos_utils import SupportedException
44 from DefineException import *
45 from const import *
46
47 class BModulePage(BWizardPage):
48     """
49     Page of the wizard that permits to select and configurate the BeRTOS modules.
50     """
51     
52     def __init__(self):
53         BWizardPage.__init__(self, UI_LOCATION + "/module_select.ui")
54         self.setTitle(self.tr("Configure the BeRTOS modules"))
55         self._control_group = QControlGroup()
56         ## special connection needed for the QControlGroup
57         self.connect(self._control_group, SIGNAL("stateChanged"), self.saveValue)
58     
59     ## Overloaded BWizardPage methods ##
60
61     def setupUi(self):
62         """
63         Overload of BWizardPage setupUi method.
64         """
65         self.pageContent.moduleTree.clear()
66         self.pageContent.moduleTree.setHeaderHidden(True)
67         self.pageContent.propertyTable.horizontalHeader().setResizeMode(QHeaderView.Stretch)
68         self.pageContent.propertyTable.horizontalHeader().setVisible(False)
69         self.pageContent.propertyTable.verticalHeader().setResizeMode(QHeaderView.ResizeToContents)
70         self.pageContent.propertyTable.verticalHeader().setVisible(False)
71         self.pageContent.propertyTable.setColumnCount(2)
72         self.pageContent.propertyTable.setRowCount(0)
73         self.pageContent.moduleLabel.setVisible(False)
74         self.pageContent.warningLabel.setVisible(False)
75     
76     def connectSignals(self):
77         """
78         Overload of the BWizardPage connectSignals method.
79         """
80         self.connect(self.pageContent.moduleTree, SIGNAL("itemPressed(QTreeWidgetItem*, int)"), self.fillPropertyTable)
81         self.connect(self.pageContent.moduleTree, SIGNAL("itemPressed(QTreeWidgetItem*, int)"), self.moduleClicked)
82         self.connect(self.pageContent.moduleTree, SIGNAL("itemChanged(QTreeWidgetItem*, int)"), self.dependencyCheck)
83         self.connect(self.pageContent.propertyTable, SIGNAL("itemSelectionChanged()"), self.showPropertyDescription)
84
85     def reloadData(self):
86         """
87         Overload of the BWizardPage reloadData method.
88         """
89         QApplication.instance().setOverrideCursor(Qt.WaitCursor)
90         self.setupUi()
91         self.loadModuleData()
92         self.fillModuleTree()
93         QApplication.instance().restoreOverrideCursor()
94     
95     ####
96     
97     ## Slots ##
98
99     def moduleClicked(self, item, column):
100         self.setBold(item, False)
101
102     def fillPropertyTable(self):
103         """
104         Slot called when the user selects a module from the module tree.
105         Fills the property table using the configuration parameters defined in
106         the source tree.
107         """
108         module = self.currentModule()
109         if module:
110             try:
111                 supported = bertos_utils.isSupported(self.project(), module=module)
112             except SupportedException, e:
113                 self.exceptionOccurred(self.tr("Error evaluating \"%1\" for module %2").arg(e.support_string).arg(module))
114                 supported = True
115             self._control_group.clear()
116             configuration = self.projectInfo("MODULES")[module]["configuration"]
117             module_description = self.projectInfo("MODULES")[module]["description"]
118             self.pageContent.moduleLabel.setText(module_description)
119             self.pageContent.moduleLabel.setVisible(True)
120             if not supported:
121                 self.pageContent.warningLabel.setVisible(True)
122                 selected_cpu = self.projectInfo("CPU_NAME")
123                 self.pageContent.warningLabel.setText(self.tr("<font color='#FF0000'>Warning: the selected module, \
124                     is not completely supported by the %1.</font>").arg(selected_cpu))
125             else:
126                 self.pageContent.warningLabel.setVisible(False)
127             self.pageContent.propertyTable.clear()
128             self.pageContent.propertyTable.setRowCount(0)
129             if configuration != "":
130                 configurations = self.projectInfo("CONFIGURATIONS")[configuration]
131                 param_list = sorted(configurations["paramlist"])
132                 index = 0
133                 for i, property in param_list:
134                     if "type" in configurations[property]["informations"] and configurations[property]["informations"]["type"] == "autoenabled":
135                         # Doesn't show the hidden fields
136                         continue
137                     try:
138                         param_supported = bertos_utils.isSupported(self.project(), property_id=(configuration, property))
139                     except SupportedException, e:
140                         self.exceptionOccurred(self.tr("Error evaluating \"%1\" for parameter %2").arg(e.support_string).arg(property))
141                         param_supported = True
142                     if not param_supported:
143                         # Doesn't show the unsupported parameters
144                         continue
145                     # Set the row count to the current index + 1
146                     self.pageContent.propertyTable.setRowCount(index + 1)
147                     item = QTableWidgetItem(configurations[property]["brief"])
148                     item.setData(Qt.UserRole, qvariant_converter.convertString(property))
149                     self.pageContent.propertyTable.setItem(index, 0, item)
150                     if "type" in configurations[property]["informations"] and configurations[property]["informations"]["type"] == "boolean":
151                         self.insertCheckBox(index, configurations[property]["value"])
152                     elif "type" in configurations[property]["informations"] and configurations[property]["informations"]["type"] == "enum":
153                         self.insertComboBox(index, configurations[property]["value"], configurations[property]["informations"]["value_list"])
154                     elif "type" in configurations[property]["informations"] and configurations[property]["informations"]["type"] == "int":
155                         self.insertSpinBox(index, configurations[property]["value"], configurations[property]["informations"])
156                     else:
157                         # Not defined type, rendered as a text field
158                         self.pageContent.propertyTable.setItem(index, 1, QTableWidgetItem(configurations[property]["value"]))
159                     index += 1
160             if self.pageContent.propertyTable.rowCount() == 0:
161                 module_label = self.pageContent.moduleLabel.text()
162                 module_label += "\n\nNo configuration needed."
163                 self.pageContent.moduleLabel.setText(module_label) 
164         else:
165             self.pageContent.moduleLabel.setText("")
166             self.pageContent.moduleLabel.setVisible(False)
167             self.pageContent.propertyTable.clear()
168             self.pageContent.propertyTable.setRowCount(0)
169
170     def dependencyCheck(self, item):
171         """
172         Checks the dependencies of the module associated with the given item.
173         """
174         checked = False
175         module = unicode(item.text(0))
176         if item.checkState(0) == Qt.Checked:
177             self.moduleSelected(module)
178         else:
179             self.moduleUnselected(module)
180             self.removeFileDependencies(module)
181
182     def showPropertyDescription(self):
183         """
184         Slot called when the property selection changes. Shows the description
185         of the selected property.
186         """
187         self.resetPropertyDescription()
188         configurations = self.currentModuleConfigurations()
189         if self.currentProperty() in configurations:
190             description = configurations[self.currentProperty()]["brief"]
191             name = self.currentProperty()
192             self.currentPropertyItem().setText(description + "\n" + name)
193
194     def saveValue(self, index):
195         """
196         Slot called when the user modifies one of the configuration parameters.
197         It stores the new value."""
198         property = qvariant_converter.getString(self.pageContent.propertyTable.item(index, 0).data(Qt.UserRole))
199         configuration = self.projectInfo("MODULES")[self.currentModule()]["configuration"]
200         configurations = self.projectInfo("CONFIGURATIONS")
201         if "type" not in configurations[configuration][property]["informations"] or configurations[configuration][property]["informations"]["type"] == "int":
202             configurations[configuration][property]["value"] = unicode(int(self.pageContent.propertyTable.cellWidget(index, 1).value()))
203         elif configurations[configuration][property]["informations"]["type"] == "enum":
204             configurations[configuration][property]["value"] = unicode(self.pageContent.propertyTable.cellWidget(index, 1).currentText())
205         elif configurations[configuration][property]["informations"]["type"] == "boolean":
206             if self.pageContent.propertyTable.cellWidget(index, 1).isChecked():
207                 configurations[configuration][property]["value"] = "1"
208             else:
209                 configurations[configuration][property]["value"] = "0"
210         self.setProjectInfo("CONFIGURATIONS", configurations)
211         if self.moduleItem(self.currentModule()).checkState(0) == Qt.Checked:
212             self.dependencyCheck(self.moduleItem(self.currentModule()))
213
214     ####
215     
216     def loadModuleData(self):
217         """
218         Loads the module data.
219         """
220         # Load the module data only if it isn't already loaded
221         if not self.projectInfo("MODULES") \
222                 and not self.projectInfo("LISTS") \
223                 and not self.projectInfo("CONFIGURATIONS"):
224             try:
225                 bertos_utils.loadModuleData(self.project())
226             except ModuleDefineException, e:
227                 self.exceptionOccurred(self.tr("Error parsing line '%2' in file %1").arg(e.path).arg(e.line))
228             except EnumDefineException, e:
229                 self.exceptionOccurred(self.tr("Error parsing line '%2' in file %1").arg(e.path).arg(e.line))
230             except ConfigurationDefineException, e:
231                 self.exceptionOccurred(self.tr("Error parsing line '%2' in file %1").arg(e.path).arg(e.line))
232     
233     def fillModuleTree(self):
234         """
235         Fills the module tree with the module entries separated in categories.
236         """
237         self.pageContent.moduleTree.clear()
238         modules = self.projectInfo("MODULES")
239         if not modules:
240             return
241         categories = {}
242         for module, information in modules.items():
243             if information["category"] not in categories:
244                 categories[information["category"]] = []
245             categories[information["category"]].append(module)
246         for category, module_list in categories.items():
247             item = QTreeWidgetItem(QStringList([category]))
248             for module in module_list:
249                 enabled = modules[module]["enabled"]
250                 module_item = QTreeWidgetItem(item, QStringList([module]))
251                 try:
252                     supported = bertos_utils.isSupported(self.project(), module=module)
253                 except SupportedException, e:
254                     self.exceptionOccurred(self.tr("Error evaluating \"%1\" for module %2").arg(e.support_string).arg(module))
255                     supported = True
256                 if not supported:
257                     module_item.setForeground(0, QBrush(QColor(Qt.red)))
258                 if enabled:
259                     module_item.setCheckState(0, Qt.Checked)
260                 else:
261                     module_item.setCheckState(0, Qt.Unchecked)
262             self.pageContent.moduleTree.addTopLevelItem(item)
263         self.pageContent.moduleTree.sortItems(0, Qt.AscendingOrder)
264         self.fillPropertyTable()
265             
266     def insertCheckBox(self, index, value):
267         """
268         Inserts in the table at index a checkbox for a boolean property setted
269         to value.
270         """
271         check_box = QCheckBox()
272         self.pageContent.propertyTable.setCellWidget(index, 1, check_box)
273         if value == "1":
274             check_box.setChecked(True)
275         else:
276             check_box.setChecked(False)
277         self._control_group.addControl(index, check_box)
278     
279     def insertComboBox(self, index, value, value_list):
280         """
281         Inserts in the table at index a combobox for an enum property setted
282         to value.
283         """
284         try:
285             enum = self.projectInfo("LISTS")[value_list]
286             combo_box = QComboBox()
287             self.pageContent.propertyTable.setCellWidget(index, 1, combo_box)
288             for i, element in enumerate(enum):
289                 combo_box.addItem(element)
290                 if element == value:
291                     combo_box.setCurrentIndex(i)
292             self._control_group.addControl(index, combo_box)
293         except KeyError:
294             self.exceptionOccurred(self.tr("Define list \"%1\" not found. Check definition files.").arg(value_list))
295             self.pageContent.propertyTable.setItem(index, 1, QTableWidgetItem(value))
296     
297     def insertSpinBox(self, index, value, informations):
298         """
299         Inserts in the table at index a spinbox for an int, a long or an unsigned
300         long property setted to value.
301         """
302         # int, long or undefined type property
303         spin_box = None
304         if bertos_utils.isLong(informations) or bertos_utils.isUnsignedLong(informations):
305             spin_box = QDoubleSpinBox()
306             spin_box.setDecimals(0)
307         else:
308             spin_box = QSpinBox()
309         self.pageContent.propertyTable.setCellWidget(index, 1, spin_box)
310         minimum = -32768
311         maximum = 32767
312         suff = ""
313         if bertos_utils.isLong(informations):
314             minimum = -2147483648
315             maximum = 2147483647
316             suff = "L"
317         elif bertos_utils.isUnsigned(informations):
318             minimum = 0
319             maximum = 65535
320             suff = "U"
321         elif bertos_utils.isUnsignedLong(informations):
322             minimum = 0
323             maximum = 4294967295
324             suff = "UL"
325         if "min" in informations:
326             minimum = int(informations["min"])
327         if "max" in informations:
328             maximum = int(informations["max"])
329         spin_box.setRange(minimum, maximum)
330         spin_box.setSuffix(suff)
331         spin_box.setValue(int(value.replace("L", "").replace("U", "")))
332         self._control_group.addControl(index, spin_box)
333         
334     
335     def currentModule(self):
336         """
337         Retuns the current module name.
338         """
339         current_module = self.pageContent.moduleTree.currentItem()
340         # return only the child items
341         if current_module and current_module.parent():
342             return unicode(current_module.text(0))
343         else:
344             return None
345
346     def moduleItem(self, module):
347         for top_level_index in range(self.pageContent.moduleTree.topLevelItemCount()):
348             top_level_item = self.pageContent.moduleTree.topLevelItem(top_level_index)
349             for child_index in range(top_level_item.childCount()):
350                 child_item = top_level_item.child(child_index)
351                 if unicode(child_item.text(0)) == module:
352                     return child_item
353         return None
354     
355     def currentModuleConfigurations(self):
356         """
357         Returns the current module configuration.
358         """
359         return self.configurations(self.currentModule())
360     
361     def currentProperty(self):
362         """
363         Rerturns the current property from the property table.
364         """
365         return qvariant_converter.getString(self.pageContent.propertyTable.item(self.pageContent.propertyTable.currentRow(), 0).data(Qt.UserRole))
366     
367     def currentPropertyItem(self):
368         """
369         Returns the QTableWidgetItem of the current property.
370         """
371         return self.pageContent.propertyTable.item(self.pageContent.propertyTable.currentRow(), 0)
372     
373     def configurations(self, module):
374         """
375         Returns the configuration for the selected module.
376         """
377         configuration = self.projectInfo("MODULES")[module]["configuration"]
378         if len(configuration) > 0:
379             return self.projectInfo("CONFIGURATIONS")[configuration]
380         else:
381             return {}
382     
383     def resetPropertyDescription(self):
384         """
385         Resets the label for each property table entry.
386         """
387         for index in range(self.pageContent.propertyTable.rowCount()):
388             property_name = qvariant_converter.getString(self.pageContent.propertyTable.item(index, 0).data(Qt.UserRole))
389             # Awful solution! Needed because if the user change the module, the selection changed...
390             if property_name not in self.currentModuleConfigurations():
391                 break
392             self.pageContent.propertyTable.item(index, 0).setText(self.currentModuleConfigurations()[property_name]['brief'])
393     
394     def setBold(self, item, bold):
395         self.pageContent.moduleTree.blockSignals(True)
396         font = item.font(0)
397         font.setBold(bold)
398         item.setFont(0, font)
399         self.pageContent.moduleTree.blockSignals(False)
400
401     def moduleSelected(self, selectedModule):
402         """
403         Resolves the selection dependencies.
404         """
405         qApp.setOverrideCursor(Qt.WaitCursor)
406         modules = self.projectInfo("MODULES")
407         modules[selectedModule]["enabled"] = True
408         self.setProjectInfo("MODULES", modules)
409         depends = self.projectInfo("MODULES")[selectedModule]["depends"]
410         unsatisfied = []
411         if self.pageContent.automaticFix.isChecked():
412             unsatisfied = self.selectDependencyCheck(selectedModule)
413         if len(unsatisfied) > 0:
414             for module in unsatisfied:
415                 modules = self.projectInfo("MODULES")
416                 modules[module]["enabled"] = True
417             for category in range(self.pageContent.moduleTree.topLevelItemCount()):
418                 item = self.pageContent.moduleTree.topLevelItem(category)
419                 for child in range(item.childCount()):
420                     if unicode(item.child(child).text(0)) in unsatisfied:
421                         self.setBold(item.child(child), True)
422                         self.setBold(item, True)
423                         item.child(child).setCheckState(0, Qt.Checked)
424         qApp.restoreOverrideCursor()
425     
426     def moduleUnselected(self, unselectedModule):
427         """
428         Resolves the unselection dependencies.
429         """
430         qApp.setOverrideCursor(Qt.WaitCursor)
431         modules = self.projectInfo("MODULES")
432         modules[unselectedModule]["enabled"] = False
433         self.setProjectInfo("MODULES", modules)
434         unsatisfied = []
435         unsatisfied_params = []
436         if self.pageContent.automaticFix.isChecked():
437             unsatisfied, unsatisfied_params = self.unselectDependencyCheck(unselectedModule)
438         if len(unsatisfied) > 0 or len(unsatisfied_params) > 0:
439             message = []
440             heading = self.tr("The module %1 is needed by").arg(unselectedModule)
441             message.append(heading)
442             module_list = ", ".join(unsatisfied)
443             param_list = ", ".join(["%s (%s)" %(param_name, module) for module, param_name in unsatisfied_params])
444             if module_list:
445                 message.append(QString(module_list))
446             if module_list and param_list:
447                 message.append(self.tr("and by"))
448             if param_list:
449                 message.append(QString(param_list))
450             message_str = QStringList(message).join(" ")
451             message_str.append(self.tr("\n\nDo you want to automatically fix these conflicts?"))
452             qApp.restoreOverrideCursor()
453             choice = QMessageBox.warning(self, self.tr("Dependency error"), message_str, QMessageBox.Yes | QMessageBox.No, QMessageBox.Yes)
454             qApp.setOverrideCursor(Qt.WaitCursor)
455             if choice == QMessageBox.Yes:
456                 for module in unsatisfied:
457                     modules = self.projectInfo("MODULES")
458                     modules[module]["enabled"] = False
459                 for category in range(self.pageContent.moduleTree.topLevelItemCount()):
460                     item = self.pageContent.moduleTree.topLevelItem(category)
461                     for child in range(item.childCount()):
462                         if unicode(item.child(child).text(0)) in unsatisfied:
463                             item.child(child).setCheckState(0, Qt.Unchecked)
464                 for module, param in unsatisfied_params:
465                     configuration_file = self.projectInfo("MODULES")[module]["configuration"]
466                     configurations = self.projectInfo("CONFIGURATIONS")
467                     configurations[configuration_file][param]["value"] = "0"
468                     self.setProjectInfo("CONFIGURATIONS", configurations)
469             qApp.restoreOverrideCursor()
470     
471     def selectDependencyCheck(self, module):
472         """
473         Returns the list of unsatisfied dependencies after a selection.
474         """
475         unsatisfied = set()
476         modules = self.projectInfo("MODULES")
477         files = self.projectInfo("FILES")
478         configurations = self.projectInfo("CONFIGURATIONS").get(modules[module]["configuration"], {"paramlist": ()})
479         conditional_deps = ()
480         for i, param_name in configurations["paramlist"]:
481             information = configurations[param_name]
482             if information["informations"]["type"] == "boolean" and \
483                 information["value"] != "0" and \
484                 "conditional_deps" in information["informations"]:
485
486                 conditional_deps += information["informations"]["conditional_deps"]
487
488         for dependency in modules[module]["depends"] + conditional_deps:
489             if dependency in modules and not modules[dependency]["enabled"]:
490                 unsatisfied |= set([dependency])
491                 if dependency not in unsatisfied:
492                     unsatisfied |= self.selectDependencyCheck(dependency)
493             if dependency not in modules:
494                 if dependency in files:
495                     files[dependency] += 1
496                 else:
497                     files[dependency] = 1
498         self.setProjectInfo("FILES", files)
499         return unsatisfied
500     
501     def unselectDependencyCheck(self, dependency):
502         """
503         Returns the list of unsatisfied dependencies after an unselection.
504         """
505         unsatisfied = set()
506         unsatisfied_params = set()
507         modules = self.projectInfo("MODULES")
508         for module, informations in modules.items():
509             configurations = self.projectInfo("CONFIGURATIONS").get(informations["configuration"], {"paramlist": ()})
510             conditional_deps = {}
511             for i, param_name in configurations["paramlist"]:
512                 information = configurations[param_name]
513                 if information["informations"]["type"] == "boolean" and information["value"] != "0" and "conditional_deps" in information["informations"]:
514                     for dep in information["informations"]["conditional_deps"]:
515                         if not dep in conditional_deps:
516                             conditional_deps[dep] = []
517                         conditional_deps[dep].append((module, param_name))
518             if dependency in informations["depends"] and informations["enabled"]:
519                 unsatisfied |= set([module])
520                 if dependency not in unsatisfied:
521                     tmp = self.unselectDependencyCheck(module)
522                     unsatisfied |= tmp[0]
523                     unsatisfied_params |= tmp[1]
524             if dependency in conditional_deps:
525                 unsatisfied_params |= set(conditional_deps[dependency])
526         return unsatisfied, unsatisfied_params
527     
528     def removeFileDependencies(self, module):
529         """
530         Removes the files dependencies of the given module.
531         """
532         modules = self.projectInfo("MODULES")
533         files = self.projectInfo("FILES")
534         dependencies = modules[module]["depends"]
535         for dependency in dependencies:
536             if dependency in files:
537                 files[dependency] -= 1
538                 if files[dependency] == 0:
539                     del files[dependency]
540         self.setProjectInfo("FILES", files)
541
542 class QControlGroup(QObject):
543     """
544     Simple class that permit to connect different signals of different widgets
545     with a slot that emit a signal. Permits to group widget and to understand which of
546     them has sent the signal.
547     """
548     
549     def __init__(self):
550         QObject.__init__(self)
551         self._controls = {}
552     
553     def addControl(self, id, control):
554         """
555         Add a control.
556         """
557         self._controls[id] = control
558         if type(control) == QCheckBox:
559             self.connect(control, SIGNAL("stateChanged(int)"), lambda: self.stateChanged(id))
560         elif type(control) == QSpinBox:
561             self.connect(control, SIGNAL("valueChanged(int)"), lambda: self.stateChanged(id))
562         elif type(control) == QComboBox:
563             self.connect(control, SIGNAL("currentIndexChanged(int)"), lambda: self.stateChanged(id))
564         elif type(control) == QDoubleSpinBox:
565             self.connect(control, SIGNAL("valueChanged(double)"), lambda: self.stateChanged(id))
566     
567     def clear(self):
568         """
569         Remove all the controls.
570         """
571         self._controls = {}
572     
573     def stateChanged(self, id):
574         """
575         Slot called when the value of one of the stored widget changes. It emits
576         another signal.
577         """
578         self.emit(SIGNAL("stateChanged"), id)