Add support for conditional dependencies (To be tested heavily!)
[bertos.git] / wizard / BModulePage.py
index e0bcd9db956545199a0ee753581b8121ca2ce737..a6307d7a0abbefeb5cc61e36c4f52a486d0a5336 100644 (file)
@@ -1,8 +1,32 @@
 #!/usr/bin/env python
 # encoding: utf-8
 #
-# Copyright 2009 Develer S.r.l. (http://www.develer.com/)
-# All rights reserved.
+# This file is part of BeRTOS.
+#
+# Bertos is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
+#
+# As a special exception, you may use this file as part of a free software
+# library without restriction.  Specifically, if other files instantiate
+# templates or use macros or inline functions from this file, or you compile
+# this file and link it with other files to produce an executable, this
+# file does not by itself cause the resulting executable to be covered by
+# the GNU General Public License.  This exception does not however
+# invalidate any other reasons why the executable file might be covered by
+# the GNU General Public License.
+#
+# Copyright 2008 Develer S.r.l. (http://www.develer.com/)
 #
 # $Id$
 #
@@ -53,6 +77,7 @@ class BModulePage(BWizardPage):
         Overload of the BWizardPage connectSignals method.
         """
         self.connect(self.pageContent.moduleTree, SIGNAL("itemPressed(QTreeWidgetItem*, int)"), self.fillPropertyTable)
+        self.connect(self.pageContent.moduleTree, SIGNAL("itemPressed(QTreeWidgetItem*, int)"), self.moduleClicked)
         self.connect(self.pageContent.moduleTree, SIGNAL("itemChanged(QTreeWidgetItem*, int)"), self.dependencyCheck)
         self.connect(self.pageContent.propertyTable, SIGNAL("itemSelectionChanged()"), self.showPropertyDescription)
 
@@ -70,6 +95,9 @@ class BModulePage(BWizardPage):
     
     ## Slots ##
 
+    def moduleClicked(self, item, column):
+        self.setBold(item, False)
+
     def fillPropertyTable(self):
         """
         Slot called when the user selects a module from the module tree.
@@ -77,11 +105,11 @@ class BModulePage(BWizardPage):
         the source tree.
         """
         module = self.currentModule()
-        if module is not None:
+        if module:
             try:
                 supported = bertos_utils.isSupported(self.project(), module=module)
             except SupportedException, e:
-                self.exceptionOccurred(self.tr("Error evaluating \"%1\" for module %2").arg(e.support_string).arg(selectedModule))
+                self.exceptionOccurred(self.tr("Error evaluating \"%1\" for module %2").arg(e.support_string).arg(module))
                 supported = True
             self._control_group.clear()
             configuration = self.projectInfo("MODULES")[module]["configuration"]
@@ -108,7 +136,7 @@ class BModulePage(BWizardPage):
                     try:
                         param_supported = bertos_utils.isSupported(self.project(), property_id=(configuration, property))
                     except SupportedException, e:
-                        self.exceptionOccurred(self.tr("Error evaluating \"%1\" for module %2").arg(e.support_string).arg(selectedModule))
+                        self.exceptionOccurred(self.tr("Error evaluating \"%1\" for parameter %2").arg(e.support_string).arg(property))
                         param_supported = True
                     if not param_supported:
                         # Doesn't show the unsupported parameters
@@ -118,9 +146,9 @@ class BModulePage(BWizardPage):
                     item = QTableWidgetItem(configurations[property]["brief"])
                     item.setData(Qt.UserRole, qvariant_converter.convertString(property))
                     self.pageContent.propertyTable.setItem(index, 0, item)
-                    if "type" in configurations[property]["informations"].keys() and configurations[property]["informations"]["type"] == "boolean":
+                    if "type" in configurations[property]["informations"] and configurations[property]["informations"]["type"] == "boolean":
                         self.insertCheckBox(index, configurations[property]["value"])
-                    elif "type" in configurations[property]["informations"].keys() and configurations[property]["informations"]["type"] == "enum":
+                    elif "type" in configurations[property]["informations"] and configurations[property]["informations"]["type"] == "enum":
                         self.insertComboBox(index, configurations[property]["value"], configurations[property]["informations"]["value_list"])
                     elif "type" in configurations[property]["informations"] and configurations[property]["informations"]["type"] == "int":
                         self.insertSpinBox(index, configurations[property]["value"], configurations[property]["informations"])
@@ -131,7 +159,12 @@ class BModulePage(BWizardPage):
             if self.pageContent.propertyTable.rowCount() == 0:
                 module_label = self.pageContent.moduleLabel.text()
                 module_label += "\n\nNo configuration needed."
-                self.pageContent.moduleLabel.setText(module_label)
+                self.pageContent.moduleLabel.setText(module_label) 
+        else:
+            self.pageContent.moduleLabel.setText("")
+            self.pageContent.moduleLabel.setVisible(False)
+            self.pageContent.propertyTable.clear()
+            self.pageContent.propertyTable.setRowCount(0)
 
     def dependencyCheck(self, item):
         """
