class MainDialog(gobject.GObject): search_text = "" _bullet_cache = {} __gsignals__ = { "bill-added": (gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE, (gobject.TYPE_PYOBJECT,)), "bill-updated": (gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE, (gobject.TYPE_PYOBJECT,)), "bill-removed": (gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE, (gobject.TYPE_PYOBJECT,)), } def __init__(self): gobject.GObject.__init__(self) if exists(join(USER_CFG_PATH, CFG_NAME)): from lib.migrate_to_gconf import migrate migrate(join(USER_CFG_PATH, CFG_NAME)) self.gconf_client = Configuration() self.message = Message() # Connects to the database self.actions = Actions() self.ui = gtk.Builder() self.ui.add_from_file(os.path.join(DEFAULT_CFG_PATH, "main.ui")) self.window = self.ui.get_object("main_window") self.window.set_title("%s" % common.APPNAME) self.window.set_icon_from_file(common.APP_ICON) # ViewBill self.list = ViewBill() self.list.connect("cursor_changed", self._on_list_cursor_changed) self.list.connect("row_activated", self._on_list_row_activated) self.list.connect("button_press_event", self._on_list_button_press_event) self.ui.get_object("bill_box").add(self.list) # Toolbar self.toolbar = self.ui.get_object("toolbar") # Menubar self._populate_menubar() # Statusbar self.statusbar = Statusbar() self.ui.get_object("statusbar_box").add(self.statusbar) # Restore timeline zoom timeline_count = self.gconf_client.get("timeline_count") # Timeline self.timeline = Timeline(count=timeline_count, callback=self.on_timeline_cb) self.timeline.connect("range-changed", self._on_timeline_changed) self.timeline.connect("button-press-event", self._on_timeline_click) self.timeline.connect("cleared", self._on_timeline_cleared) self.ui.get_object("timeline_box").add(self.timeline) # Chart self.chart = ChartWidget() self.chart.set_border_width(10) self.ui.get_object("chart_box").add(self.chart) # Restore position and size of window width = self.gconf_client.get("window_width") height = self.gconf_client.get("window_height") x = self.gconf_client.get("window_position_x") y = self.gconf_client.get("window_position_y") if width and height: self.window.resize(width, height) if x and y: self.window.move(x, y) self.window.show_all() # Whether to display toolbar or not self.on_showToolbar_toggled(self.ui.get_object("showToolbar")) self.list.grab_focus() if self.gconf_client.get("start_in_tray"): self.window.hide() self.toggle_buttons() # Connects to the Daemon self.iface = None iface = get_dbus_interface(common.DBUS_INTERFACE, common.DBUS_PATH) if iface: iface.connect_to_signal("bill_edited", self.reloadTreeView) iface.connect_to_signal("bill_edited", self.reloadTimeline) iface.connect_to_signal("show_main_window", self.window.show) self.iface = iface gobject.timeout_add(2000, self._send_tray_hints) self.set_action_strings() self.ui.connect_signals(self) # populate treeview self.reloadTreeView() self.notify = NotifyIcon(self) # Integrate with Ubuntu Unity if UNITY: self.unity = UnityIntegration(self) def set_action_strings(self): # for some reason the actions strings do not get translated yet # so we define them here so they would be picked up by the pyfile scanner self.ui.get_object("newBill").set_label(_("_New")) self.ui.get_object("newBill").set_tooltip(_("Add new bill")) self.ui.get_object("editBill").set_label(_("_Edit")) self.ui.get_object("editBill").set_tooltip(_("Edit a bill")) self.ui.get_object("removeBill").set_label(_("_Delete")) self.ui.get_object("removeBill").set_tooltip(_("Delete selected bill")) self.ui.get_object("markPaid").set_label(_("P_aid")) self.ui.get_object("markPaid").set_tooltip(_("Mark as paid")) self.ui.get_object("markNotPaid").set_label(_("No_t Paid")) self.ui.get_object("markNotPaid").set_tooltip(_("Mark as not paid")) self.ui.get_object("showToolbar").set_label(_("_Show Toolbar")) self.ui.get_object("showToolbar").set_tooltip(_("Show the toolbar")) # Methods: UI def _send_tray_hints(self): self.iface.set_tray_hints(force_string(self.notify.get_hints())) gobject.timeout_add(60000, self._send_tray_hints) def get_window_visibility(self): return self.window.get_property("visible") def show_hide_window(self): if self.window.get_property("visible"): self.window.hide() else: self.window.show() def get_selected_record(self): """ Returns a bill object from the current selected record """ if len(self.list.listStore) > 0: model_ = self.list.get_model() if self.list.get_cursor()[0]: index = self.list.get_cursor()[0][0] else: index = 0 b_id = model_[index][0] records = self.actions.get_bills(id=b_id) self.currentrecord = records[0] else: self.currentrecord = None print "Current record is: %s" % self.currentrecord def populate_view(self, records): """ Populates the treeview control with the records passed """ # Reset list self.list.listStore.clear() if not records: return 0 # Loops through bills collection path = 0 for rec in records: # Format the record prior to adding it to treeview row = self.format_row(rec) self.list.add(row) # Set the cursor to the first (top) record self.list.set_cursor(path) # Returns how many records there are in the treeview return len(records) def reloadTreeView(self, *arg): # Update list with updated record status = self.gconf_client.get("show_paid_bills") path = self.list.get_cursor()[0] self.list.listStore.clear() self.currentrecord = None first = self.timeline.start_date last = self.timeline.end_date # Get list of records records = self.actions.get_interval_bills(first, last, status) # Populate treeview self.populate_view(records) # Update status bar self.update_statusbar() # populate chart self._populate_chart(status, first, last) return len(records) def format_row(self, row): """ Formats a bill to be displayed as a row. """ categoryName = row.category.name if row.category else _("None") categoryColor = row.category.color if row.category else "#d3d7cf" formatted = [ row.id, create_pixbuf(color=categoryColor), categoryName, row.payee, row.dueDate.strftime(_("%m/%d").encode("ASCII")), row.amount, row.notes, int(row.paid), None, ] return formatted def _populate_chart(self, status, start, end): records = [] categories = [] totals = [] records = self.actions.get_monthly_totals(start, end, status) # Chart widget takes data in format (('CategoryName', amount),) categories = [cat or "None" for cat, total in records] totals = [float(total) for cat, total in records] # records = [(c if c else 'None',float(t)) for c,t in records] # set bar colors all_categories = self.actions.get_categories() self.chart.chart.key_colors = dict([(cat.name or "None", cat.color) for cat in all_categories]) self.chart.plot(categories, totals) def _populate_menubar(self): try: saved_view = self.gconf_client.get("show_paid_bills") except: saved_view = 1 self.gconf_client.set("show_paid_bills", saved_view) if saved_view == 0: self.ui.get_object("showNotPaid").set_active(True) elif saved_view == 1: self.ui.get_object("showPaid").set_active(True) else: self.ui.get_object("showAll").set_active(True) # Check whether we display the toolbar or not self.ui.get_object("showToolbar").set_active(self.gconf_client.get("show_toolbar")) def add_bill(self): selectedDate = self.timeline.value records = dialogs.add_dialog(parent=self.window, selectedDate=selectedDate) # Checks if the user did not cancel the action if records: # Add new bill to database for rec in records: bill_id = self.actions.add(rec) bill = self.actions.get_bills(id=bill_id)[0] if bill: self.list.add(self.format_row(bill)) self.update_statusbar() # Reload records tree (something changed) self.reloadTreeView() self.reloadTimeline() def edit_bill(self): records = dialogs.edit_dialog(parent=self.window, record=self.currentrecord) # Checks if the user did not cancel the action if records: for rec in records: # Edit bill to database rec = self.actions.edit(rec) self.emit("bill-updated", rec) # Reload records tree (something changed) self.reloadTreeView() self.reloadTimeline() def remove_bill(self): self.actions.delete(self.currentrecord) self.list.remove() self.emit("bill-removed", None) self.update_statusbar() self.reloadTreeView() self.reloadTimeline() def toggle_bill_paid(self): # Fetch record from database record = self.actions.get_bills(id=self.currentrecord.id)[0] # Toggle paid field record.paid = False if record.paid else True # Edit bill in the database transaction = self.actions.add(record) self.emit("bill-updated", record) # Update our current copy self.currentrecord = self.actions.get_bills(id=self.currentrecord.id)[0] # Update timeline widget to reflect change self._bullet_cache[self.currentrecord.dueDate] = [self.currentrecord] # Update list with updated record idx = self.list.get_cursor()[0][0] self.update_statusbar(idx) self.reloadTreeView() self.reloadTimeline() def about(self): dialogs.about_dialog(parent=self.window) def preferences(self): dialogs.preferences_dialog(parent=self.window) # Methods def _quit_application(self): self.save_position() self.save_size() self.save_timeline_zoom() gtk.main_quit() return False def save_position(self): x, y = self.window.get_position() self.gconf_client.set("window_position_x", x) self.gconf_client.set("window_position_y", y) def save_size(self): width, height = self.window.get_size() self.gconf_client.set("window_width", width) self.gconf_client.set("window_height", height) def save_timeline_zoom(self): count = self.timeline.count self.gconf_client.set("timeline_count", count) def toggle_buttons(self, paid=None): """ Toggles all buttons conform number of records present and their state """ for widget in ["editBill", "removeBill", "markPaid", "markNotPaid"]: self.ui.get_object(widget).set_sensitive(len(self.list.listStore) > 0) if len(self.list.listStore) > 0: self.ui.get_object("markPaid").set_sensitive(paid == False) self.ui.get_object("markNotPaid").set_sensitive(paid == True) def update_statusbar(self, index=0): """ This function is used to update status bar informations about the list """ records = len(self.list.listStore) # Record count self.statusbar.Records(records) if self.currentrecord: # Display the status self.statusbar.Notes(self.currentrecord.notes) # Toggles toolbar buttons on/off self.toggle_buttons(self.currentrecord.paid) else: # Clear the status for the selected row self.statusbar.Notes("") # Toggles toolbar buttons on/off self.toggle_buttons() show_paid_bills = self.gconf_client.get("show_paid_bills") if show_paid_bills is 0: self.statusbar.Info(_("Not Paid Only")) elif show_paid_bills is 1: self.statusbar.Info(_("Paid Only")) else: self.statusbar.Info("") # Event handlers def _on_list_button_press_event(self, widget, event): """ This function will handle the signal to show a popup menu sent by a right click on tvBill widget. """ if event.button == 3 and event.type == gtk.gdk.BUTTON_PRESS: self.get_selected_record() c = self.ui.get_object("context_menu") c.popup(None, None, None, event.button, event.get_time()) def _on_list_row_activated(self, widget, path, column): self._on_list_cursor_changed(widget) self.on_editBill_activate(None) def _on_list_cursor_changed(self, widget): # Get currently selected bill self.get_selected_record() # Update statusbar self.update_statusbar() def on_newBill_activate(self, toolbutton): self.add_bill() def on_editBill_activate(self, toolbutton): if self.currentrecord: self.edit_bill() def on_removeBill_activate(self, toolbutton): if self.currentrecord: resp = self.message.ShowQuestionYesNo( _('Do you really want to delete "%s"?') % self.currentrecord.payee, self.window, _("Confirmation") ) if resp: self.remove_bill() def on_markNotPaid_activate(self, toolbutton): self.on_markPaid_activate(toolbutton) # forward def on_markPaid_activate(self, toolbutton): if self.currentrecord: self.toggle_bill_paid() def on_btnAbout_activate(self, toolbutton): self.about() def on_btnPrefs_activate(self, toolbutton): self.preferences() def on_btnQuit_activate(self, toolbutton): self._quit_application() def on_delete_event(self, widget, event, data=None): self._quit_application() def _on_timeline_changed(self, widget, args): self.reloadTreeView() def _on_timeline_cleared(self, widget, args): self._bullet_cache = {} def _on_timeline_click(self, widget, event): if event.button == 1 and event.type == gtk.gdk._2BUTTON_PRESS: self.add_bill() def switch_view(self, view_number): self.gconf_client.set("show_paid_bills", view_number) self.reloadTreeView() self.reloadTimeline() def on_showNotPaid_toggled(self, action): if action.get_active(): self.switch_view(0) def on_showPaid_toggled(self, action): if action.get_active(): self.switch_view(1) def on_showAll_toggled(self, action): if action.get_active(): self.switch_view(2) def on_showToolbar_toggled(self, action): # Toggle toolbar's visibility if action.get_active(): self.toolbar.show_all() else: self.toolbar.hide_all() self.gconf_client.set("show_toolbar", action.get_active()) def reloadTimeline(self, *args): self._bullet_cache = {} self.timeline.refresh() def on_timeline_cb(self, date, display_type, force=False): # TODO: Improve tooltip # TODO: Improve cache if date in self._bullet_cache.keys() and not force: return None self._bullet_cache[date] = self.actions.get_bills(dueDate=date) if self._bullet_cache[date]: status = self.gconf_client.get("show_paid_bills") amount = 0 amount_not_paid = 0 tooltip = "" bullet = Event() bullet.date = date for bill in self._bullet_cache[date]: amount += bill.amount if tooltip: tooltip += "\n" tooltip += bill.payee + " (" + str(float_to_currency(bill.amount)) + ")" if bill.paid: tooltip += " [%s]" % _("Paid") if status == 0: return False bullet.status = bullet.status | bullet.PAID else: amount_not_paid += bill.amount if date <= datetime.date.today(): if status == 1: return False bullet.status = bullet.status | bullet.OVERDUE else: if status == 1: return False bullet.status = bullet.status | bullet.TO_BE_PAID if bill.notes: tooltip += " - " + bill.notes bullet.amountdue = amount_not_paid if amount_not_paid else amount bullet.payee = bill.payee bills = len(self._bullet_cache[date]) if bills > 1: bullet.multi = bills bullet.payee = "%d bills" % bills bullet.tooltip = tooltip return bullet return None
class PrefDialog(gtk.Dialog): """ Class used to generate dialog to allow user to edit preferences. """ def __init__(self, parent=None): title = _("Preferences") gtk.Dialog.__init__(self, title=title, parent=parent, flags=gtk.DIALOG_MODAL | gtk.DIALOG_DESTROY_WITH_PARENT | gtk.DIALOG_NO_SEPARATOR, buttons=(gtk.STOCK_CLOSE, gtk.RESPONSE_ACCEPT)) self.set_position(gtk.WIN_POS_CENTER) self.set_border_width(6) self.set_resizable(False) self.set_icon_from_file(common.APP_ICON) self.props.skip_taskbar_hint = True self.gconf_client = Settings() self._initialize_dialog_widgets() self._populate_fields() self._connect_fields() self.connect("response", lambda self, *args: self.destroy()) def _initialize_dialog_widgets(self): self.topcontainer = gtk.VBox(homogeneous=False, spacing=18) self.topcontainer.set_border_width(6) # Alert Group alertFrame = gtk.VBox(homogeneous=False, spacing=6) alertAlignment = gtk.Alignment() alertAlignment.set_padding(0, 0, 12, 0) title = gtk.Label() title.set_markup(_("<b>Alarms</b>")) title.set_alignment(0.00, 0.50) alertFrame.pack_start(title) alertFrame.pack_start(alertAlignment) alertContainer = gtk.VBox(homogeneous=False, spacing=6) self.alertCheckbox = gtk.CheckButton(_('_Alert before due date:'), use_underline=True) self.alertSpinButton = gtk.SpinButton() self.alertSpinButton.set_range(0, 360) self.alertSpinButton.spin(gtk.SPIN_STEP_FORWARD) self.alertSpinButton.set_increments(1, 7) alertDays = gtk.Label("%s" % _('day(s).')) self.notificationTime = TimeWidget() alertPreferredTime = gtk.Label() alertPreferredTime.set_markup_with_mnemonic(_('_Preferred time:')) alertPreferredTime.set_mnemonic_widget( self.notificationTime.hourSpinner) alertPreferredTime.set_alignment(0.00, 0.50) alertDefinition = gtk.Label( _('Get alerted when individual bills are due.')) alertDefinition.set_alignment(0.00, 0.90) # Add label defining what an alarm means. alertContainer.pack_start(alertDefinition, expand=False, fill=True, padding=0) # Container for alert checkbox and spin button for day selection. hbox = gtk.HBox(homogeneous=False, spacing=4) hbox.pack_start(self.alertCheckbox, expand=False, fill=True, padding=0) hbox.pack_start(self.alertSpinButton, expand=False, fill=False, padding=0) hbox.pack_start(alertDays, expand=False, fill=False, padding=0) alertContainer.pack_start(hbox, expand=False, fill=True, padding=0) # Container for preferred time for alerts. hbox = gtk.HBox(homogeneous=False, spacing=12) hbox.pack_start(alertPreferredTime, expand=False, fill=True, padding=0) hbox.pack_start(self.notificationTime, expand=True, fill=True, padding=0) alertContainer.pack_start(hbox, expand=False, fill=True, padding=0) alertAlignment.add(alertContainer) # Notification Group notifyFrame = gtk.VBox(homogeneous=False, spacing=6) title = gtk.Label() title.set_markup(_("<b>Notifications</b>")) title.set_alignment(0.00, 0.50) notifyAlignment = gtk.Alignment() notifyAlignment.set_padding(0, 0, 12, 0) notifyFrame.pack_start(title) notifyFrame.pack_start(notifyAlignment) notificationDefinition = gtk.Label( _('Define when to be notified of upcoming bills.')) notificationDefinition.set_alignment(0.00, 0.90) notificationsContainer = gtk.VBox(homogeneous=False, spacing=6) # Add label defining what a definition means. notificationsContainer.pack_start(notificationDefinition, expand=False, fill=False, padding=0) self.notifyCheckbox = gtk.CheckButton(_('_Notify before due date:'), use_underline=True) self.notifySpinButton = gtk.SpinButton() self.notifySpinButton.set_range(0, 360) self.notifySpinButton.spin(gtk.SPIN_STEP_FORWARD) self.notifySpinButton.set_increments(1, 7) notifyDays = gtk.Label("%s" % _('day(s).')) # Container for notification checkbox and spin button for day selection. hbox = gtk.HBox(homogeneous=False, spacing=4) hbox.pack_start(self.notifyCheckbox, expand=False, fill=True, padding=0) hbox.pack_start(self.notifySpinButton, expand=False, fill=False, padding=0) hbox.pack_start(notifyDays, expand=False, fill=False, padding=0) notificationsContainer.pack_start(hbox, expand=False, fill=True, padding=0) notifyAlignment.add(notificationsContainer) # Alert Type Group alertTypeFrame = gtk.VBox(homogeneous=False, spacing=6) title = gtk.Label() title.set_markup(_("<b>Alert Type</b>")) title.set_alignment(0.00, 0.50) alertTypeAlignment = gtk.Alignment() alertTypeAlignment.set_padding(0, 0, 12, 0) alertTypeFrame.pack_start(title) alertTypeFrame.pack_start(alertTypeAlignment) vbox = gtk.VBox(homogeneous=False, spacing=6) hbox = gtk.HBox(homogeneous=False, spacing=12) self.alertBubble = gtk.RadioButton(label=_("Notification _Bubble")) self.alertDialog = gtk.RadioButton(group=self.alertBubble, label=_("Alert _Dialog")) hbox.pack_start(self.alertBubble, expand=False, fill=False, padding=0) hbox.pack_start(self.alertDialog, expand=False, fill=False, padding=0) vbox.pack_start(hbox, expand=False, fill=False, padding=0) alertTypeAlignment.add(vbox) # Daemon Warning daemonContainer = gtk.VBox(homogeneous=False, spacing=6) self.daemonLabel = gtk.Label( _("<b>Warning:</b> BillReminder Notifier is \n" \ "not running! You need to start it in order \n" \ " to receive notifications.")) self.daemonLabel.set_justify(gtk.JUSTIFY_CENTER) self.daemonLabel.set_use_markup(True) daemonImage = gtk.Image() daemonImage.set_from_stock('gtk-execute', 2) self.daemonButton = gtk.Button(label=_("_Start BillReminder Notifier")) self.daemonButton.set_relief(gtk.RELIEF_NONE) self.daemonButton.set_image(daemonImage) daemonContainer.pack_start(self.daemonLabel, expand=False, fill=True, padding=0) daemonContainer.pack_start(self.daemonButton, expand=False, fill=True, padding=0) # Everything self.topcontainer.pack_start(alertFrame, expand=False, fill=True, padding=0) self.topcontainer.pack_start(notifyFrame, expand=False, fill=True, padding=0) self.topcontainer.pack_start(alertTypeFrame, expand=False, fill=True, padding=0) if not utils.verify_dbus_service(common.DBUS_INTERFACE): self.topcontainer.pack_start(daemonContainer, expand=False, fill=True, padding=0) self.vbox.pack_start(self.topcontainer, expand=False, fill=True) self.show_all() def _populate_fields(self): self.notifyCheckbox.set_active( self.gconf_client.get('show_before_alarm')) self.alertCheckbox.set_active(self.gconf_client.get('show_alarm')) if not self.gconf_client.get('use_alert_dialog'): self.alertBubble.set_active(True) else: self.alertDialog.set_active(True) # Number of days before showing alarm adays = self.gconf_client.get('notification_days_limit') self.notifySpinButton.set_value(adays and adays or 3) self.alertSpinButton.set_value( self.gconf_client.get('show_alarm_before_days')) atime = self.gconf_client.get('show_alarm_at_time') atime = atime.split(":") self.notificationTime.setHourMinute(atime[0], atime[1]) def _connect_fields(self): self.notificationTime.hourSpinner.connect("value_changed", self._on_time_changed) self.notificationTime.minuteSpinner.connect("value_changed", self._on_time_changed) self.notifyCheckbox.connect("toggled", self._on_checkbox_toggled, 'show_before_alarm') self.alertCheckbox.connect("toggled", self._on_checkbox_toggled, 'show_alarm') self.notifySpinButton.connect("changed", self._on_spin_changed, 'notification_days_limit') self.alertSpinButton.connect("changed", self._on_spin_changed, 'show_alarm_before_days') self.alertDialog.connect("toggled", self._on_checkbox_toggled, 'use_alert_dialog') self.daemonButton.connect("clicked", self._launch_daemon) def _on_time_changed(self, spin): alarm = self.notificationTime.getTime() alarm = ":".join(["%.02d" % x for x in alarm]) self.gconf_client.set('show_alarm_at_time', alarm) def _on_checkbox_toggled(self, togglebutton, item): self.gconf_client.set(item, togglebutton.get_active()) def _on_spin_changed(self, obj, item): self.gconf_client.set(item, int(obj.get_value())) def _launch_daemon(self, button): Popen('billreminderd', shell=True) button.hide() self.daemonLabel.set_markup( _("<b>Note:</b> BillReminder Notifier is now running."))
class PrefDialog(gtk.Dialog): """ Class used to generate dialog to allow user to edit preferences. """ def __init__(self, parent=None): title = _("Preferences") gtk.Dialog.__init__(self, title=title, parent=parent, flags=gtk.DIALOG_MODAL | gtk.DIALOG_DESTROY_WITH_PARENT |gtk.DIALOG_NO_SEPARATOR, buttons=(gtk.STOCK_CLOSE, gtk.RESPONSE_ACCEPT)) self.set_position(gtk.WIN_POS_CENTER) self.set_border_width(6) self.set_resizable(False) self.set_icon_from_file(common.APP_ICON) self.props.skip_taskbar_hint = True self.gconf_client = Settings() self._initialize_dialog_widgets() self._populate_fields() self._connect_fields() self.connect("response", lambda self, *args: self.destroy()) def _initialize_dialog_widgets(self): self.topcontainer = gtk.VBox(homogeneous=False, spacing=18) self.topcontainer.set_border_width(6) # Alert Group alertFrame = gtk.VBox(homogeneous=False, spacing=6) alertAlignment = gtk.Alignment() alertAlignment.set_padding(0, 0, 12, 0) title = gtk.Label() title.set_markup(_("<b>Alarms</b>")) title.set_alignment(0.00, 0.50) alertFrame.pack_start(title) alertFrame.pack_start(alertAlignment) alertContainer = gtk.VBox(homogeneous=False, spacing=6) self.alertCheckbox = gtk.CheckButton(_('_Alert before due date:'), use_underline=True) self.alertSpinButton = gtk.SpinButton() self.alertSpinButton.set_range(0, 360) self.alertSpinButton.spin(gtk.SPIN_STEP_FORWARD) self.alertSpinButton.set_increments(1, 7) alertDays = gtk.Label("%s" % _('day(s).')) self.notificationTime = TimeWidget() alertPreferredTime = gtk.Label() alertPreferredTime.set_markup_with_mnemonic(_('_Preferred time:')) alertPreferredTime.set_mnemonic_widget(self.notificationTime.hourSpinner) alertPreferredTime.set_alignment(0.00, 0.50) alertDefinition = gtk.Label(_('Get alerted when individual bills are due.')) alertDefinition.set_alignment(0.00, 0.90) # Add label defining what an alarm means. alertContainer.pack_start(alertDefinition, expand=False, fill=True, padding=0) # Container for alert checkbox and spin button for day selection. hbox = gtk.HBox(homogeneous=False, spacing=4) hbox.pack_start(self.alertCheckbox, expand=False, fill=True, padding=0) hbox.pack_start(self.alertSpinButton, expand=False, fill=False, padding=0) hbox.pack_start(alertDays, expand=False, fill=False, padding=0) alertContainer.pack_start(hbox, expand=False, fill=True, padding=0) # Container for preferred time for alerts. hbox = gtk.HBox(homogeneous=False, spacing=12) hbox.pack_start(alertPreferredTime, expand=False, fill=True, padding=0) hbox.pack_start(self.notificationTime, expand=True, fill=True, padding=0) alertContainer.pack_start(hbox, expand=False, fill=True, padding=0) alertAlignment.add(alertContainer) # Notification Group notifyFrame = gtk.VBox(homogeneous=False, spacing=6) title = gtk.Label() title.set_markup(_("<b>Notifications</b>")) title.set_alignment(0.00, 0.50) notifyAlignment = gtk.Alignment() notifyAlignment.set_padding(0, 0, 12, 0) notifyFrame.pack_start(title) notifyFrame.pack_start(notifyAlignment) notificationDefinition = gtk.Label(_('Define when to be notified of upcoming bills.')) notificationDefinition.set_alignment(0.00, 0.90) notificationsContainer = gtk.VBox(homogeneous=False, spacing=6) # Add label defining what a definition means. notificationsContainer.pack_start(notificationDefinition, expand=False, fill=False, padding=0) self.notifyCheckbox = gtk.CheckButton(_('_Notify before due date:'), use_underline=True) self.notifySpinButton = gtk.SpinButton() self.notifySpinButton.set_range(0, 360) self.notifySpinButton.spin(gtk.SPIN_STEP_FORWARD) self.notifySpinButton.set_increments(1, 7) notifyDays = gtk.Label("%s" % _('day(s).')) # Container for notification checkbox and spin button for day selection. hbox = gtk.HBox(homogeneous=False, spacing=4) hbox.pack_start(self.notifyCheckbox, expand=False, fill=True, padding=0) hbox.pack_start(self.notifySpinButton, expand=False, fill=False, padding=0) hbox.pack_start(notifyDays, expand=False, fill=False, padding=0) notificationsContainer.pack_start(hbox, expand=False, fill=True, padding=0) notifyAlignment.add(notificationsContainer) # Alert Type Group alertTypeFrame = gtk.VBox(homogeneous=False, spacing=6) title = gtk.Label() title.set_markup(_("<b>Alert Type</b>")) title.set_alignment(0.00, 0.50) alertTypeAlignment = gtk.Alignment() alertTypeAlignment.set_padding(0, 0, 12, 0) alertTypeFrame.pack_start(title) alertTypeFrame.pack_start(alertTypeAlignment) vbox = gtk.VBox(homogeneous=False, spacing=6) hbox = gtk.HBox(homogeneous=False, spacing=12) self.alertBubble = gtk.RadioButton(label=_("Notification _Bubble")) self.alertDialog = gtk.RadioButton(group=self.alertBubble, label=_("Alert _Dialog")) hbox.pack_start(self.alertBubble, expand=False, fill=False, padding=0) hbox.pack_start(self.alertDialog, expand=False, fill=False, padding=0) vbox.pack_start(hbox, expand=False, fill=False, padding=0) alertTypeAlignment.add(vbox) # Daemon Warning daemonContainer = gtk.VBox(homogeneous=False, spacing=6) self.daemonLabel = gtk.Label( _("<b>Warning:</b> BillReminder Notifier is \n" \ "not running! You need to start it in order \n" \ " to receive notifications.")) self.daemonLabel.set_justify(gtk.JUSTIFY_CENTER) self.daemonLabel.set_use_markup(True) daemonImage = gtk.Image() daemonImage.set_from_stock('gtk-execute', 2) self.daemonButton = gtk.Button(label=_("_Start BillReminder Notifier")) self.daemonButton.set_relief(gtk.RELIEF_NONE) self.daemonButton.set_image(daemonImage) daemonContainer.pack_start(self.daemonLabel, expand=False, fill=True, padding=0) daemonContainer.pack_start(self.daemonButton, expand=False, fill=True, padding=0) # Everything self.topcontainer.pack_start(alertFrame, expand=False, fill=True, padding=0) self.topcontainer.pack_start(notifyFrame, expand=False, fill=True, padding=0) self.topcontainer.pack_start(alertTypeFrame, expand=False, fill=True, padding=0) if not utils.verify_dbus_service(common.DBUS_INTERFACE): self.topcontainer.pack_start(daemonContainer, expand=False, fill=True, padding=0) self.vbox.pack_start(self.topcontainer, expand=False, fill=True) self.show_all() def _populate_fields(self): self.notifyCheckbox.set_active(self.gconf_client.get('show_before_alarm')) self.alertCheckbox.set_active(self.gconf_client.get('show_alarm')) if not self.gconf_client.get('use_alert_dialog'): self.alertBubble.set_active(True) else: self.alertDialog.set_active(True) # Number of days before showing alarm adays = self.gconf_client.get('notification_days_limit') self.notifySpinButton.set_value(adays and adays or 3) self.alertSpinButton.set_value(self.gconf_client.get('show_alarm_before_days')) atime = self.gconf_client.get('show_alarm_at_time') atime = atime.split(":") self.notificationTime.setHourMinute(atime[0], atime[1]) def _connect_fields(self): self.notificationTime.hourSpinner.connect("value_changed", self._on_time_changed) self.notificationTime.minuteSpinner.connect("value_changed", self._on_time_changed) self.notifyCheckbox.connect("toggled", self._on_checkbox_toggled, 'show_before_alarm') self.alertCheckbox.connect("toggled", self._on_checkbox_toggled, 'show_alarm') self.notifySpinButton.connect("changed", self._on_spin_changed, 'notification_days_limit') self.alertSpinButton.connect("changed", self._on_spin_changed, 'show_alarm_before_days') self.alertDialog.connect("toggled", self._on_checkbox_toggled, 'use_alert_dialog') self.daemonButton.connect("clicked", self._launch_daemon) def _on_time_changed(self, spin): alarm = self.notificationTime.getTime() alarm = ":".join(["%.02d" % x for x in alarm]) self.gconf_client.set('show_alarm_at_time', alarm) def _on_checkbox_toggled(self, togglebutton, item): self.gconf_client.set(item, togglebutton.get_active()) def _on_spin_changed(self, obj, item): self.gconf_client.set(item, int(obj.get_value())) def _launch_daemon(self, button): Popen('billreminderd', shell=True) button.hide() self.daemonLabel.set_markup( _("<b>Note:</b> BillReminder Notifier is now running."))
class MainDialog(gobject.GObject): search_text = "" _bullet_cache = {} __gsignals__ = { 'bill-added': (gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE, (gobject.TYPE_PYOBJECT, )), 'bill-updated': (gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE, (gobject.TYPE_PYOBJECT, )), 'bill-removed': (gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE, (gobject.TYPE_PYOBJECT, )) } def __init__(self): gobject.GObject.__init__(self) if exists(join(USER_CFG_PATH, CFG_NAME)): from lib.migrate_to_gconf import migrate migrate(join(USER_CFG_PATH, CFG_NAME)) self.gconf_client = Configuration() self.message = Message() # Connects to the database self.actions = Actions() self.ui = gtk.Builder() self.ui.add_from_file(os.path.join(DEFAULT_CFG_PATH, "main.ui")) self.window = self.ui.get_object("main_window") self.window.set_title("%s" % common.APPNAME) self.window.set_icon_from_file(common.APP_ICON) # ViewBill self.list = ViewBill() self.list.connect('cursor_changed', self._on_list_cursor_changed) self.list.connect('row_activated', self._on_list_row_activated) self.list.connect('button_press_event', self._on_list_button_press_event) self.ui.get_object("bill_box").add(self.list) # Toolbar self.toolbar = self.ui.get_object("toolbar") # Menubar self._populate_menubar() # Statusbar self.statusbar = Statusbar() self.ui.get_object("statusbar_box").add(self.statusbar) # Restore timeline zoom timeline_count = self.gconf_client.get('timeline_count') # Timeline self.timeline = Timeline(count=timeline_count, callback=self.on_timeline_cb) self.timeline.connect("value-changed", self._on_timeline_changed) self.timeline.connect("cleared", self._on_timeline_cleared) self.ui.get_object("timeline_box").add(self.timeline) # Chart self.chart = ChartWidget() self.chart.set_border_width(10) self.ui.get_object("chart_box").add(self.chart) # Restore position and size of window width = self.gconf_client.get('window_width') height = self.gconf_client.get('window_height') x = self.gconf_client.get('window_position_x') y = self.gconf_client.get('window_position_y') if width and height: self.window.resize(width, height) if x and y: self.window.move(x, y) self.window.show_all() # Whether to display toolbar or not self.on_showToolbar_toggled(self.ui.get_object("showToolbar")) self.list.grab_focus() if self.gconf_client.get('start_in_tray'): self.window.hide() self.toggle_buttons() # Connects to the Daemon self.iface = None iface = get_dbus_interface(common.DBUS_INTERFACE, common.DBUS_PATH) if iface: iface.connect_to_signal("bill_edited", self.reloadTreeView) iface.connect_to_signal("bill_edited", self.reloadTimeline) iface.connect_to_signal("show_main_window", self.window.show) self.iface = iface timeout_add(2000, self._send_tray_hints) self.set_action_strings() self.ui.connect_signals(self) # populate treeview self.reloadTreeView() self.notify = NotifyIcon(self) # Integrate with Ubuntu Unity if UNITY: self.unity = UnityIntegration(self) def set_action_strings(self): # for some reason the actions strings do not get translated yet # so we define them here so they would be picked up by the pyfile scanner self.ui.get_object("newBill").set_label(_("_New")) self.ui.get_object("newBill").set_tooltip(_("Add new bill")) self.ui.get_object("editBill").set_label(_("_Edit")) self.ui.get_object("editBill").set_tooltip(_("Edit a bill")) self.ui.get_object("removeBill").set_label(_("_Delete")) self.ui.get_object("removeBill").set_tooltip(_("Delete selected bill")) self.ui.get_object("markPaid").set_label(_("P_aid")) self.ui.get_object("markPaid").set_tooltip(_("Mark as paid")) self.ui.get_object("markNotPaid").set_label(_("No_t Paid")) self.ui.get_object("markNotPaid").set_tooltip(_("Mark as not paid")) self.ui.get_object("showToolbar").set_label(_("_Show Toolbar")) self.ui.get_object("showToolbar").set_tooltip(_("Show the toolbar")) # Methods: UI def _send_tray_hints(self): self.iface.set_tray_hints(force_string(self.notify.get_hints())) timeout_add(60000, self._send_tray_hints) def get_window_visibility(self): return self.window.get_property("visible") def show_hide_window(self): if self.window.get_property("visible"): self.window.hide() else: self.window.show() def get_selected_record(self): """ Returns a bill object from the current selected record """ if len(self.list.listStore) > 0: model_ = self.list.get_model() if self.list.get_cursor()[0]: index = self.list.get_cursor()[0][0] else: index = 0 b_id = model_[index][0] records = self.actions.get_bills(id=b_id) self.currentrecord = records[0] else: self.currentrecord = None print "Current record is: %s" % self.currentrecord def populate_view(self, records): """ Populates the treeview control with the records passed """ # Reset list self.list.listStore.clear() if not records: return 0 # Loops through bills collection path = 0 for rec in records: # Format the record prior to adding it to treeview row = self.format_row(rec) self.list.add(row) # Set the cursor to the first (top) record self.list.set_cursor(path) # Returns how many records there are in the treeview return len(records) def reloadTreeView(self, *arg): # Update list with updated record status = self.gconf_client.get('show_paid_bills') path = self.list.get_cursor()[0] self.list.listStore.clear() self.currentrecord = None first = self.timeline.start_date last = self.timeline.end_date # Get list of records records = self.actions.get_interval_bills(first, last, status) # Populate treeview self.populate_view(records) # Update status bar self.update_statusbar() # populate chart self._populate_chart(status, first, last) return len(records) def format_row(self, row): """ Formats a bill to be displayed as a row. """ categoryName = row.category.name if row.category else _('None') categoryColor = row.category.color if row.category else '#d3d7cf' formatted = [ row.id, create_pixbuf(color=categoryColor), categoryName, row.payee, row.dueDate.strftime(_('%m/%d').encode('ASCII')), row.amount, row.notes, int(row.paid), None ] return formatted def _populate_chart(self, status, start, end): records = [] categories = [] totals = [] records = self.actions.get_monthly_totals(start, end, status) # Chart widget takes data in format (('CategoryName', amount),) categories = [cat or 'None' for cat, total in records] totals = [float(total) for cat, total in records] #records = [(c if c else 'None',float(t)) for c,t in records] # set bar colors all_categories = self.actions.get_categories() self.chart.chart.key_colors = dict([(cat.name or 'None', cat.color) for cat in all_categories]) self.chart.plot(categories, totals) def _populate_menubar(self): try: saved_view = self.gconf_client.get('show_paid_bills') except: saved_view = 1 self.gconf_client.set("show_paid_bills", saved_view) if saved_view == 0: self.ui.get_object("showNotPaid").set_active(True) elif saved_view == 1: self.ui.get_object("showPaid").set_active(True) else: self.ui.get_object("showAll").set_active(True) # Check whether we display the toolbar or not self.ui.get_object("showToolbar").set_active( self.gconf_client.get('show_toolbar')) def add_bill(self): selectedDate = self.timeline.value records = dialogs.add_dialog(parent=self.window, selectedDate=selectedDate) # Checks if the user did not cancel the action if records: # Add new bill to database for rec in records: bill_id = self.actions.add(rec) bill = self.actions.get_bills(id=bill_id)[0] if bill: self.list.add(self.format_row(bill)) self.update_statusbar() # Reload records tree (something changed) self.reloadTreeView() self.reloadTimeline() def edit_bill(self): records = dialogs.edit_dialog(parent=self.window, record=self.currentrecord) # Checks if the user did not cancel the action if records: for rec in records: # Edit bill to database rec = self.actions.edit(rec) self.emit('bill-updated', rec) # Reload records tree (something changed) self.reloadTreeView() self.reloadTimeline() def remove_bill(self): self.actions.delete(self.currentrecord) self.list.remove() self.emit('bill-removed', None) self.update_statusbar() self.reloadTreeView() self.reloadTimeline() def toggle_bill_paid(self): # Fetch record from database record = self.actions.get_bills(id=self.currentrecord.id)[0] # Toggle paid field record.paid = False if record.paid else True # Edit bill in the database transaction = self.actions.add(record) self.emit('bill-updated', record) # Update our current copy self.currentrecord = self.actions.get_bills( id=self.currentrecord.id)[0] # Update timeline widget to reflect change self._bullet_cache[self.currentrecord.dueDate] = [self.currentrecord] # Update list with updated record idx = self.list.get_cursor()[0][0] self.update_statusbar(idx) self.reloadTreeView() self.reloadTimeline() def about(self): dialogs.about_dialog(parent=self.window) def preferences(self): dialogs.preferences_dialog(parent=self.window) # Methods def _quit_application(self): self.save_position() self.save_size() self.save_timeline_zoom() gtk.main_quit() return False def save_position(self): x, y = self.window.get_position() self.gconf_client.set('window_position_x', x) self.gconf_client.set('window_position_y', y) def save_size(self): width, height = self.window.get_size() self.gconf_client.set('window_width', width) self.gconf_client.set('window_height', height) def save_timeline_zoom(self): count = self.timeline.count self.gconf_client.set('timeline_count', count) def toggle_buttons(self, paid=None): """ Toggles all buttons conform number of records present and their state """ for widget in ["editBill", "removeBill", "markPaid", "markNotPaid"]: self.ui.get_object(widget).set_sensitive( len(self.list.listStore) > 0) if len(self.list.listStore) > 0: self.ui.get_object("markPaid").set_sensitive(paid == False) self.ui.get_object("markNotPaid").set_sensitive(paid == True) def update_statusbar(self, index=0): """ This function is used to update status bar informations about the list """ records = len(self.list.listStore) # Record count self.statusbar.Records(records) if self.currentrecord: # Display the status self.statusbar.Notes(self.currentrecord.notes) # Toggles toolbar buttons on/off self.toggle_buttons(self.currentrecord.paid) else: # Clear the status for the selected row self.statusbar.Notes("") # Toggles toolbar buttons on/off self.toggle_buttons() show_paid_bills = self.gconf_client.get('show_paid_bills') if show_paid_bills is 0: self.statusbar.Info(_("Not Paid Only")) elif show_paid_bills is 1: self.statusbar.Info(_("Paid Only")) else: self.statusbar.Info('') # Event handlers def _on_list_button_press_event(self, widget, event): """ This function will handle the signal to show a popup menu sent by a right click on tvBill widget. """ if event.button == 3 and event.type == gtk.gdk.BUTTON_PRESS: self.get_selected_record() c = self.ui.get_object("context_menu") c.popup(None, None, None, event.button, event.get_time()) def _on_list_row_activated(self, widget, path, column): self._on_list_cursor_changed(widget) self.on_editBill_activate(None) def _on_list_cursor_changed(self, widget): # Get currently selected bill self.get_selected_record() # Update statusbar self.update_statusbar() def on_newBill_activate(self, toolbutton): self.add_bill() def on_editBill_activate(self, toolbutton): if self.currentrecord: self.edit_bill() def on_removeBill_activate(self, toolbutton): if self.currentrecord: resp = self.message.ShowQuestionYesNo( _("Do you really want to delete \"%s\"?") % \ self.currentrecord.payee, self.window, _("Confirmation")) if resp: self.remove_bill() def on_markNotPaid_activate(self, toolbutton): self.on_markPaid_activate(toolbutton) # forward def on_markPaid_activate(self, toolbutton): if self.currentrecord: self.toggle_bill_paid() def on_btnAbout_activate(self, toolbutton): self.about() def on_btnPrefs_activate(self, toolbutton): self.preferences() def on_btnQuit_activate(self, toolbutton): self._quit_application() def on_delete_event(self, widget, event, data=None): self._quit_application() def _on_timeline_changed(self, widget, args): self.reloadTreeView() def _on_timeline_cleared(self, widget, args): self._bullet_cache = {} def switch_view(self, view_number): self.gconf_client.set('show_paid_bills', view_number) self.reloadTreeView() self.reloadTimeline() def on_showNotPaid_toggled(self, action): if action.get_active(): self.switch_view(0) def on_showPaid_toggled(self, action): if action.get_active(): self.switch_view(1) def on_showAll_toggled(self, action): if action.get_active(): self.switch_view(2) def on_showToolbar_toggled(self, action): # Toggle toolbar's visibility if action.get_active(): self.toolbar.show_all() else: self.toolbar.hide_all() self.gconf_client.set("show_toolbar", action.get_active()) def reloadTimeline(self, *args): self._bullet_cache = {} self.timeline.refresh() def on_timeline_cb(self, date, display_type, force=False): # TODO: Improve tooltip # TODO: Improve cache if date in self._bullet_cache.keys() and not force: return None self._bullet_cache[date] = self.actions.get_bills(dueDate=date) if self._bullet_cache[date]: status = self.gconf_client.get('show_paid_bills') amount = 0 amount_not_paid = 0 tooltip = '' bullet = Event() bullet.date = date for bill in self._bullet_cache[date]: amount += bill.amount if tooltip: tooltip += '\n' tooltip += bill.payee + ' (' + str( float_to_currency(bill.amount)) + ')' if bill.paid: tooltip += ' [%s]' % _('Paid') if status == 0: return False bullet.status = bullet.status | bullet.PAID else: amount_not_paid += bill.amount if date <= datetime.date.today(): if status == 1: return False bullet.status = bullet.status | bullet.OVERDUE else: if status == 1: return False bullet.status = bullet.status | bullet.TO_BE_PAID if bill.notes: tooltip += ' - ' + bill.notes bullet.amountdue = amount_not_paid if amount_not_paid else amount bullet.payee = bill.payee bills = len(self._bullet_cache[date]) if bills > 1: bullet.multi = bills bullet.payee = '%d bills' % bills bullet.tooltip = tooltip return bullet return None