def __init__(self, parent=None): """ Loads the ui layout file. Populates the model and does some layout adjustments. :param parent | FinanceagerWindow """ super(StatisticsWindow, self).__init__(parent) loadUi(__file__, self) self.__model = QtGui.QStandardItemModel(self.tableView) self.__model.setHorizontalHeaderLabels( ['Expenditures', 'Receipts', 'Differences']) self.__model.setVerticalHeaderLabels(_MONTHS_ + ['TOTAL']) monthsTabWidget = self.parentWidget().monthsTabWidget for r in range(12): self.__model.setItem(r, 0, monthsTabWidget.widget(r).expendituresModel().valueItem()) self.__model.setItem(r, 1, monthsTabWidget.widget(r).receiptsModel().valueItem()) self.__model.setItem(r, 2, ExpenseItem()) for c in range(3): self.__model.setItem(12, c, ExpenseItem()) self.__totals = [0, 0] self.updateTotalItems(QtGui.QStandardItem()) self.tableView.setModel(self.__model) self.tableView.horizontalHeader().setResizeMode(QtGui.QHeaderView.Stretch) self.tableView.adjustSize() self.setWindowTitle('Statistics of ' + str(parent.year())) self.setFixedSize(self.size()) # CONNECTIONS self.__model.itemChanged.connect(self.updateTotalItems)
def parseXMLtoModel(self, childList, appender): """ Recursive function. Takes child from childList, creates appropriate item and appends it to the appender. At the initial call, appender is a MonthTab's model. Later, appender is a CategoryItem. :param childList | list of xml children appender | items.Item """ if issubclass(appender.__class__, QStandardItemModel): appender.clear() appender.setHorizontalHeaderLabels(_HEADERLABELS_) for child in childList: name = unicode(child.get('name')) value = str(child.get('value')) if child.tag == 'model': appender.setValueItem(value) self.parseXMLtoModel(child.getchildren(), appender) elif child.tag == 'category': catItem = CategoryItem(name) appender.appendRow([catItem, SumItem(value), EmptyItem()]) self.parseXMLtoModel(child.getchildren(), catItem) else: day = unicode(child.get('date')) dateItem = DateItem(day, self.monthIndex() + 1, self.__mainWindow.year()) appender.appendRow( [EntryItem(name), ExpenseItem(value), dateItem], False)
def doAction(self, popList, appendList): """ Resets the current state to the state given in popList and appends the current state to the appendList. Enables the buttons in MainWindow accordingly. :param popList | list[ItemRow] appendList | list[ItemRow] """ itemRow = popList.pop() category = itemRow.category if category is None: return row = itemRow.row if itemRow.removed: name, expense, date = itemRow.content expenseItem = ExpenseItem(expense) newRow = [EntryItem(name), expenseItem, DateItem(date)] itemRow.category.insertRow(row, newRow) itemRow.category.model().setSumItem(expenseItem) else: content = itemRow.content[0] child = category.child(row, itemRow.col) replaced = str(child.value()) if row == 1: child.setValue(float(content)) else: child.setText(content) replacedRow = ItemRow(category, (replaced, ), row, itemRow.col) appendList.append(replacedRow) self.__mainWindow.action_Undo.setEnabled(len(self.__items)) self.__mainWindow.action_Redo.setEnabled(len(self.__undoneItems))
def _newItemRow(self): """ Creates new items according to user input. :return list[EntryItem, ExpenseItem, DateItem] """ entryItem = EntryItem(unicode(self.nameLineEdit.text())) expenseItem = ExpenseItem(self.valueDoubleSpinBox.value()) dateItem = DateItem(self.dateEdit.date()) return [entryItem, expenseItem, dateItem]
def __init__(self, parent=None, categories=None, filled=True): """ Initialized with filled=False if filled with data from xml file. filled=True will fill the models with default categories. :param parent | FinanceagerWindow categories | list[str] filled | bool """ super(BalanceModel, self).__init__(parent) self.__mainWindow = parent self.__valueItem = ExpenseItem() self.setHorizontalHeaderLabels(_HEADERLABELS_) if filled: for category in categories: self.appendRow( [CategoryItem(category), SumItem(), EmptyItem()]) # CONNECTIONS self.itemChanged.connect(self.validate)
def setValueItem(self, value): """ Called by FinanceagerWindow.parseXMLtoModel. """ self.__valueItem = ExpenseItem(value)
class BalanceModel(QtGui.QStandardItemModel): """ BalanceModel class for the Financeager application. """ def __init__(self, parent=None, categories=None, filled=True): """ Initialized with filled=False if filled with data from xml file. filled=True will fill the models with default categories. :param parent | FinanceagerWindow categories | list[str] filled | bool """ super(BalanceModel, self).__init__(parent) self.__mainWindow = parent self.__valueItem = ExpenseItem() self.setHorizontalHeaderLabels(_HEADERLABELS_) if filled: for category in categories: self.appendRow( [CategoryItem(category), SumItem(), EmptyItem()]) # CONNECTIONS self.itemChanged.connect(self.validate) def categoriesStringList(self): """ Returns all the names of all the child categories in a string list. Called from FinanceagerWindow.newEntry() to evaluate what model (expenditures/receipts) to append the new entry to. :return list[str] """ return [unicode(self.item(r).text()) for r in range(self.rowCount())] def child(self, row, col): """ Helper function to make BalanceModel fit into the MonthTab.writeToXML routine. The returned child is a category. :param row, col | int, int :return item """ index = self.index(row, col) return self.itemFromIndex(index) def clear(self): """ Reimplementation. Also sets the header labels. """ super(BalanceModel, self).clear() self.setHorizontalHeaderLabels(_HEADERLABELS_) def setSumItem(self, item, oldValue=0): """ Fetches the sumItem corresponding to item and updates it. Also updates the model's value item. :param item | ExpenseItem oldValue | float """ sumItem = item.model().child(item.parent().row(), 1) sumItem.increment(item.value(), oldValue) self.updateValueItem() def setValueItem(self, value): """ Called by FinanceagerWindow.parseXMLtoModel. """ self.__valueItem = ExpenseItem(value) def updateValueItem(self): """ Holds the sum of the model values (sum of category values). """ value = 0.0 for r in range(self.rowCount()): value += self.child(r, 1).value() self.__valueItem.setValue(value) def validate(self, item): """ Called whenever an item is changed. Calls subfunction according to the type of item. Also updates the UndoContainer with a new ItemRow. :param item | item emitted from itemChanged() signal """ #FIXME should be moved into Item classes #return a bool and an error code if False valid = True if isinstance(item, ExpenseItem): valid = self.validateFloat(item) elif isinstance(item, DateItem): valid = self.validateDate(item) elif isinstance(item, EntryItem): valid = self.validateEntry(item) replaced = unicode(item.value()) if valid: replacedRow = ItemRow(item.parent(), (replaced, ), item.row(), item.column()) self.__mainWindow.undoContainer.addAction(replacedRow) self.__mainWindow.action_Undo.setEnabled(True) self.__mainWindow.updateSearchDialog(item) def validateDate(self, item): """ Does a two-stage check if user gives a new date as input. First, QRegExpValidator checks if the input matches the regular expression '\d{1,2}\.', i.e. a one- or two-digit number followed by a dot. If this is accepted, a QDate with the new day is created. If this is valid (depends on the month, internally handled by QDate), the new date is assigned to the item. If any of those validations fails, the user is prompted with a warning and the date is reset. :param item | DateItem :return valid | bool """ state = _DATEVALIDATOR_.validate(item.text(), 0)[0] if state == QtGui.QValidator.Acceptable: newDay = unicode(item.text()) #str 'dd. year, month, _ = item.data().toDate().getDate() date = QDate(year, month, int(newDay[:-1])) #skip trailing . of day if date.isValid(): item.setData(date) return True QtGui.QMessageBox.warning( None, 'Invalid input!', 'Please enter a valid day of the format \'dd.\'') item.setText(str(item.data().toDate().day()) + '.') def validateFloat(self, item): """ Prompts the user with a warning if he gives a non-float input. :param item | ValueItem :return valid | bool """ try: newValue = float(item.text()) oldValue = item.value() item.setValue(newValue) self.setSumItem(item, oldValue) return True except ValueError: QtGui.QMessageBox.warning( None, 'Invalid Input!', 'Please enter a floating point or integer number.') item.setText(str(item.value())) def validateEntry(self, item): """ Prompts the user with a warning if he gives a zero-length input as entry name. :param item | EntryItem :return valid | bool """ if item.text().length(): return True else: QtGui.QMessageBox.warning( None, 'Invalid Input!', 'Please enter a string of non-zero length.') item.setText(str(item.value())) def value(self): return self.__valueItem.value() def valueItem(self): return self.__valueItem def xmlTag(self): return 'model'