@@ -152,7 +185,7 @@ class BModulePage(BWizardPage):
         """
         self.resetPropertyDescription()
         configurations = self.currentModuleConfigurations()
-        if self.currentProperty() in configurations.keys():
+        if self.currentProperty() in configurations:
             description = configurations[self.currentProperty()]["brief"]
             name = self.currentProperty()
             self.currentPropertyItem().setText(description + "\n" + name)
@@ -164,8 +197,8 @@ class BModulePage(BWizardPage):
         property = qvariant_converter.getString(self.pageContent.propertyTable.item(index, 0).data(Qt.UserRole))
         configuration = self.projectInfo("MODULES")[self.currentModule()]["configuration"]
         configurations = self.projectInfo("CONFIGURATIONS")
-        if "type" not in configurations[configuration][property]["informations"].keys() or configurations[configuration][property]["informations"]["type"] == "int":
-            configurations[configuration][property]["value"] = str(int(self.pageContent.propertyTable.cellWidget(index, 1).value()))
+        if "type" not in configurations[configuration][property]["informations"] or configurations[configuration][property]["informations"]["type"] == "int":
+            configurations[configuration][property]["value"] = unicode(int(self.pageContent.propertyTable.cellWidget(index, 1).value()))
         elif configurations[configuration][property]["informations"]["type"] == "enum":
             configurations[configuration][property]["value"] = unicode(self.pageContent.propertyTable.cellWidget(index, 1).currentText())
         elif configurations[configuration][property]["informations"]["type"] == "boolean":
@@ -174,6 +207,8 @@ class BModulePage(BWizardPage):
             else:
                 configurations[configuration][property]["value"] = "0"
         self.setProjectInfo("CONFIGURATIONS", configurations)
+        if self.moduleItem(self.currentModule()).checkState(0) == Qt.Checked:
+            self.dependencyCheck(self.moduleItem(self.currentModule()))
 
     ####
     
@@ -182,9 +217,9 @@ class BModulePage(BWizardPage):
         Loads the module data.
         """
         # Load the module data only if it isn't already loaded
-        if self.projectInfo("MODULES") == None \
-                and self.projectInfo("LISTS") == None \
-                and self.projectInfo("CONFIGURATIONS") == None:
+        if not self.projectInfo("MODULES") \
+                and not self.projectInfo("LISTS") \
+                and not self.projectInfo("CONFIGURATIONS"):
             try:
                 bertos_utils.loadModuleData(self.project())
             except ModuleDefineException, e:
@@ -198,12 +233,13 @@ class BModulePage(BWizardPage):
         """
         Fills the module tree with the module entries separated in categories.
         """
+        self.pageContent.moduleTree.clear()
         modules = self.projectInfo("MODULES")
-        if modules is None:
+        if not modules:
             return
         categories = {}
         for module, information in modules.items():
-            if information["category"] not in categories.keys():
+            if information["category"] not in categories:
                 categories[information["category"]] = []
             categories[information["category"]].append(module)
         for category, module_list in categories.items():
@@ -214,7 +250,7 @@ class BModulePage(BWizardPage):
                 try:
                     supported = bertos_utils.isSupported(self.project(), module=module)
                 except SupportedException, e:
