def setEditorData(self, editor: EditorAndDeleterFactory, index: QtCore.QModelIndex): editor.lineEdit.setText(index.data()) def deleteRow(self): editor.parent().parent().setFocus() #Return focus to the list. editor.setParent(None) #If we remove the editor parent, we segfault. index.model().removeRow(index.row()) editor.delete.clicked.connect(deleteRow)
def setEditorData( # pylint: disable=C0103 self, editor: QtWidgets.QWidget, index: QtCore.QModelIndex) -> None: if index.isValid(): i = index.data(QtCore.Qt.UserRole) if isinstance(editor, widgets.CustomItemWidget): editor.data = i.data super().setEditorData(editor, index)
def paint(self, painter: QtGui.QPainter, option, index: QtCore.QModelIndex): background = index.data(QtCore.Qt.BackgroundRole) if isinstance(background, QtGui.QBrush): if background.color() != QtCore.Qt.white: # WARNING Чтобы drawText работал правильно, нужно чтобы в ui форме в QTable # был явно прописан размер шрифта!!! painter.fillRect(option.rect, background) text_rect = option.rect # Не знаю как получить нормальный прямоугольник для отрисовки текста, поэтому только так text_rect.setLeft(text_rect.left() + 3) painter.drawText(text_rect, option.displayAlignment, index.data()) else: super().paint(painter, option, index) else: super().paint(painter, option, index)
def setEditorData(self, editor: QtWidgets.QPushButton, index: QtCore.QModelIndex): if index.isValid(): i = index.data(QtCore.Qt.UserRole) if isinstance(editor, options.CustomItemWidget): editor.data = i.data super().setEditorData(editor, index)
def scriptStopped(self, index: QtCore.QModelIndex): script = index.data(Qt.UserRole) script['process'] = None self.scripts.setData(index, script, Qt.UserRole ) self.updateRunButton() log.info(f"stop script {script['path']}")
def paint( self, painter: QtGui.QPainter, option: QtWidgets.QStyleOptionViewItem, idx: QtCore.QModelIndex, ) -> None: font_family = idx.data(QtCore.Qt.DisplayRole) font = QtGui.QFont(option.font) font.setPointSize(QtGui.QFontInfo(font).pointSize() * 3 / 2) font2 = QtGui.QFont(font) font2.setFamily(font_family) if option.state & QtWidgets.QStyle.State_Selected: painter.save() painter.setBrush(option.palette.highlight()) painter.setPen(QtCore.Qt.NoPen) painter.drawRect(option.rect) painter.setPen(QtGui.QPen(option.palette.highlightedText(), 0)) icon = self.bitmap if QtGui.QFontDatabase().isSmoothlyScalable(font_family): icon = self.truetype actual_size = icon.actualSize(option.rect.size()) icon.paint(painter, option.rect, QtCore.Qt.AlignLeft | QtCore.Qt.AlignVCenter) if option.direction == QtCore.Qt.RightToLeft: option.rect.setRight(option.rect.right() - actual_size.width() - 4) else: option.rect.setLeft(option.rect.left() + actual_size.width() + 4) half1 = QtCore.QRect(option.rect) half2 = QtCore.QRect(option.rect) half1.setRight(half1.right() / 2) half2.setLeft(half1.right()) painter.drawText( half1, QtCore.Qt.AlignVCenter | QtCore.Qt.AlignLeading | QtCore.Qt.TextSingleLine, font_family, ) old = painter.font() painter.setFont(font2) painter.drawText( half2, QtCore.Qt.AlignVCenter | QtCore.Qt.AlignLeading | QtCore.Qt.TextSingleLine, self.sample_text, ) painter.setFont(old) if option.state & QtWidgets.QStyle.State_Selected: painter.restore()
def setModelData(self, editor: QWidget, model: QAbstractItemModel, index: QModelIndex) -> None: # if index.data(): # model.setData(index, str(self.mTxt_address.text())) print(index.data()) if isinstance(editor, FileAddressEditor): model.setData(index, editor.text()) else: super(addressTableDelegate, self).setModelData(editor, model, index)
def removeActor(self, idx: qc.QModelIndex) -> None: actor = idx.data(qc.Qt.UserRole) if util.is_actor_in_use(self.flow_data.flow.flowchart.events, actor): q.QMessageBox.critical( self, 'Cannot remove actor', f'{actor.identifier} cannot be removed because it is used by events. Please remove any references to this actor and try again.' ) return self.flow_data.actor_model.remove(actor)
def cFlagListDoubleClick(self, model_index: QModelIndex) -> None: """ @param model_index: model index that was double clicked @type model_index: QModelIndex @return: None @rtype: None """ self.appendCurrentCFlags(model_index.data()) self.View.cflagsList.setFocus(True)
def getLinkedItem(self, index: QModelIndex) -> QModelIndex: if not index.data(IS_LINK_ROLE): return QModelIndex() try: reference = self.data(index, OBJECT_ROLE) objStore = self.data(index, PACKAGE_ROLE).objStore obj = reference.resolve(objStore) linkedPackItem, = self.data(index, PACK_ITEM_ROLE).model().match(QModelIndex(), OBJECT_ROLE, obj, hits=1) return linkedPackItem except AttributeError: return QModelIndex()
def cFlagCurrentDoubleClicked(self, modelIndex: QModelIndex) -> None: """ @param modelIndex: @type modelIndex: @return: @rtype: """ self.removeCurrentCFlag(modelIndex.data()) self.View.cflagsCurrentList.setFocus(True) return None
def paint(self, painter: QPainter, option: QStyleOptionViewItem, index: QModelIndex): item_type = index.data(Qt.AccessibleDescriptionRole) if item_type == "parent": parent_option = option parent_option.state |= QStyle.State_Enabled super().paint(painter, parent_option, index) elif item_type == "child": child_option = option indent = option.fontMetrics.width(4 * " ") child_option.rect.adjust(indent, 0, 0, 0) child_option.textElideMode = Qt.ElideNone super().paint(painter, child_option, index) else: super().paint(painter, option, index)
def selection_changed(self, curr_index: QModelIndex, prev_index: QModelIndex): """ Updates the information about currently selected account. """ if not curr_index.isValid(): return None # Make sure selection is visible in the view self.selection.setCurrentIndex( curr_index, QItemSelectionModel.SelectCurrent) acc = curr_index.data(role=Qt.UserRole) # Set the type of account self.typeBox.setCurrentText(acc.type) # Set the checkboxes self.closedBox.setChecked(acc.closed) self.exBudgetBox.setChecked(acc.exbudget)
def account_clicked(self, index: QModelIndex): """ Opens the list of transactions for given index in the account tree model. """ account = index.data(role=Qt.UserRole) if not isinstance(account, Account): return transaction_manager = TransactionsRoll(self.orm, account) self.menuBar.setEnabled(False) transaction_manager.exec() self.menuBar.setEnabled(True) # Update if there was changes self.show_accounts() self.show_budget_report()
class textEditView(QTextEdit): def __init__(self, parent=None, index=None, html=None, spellcheck=None, highlighting=False, dict="", autoResize=False): QTextEdit.__init__(self, parent) self._column = Outline.text self._index = None self._indexes = None self._model = None self._placeholderText = self.placeholderText() self._updating = False self._item = None self._highlighting = highlighting self._textFormat = "text" self.setAcceptRichText(False) # When setting up a theme, this becomes true. self._fromTheme = False self._themeData = None self._highlighterClass = BasicHighlighter if spellcheck is None: spellcheck = settings.spellcheck self.spellcheck = spellcheck self.currentDict = dict if dict else settings.dict self._defaultFontSize = qApp.font().pointSize() self.highlighter = None self.setAutoResize(autoResize) self._defaultBlockFormat = QTextBlockFormat() self._defaultCharFormat = QTextCharFormat() self.highlightWord = "" self.highligtCS = False self._dict = None # self.document().contentsChanged.connect(self.submit, F.AUC) # Submit text changed only after 500ms without modifications self.updateTimer = QTimer() self.updateTimer.setInterval(500) self.updateTimer.setSingleShot(True) self.updateTimer.timeout.connect(self.submit) # self.updateTimer.timeout.connect(lambda: print("Timeout")) self.updateTimer.stop() self.document().contentsChanged.connect(self.updateTimer.start, F.AUC) # self.document().contentsChanged.connect(lambda: print("Document changed")) # self.document().contentsChanged.connect(lambda: print(self.objectName(), "Contents changed")) self.setEnabled(False) if index: self.setCurrentModelIndex(index) elif html: self.document().setHtml(html) self.setReadOnly(True) # Spellchecking if enchant and self.spellcheck: try: self._dict = enchant.Dict(self.currentDict if self.currentDict else self.getDefaultLocale()) except enchant.errors.DictNotFoundError: self.spellcheck = False else: self.spellcheck = False if self._highlighting and not self.highlighter: self.highlighter = self._highlighterClass(self) self.highlighter.setDefaultBlockFormat(self._defaultBlockFormat) def getDefaultLocale(self): default_locale = enchant.get_default_language() if default_locale is None: default_locale = QLocale.system().name() if default_locale is None: default_locale = enchant.list_dicts()[0][0] return default_locale def setModel(self, model): self._model = model try: self._model.dataChanged.connect(self.update, F.AUC) except TypeError: pass def setColumn(self, col): self._column = col def setHighlighting(self, val): self._highlighting = val def setDefaultBlockFormat(self, bf): self._defaultBlockFormat = bf if self.highlighter: self.highlighter.setDefaultBlockFormat(bf) def setCurrentModelIndex(self, index): self._indexes = None if index.isValid(): self.setEnabled(True) if index.column() != self._column: index = index.sibling(index.row(), self._column) self._index = QPersistentModelIndex(index) self.setPlaceholderText(self._placeholderText) if not self._model: self.setModel(index.model()) self.setupEditorForIndex(self._index) self.loadFontSettings() self.updateText() else: self._index = QModelIndex() self.setPlainText("") self.setEnabled(False) def currentIndex(self): """ Getter function used to normalized views access with QAbstractItemViews. """ if self._index: return self._index else: return QModelIndex() def getSelection(self): """ Getter function used to normalized views access with QAbstractItemViews. """ return [self.currentIndex()] def setCurrentModelIndexes(self, indexes): self._index = None self._indexes = [] for i in indexes: if i.isValid(): self.setEnabled(True) if i.column() != self._column: i = i.sibling(i.row(), self._column) self._indexes.append(QModelIndex(i)) if not self._model: self.setModel(i.model()) self.updateText() def setupEditorForIndex(self, index): # Setting highlighter if self._highlighting: self.highlighter = self._highlighterClass(self) self.highlighter.setDefaultBlockFormat(self._defaultBlockFormat) self.highlighter.updateColorScheme() def loadFontSettings(self): if self._fromTheme or \ not self._index or \ type(self._index.model()) != outlineModel or \ self._column != Outline.text: return opt = settings.textEditor f = QFont() f.fromString(opt["font"]) background = (opt["background"] if not opt["backgroundTransparent"] else "transparent") foreground = opt["fontColor"] # if not opt["backgroundTransparent"] # else S.text # self.setFont(f) self.setStyleSheet("""QTextEdit{{ background: {bg}; color: {foreground}; font-family: {ff}; font-size: {fs}; margin: {mTB}px {mLR}px; {maxWidth} }} """.format( bg=background, foreground=foreground, ff=f.family(), fs="{}pt".format(str(f.pointSize())), mTB = opt["marginsTB"], mLR = opt["marginsLR"], maxWidth = "max-width: {}px;".format(opt["maxWidth"]) if opt["maxWidth"] else "", ) ) self._defaultFontSize = f.pointSize() # We set the parent background to the editor's background in case # there are margins. We check that the parent class is a QWidget because # if textEditView is used in fullScreenEditor, then we don't want to # set the background. if self.parent().__class__ == QWidget: self.parent().setStyleSheet(""" QWidget#{name}{{ background: {bg}; }}""".format( # We style by name, otherwise all inheriting widgets get the same # colored background, for example context menu. name=self.parent().objectName(), bg=background, )) cf = QTextCharFormat() # cf.setFont(f) # cf.setForeground(QColor(opt["fontColor"])) self.setCursorWidth(opt["cursorWidth"]) bf = QTextBlockFormat() bf.setLineHeight(opt["lineSpacing"], bf.ProportionalHeight) bf.setTextIndent(opt["tabWidth"] * 1 if opt["indent"] else 0) bf.setTopMargin(opt["spacingAbove"]) bf.setBottomMargin(opt["spacingBelow"]) bf.setAlignment(Qt.AlignLeft if opt["textAlignment"] == 0 else Qt.AlignCenter if opt["textAlignment"] == 1 else Qt.AlignRight if opt["textAlignment"] == 2 else Qt.AlignJustify) self._defaultCharFormat = cf self._defaultBlockFormat = bf if self.highlighter: self.highlighter.updateColorScheme() self.highlighter.setMisspelledColor(QColor(opt["misspelled"])) self.highlighter.setDefaultCharFormat(self._defaultCharFormat) self.highlighter.setDefaultBlockFormat(self._defaultBlockFormat) def update(self, topLeft, bottomRight): if self._updating: return if self._index and self._index.isValid(): if topLeft.parent() != self._index.parent(): return # print("Model changed: ({}:{}), ({}:{}/{}), ({}:{}) for {} of {}".format( # topLeft.row(), topLeft.column(), # self._index.row(), self._index.row(), self._column, # bottomRight.row(), bottomRight.column(), # self.objectName(), self.parent().objectName())) if topLeft.row() <= self._index.row() <= bottomRight.row(): if topLeft.column() <= self._column <= bottomRight.column(): self.updateText() elif self._indexes: update = False for i in self._indexes: if topLeft.row() <= i.row() <= bottomRight.row(): update = True if update: self.updateText() def disconnectDocument(self): try: self.document().contentsChanged.disconnect(self.updateTimer.start) except: pass def reconnectDocument(self): self.document().contentsChanged.connect(self.updateTimer.start, F.AUC) def updateText(self): if self._updating: return # print("Updating", self.objectName()) self._updating = True if self._index: self.disconnectDocument() if self.toPlainText() != F.toString(self._index.data()): # print(" Updating plaintext") self.document().setPlainText(F.toString(self._index.data())) self.reconnectDocument() elif self._indexes: self.disconnectDocument() t = [] same = True for i in self._indexes: item = i.internalPointer() t.append(F.toString(item.data(self._column))) for t2 in t[1:]: if t2 != t[0]: same = False break if same: self.document().setPlainText(t[0]) else: self.document().setPlainText("") if not self._placeholderText: self._placeholderText = self.placeholderText() self.setPlaceholderText(self.tr("Various")) self.reconnectDocument() self._updating = False def submit(self): self.updateTimer.stop() if self._updating: return # print("Submitting", self.objectName()) if self._index and self._index.isValid(): # item = self._index.internalPointer() if self.toPlainText() != self._index.data(): # print(" Submitting plain text") self._updating = True self._model.setData(QModelIndex(self._index), self.toPlainText()) self._updating = False elif self._indexes: self._updating = True for i in self._indexes: item = i.internalPointer() if self.toPlainText() != F.toString(item.data(self._column)): print("Submitting many indexes") self._model.setData(i, self.toPlainText()) self._updating = False def keyPressEvent(self, event): if event.key() == Qt.Key_V and event.modifiers() & Qt.ControlModifier: text = QApplication.clipboard().text() self.insertPlainText(text) else: QTextEdit.keyPressEvent(self, event) if event.key() == Qt.Key_Space: self.submit() # ----------------------------------------------------------------------------------------------------- # Resize stuff def resizeEvent(self, e): QTextEdit.resizeEvent(self, e) if self._autoResize: self.sizeChange() def sizeChange(self): opt = settings.textEditor docHeight = self.document().size().height() + 2 * opt["marginsTB"] if self.heightMin <= docHeight <= self.heightMax: self.setMinimumHeight(docHeight) def setAutoResize(self, val): self._autoResize = val if self._autoResize: self.document().contentsChanged.connect(self.sizeChange) self.heightMin = 0 self.heightMax = 65000 self.sizeChange() ############################################################################### # SPELLCHECKING ############################################################################### # Based on http://john.nachtimwald.com/2009/08/22/qplaintextedit-with-in-line-spell-check/ def setDict(self, d): self.currentDict = d if d and enchant.dict_exists(d): self._dict = enchant.Dict(d) if self.highlighter: self.highlighter.rehighlight() def toggleSpellcheck(self, v): self.spellcheck = v if enchant and self.spellcheck and not self._dict: if self.currentDict and enchant.dict_exists(self.currentDict): self._dict = enchant.Dict(self.currentDict) elif enchant.get_default_language() and enchant.dict_exists(enchant.get_default_language()): self._dict = enchant.Dict(enchant.get_default_language()) else: self.spellcheck = False if self.highlighter: self.highlighter.rehighlight() else: self.spellcheck = False def mousePressEvent(self, event): if event.button() == Qt.RightButton: # Rewrite the mouse event to a left button event so the cursor is # moved to the location of the pointer. event = QMouseEvent(QEvent.MouseButtonPress, event.pos(), Qt.LeftButton, Qt.LeftButton, Qt.NoModifier) QTextEdit.mousePressEvent(self, event) def wheelEvent(self, event): """ We catch wheelEvent if key modifier is CTRL to change font size. Note: this should be in a class specific for main textEditView (#TODO). """ if event.modifiers() & Qt.ControlModifier: # Get the wheel angle. d = event.angleDelta().y() / 120 # Update settings f = QFont() f.fromString(settings.textEditor["font"]) f.setPointSizeF(f.pointSizeF() + d) settings.textEditor["font"] = f.toString() # Update font to all textEditView. Drastically. for w in F.mainWindow().findChildren(textEditView, QRegExp(".*")): w.loadFontSettings() # We tell the world that we accepted this event event.accept() return QTextEdit.wheelEvent(self, event) class SpellAction(QAction): """A special QAction that returns the text in a signal. Used for spellcheck.""" correct = pyqtSignal(str) def __init__(self, *args): QAction.__init__(self, *args) self.triggered.connect(lambda x: self.correct.emit( str(self.text()))) def contextMenuEvent(self, event): # Based on http://john.nachtimwald.com/2009/08/22/qplaintextedit-with-in-line-spell-check/ popup_menu = self.createStandardContextMenu() popup_menu.exec_(event.globalPos()) def createStandardContextMenu(self): popup_menu = QTextEdit.createStandardContextMenu(self) if not self.spellcheck: return popup_menu # Select the word under the cursor. # But only if there is no selection (otherwise it's impossible to select more text to copy/cut) cursor = self.textCursor() if not cursor.hasSelection(): cursor.select(QTextCursor.WordUnderCursor) self.setTextCursor(cursor) # Check if the selected word is misspelled and offer spelling # suggestions if it is. if self._dict and cursor.hasSelection(): text = str(cursor.selectedText()) valid = self._dict.check(text) selectedWord = cursor.selectedText() if not valid: spell_menu = QMenu(self.tr('Spelling Suggestions'), self) spell_menu.setIcon(F.themeIcon("spelling")) for word in self._dict.suggest(text): action = self.SpellAction(word, spell_menu) action.correct.connect(self.correctWord) spell_menu.addAction(action) # Only add the spelling suggests to the menu if there are # suggestions. if len(spell_menu.actions()) != 0: popup_menu.insertSeparator(popup_menu.actions()[0]) # Adds: add to dictionary addAction = QAction(self.tr("&Add to dictionary"), popup_menu) addAction.setIcon(QIcon.fromTheme("list-add")) addAction.triggered.connect(self.addWordToDict) addAction.setData(selectedWord) popup_menu.insertAction(popup_menu.actions()[0], addAction) # Adds: suggestions popup_menu.insertMenu(popup_menu.actions()[0], spell_menu) # popup_menu.insertSeparator(popup_menu.actions()[0]) # If word was added to custom dict, give the possibility to remove it elif valid and self._dict.is_added(selectedWord): popup_menu.insertSeparator(popup_menu.actions()[0]) # Adds: remove from dictionary rmAction = QAction(self.tr("&Remove from custom dictionary"), popup_menu) rmAction.setIcon(QIcon.fromTheme("list-remove")) rmAction.triggered.connect(self.rmWordFromDict) rmAction.setData(selectedWord) popup_menu.insertAction(popup_menu.actions()[0], rmAction) return popup_menu def correctWord(self, word): """ Replaces the selected text with word. """ cursor = self.textCursor() cursor.beginEditBlock() cursor.removeSelectedText() cursor.insertText(word) cursor.endEditBlock() def addWordToDict(self): word = self.sender().data() self._dict.add(word) self.highlighter.rehighlight() def rmWordFromDict(self): word = self.sender().data() self._dict.remove(word) self.highlighter.rehighlight() ############################################################################### # FORMATTING ############################################################################### def focusOutEvent(self, event): """Submit changes just before focusing out.""" QTextEdit.focusOutEvent(self, event) self.submit() ############################################################################### # KEYBOARD SHORTCUTS ############################################################################### def callMainTreeView(self, functionName): """ The tree view in main window must have same index as the text edit that has focus. So we can pass it the call for documents edits like: duplicate, move up, etc. """ if self._index and self._column == Outline.text: function = getattr(F.mainWindow().treeRedacOutline, functionName) function() def rename(self): self.callMainTreeView("rename") def duplicate(self): self.callMainTreeView("duplicate") def moveUp(self): self.callMainTreeView("moveUp") def moveDown(self): self.callMainTreeView("moveDown")
class TreeModel(QAbstractItemModel): # Funktion hasChildren? # signals statusChanged = pyqtSignal(QModelIndex) speciesChanged = pyqtSignal(QModelIndex, int, int) calculated = pyqtSignal() itemsInserted = pyqtSignal(bool) itemsAboutToBeCalculated = pyqtSignal(bool) allItemsRemoved = pyqtSignal(bool) # class constants ItemRole = Qt.UserRole + 1 StatusRole = Qt.UserRole + 2 ColorRole = Qt.UserRole + 3 TypeRole = Qt.UserRole + 4 NameRole = Qt.UserRole + 5 ResultRole = Qt.UserRole + 6 PlantRole = Qt.UserRole + 7 ProtectionRole = Qt.UserRole + 8 SpeciesRole = Qt.UserRole + 9 TypeRole = Qt.UserRole + 10 IdentificationRole = Qt.UserRole + 11 LengthRole = Qt.UserRole + 12 CountRole = Qt.UserRole + 13 _roles = {ItemRole : "item", StatusRole : "status", ColorRole : "color", TypeRole : "type", NameRole : "name", ResultRole : "result", PlantRole : "plant", ProtectionRole : "protection", IdentificationRole : "identification", LengthRole : "length", CountRole : "count"} TYPE, IDENTIFICATION, SPECIES, NAME = range(4) def __init__(self, parent=None): super().__init__(parent) self.root = VariantItem("root item") # species registry self.species = {} self.variants = 0 # initialize class attributes self._TABLE_HEADER_LABELS = (QtCore.QT_TRANSLATE_NOOP("TreeModel", "Protection type"), QtCore.QT_TRANSLATE_NOOP("TreeModel", "Name"), QtCore.QT_TRANSLATE_NOOP("TreeModel", "Tree species"), QtCore.QT_TRANSLATE_NOOP("TreeModel", "Description of protection")) # project settings self.project = Project() self.rowsInserted.connect(self.updateSpecies) self.speciesChanged.connect(self.moveItem) self.statusChanged.connect(self.updateStatus) self.new = True self.changed = False self.filename = "" self.file = False self.read = False self.last = QModelIndex() self.current = -1 self.count = 0 # temoporary plant count for calculation help self.length = 0 # temoporary fence length for calculation help def columnCount(self, parent): return len(self._TABLE_HEADER_LABELS) def roleNames(self): return self._roles def projectData(self, key): return getattr(self.project, key) def setProjectData(self, key, value): setattr(self.project, key, value) self.changed = True def itemData(self, index): if not index.isValid(): return None item = self.getItem(index) # create the QMap as python dict data = { self.NameRole : item.name, self.ColorRole : item.color, self.StatusRole : item.status, self.PlantRole : item.plant, self.ProtectionRole : item.protection } return data def setItemData(self, index, roles): if not index.isValid(): return False item = self.getItem(index) oldSpecies = item.plant.species newSpecies = roles[self.PlantRole].species # if the species has changed, the item have to move # a fence item doesn't have already known species if not newSpecies == oldSpecies: if item.type == Fence.TYPE and newSpecies in self.species: return False self.speciesChanged.emit(index, oldSpecies, newSpecies) # update the item for role in roles: if not role in self._roles: return False attribute = self._roles[role] setattr(item, attribute, roles[role]) # update model's change status self.dataChanged.emit(index, index) self.changed = True return True def data(self, index, role=Qt.DisplayRole): if not index.isValid(): return None item = index.internalPointer() column = index.column() if role == Qt.DisplayRole: if column == TreeModel.TYPE: return QApplication.translate("VariantItem", item.protection.TYPE_DESCRIPTION) elif column == TreeModel.NAME: return item.name elif column == TreeModel.SPECIES: return library.TREESPECIES_ABBREVIATION[item.plant.species] elif column == TreeModel.IDENTIFICATION: return "{protection}{identification}".format( protection=QApplication.translate("VariantItem", item.protection.TYPE_SHORT), identification=item.identification + 1) elif role == Qt.CheckStateRole: if column == TreeModel.TYPE: if item.status: if item.type == Fence.TYPE: checked = True for child in item.children: if not child.status: checked = False if checked: return Qt.Checked else: return Qt.PartiallyChecked else: return Qt.Checked else: return Qt.Unchecked elif role == self.IdentificationRole: return "{protection}{identification}".format( protection=QApplication.translate("VariantItem", item.protection.TYPE_SHORT), identification=item.identification + 1) elif role == self.LengthRole: return self.project.length elif role == self.CountRole: return self.project.count elif role == self.TypeRole: return item.type elif role == self.NameRole: return item.name elif role == self.SpeciesRole: return item.plant.species elif role == self.ColorRole: return QColor(item.color) elif role == self.StatusRole: return item.status elif role == self.ResultRole: return item.result else: return None def setData(self, index, value, role=Qt.EditRole): item = index.internalPointer() if role == Qt.CheckStateRole: if value in (Qt.Checked, Qt.PartiallyChecked): item.status = True else: item.status = False self.dataChanged.emit(index, index) #self.statusChanged.emit(index) if item.type == Fence.TYPE: for child in item.children: child.status = item.status childIndex = self.createIndex(child.childNumber(), self.TYPE, child) self.dataChanged.emit(childIndex, childIndex) if item.type > Fence.TYPE: parent = item.parent status = False for child in parent.children: if child.status: status = True parent.status = status self.dataChanged.emit(index.parent(), index.parent()) return True return True elif role == self.ResultRole: item.result = value self.dataChanged.emit(index, index) return True elif role == self.ColorRole: item.color = value self.dataChanged.emit(index, index) return True else: return False def flags(self, index): if not index.isValid(): return Qt.NoItemFlags flags = Qt.ItemIsEnabled | Qt.ItemIsSelectable column = index.column() if column == TreeModel.TYPE: if index.data() == Fence.TYPE_DESCRIPTION: return flags | Qt.ItemIsUserCheckable | 64 else: return flags | Qt.ItemIsUserCheckable else: return flags def headerData(self, section, orientation, role=Qt.DisplayRole): if orientation == Qt.Horizontal and role == Qt.DisplayRole: return QApplication.translate("TreeModel", self._TABLE_HEADER_LABELS[section]) return None def index(self, row, column, parent=QModelIndex()): # kann Fehler verursachen! if not self.hasIndex(row, column, parent): return QModelIndex() if not parent.isValid(): parentItem = self.root else: parentItem = parent.internalPointer() childItem = parentItem.child(row) if childItem: return self.createIndex(row, column, childItem) else: return QModelIndex() def parent(self, index): if not index.isValid(): return QModelIndex() child = self.getItem(index) parent = child.parent if parent == self.root: return QModelIndex() return self.createIndex(parent.childNumber(), 0, parent) def rowCount(self, parent=QModelIndex()): #if parent.column() > 0: # return 0 #if not parent.isValid(): # parent_item = self.root #else: # parent_item = parent.internalPointer() #return parent_item.childCount() item = self.getItem(parent) return item.childCount() def evaluate(self): evaluation = False # if there is a fence item with at least # one children, enable the calculation button items = self.match(self.index(0, 0), self.TypeRole, Tube.TYPE, -1, Qt.MatchRecursive) for item in items: if item.parent().isValid(): # the calculation button should be enabled evaluation = True break self.itemsAboutToBeCalculated.emit(evaluation) # automatic protection type # self.last contains an index value if not self.last.parent().isValid() and self.current == -1: if self.last.data(self.TypeRole) == Fence.TYPE: self.current = Tube.TYPE elif self.last.data(self.TypeRole) == Tube.TYPE: self.current = Fence.TYPE else: self.current = -1 else: self.current = -1 return evaluation def getItem(self, index): if index.isValid(): item = index.internalPointer() if item: return item return self.root def insertItem(self, item): return self.insertItems([item]) def insertItems(self, items): if not items: return False # extract items with type = 0 as top level items toplevel = [] species = [] for item in items[:]: # update the identification item.identification = self.variants self.variants = self.variants + 1 if item.type == 0: if not item.plant.species in self.species: toplevel.append(item) species.append(item.plant.species) items.remove(item) else: return False elif item.type > 0: if item.plant.species in self.species: parent = self.root.child(self.species[item.plant.species]) index = self.createIndex(parent.childCount(), 0, parent) self.insertRow(parent.childCount(), item, index) items.remove(item) else: return False # extract current top level items with type > 0 from root if toplevel: current = [] for child in self.root.children[:]: if child.plant.species in species: current.append(child) self.removeRow(child.childNumber()) items.extend(current) for item in toplevel: for child in items[:]: if child.plant.species == item.plant.species: item.insertChildren(item.childCount(), [child]) items.remove(child) toplevel.extend(items) else: toplevel = items # insert the new rows and emit the signal to enable the view # also do an evaluation if toplevel: self.insertRows(self.root.childCount(), toplevel) # always evaluate self.itemsInserted.emit(True) self.evaluate() return True def insertRow(self, row, item, parent=QModelIndex()): return self.insertRows(row, [item], parent) def insertRows(self, row, items, parent=QModelIndex()): item = self.getItem(parent) self.beginInsertRows(parent, row, row + len(items) - 1) item.insertChildren(row, items) self.endInsertRows() # get the index of the last item if not self.read: self.last = self.index(row + len(items) -1, 0, parent) # update model's change status self.new = False self.changed = True return True def updateStatus(self, index): # möglicherweise sollte diese Funktion in die Delegate #if not index.parent().isValid(): # item.type = Fence.TYPE item = self.getItem(index) if item.type == Fence.TYPE: for child in item.children: child.status = item.status childIndex = self.createIndex(child.childNumber(), self.TYPE, child) self.dataChanged.emit(childIndex, childIndex) if item.type > Fence.TYPE: parent = item.parent status = False for child in parent.children: if child.status: status = True parent.status = status self.dataChanged.emit(index.parent(), index.parent()) def updateSpecies(self, parent, first, last): if not parent.isValid(): item = self.getItem(parent) for row in range(first, last + 1): child = item.child(row) if child.type == Fence.TYPE: self.species[child.plant.species] = row def removeItem(self, position, parent=QModelIndex()): return self.removeItems(position, 1, parent) def removeItems(self, position, rows, parent=QModelIndex()): item = self.getItem(parent) if position < 0 or position + rows - 1 > item.childCount(): return False # first it's necessary to remove the species entry for row in range(position, position + rows): child = item.child(row) if child.type == Fence.TYPE: del self.species[child.plant.species] # now the child items can be removed # and do an evaluation self.removeRows(position, rows, parent) self.evaluate() # if the model is empty, emit the signal # to disable the view if not self.rowCount(): self.allItemsRemoved.emit(True) return True def removeRow(self, row, parent=QModelIndex()): return self.removeRows(row, 1, parent) def removeRows(self, row, count, parent=QModelIndex()): item = self.getItem(parent) self.beginRemoveRows(parent, row, row + count -1) item.removeChildren(row, count) self.endRemoveRows() # get the index of the last item self.last = QModelIndex() # update model's change status self.changed = True return True def moveItem(self, index, oldSpecies, newSpecies): # item.type durch index.parent is valid ersetzen?! item = self.getItem(index) parent = item.parent if item.type == Fence.TYPE: # first update the species registry del self.species[oldSpecies] self.species[newSpecies] = index.row() # simply clear item's internal list and # move child item's to tree root if item.hasChildren(): count = item.childCount() child = parent.childCount() self.moveRows(index, 0, count, index.parent(), child) # if there are any child items with the same species # within tree root, add them to the current item for child in parent.children[:]: if child.type > Fence.TYPE and child.plant.species == newSpecies: count = item.childCount() row = child.childNumber() self.moveRow(index.parent(), row, index, count) elif item.type > Fence.TYPE: if newSpecies in self.species: newParent = self.root.child(self.species[newSpecies]) newIndex = self.createIndex(newParent.childNumber(), 0, newParent) child = newParent.childCount() self.moveRow(index.parent(), index.row(), newIndex, child) elif index.parent().isValid(): # only if the parent is valid, the item # can be moved to the tree root child = self.root.childCount() self.moveRow(index.parent(), index.row(), QModelIndex(), child) # after moving items do an evaluation #self.evaluate() def moveRow(self, source, row, destination, child): return self.moveRows(source, row, 1, destination, child) def moveRows(self, source, row, count, destination, child): sourceParent = self.getItem(source) destinationParent = self.getItem(destination) self.beginMoveRows(source, row, row + count - 1, destination, child) destinationParent.insertChildren(child, sourceParent.children[row:row+count]) sourceParent.removeChildren(row, count) self.endMoveRows() # update model's change status self.changed = True return True def saveFile(self, xmlfile=""): if xmlfile: self.filename = xmlfile self.file = True elif self.file: xmlfile = self.filename else: return False writer = XMLFileWriter() success = writer.writeXMLFile(self.project.__dict__, self.root, xmlfile) # update model's change status if success: self.changed = False return success def readFile(self, xmlfile): if xmlfile: self.filename = xmlfile self.file = True else: return False # wichtig, um keine Vorauswahl zu haben self.read = True reader = XMLFileReader() success = reader.readXMLFile(xmlfile) if success: if not self.project.update(reader.getProject()): success = False if not self.insertItems(reader.getItems()): success = False # TODO self.dataChanged.emit(QModelIndex(), QModelIndex()) # update model's change status and clear model if necessary self.new = False self.changed = success self.read = False if not success: self.clear() return success def clear(self): # first call beginResetModel() self.beginResetModel() # now clear all model data self.project = Project() self.root = VariantItem("root item") self.species = {} self.variants = 0 self.new = True self.changed = False self.filename = "" self.file = False self.read = False self.last = QModelIndex() self.current = -1 self.count = 0 self.length = 0 # it's necessary to call endResetModel() self.endResetModel() def calculate(self): shelters = [] for child in self.root.children: if child.type == Fence.TYPE and child.hasChildren(): # fence result is set to fence length #child.result = child.protection.length for shelter in child.children: # the following operations are based on the example calculation # of Dr. Anton Hammer <*****@*****.**> slope = shelter.sumCosts() * (1 - shelter.plant.mortality) - child.sumCosts() result = child.protection.installation / slope # update result in model's item index = self.createIndex(shelter.childNumber(), 0, shelter) self.setData(index, result, TreeModel.ResultRole) else: shelters.append(child.name) # an empty list means that the calculation was successful if not shelters: self.calculated.emit() return shelters
def on_projectTree_doubleClicked(self, idx: QModelIndex): p = idx.data(Qt.UserRole).page if p is not None: self.openPage.emit(p)