X-Git-Url: http://git.maemo.org/git/?a=blobdiff_plain;f=src%2Fgc_views.py;h=d0c46e765b81892236cba52e19a1fabe9516f962;hb=ef0ce320dac5547892e7cb63698bde95a7063f01;hp=b6cd02c6ff118010b7f715c27104e488a96781b8;hpb=144d376e648107e77676832acc27baa6908efe53;p=gc-dialer diff --git a/src/gc_views.py b/src/gc_views.py index b6cd02c..d0c46e7 100644 --- a/src/gc_views.py +++ b/src/gc_views.py @@ -1,30 +1,35 @@ #!/usr/bin/python2.5 -# DialCentral - Front end for Google's Grand Central service. -# Copyright (C) 2008 Mark Bergman bergman AT merctech DOT com -# -# This library is free software; you can redistribute it and/or -# modify it under the terms of the GNU Lesser General Public -# License as published by the Free Software Foundation; either -# version 2.1 of the License, or (at your option) any later version. -# -# This library 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 -# Lesser General Public License for more details. -# -# You should have received a copy of the GNU Lesser General Public -# License along with this library; if not, write to the Free Software -# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA +""" +DialCentral - Front end for Google's Grand Central service. +Copyright (C) 2008 Mark Bergman bergman AT merctech DOT com + +This library is free software; you can redistribute it and/or +modify it under the terms of the GNU Lesser General Public +License as published by the Free Software Foundation; either +version 2.1 of the License, or (at your option) any later version. + +This library 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 +Lesser General Public License for more details. + +You should have received a copy of the GNU Lesser General Public +License along with this library; if not, write to the Free Software +Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA +""" + +from __future__ import with_statement import threading import time import warnings -import traceback import gobject import gtk +import gtk_toolbox + def make_ugly(prettynumber): """ @@ -303,10 +308,23 @@ class MergedAddressBook(object): class PhoneTypeSelector(object): + ACTION_CANCEL = "cancel" + ACTION_SELECT = "select" + ACTION_DIAL = "dial" + ACTION_SEND_SMS = "sms" + def __init__(self, widgetTree, gcBackend): self._gcBackend = gcBackend self._widgetTree = widgetTree + self._dialog = self._widgetTree.get_widget("phonetype_dialog") + self._smsDialog = SmsEntryDialog(self._widgetTree, self._gcBackend) + + self._smsButton = self._widgetTree.get_widget("sms_button") + self._smsButton.connect("clicked", self._on_phonetype_send_sms) + + self._dialButton = self._widgetTree.get_widget("dial_button") + self._dialButton.connect("clicked", self._on_phonetype_dial) self._selectButton = self._widgetTree.get_widget("select_button") self._selectButton.connect("clicked", self._on_phonetype_select) @@ -317,6 +335,7 @@ class PhoneTypeSelector(object): self._typemodel = gtk.ListStore(gobject.TYPE_STRING, gobject.TYPE_STRING) self._typeviewselection = None + self._message = self._widgetTree.get_widget("phoneSelectionMessage") typeview = self._widgetTree.get_widget("phonetypes") typeview.connect("row-activated", self._on_phonetype_select) typeview.set_model(self._typemodel) @@ -331,30 +350,123 @@ class PhoneTypeSelector(object): self._typeviewselection = typeview.get_selection() self._typeviewselection.set_mode(gtk.SELECTION_SINGLE) - def run(self, contactDetails): + self._action = self.ACTION_CANCEL + + def run(self, contactDetails, message = ""): self._typemodel.clear() for phoneType, phoneNumber in contactDetails: + # @bug this isn't populating correctly for recent and messages but it is for contacts + print repr(phoneNumber), repr(phoneType) self._typemodel.append((phoneNumber, "%s - %s" % (make_pretty(phoneNumber), phoneType))) + # @todo Need to decide how how to handle the single phone number case + if message: + self._message.set_markup(message) + self._message.show() + else: + self._message.set_markup("") + self._message.hide() + userResponse = self._dialog.run() if userResponse == gtk.RESPONSE_OK: - model, itr = self._typeviewselection.get_selected() - if itr: - phoneNumber = self._typemodel.get_value(itr, 0) + phoneNumber = self._get_number() else: phoneNumber = "" + if not phoneNumber: + self._action = self.ACTION_CANCEL + + if self._action == self.ACTION_SEND_SMS: + smsMessage = self._smsDialog.run(phoneNumber, message) + else: + smsMessage = "" + if not smsMessage: + phoneNumber = "" + self._action = self.ACTION_CANCEL self._typeviewselection.unselect_all() self._dialog.hide() + return self._action, phoneNumber, smsMessage + + def _get_number(self): + model, itr = self._typeviewselection.get_selected() + if not itr: + return "" + + phoneNumber = self._typemodel.get_value(itr, 0) return phoneNumber + def _on_phonetype_dial(self, *args): + self._dialog.response(gtk.RESPONSE_OK) + self._action = self.ACTION_DIAL + + def _on_phonetype_send_sms(self, *args): + self._dialog.response(gtk.RESPONSE_OK) + self._action = self.ACTION_SEND_SMS + def _on_phonetype_select(self, *args): self._dialog.response(gtk.RESPONSE_OK) + self._action = self.ACTION_SELECT def _on_phonetype_cancel(self, *args): self._dialog.response(gtk.RESPONSE_CANCEL) + self._action = self.ACTION_CANCEL + + +class SmsEntryDialog(object): + + MAX_CHAR = 160 + + def __init__(self, widgetTree, gcBackend): + self._gcBackend = gcBackend + self._widgetTree = widgetTree + self._dialog = self._widgetTree.get_widget("smsDialog") + + self._smsButton = self._widgetTree.get_widget("sendSmsButton") + self._smsButton.connect("clicked", self._on_send) + + self._cancelButton = self._widgetTree.get_widget("cancelSmsButton") + self._cancelButton.connect("clicked", self._on_cancel) + + self._letterCountLabel = self._widgetTree.get_widget("smsLetterCount") + self._message = self._widgetTree.get_widget("smsMessage") + self._smsEntry = self._widgetTree.get_widget("smsEntry") + self._smsEntry.get_buffer().connect("changed", self._on_entry_changed) + + def run(self, number, message = ""): + if message: + self._message.set_markup(message) + self._message.show() + else: + self._message.set_markup("") + self._message.hide() + self._smsEntry.get_buffer().set_text("") + self._update_letter_count() + + userResponse = self._dialog.run() + if userResponse == gtk.RESPONSE_OK: + entryBuffer = self._smsEntry.get_buffer() + enteredMessage = entryBuffer.get_text(entryBuffer.get_start_iter(), entryBuffer.get_end_iter()) + enteredMessage = enteredMessage[0:self.MAX_CHAR] + else: + enteredMessage = "" + + self._dialog.hide() + return enteredMessage + + def _update_letter_count(self, *args): + entryLength = self._smsEntry.get_buffer().get_char_count() + self._letterCountLabel.set_text(str(self.MAX_CHAR - entryLength)) + + def _on_entry_changed(self, *args): + self._update_letter_count() + + def _on_send(self, *args): + self._dialog.response(gtk.RESPONSE_OK) + + def _on_cancel(self, *args): + self._dialog.response(gtk.RESPONSE_CANCEL) class Dialpad(object): @@ -401,8 +513,7 @@ class Dialpad(object): self._prettynumber = make_pretty(self._phonenumber) self._numberdisplay.set_label("%s" % (self._prettynumber)) except TypeError, e: - warnings.warn(traceback.format_exc()) - self._errorDisplay.push_message(e.message) + self._errorDisplay.push_exception(e) def clear(self): self.set_number("") @@ -478,8 +589,7 @@ class AccountInfo(object): try: callbackNumbers = self._backend.get_callback_numbers() except RuntimeError, e: - warnings.warn(traceback.format_exc()) - self._errorDisplay.push_message(e.message) + self._errorDisplay.push_exception(e) return for number, description in callbackNumbers.iteritems(): @@ -490,8 +600,7 @@ class AccountInfo(object): try: callbackNumber = self._backend.get_callback_number() except RuntimeError, e: - warnings.warn(traceback.format_exc()) - self._errorDisplay.push_message(e.message) + self._errorDisplay.push_exception(e) return self._callbackCombo.get_child().set_text(make_pretty(callbackNumber)) @@ -508,8 +617,7 @@ class AccountInfo(object): else: self._backend.set_callback_number(text) except RuntimeError, e: - warnings.warn(traceback.format_exc()) - self._errorDisplay.push_message(e.message) + self._errorDisplay.push_exception(e) class RecentCallsView(object): @@ -525,9 +633,14 @@ class RecentCallsView(object): self._onRecentviewRowActivatedId = 0 textrenderer = gtk.CellRendererText() - self._recentviewColumn = gtk.TreeViewColumn("Calls", textrenderer, text=1) + # @todo Make seperate columns for each item in recent item payload + self._recentviewColumn = gtk.TreeViewColumn("Calls") + self._recentviewColumn.pack_start(textrenderer, expand=True) + self._recentviewColumn.add_attribute(textrenderer, "text", 1) self._recentviewColumn.set_sizing(gtk.TREE_VIEW_COLUMN_FIXED) + self._phoneTypeSelector = PhoneTypeSelector(widgetTree, self._backend) + def enable(self): assert self._backend.is_authed() self._recentview.set_model(self._recentmodel) @@ -543,7 +656,7 @@ class RecentCallsView(object): self._recentview.remove_column(self._recentviewColumn) self._recentview.set_model(None) - def number_selected(self, number): + def number_selected(self, action, number, message): """ @note Actual dial function is patched in later """ @@ -567,23 +680,15 @@ class RecentCallsView(object): try: recentItems = self._backend.get_recent() except RuntimeError, e: - warnings.warn(traceback.format_exc()) - gtk.gdk.threads_enter() - try: - self._errorDisplay.push_message(e.message) - finally: - gtk.gdk.threads_leave() + self._errorDisplay.push_exception_with_lock(e) self._recenttime = 0.0 recentItems = [] for personsName, phoneNumber, date, action in recentItems: description = "%s on %s from/to %s - %s" % (action.capitalize(), date, personsName, phoneNumber) item = (phoneNumber, description) - gtk.gdk.threads_enter() - try: + with gtk_toolbox.gtk_lock(): self._recentmodel.append(item) - finally: - gtk.gdk.threads_leave() return False @@ -592,10 +697,112 @@ class RecentCallsView(object): if not itr: return - self.number_selected(self._recentmodel.get_value(itr, 0)) + number = self._recentmodel.get_value(itr, 0) + number = make_ugly(number) + contactPhoneNumbers = [("Phone", number)] + description = self._recentmodel.get_value(itr, 1) + print "Activated Recent Row:", repr(contactPhoneNumbers), repr(description) + + action, phoneNumber, message = self._phoneTypeSelector.run(contactPhoneNumbers, message = description) + if action == PhoneTypeSelector.ACTION_CANCEL: + return + assert phoneNumber + + self.number_selected(action, phoneNumber, message) self._recentviewselection.unselect_all() +class MessagesView(object): + + def __init__(self, widgetTree, backend, errorDisplay): + self._errorDisplay = errorDisplay + self._backend = backend + + self._messagetime = 0.0 + self._messagemodel = gtk.ListStore(gobject.TYPE_STRING, gobject.TYPE_STRING) + self._messageview = widgetTree.get_widget("messages_view") + self._messageviewselection = None + self._onMessageviewRowActivatedId = 0 + + textrenderer = gtk.CellRendererText() + # @todo Make seperate columns for each item in message payload + self._messageviewColumn = gtk.TreeViewColumn("Messages") + self._messageviewColumn.pack_start(textrenderer, expand=True) + self._messageviewColumn.add_attribute(textrenderer, "markup", 1) + self._messageviewColumn.set_sizing(gtk.TREE_VIEW_COLUMN_FIXED) + + self._phoneTypeSelector = PhoneTypeSelector(widgetTree, self._backend) + + def enable(self): + assert self._backend.is_authed() + self._messageview.set_model(self._messagemodel) + + self._messageview.append_column(self._messageviewColumn) + self._messageviewselection = self._messageview.get_selection() + self._messageviewselection.set_mode(gtk.SELECTION_SINGLE) + + self._onMessageviewRowActivatedId = self._messageview.connect("row-activated", self._on_messageview_row_activated) + + def disable(self): + self._messageview.disconnect(self._onMessageviewRowActivatedId) + self._messageview.remove_column(self._messageviewColumn) + self._messageview.set_model(None) + + def number_selected(self, action, number, message): + """ + @note Actual dial function is patched in later + """ + raise NotImplementedError + + def update(self): + if (time.time() - self._messagetime) < 300: + return + backgroundPopulate = threading.Thread(target=self._idly_populate_messageview) + backgroundPopulate.setDaemon(True) + backgroundPopulate.start() + + def clear(self): + self._messagetime = 0.0 + self._messagemodel.clear() + + def _idly_populate_messageview(self): + self._messagetime = time.time() + self._messagemodel.clear() + + try: + messageItems = self._backend.get_messages() + except RuntimeError, e: + self._errorDisplay.push_exception_with_lock(e) + self._messagetime = 0.0 + messageItems = [] + + for header, number, relativeDate, message in messageItems: + number = make_ugly(number) + print "Discarding", header, relativeDate + item = (number, message) + with gtk_toolbox.gtk_lock(): + self._messagemodel.append(item) + + return False + + def _on_messageview_row_activated(self, treeview, path, view_column): + model, itr = self._messageviewselection.get_selected() + if not itr: + return + + contactPhoneNumbers = [("Phone", self._messagemodel.get_value(itr, 0))] + description = self._messagemodel.get_value(itr, 1) + print repr(contactPhoneNumbers), repr(description) + + action, phoneNumber, message = self._phoneTypeSelector.run(contactPhoneNumbers, message = description) + if action == PhoneTypeSelector.ACTION_CANCEL: + return + assert phoneNumber + + self.number_selected(action, phoneNumber, message) + self._messageviewselection.unselect_all() + + class ContactsView(object): def __init__(self, widgetTree, backend, errorDisplay): @@ -614,7 +821,7 @@ class ContactsView(object): self._contactsview = widgetTree.get_widget("contactsview") self._contactColumn = gtk.TreeViewColumn("Contact") - displayContactSource = True + displayContactSource = False if displayContactSource: textrenderer = gtk.CellRendererText() self._contactColumn.pack_start(textrenderer, expand=False) @@ -672,7 +879,7 @@ class ContactsView(object): self._contactsview.set_model(None) self._contactsview.remove_column(self._contactColumn) - def number_selected(self, number): + def number_selected(self, action, number, message): """ @note Actual dial function is patched in later """ @@ -727,17 +934,11 @@ class ContactsView(object): try: contacts = addressBook.get_contacts() except RuntimeError, e: - warnings.warn(traceback.format_exc()) contacts = [] self._contactstime = 0.0 - gtk.gdk.threads_enter() - try: - self._errorDisplay.push_message(e.message) - finally: - gtk.gdk.threads_leave() + self._errorDisplay.push_exception_with_lock(e) for contactId, contactName in contacts: - # contactType = (addressBook.contact_source_short_name(contactId), ) - contactType = ("", ) # Due to popular demand + contactType = (addressBook.contact_source_short_name(contactId), ) self._contactsmodel.append(contactType + (contactName, "", contactId) + ("", )) # restart the treeview data rendering @@ -759,23 +960,22 @@ class ContactsView(object): return contactId = self._contactsmodel.get_value(itr, 3) + contactName = self._contactsmodel.get_value(itr, 1) try: contactDetails = self._addressBook.get_contact_details(contactId) except RuntimeError, e: - warnings.warn(traceback.format_exc()) contactDetails = [] self._contactstime = 0.0 - self._errorDisplay.push_message(e.message) - contactDetails = [phoneNumber for phoneNumber in contactDetails] + self._errorDisplay.push_exception(e) + contactPhoneNumbers = [phoneNumber for phoneNumber in contactDetails] - if len(contactDetails) == 0: - phoneNumber = "" - elif len(contactDetails) == 1: - phoneNumber = contactDetails[0][1] - else: - phoneNumber = self._phoneTypeSelector.run(contactDetails) + if len(contactPhoneNumbers) == 0: + return - if 0 < len(phoneNumber): - self.number_selected(phoneNumber) + action, phoneNumber, message = self._phoneTypeSelector.run(contactPhoneNumbers, message = contactName) + if action == PhoneTypeSelector.ACTION_CANCEL: + return + assert phoneNumber + self.number_selected(action, phoneNumber, message) self._contactsviewselection.unselect_all()