-                    self.exceptionOccurred(self.tr("Error evaluating \"%1\" for module %2").arg(e.support_string).arg(selectedModule))
+                    self.exceptionOccurred(self.tr("Error evaluating \"%1\" for module %2").arg(e.support_string).arg(module))
                     supported = True
                 if not supported:
                     module_item.setForeground(0, QBrush(QColor(Qt.red)))
@@ -224,6 +260,7 @@ class BModulePage(BWizardPage):
                     module_item.setCheckState(0, Qt.Unchecked)
             self.pageContent.moduleTree.addTopLevelItem(item)
         self.pageContent.moduleTree.sortItems(0, Qt.AscendingOrder)
+        self.fillPropertyTable()
             
     def insertCheckBox(self, index, value):
         """
@@ -284,9 +321,9 @@ class BModulePage(BWizardPage):
             minimum = 0
             maximum = 4294967295
             suff = "UL"
-        if "min" in informations.keys():
+        if "min" in informations:
             minimum = int(informations["min"])
-        if "max" in informations.keys():
+        if "max" in informations:
             maximum = int(informations["max"])
         spin_box.setRange(minimum, maximum)
         spin_box.setSuffix(suff)
@@ -300,10 +337,19 @@ class BModulePage(BWizardPage):
         """
         current_module = self.pageContent.moduleTree.currentItem()
         # return only the child items
-        if current_module is not None and current_module.parent() is not None:
+        if current_module and current_module.parent():
             return unicode(current_module.text(0))
         else:
             return None
+
+    def moduleItem(self, module):
+        for top_level_index in range(self.pageContent.moduleTree.topLevelItemCount()):
+            top_level_item = self.pageContent.moduleTree.topLevelItem(top_level_index)
+            for child_index in range(top_level_item.childCount()):
+                child_item = top_level_item.child(child_index)
+                if unicode(child_item.text(0)) == module:
+                    return child_item
+        return None
     
     def currentModuleConfigurations(self):
         """
@@ -340,10 +386,17 @@ class BModulePage(BWizardPage):
         for index in range(self.pageContent.propertyTable.rowCount()):
             property_name = qvariant_converter.getString(self.pageContent.propertyTable.item(index, 0).data(Qt.UserRole))
             # Awful solution! Needed because if the user change the module, the selection changed...
-            if property_name not in self.currentModuleConfigurations().keys():
+            if property_name not in self.currentModuleConfigurations():
                 break
             self.pageContent.propertyTable.item(index, 0).setText(self.currentModuleConfigurations()[property_name]['brief'])
     
+    def setBold(self, item, bold):
+        self.pageContent.moduleTree.blockSignals(True)
+        font = item.font(0)
+        font.setBold(bold)
+        item.setFont(0, font)
+        self.pageContent.moduleTree.blockSignals(False)
+
     def moduleSelected(self, selectedModule):
         """
         Resolves the selection dependencies.
@@ -363,6 +416,8 @@ class BModulePage(BWizardPage):
                 item = self.pageContent.moduleTree.topLevelItem(category)
                 for child in range(item.childCount()):
                     if unicode(item.child(child).text(0)) in unsatisfied:
+                        self.setBold(item.child(child), True)
+                        self.setBold(item, True)
                         item.child(child).setCheckState(0, Qt.Checked)
     
     def moduleUnselected(self, unselectedModule):
@@ -373,12 +428,24 @@ class BModulePage(BWizardPage):
         modules[unselectedModule]["enabled"] = False
         self.setProjectInfo("MODULES", modules)
         unsatisfied = []
+        unsatisfied_params = []
         if self.pageContent.automaticFix.isChecked():
