def __init__(self, parent): ViewerWithTreeBase.__init__(self, parent, ArchitectureViewForm[0]) self.current_stereotype = '' # Create the tree model viewers tree_models = {self.ui.wdgBlocks:model.ArchitectureBlock, self.ui.wdgViews:model.View, self.ui.wdgRequirements:model.Requirement, self.ui.wdgBugs:model.Bug, self.ui.wdgActions:model.FunctionPoint} self.tree_models = tree_models self.createTreeViewers() # Let a view be shown when double clicked in the tree viewer. self.ui.wdgViews.ui.tree.itemDoubleClicked.connect(self.onView) self.ui.tabGraphicViews.tabCloseRequested.connect(self.onTabCloseRequested) self.ui.tabGraphicViews.currentChanged.connect(self.onTabChanged) self.ui.cmbRole.activated.connect(self.onRoleChanged) self.ui.btnShowStyles.clicked.connect(self.onShowStyles) self.stateWindow = StyleEditor() self.stateWindow.hide() self.ui.chkFunctionFP.stateChanged.connect(lambda i: theController.setFpAsCall(bool(i))) # Add database hooks to properly update when items are added. for cls in [model.ArchitectureBlock, model.View, model.Requirement, model.Bug, model.FunctionPoint]: event.listen(cls, 'after_update', self.onDetailUpdate) event.listen(cls, 'after_insert', self.onDetailInsert) #FIXME: also handle deletes. self.hasEvents = True
class ArchitectureView(ViewerWithTreeBase): ''' Viewer showing requirements and Use Cases. ''' DoubleClickItem = QtCore.pyqtSignal(object) def __init__(self, parent): ViewerWithTreeBase.__init__(self, parent, ArchitectureViewForm[0]) self.current_stereotype = '' # Create the tree model viewers tree_models = {self.ui.wdgBlocks:model.ArchitectureBlock, self.ui.wdgViews:model.View, self.ui.wdgRequirements:model.Requirement, self.ui.wdgBugs:model.Bug, self.ui.wdgActions:model.FunctionPoint} self.tree_models = tree_models self.createTreeViewers() # Let a view be shown when double clicked in the tree viewer. self.ui.wdgViews.ui.tree.itemDoubleClicked.connect(self.onView) self.ui.tabGraphicViews.tabCloseRequested.connect(self.onTabCloseRequested) self.ui.tabGraphicViews.currentChanged.connect(self.onTabChanged) self.ui.cmbRole.activated.connect(self.onRoleChanged) self.ui.btnShowStyles.clicked.connect(self.onShowStyles) self.stateWindow = StyleEditor() self.stateWindow.hide() self.ui.chkFunctionFP.stateChanged.connect(lambda i: theController.setFpAsCall(bool(i))) # Add database hooks to properly update when items are added. for cls in [model.ArchitectureBlock, model.View, model.Requirement, model.Bug, model.FunctionPoint]: event.listen(cls, 'after_update', self.onDetailUpdate) event.listen(cls, 'after_insert', self.onDetailInsert) #FIXME: also handle deletes. self.hasEvents = True def close(self): ''' Overrides the QWidget.close. ''' # Unsubscribe to database events. for cls in [model.ArchitectureBlock, model.View, model.Requirement, model.Bug, model.FunctionPoint]: try: event.remove(cls, 'after_update', self.onDetailUpdate) event.remove(cls, 'after_insert', self.onDetailInsert) except sqlalchemy.exc.InvalidRequestError: # No event handlers were found: ignore. pass self.hasEvents = False ViewerWithTreeBase.close(self) def clean(self): ''' Create a new database and connect to it. No check for outstanding changes necessary: all changes are stored immediatly. ''' # Clean up all tree widgets for widget in self.tree_models: widget.ui.tree.clear() # Close all views in the tab window while self.ui.tabGraphicViews.count() > 0: self.onTabCloseRequested(0) self.closeDetailsViewer() def open(self, session): ViewerWithTreeBase.open(self, session) self.stateWindow.session = session # Create the drop-down list for stylesheets stylesheets = self.session.query(model.Style.Name).all() stylesheets = [s[0] for s in stylesheets] self.ui.cmbRole.clear() def onShowStyles(self): self.stateWindow.show() def onItemChanged(self, item, column): new_name = str(item.text(0)) if new_name != item.details.Name: item.details.Name = new_name # Ensure the changes are committed. with model.sessionScope(self.session): pass def onView(self, item): ''' Called when the user double-clicks on a View. ''' details = item.details self.openView(details) def openView(self, details): ''' Open a 2D viewer for a 'View' item. Used as a slot for QT Signals. ''' # Check if this view is already open. widget = None for i in range(self.ui.tabGraphicViews.count()): view = self.ui.tabGraphicViews.widget(i) if view.details == details: # The view is already shown: bring it to the front. self.ui.tabGraphicViews.setCurrentIndex(i) widget = view if widget is None: # Add a new tab viewer = TwoDView(details, self.drop2Details, self.session) self.ui.tabGraphicViews.addTab(viewer, details.Name) self.ui.tabGraphicViews.setCurrentWidget(viewer) viewer.selectedItemChanged.connect(self.onItemSelectionChanged) viewer.scene.open_view.connect(self.openView) def onTabCloseRequested(self, index): widget = self.ui.tabGraphicViews.widget(index) self.ui.tabGraphicViews.removeTab(index) widget.close() def onTabChanged(self, index): self.updateRole() def onRoleChanged(self, _): # If the index is 0, the roles are not changed. if self.ui.cmbRole.currentIndex() == 0: return objs = Style.current_object.get() if objs: txt = str(self.ui.cmbRole.currentText()) for obj in objs: obj.setRole(txt) def onItemSelectionChanged(self, details): ''' Overload of the function inherited from the viewer base. ''' ViewerWithTreeBase.onItemSelectionChanged(self, details) self.updateRole() def updateRole(self): widget = self.ui.tabGraphicViews.currentWidget() if widget is None: return items = widget.scene.selectedItems() # Check if all items are of the same stereotype stereotypes = set() current_roles = set() for i in items: _, item = getDetails(i) stereotypes.add(item.ROLE) current_roles.add(item.role) if len(stereotypes) != 1: return stereotype = stereotypes.pop() if stereotype != self.current_stereotype: self.stereotype = stereotype # Fill the list of available roles self.ui.cmbRole.clear() roles = widget.scene.styles.findApplicableRoles(stereotype) # Add the default and empty role roles = ['--', '<default>'] + roles self.ui.cmbRole.addItems(roles) # If all items have the same role, select it if len(current_roles) == 1: role = current_roles.pop() index = 1 if not role else self.ui.cmbRole.findText(role) self.ui.cmbRole.setCurrentIndex(index) def filterRequirementsTree(self, prios, states): for item in self.ui.treeRequirements.detail_items.values(): disable = False if item.details.Priority not in prios: disable = True if len(item.details.StateChanges) == 0: disable = model.PRIORITIES[0] not in prios elif item.details.StateChanges[0].Status not in states: disable = True if item.childCount() > 0: # Never disable parent requirements. disable = False item.setDisabled(disable) def onRequirementReport(self, checked, widget): items = widget.selectedItems() if len(items) == 0: return fname = str(QtGui.QFileDialog.getSaveFileName(self, "Save report to which file?", '.', "*.rst")) if fname == '': return out = file(fname, 'w') chapters = [i.details for i in items] exportRequirementsOverview(self.session, out, chapters) def onRequirementQuestions(self, checked, widget): items = widget.selectedItems() if len(items) == 0: return fname = str(QtGui.QFileDialog.getSaveFileName(self, "Save report to which file?", '.', "*.rst")) if fname == '': return out = file(fname, 'w') chapters = [i.details for i in items] exportRequirementQuestions(self.session, out, chapters) def getTreeWidget(self, details): for widget, table in self.tree_models.items(): if isinstance(details, table): return widget return None def drop2Details(self, event): ''' This function determines which item was dropped when a view receives a drop event. The drop event originated from one of the three tree widgets. ''' data = event.mimeData().data(MIME_TYPE) stream = QtCore.QDataStream(data, QtCore.QIODevice.ReadOnly) stream.setByteOrder(QtCore.QDataStream.BigEndian) # Decode the drop data # First 4 32-bits integers representing row, column, nr of items and Qt.ItemDataRole # are stored _row = stream.readInt32() _column = stream.readInt32() _map_items = stream.readInt32() _key = stream.readInt32() # Next is a QVariant of the data set as UserRole to the TreeWidgetItem drop_path = QtCore.QVariant() stream >> drop_path #pylint: disable=W0104 source = event.source() items = source.findItems(drop_path.toString(), QtCore.Qt.MatchFixedString|QtCore.Qt.MatchRecursive, 0) return items[0].details def onDetailUpdate(self, mapper, connection, target): ''' Called when a change to the database model is committed. ''' widget = self.getTreeWidget(target) if widget is None: logging.error('onDetailUpdate called for wrong target %r'%target) return item = widget.detail_items.get(target.Id, None) if item is None: logging.error('Detail not shown in tree: %r'%target) return old_text = str(item.text(0)) if old_text != target.Name: item.setText(0, target.Name) def onDetailInsert(self, mapper, connection, target): ''' Called when a new item is inserted in the database. ''' self.addItem(target) def addItem(self, details, edit=False): ''' Add an item to a tree widget. ''' widget = self.getTreeWidget(details) if widget is None: return new_item = QtGui.QTreeWidgetItem() new_item.setText(0, details.Name) new_item.setFlags(QtCore.Qt.ItemFlags(QtCore.Qt.ItemIsEditable + QtCore.Qt.ItemIsDragEnabled + QtCore.Qt.ItemIsDropEnabled + QtCore.Qt.ItemIsSelectable + QtCore.Qt.ItemIsEnabled)) new_item.details = details parent = widget.detail_items[details.Parent] if details.Parent else None if parent: parent.addChild(new_item) else: widget.addTopLevelItem(new_item) if edit: widget.setCurrentItem(new_item) widget.editItem(new_item) widget.detail_items[details.Id] = new_item def onStyleSheetChanged(self, index): ''' Called when the user selects a new style sheet for the current view. ''' return # TODO: Re-implement # Get the ID of the style referred to name = str(self.ui.cmbStyleSheet.currentText()) id = self.session.query(model.Style.Id).filter(model.Style.Name==name).one()[0] # Set the stylesheet in the current View view = self.ui.tabGraphicViews.currentWidget() if view: details = view.details details.style = id