def ConfigureDataMappers(model, mappings, delegate): mapper = QDataWidgetMapper(model) mapper.setModel(model) mapper.setSubmitPolicy(QDataWidgetMapper.AutoSubmit) mapper.setItemDelegate(delegate(mapper)) for mapping in mappings: if hasattr(mapping[map_idx.WIDGET], "isCustom"): mapping[map_idx.WIDGET].init_db(model.database()) mapping[map_idx.WIDGET].changed.connect(mapper.submit) # if no USER property we should use QByteArray().setRawData("account_id", 10)) here mapper.addMapping(mapping[map_idx.WIDGET], model.fieldIndex(mapping[map_idx.DB_NAME])) # adjust width of QDateTimeEdits to show full date-time string if isinstance(mapping[map_idx.WIDGET], QDateTimeEdit): mapping[map_idx.WIDGET].setFixedWidth(mapping[ map_idx.WIDGET].fontMetrics().width("00/00/0000 00:00:00") * 1.25) return mapper
class PropertyEditor(QTreeWidget): def __init__(self, model, nodeFactory, fieldFactory): super(PropertyEditor, self).__init__() self._dataMapper = QDataWidgetMapper() self._dataMapper.setModel(model) self._dataMapper.setSubmitPolicy(QDataWidgetMapper.ManualSubmit) self._model = model self._currentIndex = None self._fieldFactory = fieldFactory self._nodeFactory = nodeFactory # setup ui self.setColumnCount(2) self.setHeaderLabels(['Name', 'Value']) self.setSelectionMode(QAbstractItemView.NoSelection) self.setAlternatingRowColors(True) self.setFocusPolicy(Qt.NoFocus) # create all items per component self._createAllItems() self.resizeColumnToContents(0) self.setColumnWidth(0, self.columnWidth(0) + 20) def _createAllItems(self): for type, properties in self._allProperties().items(): topItem = self._createComponentItem(type) if type == ComponentType.General: self._createNodeTypeItem(topItem) for property in properties: item = PropertyEditorItem(topItem) item.setText(0, property.label()) field = self._fieldFactory.create(property) self.setItemWidget(item, 1, field) def _allProperties(self): result = {} for type in NodeType.All(): node = self._nodeFactory.create(type) for component in node.components().values(): result[component.type()] = component.propertyMap() return result def _createComponentItem(self, type): names = ComponentType.Names() item = PropertyEditorItem() item.setText(0, names[type]) item.setData(0, Qt.UserRole, type) self.addTopLevelItem(item) item.setExpanded(True) return item def _createNodeTypeItem(self, topItem): self._nodeTypeItem = PropertyEditorItem(topItem) self._nodeTypeItem.setText(0, 'Type') font = self._nodeTypeItem.font( 1 ) # don't ask, this is how it will be the same size as in the editors font.setPointSize(font.pointSize()) self._nodeTypeItem.setFont(1, font) def changeSelection(self, current, prev): node = current.internalPointer() self._currentIndex = current items = self._itemsForNode(node) self._setMapping(current, items) def _itemsForNode(self, node): result = [] names = NodeType.Names() self._nodeTypeItem.setText(1, names[node.type()]) for i in range(self.topLevelItemCount()): item = self.topLevelItem(i) type = item.data(0, Qt.UserRole) component = node.component(type) item.setHidden(not component) if component is None: continue result.extend(self._itemsForComponent(component, item)) return result def _itemsForComponent(self, component, parentItem): result = [] for column, property in enumerate(component.propertyMap()): offset = 1 if component.type( ) == ComponentType.General else 0 # the first item of General is Type item = parentItem.child(column + offset) item.setProperty(property) result.append(item) return result def _setMapping(self, current, items): self._dataMapper.clearMapping() fields = [] for item in items: fields.append(self._mapField(current, item)) parent = current.parent() self._dataMapper.setRootIndex(parent) self._dataMapper.setCurrentModelIndex(current) for field in fields: field.changed.connect( self._dataMapper.submit ) # this MUST be after the setCurrentModelIndex def _mapField(self, current, item): property = item.property() if not property: # General/Type doesn't have a property return field = self.itemWidget(item, 1) parent = current.parent() readOnly = property.readOnly( ) or not parent.isValid() # the top level nodes are read only field.setReadOnly(readOnly) self._dataMapper.addMapping(field, property.column(), b'valueProperty') return field
class AbstractOperationDetails(QWidget): dbUpdated = Signal() def __init__(self, parent=None): QWidget.__init__(self, parent) self.model = None self.table_name = '' self.mapper = None self.modified = False self.name = "N/A" self.layout = QGridLayout(self) self.layout.setContentsMargins(2, 2, 2, 2) self.bold_font = QFont() self.bold_font.setBold(True) self.bold_font.setWeight(75) self.main_label = QLabel(self) self.main_label.setFont(self.bold_font) self.layout.addWidget(self.main_label, 0, 0, 1, 1, Qt.AlignLeft) self.commit_button = QPushButton(self) self.commit_button.setEnabled(False) self.commit_button.setText("✔") self.commit_button.setFont(self.bold_font) self.commit_button.setFixedWidth( self.commit_button.fontMetrics().width("XXX")) self.revert_button = QPushButton(self) self.revert_button.setEnabled(False) self.revert_button.setText("✖️") self.revert_button.setFont(self.bold_font) self.revert_button.setFixedWidth( self.revert_button.fontMetrics().width("XXX")) self.verticalSpacer = QSpacerItem(20, 40, QSizePolicy.Minimum, QSizePolicy.Expanding) self.horizontalSpacer = QSpacerItem(40, 20, QSizePolicy.Expanding, QSizePolicy.Minimum) def _init_db(self, table_name): self.table_name = table_name self.model = QSqlTableModel(parent=self, db=db_connection()) self.model.setTable(table_name) self.model.setEditStrategy(QSqlTableModel.OnManualSubmit) self.mapper = QDataWidgetMapper(self.model) self.mapper.setModel(self.model) self.mapper.setSubmitPolicy(QDataWidgetMapper.AutoSubmit) self.model.dataChanged.connect(self.onDataChange) self.commit_button.clicked.connect(self.saveChanges) self.revert_button.clicked.connect(self.revertChanges) def isCustom(self): return True def setId(self, id): self.model.setFilter(f"id={id}") self.mapper.setCurrentModelIndex(self.model.index(0, 0)) @Slot() def onDataChange(self, _index_start, _index_stop, _role): self.modified = True self.commit_button.setEnabled(True) self.revert_button.setEnabled(True) @Slot() def saveChanges(self): if not self.model.submitAll(): logging.fatal( g_tr('AbstractOperationDetails', "Operation submit failed: ") + self.model.lastError().text()) return False self.modified = False self.commit_button.setEnabled(False) self.revert_button.setEnabled(False) self.dbUpdated.emit() return True @Slot() def revertChanges(self): self.model.revertAll() self.modified = False self.commit_button.setEnabled(False) self.revert_button.setEnabled(False) def createNew(self, account_id=0): self.mapper.submit( ) # FIXME there is check for uncommited call before - do we need submit() here? self.model.setFilter(f"{self.table_name}.id = 0") new_record = self.prepareNew(account_id) assert self.model.insertRows(0, 1) self.model.setRecord(0, new_record) self.mapper.toLast() def prepareNew(self, account_id): new_record = self.model.record() return new_record def copyNew(self): row = self.mapper.currentIndex() new_record = self.copyToNew(row) self.model.setFilter(f"{self.table_name}.id = 0") assert self.model.insertRows(0, 1) self.model.setRecord(0, new_record) self.mapper.toLast() def copyToNew(self, row): new_record = self.model.record(row) return new_record