예제 #1
0
    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)
예제 #2
0
    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)
예제 #3
0
 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))
예제 #4
0
    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]
예제 #5
0
    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)
예제 #6
0
 def setValueItem(self, value):
     """ Called by FinanceagerWindow.parseXMLtoModel. """
     self.__valueItem = ExpenseItem(value)
예제 #7
0
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'