-            unsatisfied = self.unselectDependencyCheck(unselectedModule)
-        if len(unsatisfied) > 0:
-            message = self.tr("The module %1 is needed by the following modules:\n%2.\n\nDo you want to remove these modules too?")
-            message = message.arg(unselectedModule).arg(", ".join(unsatisfied))
-            choice = QMessageBox.warning(self, self.tr("Dependency error"), message, QMessageBox.Yes | QMessageBox.No, QMessageBox.Yes)
+            unsatisfied, unsatisfied_params = self.unselectDependencyCheck(unselectedModule)
+        if len(unsatisfied) > 0 or len(unsatisfied_params) > 0:
+            message = []
+            heading = self.tr("The module %1 is needed by").arg(unselectedModule)
+            message.append(heading)
+            module_list = ", ".join(unsatisfied)
+            param_list = ", ".join(["%s (%s)" %(param_name, module) for module, param_name in unsatisfied_params])
+            if module_list:
+                message.append(QString(module_list))
+            if module_list and param_list:
+                message.append(self.tr("and by"))
+            if param_list:
+                message.append(QString(param_list))
+            message_str = QStringList(message).join(" ")
+            message_str.append(self.tr("\n\nDo you want to automatically fix these conflicts?"))
+            choice = QMessageBox.warning(self, self.tr("Dependency error"), message_str, QMessageBox.Yes | QMessageBox.No, QMessageBox.Yes)
             if choice == QMessageBox.Yes:
                 for module in unsatisfied:
                     modules = self.projectInfo("MODULES")
@@ -388,6 +455,11 @@ class BModulePage(BWizardPage):
                     for child in range(item.childCount()):
                         if unicode(item.child(child).text(0)) in unsatisfied:
                             item.child(child).setCheckState(0, Qt.Unchecked)
+                for module, param in unsatisfied_params:
+                    configuration_file = self.projectInfo("MODULES")[module]["configuration"]
+                    configurations = self.projectInfo("CONFIGURATIONS")
+                    configurations[configuration_file][param]["value"] = "0"
+                    self.setProjectInfo("CONFIGURATIONS", configurations)
     
     def selectDependencyCheck(self, module):
         """
@@ -396,7 +468,13 @@ class BModulePage(BWizardPage):
         unsatisfied = set()
         modules = self.projectInfo("MODULES")
         files = self.projectInfo("FILES")
-        for dependency in modules[module]["depends"]:
+        configurations = self.projectInfo("CONFIGURATIONS")[modules[module]["configuration"]]
+        conditional_deps = ()
+        for i, param_name in configurations["paramlist"]:
+            information = configurations[param_name]
+            if information["informations"]["type"] == "boolean" and information["value"] != "0" and "conditional_deps" in information["informations"]:
+                conditional_deps += information["informations"]["conditional_deps"]
+        for dependency in modules[module]["depends"] + conditional_deps:
             if dependency in modules and not modules[dependency]["enabled"]:
                 unsatisfied |= set([dependency])
                 if dependency not in unsatisfied:
@@ -414,13 +492,27 @@ class BModulePage(BWizardPage):
         Returns the list of unsatisfied dependencies after an unselection.
         """
         unsatisfied = set()
+        unsatisfied_params = set()
         modules = self.projectInfo("MODULES")
         for module, informations in modules.items():
+            configurations = self.projectInfo("CONFIGURATIONS").get(informations["configuration"], {"paramlist": ()})
+            conditional_deps = {}
+            for i, param_name in configurations["paramlist"]:
+                information = configurations[param_name]
+                if information["informations"]["type"] == "boolean" and information["value"] != "0" and "conditional_deps" in information["informations"]:
+                    for dep in information["informations"]["conditional_deps"]:
+                        if not dep in conditional_deps:
+                            conditional_deps[dep] = []
+                        conditional_deps[dep].append((module, param_name))
             if dependency in informations["depends"] and informations["enabled"]:
                 unsatisfied |= set([module])
                 if dependency not in unsatisfied:
-                    unsatisfied |= self.unselectDependencyCheck(module)
-        return unsatisfied
+                    tmp = self.unselectDependencyCheck(module)
+                    unsatisfied |= tmp[0]
+                    unsatisfied_params |= tmp[1]
+            if dependency in conditional_deps:
+                unsatisfied_params |= set(conditional_deps[dependency])
+        return unsatisfied, unsatisfied_params
     
     def removeFileDependencies(self, module):
         """
@@ -472,4 +564,4 @@ class QControlGroup(QObject):
         Slot called when the value of one of the stored widget changes. It emits
         another signal.
         """
-        self.emit(SIGNAL("stateChanged"), id)
\ No newline at end of file
+        self.emit(SIGNAL("stateChanged"), id)