#!/usr/bin/env python # Copyright 2008 by Wade Brainerd. # This file is part of Finance. # # Finance is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # Finance is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with Finance. If not, see . """Finance - Home financial software for the OLPC XO.""" # Import standard Python modules. import logging import datetime import locale from gettext import gettext as _ import json import tempfile import StringIO import dbus from gi.repository import Gtk from gi.repository import Pango # Import Sugar UI modules. from sugar3.graphics.toolbutton import ToolButton from sugar3.graphics.toolbarbox import ToolbarBox from sugar3.graphics.radiotoolbutton import RadioToolButton from sugar3.graphics import style from sugar3.activity.widgets import StopButton from sugar3.activity.widgets import ActivityToolbarButton from sugar3.activity import activity from sugar3.datastore import datastore from sugar3.graphics.alert import Alert from sugar3.graphics.icon import Icon from sugar3.graphics.palettemenu import PaletteMenuItem from sugar3.graphics.palettemenu import PaletteMenuBox from sugar import profile # Import screen classes. import registerscreen import chartscreen import budgetscreen from helpbutton import HelpButton import colors from filtertoolitem import FilterToolItem import emptypanel # Set up localization. locale.setlocale(locale.LC_ALL, '') # Initialize logging. log = logging.getLogger('Finance') log.setLevel(logging.DEBUG) logging.basicConfig() DAY = 0 WEEK = 1 MONTH = 2 YEAR = 3 FOREVER = 4 # This is the main Finance activity class. # # It owns the main application window, and all the various toolbars # and options. Screens are stored in a stack, with the currently # active screen on top. class Finance(activity.Activity): def __init__(self, handle): activity.Activity.__init__(self, handle) self.set_title(_("Finance")) self.max_participants = 1 # Initialize database. # data # next_id # transactions # id, name, type, amount, date, category # budgets # category, period, amount, budget self.data = { 'next_id': 0, 'transactions': [], 'budgets': {} } self.transaction_map = {} self.visible_transactions = [] self.transaction_names = {} self.category_names = {} # self.create_test_data() # Initialize view period to the first of the month. self.period = MONTH self.period_start = self.get_this_period() # Create screens. self.register = registerscreen.RegisterScreen(self) self.chart = chartscreen.ChartScreen(self) self.budget = budgetscreen.BudgetScreen(self) self.build_toolbox() self.screenbox = Gtk.VBox() self.headerbox = self.build_header() self._active_panel = None # Add the summary data. self.startlabel = Gtk.Label() self.startamountlabel = Gtk.Label() self.creditslabel = Gtk.Label() self.debitslabel = Gtk.Label() self.balancelabel = Gtk.Label() self.balancelabel.props.margin_left = style.DEFAULT_SPACING self.balancelabel.props.margin_right = style.DEFAULT_SPACING font_size = int(style.FONT_SIZE * 1.25) font = Pango.FontDescription("Sans %d" % font_size) for label in (self.startlabel, self.startamountlabel, self.creditslabel, self.debitslabel): label.modify_font(font) label.set_hexpand(True) label.set_halign(Gtk.Align.START) label.props.margin = style.DEFAULT_PADDING self.balance_evbox = Gtk.EventBox() self.balance_evbox.add(self.balancelabel) summarybox = Gtk.Grid() summarybox.attach(self.startlabel, 0, 0, 1, 1) summarybox.attach(self.startamountlabel, 0, 1, 1, 1) summarybox.attach(self.creditslabel, 1, 0, 1, 1) summarybox.attach(self.debitslabel, 1, 1, 1, 1) summarybox.attach(self.balance_evbox, 2, 0, 1, 2) self.balance_evbox.set_halign(Gtk.Align.END) summary_evbox = Gtk.EventBox() summary_evbox.add(summarybox) summary_evbox.modify_bg(Gtk.StateType.NORMAL, style.Color('#666666').get_gdk_color()) summary_evbox.set_size_request(-1, style.GRID_CELL_SIZE) vbox = Gtk.VBox() vbox.pack_start(self.headerbox, False, False, 0) vbox.pack_start(self.screenbox, True, True, 0) vbox.pack_start(summary_evbox, False, False, 0) # Start with the empty screen. empty_panel = emptypanel.create_empty_panel( 'row-insert-credit', _('Add some credit or debit to get started!'), _('Add credit'), self.__empty_panel_btn_cb) self._set_internal_panel(empty_panel) # This has to happen last, because it calls the read_file # method when restoring from the Journal. self.set_canvas(vbox) self.show_all() def build_toolbox(self): view_tool_group = None registerbtn = RadioToolButton() registerbtn.props.icon_name = 'view-list' registerbtn.props.label = _('Register') registerbtn.set_tooltip(_("Register")) registerbtn.props.group = view_tool_group view_tool_group = registerbtn registerbtn.props.accelerator = '1' registerbtn.connect('clicked', self.register_cb) budgetbtn = RadioToolButton() budgetbtn.props.icon_name = 'budget' budgetbtn.props.label = _('Budget') budgetbtn.set_tooltip(_("Budget")) budgetbtn.props.group = view_tool_group budgetbtn.props.accelerator = '2' budgetbtn.connect('clicked', self.budget_cb) chartbtn = RadioToolButton() chartbtn.props.icon_name = 'chart' chartbtn.props.label = _('Chart') chartbtn.set_tooltip(_("Chart")) chartbtn.props.group = view_tool_group chartbtn.props.accelerator = '3' chartbtn.connect('clicked', self.chart_cb) helpbutton = self._create_help_button() helpbutton.show_all() self.toolbar_box = ToolbarBox() activity_button = ActivityToolbarButton(self) self.toolbar_box.toolbar.insert(activity_button, 0) activity_button.show() self.toolbar_box.toolbar.insert(Gtk.SeparatorToolItem(), -1) self.toolbar_box.toolbar.insert(registerbtn, -1) self.toolbar_box.toolbar.insert(budgetbtn, -1) self.toolbar_box.toolbar.insert(chartbtn, -1) self.toolbar_box.toolbar.insert(Gtk.SeparatorToolItem(), -1) self.toolbar_box.toolbar.insert(helpbutton, -1) separator = Gtk.SeparatorToolItem() separator.props.draw = False separator.set_expand(True) self.toolbar_box.toolbar.insert(separator, -1) self.toolbar_box.toolbar.insert(StopButton(self), -1) self.set_toolbar_box(self.toolbar_box) activity_button.page.insert(self._create_export_button(), -1) self.toolbar_box.show_all() def _create_export_button(self): # Add expoprt button export_data = ToolButton('save-as-data') export_data.props.tooltip = _('Export data') export_data.props.hide_tooltip_on_click = False export_data.palette_invoker.props.toggle_palette = True export_data.show() menu_box = PaletteMenuBox() export_data.props.palette.set_content(menu_box) menu_item = PaletteMenuItem(text_label=_('Export credits by day')) menu_item.connect('activate', self.__export_data_to_chart_cb, 'credit', DAY) menu_box.append_item(menu_item) menu_item = PaletteMenuItem(text_label=_('Export debits by day')) menu_item.connect('activate', self.__export_data_to_chart_cb, 'debit', DAY) menu_box.append_item(menu_item) menu_item = PaletteMenuItem(text_label=_('Export credits by month')) menu_item.connect('activate', self.__export_data_to_chart_cb, 'credit', MONTH) menu_box.append_item(menu_item) menu_item = PaletteMenuItem(text_label=_('Export debits by month')) menu_item.connect('activate', self.__export_data_to_chart_cb, 'debit', MONTH) menu_box.append_item(menu_item) menu_box.show_all() return export_data def _create_help_button(self): helpitem = HelpButton() helpitem.add_section(_('Register'), icon='view-list') helpitem.add_paragraph(registerscreen.REGISTER_HELP) helpitem.add_section(_('Budget'), icon='budget') helpitem.add_paragraph(budgetscreen.BUDGET_HELP) helpitem.add_section(_('Chart'), icon='chart') helpitem.add_paragraph(chartscreen.CHART_HELP) return helpitem def build_header(self): # Add the header. self.periodlabel = Gtk.Label() self.periodlabel.set_padding(10, 0) self._period_label_item = Gtk.ToolItem() self._period_label_item.add(self.periodlabel) self.periodlabel.set_halign(Gtk.Align.END) self._period_label_item.set_size_request(style.GRID_CELL_SIZE * 5, -1) headerbox = Gtk.Toolbar() headerbox.modify_bg(Gtk.StateType.NORMAL, style.Color('#424242').get_gdk_color()) headerbox.set_size_request(-1, style.GRID_CELL_SIZE) # register buttons self.newcreditbtn = ToolButton('row-insert-credit') self.newcreditbtn.set_tooltip(_("New Credit")) self.newcreditbtn.props.accelerator = 'A' self.newcreditbtn.connect('clicked', self.__newcredit_cb) self.newdebitbtn = ToolButton('row-insert-debit') self.newdebitbtn.set_tooltip(_("New Debit")) self.newdebitbtn.props.accelerator = 'D' self.newdebitbtn.connect('clicked', self.__newdebit_cb) self.eraseitembtn = ToolButton('basket') self.eraseitembtn.set_tooltip(_("Erase Transaction")) self.eraseitembtn.props.accelerator = 'E' self.eraseitembtn.connect('clicked', self.__eraseitem_cb) headerbox.insert(self.newcreditbtn, -1) headerbox.insert(self.newdebitbtn, -1) headerbox.insert(self.eraseitembtn, -1) self.header_separator_visible = Gtk.SeparatorToolItem() headerbox.insert(self.header_separator_visible, -1) self.export_image = ToolButton('save-as-image') self.export_image.set_tooltip(_("Save as Image")) self.export_image.connect('clicked', self.__save_image_cb) headerbox.insert(self.export_image, -1) self._header_separator = Gtk.SeparatorToolItem() self._header_separator.props.draw = False self._header_separator.set_expand(True) headerbox.insert(self._header_separator, -1) # period controls self.thisperiodbtn = ToolButton('go-down') self.thisperiodbtn.props.accelerator = 'Down' self.thisperiodbtn.connect('clicked', self.thisperiod_cb) self.prevperiodbtn = ToolButton('go-previous-paired') self.prevperiodbtn.props.accelerator = 'Left' self.prevperiodbtn.connect('clicked', self.prevperiod_cb) self.nextperiodbtn = ToolButton('go-next-paired') self.nextperiodbtn.props.accelerator = 'Right' self.nextperiodbtn.connect('clicked', self.nextperiod_cb) period_options = {DAY: _('Day'), WEEK: _('Week'), MONTH: _('Month'), YEAR: _('Year'), FOREVER: _('Forever')} periodcombo = FilterToolItem('calendar', MONTH, period_options, _('Select period')) periodcombo.connect('changed', self.__period_changed_cb) headerbox.insert(periodcombo, -1) headerbox.insert(self.prevperiodbtn, -1) headerbox.insert(self.nextperiodbtn, -1) headerbox.insert(self.thisperiodbtn, -1) headerbox.insert(self._period_label_item, -1) return headerbox def show_header_controls(self): for child in self.headerbox.get_children(): child.show() if self._active_panel == self.register: if child in (self.header_separator_visible, self.export_image): child.hide() elif self._active_panel == self.budget: if child in (self.newcreditbtn, self.newdebitbtn, self.eraseitembtn, self.header_separator_visible, self.export_image): child.hide() elif self._active_panel == self.chart: # Use NOT here if child not in (self.newcreditbtn, self.newdebitbtn, self.header_separator_visible, self.export_image): child.hide() def register_cb(self, widget): self._set_internal_panel(self.register) self.show_header_controls() self.newcreditbtn.set_tooltip(_("New Credit")) self.newdebitbtn.set_tooltip(_("New Debit")) def budget_cb(self, widget): self._set_internal_panel(self.budget) self.show_header_controls() def chart_cb(self, widget): self._set_internal_panel(self.chart) self.show_header_controls() self.newcreditbtn.set_tooltip(_("Show Credits")) self.newdebitbtn.set_tooltip(_("Show Debits")) def _set_internal_panel(self, widget): if self.screenbox.get_children(): self.screenbox.remove(self.screenbox.get_children()[0]) self.screenbox.pack_start(widget, True, True, 0) widget.show_all() self._active_panel = widget self.build_screen() def build_screen(self): self.build_visible_transactions() if hasattr(self._active_panel, 'build'): self._active_panel.build() self.update_header() self.update_summary() self.update_toolbar() def __empty_panel_btn_cb(self, button): self._set_internal_panel(self.register) self.register.new_credit() def __newcredit_cb(self, widget): if self._active_panel == self.chart: # in the case of chart, select the graphic self.chart.set_mode(self.chart.CHART_CREDIT) return # this check is used when the emptypanle is visible if self._active_panel != self.register: self._set_internal_panel(self.register) self.register.new_credit() def __newdebit_cb(self, widget): if self._active_panel == self.chart: # in the case of chart, select the graphic self.chart.set_mode(self.chart.CHART_DEBIT) return # this check is used when the emptypanle is visible if self._active_panel != self.register: self._set_internal_panel(self.register) self.register.new_debit() def __eraseitem_cb(self, widget): self.register.erase_item() self.build_screen() def update_header(self): if self.period == DAY: # TRANS: representation of the "Day" period text = self.period_start.strftime(_("%B %d, %Y")) elif self.period == WEEK: # TRANS: representation of the "Week of" period text = _('Week of') + self.period_start.strftime(_(" %B %d, %Y")) elif self.period == MONTH: # TRANS: representation of the "Month" period text = self.period_start.strftime(_("%B, %Y")) elif self.period == YEAR: # TRANS: representation of the "Year" period text = self.period_start.strftime(_("%Y")) elif self.period == FOREVER: text = _('Forever') self.periodlabel.set_markup( "" + text + "") def update_summary(self): # Calculate starting balance. start = 0.0 for t in self.data['transactions']: d = t['date'] if d < self.period_start.toordinal(): if t['type'] == 'credit': start += t['amount'] else: start -= t['amount'] # Calculate totals for this period. credit_count = 0 credit_total = 0.0 debit_count = 0 debit_total = 0.0 total = start for t in self.visible_transactions: if t['type'] == 'credit': credit_count += 1 credit_total += t['amount'] total += t['amount'] else: debit_count += 1 debit_total += t['amount'] total -= t['amount'] # Update Balance. if total >= 0.0: balancecolor = colors.CREDIT_COLOR else: balancecolor = colors.DEBIT_COLOR balance = \ "%s %s" % \ (_('Balance: '), locale.currency(total)) self.balancelabel.set_markup(balance) self.balance_evbox.modify_bg( Gtk.StateType.NORMAL, style.Color(balancecolor).get_gdk_color()) self.startlabel.set_markup( "%s" % _('Starting Balance:')) self.startamountlabel.set_markup( "%s" % locale.currency(start)) self.creditslabel.set_markup( "%s" % (_('%s in %d credits') % (locale.currency(credit_total), credit_count))) self.debitslabel.set_markup( "%s" % (_('%s in %d debits') % (locale.currency(debit_total), debit_count))) def update_toolbar(self): # Disable the navigation when Forever is selected. next_prev = self.period != FOREVER self.prevperiodbtn.set_sensitive(next_prev) self.thisperiodbtn.set_sensitive(next_prev) self.nextperiodbtn.set_sensitive(next_prev) # This is a HACK to translate the string properly # http://bugs.sugarlabs.org/ticket/3190 if self.period == MONTH: text_previous_period = _('Previous Month') text_this_period = _('This Month') text_next_period = _('Next Month') elif self.period == WEEK: text_previous_period = _('Previous Week') text_this_period = _('This Week') text_next_period = _('Next Week') elif self.period == DAY: text_previous_period = _('Previous Day') text_this_period = _('This Day') text_next_period = _('Next Day') elif self.period == YEAR: text_previous_period = _('Previous Year') text_this_period = _('This Year') text_next_period = _('Next Year') # Update the label self.period to reflect the period. self.prevperiodbtn.set_tooltip(text_previous_period) self.thisperiodbtn.set_tooltip(text_this_period) self.nextperiodbtn.set_tooltip(text_next_period) def get_this_period(self): today = datetime.date.today() if self.period == DAY: return today elif self.period == WEEK: while today.weekday() != 0: today -= datetime.timedelta(days=1) return today elif self.period == MONTH: return datetime.date(today.year, today.month, 1) elif self.period == YEAR: return datetime.date(today.year, 1, 1) elif self.period == FOREVER: return datetime.date(1900, 1, 1) def get_next_period(self, start): if self.period == DAY: return start + datetime.timedelta(days=1) elif self.period == WEEK: return start + datetime.timedelta(days=7) elif self.period == MONTH: if start.month == 12: return datetime.date(start.year + 1, 1, 1) else: return datetime.date(start.year, start.month + 1, 1) elif self.period == YEAR: return datetime.date(start.year + 1, 1, 1) def get_prev_period(self, start): if self.period == DAY: return start - datetime.timedelta(days=1) elif self.period == WEEK: return start - datetime.timedelta(days=7) elif self.period == MONTH: if start.month == 1: return datetime.date(start.year - 1, 12, 1) else: return datetime.date(start.year, start.month - 1, 1) elif self.period == YEAR: return datetime.date(start.year - 1, 1, 1) def thisperiod_cb(self, widget): if self.period != FOREVER: self.period_start = self.get_this_period() self.build_screen() def nextperiod_cb(self, widget): if self.period != FOREVER: self.period_start = self.get_next_period(self.period_start) self.build_screen() def prevperiod_cb(self, widget): if self.period != FOREVER: self.period_start = self.get_prev_period(self.period_start) self.build_screen() def __period_changed_cb(self, widget, value): self.period = int(value) self.update_toolbar() # Jump to 'this period'. self.period_start = self.get_this_period() self.build_screen() def build_visible_transactions(self): if self.period == FOREVER: self.visible_transactions = self.data['transactions'] else: period_start_ord = self.period_start.toordinal() period_end_ord = self.get_next_period( self.period_start).toordinal() self.visible_transactions = [] for t in self.data['transactions']: d = t['date'] if d >= period_start_ord and d < period_end_ord: self.visible_transactions.append(t) self.visible_transactions.sort(lambda a, b: a['date'] - b['date']) def build_transaction_map(self): self.transaction_map = {} for t in self.data['transactions']: self.transaction_map[t['id']] = t def create_transaction(self, name='', type='debit', amount=0, category='', date=datetime.date.today()): id = self.data['next_id'] self.data['next_id'] += 1 t = { 'id': id, 'name': name, 'type': type, 'amount': amount, 'date': date.toordinal(), 'category': category } self.data['transactions'].append(t) self.transaction_map[id] = t self.build_visible_transactions() return id def destroy_transaction(self, id): t = self.transaction_map[id] self.data['transactions'].remove(t) del self.transaction_map[id] def build_names(self): self.transaction_names = {} self.category_names = {} for t in self.data['transactions']: self.transaction_names[t['name']] = 1 self.category_names[t['category']] = 1 def create_test_data(self): cur_date = datetime.date.today() cur_date = datetime.date(cur_date.year, cur_date.month, 1) self.create_transaction('Initial Balance', type='credit', amount=632, category='Initial Balance', date=cur_date) cur_date += datetime.timedelta(days=1) self.create_transaction('Fix Car', amount=75.84, category='Transportation', date=cur_date) cur_date += datetime.timedelta(days=2) self.create_transaction('Adopt Cat', amount=100, category='Pets', date=cur_date) self.create_transaction('New Coat', amount=25.53, category='Clothing', date=cur_date) cur_date += datetime.timedelta(days=2) self.create_transaction('Pay Rent', amount=500, category='Housing', date=cur_date) cur_date += datetime.timedelta(days=1) self.create_transaction('Funky Cafe', amount=5.20, category='Food', date=cur_date) self.create_transaction('Groceries', amount=50.92, category='Food', date=cur_date) self.create_transaction('Cat Food', amount=5.40, category='Pets', date=cur_date) cur_date += datetime.timedelta(days=4) self.create_transaction('Paycheck', type='credit', amount=700, category='Paycheck', date=cur_date) self.create_transaction('Gas', amount=21.20, category='Transportation', date=cur_date) cur_date += datetime.timedelta(days=2) self.create_transaction('Cat Toys', amount=10.95, category='Pets', date=cur_date) self.create_transaction('Gift for Sister', amount=23.20, category='Gifts', date=cur_date) cur_date += datetime.timedelta(days=2) self.create_transaction('Pay Rent', amount=500, category='Housing', date=cur_date) cur_date += datetime.timedelta(days=1) self.create_transaction('Funky Cafe', amount=5.20, category='Food', date=cur_date) self.create_transaction('Groceries', amount=50.92, category='Food', date=cur_date) self.create_transaction('Cat Food', amount=5.40, category='Pets', date=cur_date) cur_date += datetime.timedelta(days=4) self.create_transaction('Paycheck', type='credit', amount=700, category='Paycheck', date=cur_date) self.create_transaction('Gas', amount=21.20, category='Transportation', date=cur_date) cur_date += datetime.timedelta(days=2) self.create_transaction('Cat Toys', amount=10.95, category='Pets', date=cur_date) self.create_transaction('Gift for Sister', amount=23.20, category='Gifts', date=cur_date) cur_date += datetime.timedelta(days=2) self.create_transaction('Pay Rent', amount=500, category='Housing', date=cur_date) cur_date += datetime.timedelta(days=1) self.create_transaction('Funky Cafe', amount=5.20, category='Food', date=cur_date) self.create_transaction('Groceries', amount=50.92, category='Food', date=cur_date) self.create_transaction('Cat Food', amount=5.40, category='Pets', date=cur_date) cur_date += datetime.timedelta(days=4) self.create_transaction('Paycheck', type='credit', amount=700, category='Paycheck', date=cur_date) self.create_transaction('Gas', amount=21.20, category='Transportation', date=cur_date) cur_date += datetime.timedelta(days=2) self.create_transaction('Cat Toys', amount=10.95, category='Pets', date=cur_date) self.create_transaction('Gift for Sister', amount=23.20, category='Gifts', date=cur_date) cur_date += datetime.timedelta(days=2) self.create_transaction('Pay Rent', amount=500, category='Housing', date=cur_date) cur_date += datetime.timedelta(days=1) self.create_transaction('Funky Cafe', amount=5.20, category='Food', date=cur_date) self.create_transaction('Groceries', amount=50.92, category='Food', date=cur_date) self.create_transaction('Cat Food', amount=5.40, category='Pets', date=cur_date) cur_date += datetime.timedelta(days=4) self.create_transaction('Paycheck', type='credit', amount=700, category='Paycheck', date=cur_date) self.create_transaction('Gas', amount=21.20, category='Transportation', date=cur_date) cur_date += datetime.timedelta(days=2) self.create_transaction('Cat Toys', amount=10.95, category='Pets', date=cur_date) self.create_transaction('Gift for Sister', amount=23.20, category='Gifts', date=cur_date) cur_date += datetime.timedelta(days=2) self.create_transaction('Pay Rent', amount=500, category='Housing', date=cur_date) cur_date += datetime.timedelta(days=1) self.create_transaction('Funky Cafe', amount=5.20, category='Food', date=cur_date) self.create_transaction('Groceries', amount=50.92, category='Food', date=cur_date) self.create_transaction('Cat Food', amount=5.40, category='Pets', date=cur_date) cur_date += datetime.timedelta(days=4) self.create_transaction('Paycheck', type='credit', amount=700, category='Paycheck', date=cur_date) self.create_transaction('Gas', amount=21.20, category='Transportation', date=cur_date) cur_date += datetime.timedelta(days=2) self.create_transaction('Cat Toys', amount=10.95, category='Pets', date=cur_date) self.create_transaction('Gift for Sister', amount=23.20, category='Gifts', date=cur_date) cur_date += datetime.timedelta(days=2) self.create_transaction('Pay Rent', amount=500, category='Housing', date=cur_date) cur_date += datetime.timedelta(days=1) self.create_transaction('Funky Cafe', amount=5.20, category='Food', date=cur_date) self.create_transaction('Groceries', amount=50.92, category='Food', date=cur_date) self.create_transaction('Cat Food', amount=5.40, category='Pets', date=cur_date) cur_date += datetime.timedelta(days=4) self.create_transaction('Paycheck', type='credit', amount=700, category='Paycheck', date=cur_date) self.create_transaction('Gas', amount=21.20, category='Transportation', date=cur_date) cur_date += datetime.timedelta(days=2) self.create_transaction('Cat Toys', amount=10.95, category='Pets', date=cur_date) self.create_transaction('Gift for Sister', amount=23.20, category='Gifts', date=cur_date) cur_date += datetime.timedelta(days=2) self.create_transaction('Pay Rent', amount=500, category='Housing', date=cur_date) cur_date += datetime.timedelta(days=1) self.create_transaction('Funky Cafe', amount=5.20, category='Food', date=cur_date) self.create_transaction('Groceries', amount=50.92, category='Food', date=cur_date) self.create_transaction('Cat Food', amount=5.40, category='Pets', date=cur_date) cur_date += datetime.timedelta(days=4) self.create_transaction('Paycheck', type='credit', amount=700, category='Paycheck', date=cur_date) self.create_transaction('Gas', amount=21.20, category='Transportation', date=cur_date) cur_date += datetime.timedelta(days=2) self.create_transaction('Cat Toys', amount=10.95, category='Pets', date=cur_date) self.create_transaction('Gift for Sister', amount=23.20, category='Gifts', date=cur_date) self.build_transaction_map() self.build_names() def read_file(self, file_path): if self.metadata['mime_type'] != 'text/plain': return fd = open(file_path, 'r') try: text = fd.read() self.data = json.loads(text) finally: fd.close() if self.data['transactions']: self._set_internal_panel(self.register) self.show_header_controls() self.build_transaction_map() self.build_names() self.build_screen() def write_file(self, file_path): if not self.metadata['mime_type']: self.metadata['mime_type'] = 'text/plain' fd = open(file_path, 'w') try: text = json.dumps(self.data) fd.write(text) finally: fd.close() def __save_image_cb(self, widget): image_file = tempfile.NamedTemporaryFile(mode='w+b', suffix='.png') journal_entry = datastore.create() journal_entry.metadata['title'] = self.chart.title journal_entry.metadata['keep'] = '0' journal_entry.metadata['mime_type'] = 'image/png' # generate the image self.chart.generate_image(image_file.file, 800, 600) image_file.file.close() journal_entry.file_path = image_file.name # generate the preview preview_str = StringIO.StringIO() self.chart.generate_image(preview_str, activity.PREVIEW_SIZE[0], activity.PREVIEW_SIZE[1]) journal_entry.metadata['preview'] = dbus.ByteArray( preview_str.getvalue()) logging.error('Create %s image file', image_file.name) datastore.write(journal_entry) self._show_journal_alert( _('Chart created'), _('Open in the Journal'), journal_entry.object_id) def _show_journal_alert(self, title, msg, object_id): open_alert = Alert() open_alert.props.title = title open_alert.props.msg = msg open_icon = Icon(icon_name='zoom-activity') open_alert.add_button(Gtk.ResponseType.APPLY, _('Show in Journal'), open_icon) open_icon.show() ok_icon = Icon(icon_name='dialog-ok') open_alert.add_button(Gtk.ResponseType.OK, _('Ok'), ok_icon) ok_icon.show() # Remove other alerts for alert in self._alerts: self.remove_alert(alert) self.add_alert(open_alert) open_alert.connect('response', self.__open_response_cb, object_id) open_alert.show() def __open_response_cb(self, alert, response_id, object_id): if response_id is Gtk.ResponseType.APPLY: activity.show_object_in_journal(object_id) self.remove_alert(alert) def __export_data_to_chart_cb(self, widget, type_movement, period): """ type_movement = 'debit' or 'credit' period DAY = 0 MONTH = 2 """ logging.debug('export data %s %s', type_movement, period) chart_params = {} axis = {'tickFont': 'Sans', 'labelFont': 'Sans', 'labelFontSize': 14, 'labelColor': '#666666', 'lineColor': '#b3b3b3', 'tickColor': '#000000', 'tickFontSize': 12} chart_params['font_options'] = { 'titleFont': 'Sans', 'titleFontSize': 12, 'titleColor': '#000000', 'axis': axis} if type_movement == 'credit': what_filter = 'Credits' elif type_movement == 'debit': what_filter = 'Debits' else: logging.error('ERROR type_movement should be credit or debit') if period == DAY: when = 'day' elif period == MONTH: when = 'month' else: logging.error('ERROR period should be DAY or MONTH') title = _('%s by %s') % (what_filter, when) chart_params['title'] = title chart_params['x_label'] = '' chart_params['y_label'] = '' chart_params['current_chart.type'] = 1 xo_color = profile.get_color() chart_params['chart_line_color'] = xo_color.get_stroke_color() chart_params['chart_color'] = xo_color.get_fill_color() """ 'chart_data': [ ['hello', 200.0], ['mrch', 100.0]], """ transactions = self.data['transactions'] groups = {} for transaction in transactions: if transaction['type'] == type_movement: date = transaction['date'] if period == DAY: group = date elif period == MONTH: d = datetime.date.fromordinal(date) group = datetime.date(d.year, d.month, 1).toordinal() if group in groups.keys(): groups[group] = groups[group] + transaction['amount'] else: groups[group] = transaction['amount'] data = [] for group in sorted(groups.keys()): if period == DAY: label = datetime.date.fromordinal(group).isoformat() elif period == MONTH: d = datetime.date.fromordinal(group) label = '%s-%s' % (d.year, d.month) data.append([label, groups[group]]) chart_params['chart_data'] = data logging.debug('chart_data %s', chart_params) # save to the journal data_file = tempfile.NamedTemporaryFile(mode='w+b', suffix='.json') journal_entry = datastore.create() journal_entry.metadata['title'] = title journal_entry.metadata['keep'] = '0' journal_entry.metadata['mime_type'] = 'application/x-chart-activity' journal_entry.metadata['activity'] = 'org.sugarlabs.SimpleGraph' json.dump(chart_params, data_file.file) data_file.file.close() journal_entry.file_path = data_file.name logging.debug('Create %s data file', data_file.name) datastore.write(journal_entry) self._show_journal_alert( _('Exported data'), _('Open in the Journal'), journal_entry.object_id)