X-Git-Url: http://git.maemo.org/git/?a=blobdiff_plain;f=src%2Fgv_views.py;h=9b56bee50e75f5b7085531b3f0e47bd0e0e3f4b4;hb=e9f715b4a8367af16cb6586a6927e9846c477310;hp=91f75d2a5088c3bc97c7f02941211a5a7f4aa05b;hpb=09ab0b4769d267e9ab8b98f549b6def319d68c96;p=gc-dialer diff --git a/src/gv_views.py b/src/gv_views.py index 91f75d2..9b56bee 100644 --- a/src/gv_views.py +++ b/src/gv_views.py @@ -1,7 +1,7 @@ #!/usr/bin/python2.5 """ -DialCentral - Front end for Google's Grand Central service. +DialCentral - Front end for Google's GoogleVoice service. Copyright (C) 2008 Mark Bergman bergman AT merctech DOT com This library is free software; you can redistribute it and/or @@ -17,12 +17,16 @@ 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 + +@todo Collapse voicemails +@todo Alternate UI for dialogs (stackables) """ from __future__ import with_statement import ConfigParser -import warnings +import logging +import itertools import gobject import pango @@ -30,9 +34,13 @@ import gtk import gtk_toolbox import hildonize +import gv_backend import null_backend +_moduleLogger = logging.getLogger("gv_views") + + def make_ugly(prettynumber): """ function to take a phone number and strip out all non-numeric @@ -109,6 +117,54 @@ def abbrev_relative_date(date): return "%s %s" % (parts[0], parts[1][0]) +def _collapse_message(messageLines, maxCharsPerLine, maxLines): + lines = 0 + + numLines = len(messageLines) + for line in messageLines[0:min(maxLines, numLines)]: + linesPerLine = max(1, int(len(line) / maxCharsPerLine)) + allowedLines = maxLines - lines + acceptedLines = min(allowedLines, linesPerLine) + acceptedChars = acceptedLines * maxCharsPerLine + + if acceptedChars < (len(line) + 3): + suffix = "..." + else: + acceptedChars = len(line) # eh, might as well complete the line + suffix = "" + abbrevMessage = "%s%s" % (line[0:acceptedChars], suffix) + yield abbrevMessage + + lines += acceptedLines + if maxLines <= lines: + break + + +def collapse_message(message, maxCharsPerLine, maxLines): + r""" + >>> collapse_message("Hello", 60, 2) + 'Hello' + >>> collapse_message("Hello world how are you doing today? 01234567890123456789012345678901234567890123456789012345678901234567890123456789", 60, 2) + 'Hello world how are you doing today? 01234567890123456789012...' + >>> collapse_message('''Hello world how are you doing today? + ... 01234567890123456789 + ... 01234567890123456789 + ... 01234567890123456789 + ... 01234567890123456789''', 60, 2) + 'Hello world how are you doing today?\n01234567890123456789' + >>> collapse_message(''' + ... Hello world how are you doing today? 01234567890123456789012345678901234567890123456789012345678901234567890123456789 + ... Hello world how are you doing today? 01234567890123456789012345678901234567890123456789012345678901234567890123456789 + ... Hello world how are you doing today? 01234567890123456789012345678901234567890123456789012345678901234567890123456789 + ... Hello world how are you doing today? 01234567890123456789012345678901234567890123456789012345678901234567890123456789 + ... Hello world how are you doing today? 01234567890123456789012345678901234567890123456789012345678901234567890123456789 + ... Hello world how are you doing today? 01234567890123456789012345678901234567890123456789012345678901234567890123456789''', 60, 2) + '\nHello world how are you doing today? 01234567890123456789012...' + """ + messageLines = message.split("\n") + return "\n".join(_collapse_message(messageLines, maxCharsPerLine, maxLines)) + + class MergedAddressBook(object): """ Merger of all addressbooks @@ -258,232 +314,226 @@ class MergedAddressBook(object): return (contactData for (lastName, contactData) in contactsWithKey) -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._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) - - self._cancelButton = self._widgetTree.get_widget("cancel_button") - self._cancelButton.connect("clicked", self._on_phonetype_cancel) - - self._typemodel = gtk.ListStore(gobject.TYPE_STRING, gobject.TYPE_STRING) - self._typeviewselection = None - - self._message = self._widgetTree.get_widget("phoneSelectionMessage") - self._messageViewport = self._widgetTree.get_widget("phoneSelectionMessage_viewport") - self._scrollWindow = self._widgetTree.get_widget("phoneSelectionMessage_scrolledwindow") - self._typeview = self._widgetTree.get_widget("phonetypes") - self._typeview.connect("row-activated", self._on_phonetype_select) - - self._action = self.ACTION_CANCEL - - def run(self, contactDetails, message = "", parent = None): - self._action = self.ACTION_CANCEL - self._typemodel.clear() - self._typeview.set_model(self._typemodel) - - # Add the column to the treeview - textrenderer = gtk.CellRendererText() - numberColumn = gtk.TreeViewColumn("Phone Numbers", textrenderer, text=0) - self._typeview.append_column(numberColumn) - - textrenderer = gtk.CellRendererText() - typeColumn = gtk.TreeViewColumn("Phone Type", textrenderer, text=1) - self._typeview.append_column(typeColumn) - - self._typeviewselection = self._typeview.get_selection() - self._typeviewselection.set_mode(gtk.SELECTION_SINGLE) - - for phoneType, phoneNumber in contactDetails: - display = " - ".join((phoneNumber, phoneType)) - display = phoneType - row = (phoneNumber, display) - self._typemodel.append(row) - - self._typeviewselection.select_iter(self._typemodel.get_iter_first()) - if message: - self._message.set_markup(message) - self._message.show() - else: - self._message.set_markup("") - self._message.hide() - - if parent is not None: - self._dialog.set_transient_for(parent) - - try: - self._dialog.show() - adjustment = self._scrollWindow.get_vadjustment() - dx = self._message.get_allocation().height - self._messageViewport.get_allocation().height - dx = max(dx, 0) - adjustment.value = dx - - userResponse = self._dialog.run() - finally: - self._dialog.hide() - - if userResponse == gtk.RESPONSE_OK: - phoneNumber = self._get_number() - phoneNumber = make_ugly(phoneNumber) - else: - phoneNumber = "" - if not phoneNumber: - self._action = self.ACTION_CANCEL - - if self._action == self.ACTION_SEND_SMS: - smsMessage = self._smsDialog.run(phoneNumber, message, parent) - if not smsMessage: - phoneNumber = "" - self._action = self.ACTION_CANCEL - else: - smsMessage = "" - - self._typeviewselection.unselect_all() - self._typeview.remove_column(numberColumn) - self._typeview.remove_column(typeColumn) - self._typeview.set_model(None) - - 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): - """ @todo Add multi-SMS messages like GoogleVoice """ + ACTION_CANCEL = "cancel" + ACTION_DIAL = "dial" + ACTION_SEND_SMS = "sms" + MAX_CHAR = 160 def __init__(self, widgetTree): + self._clipboard = gtk.clipboard_get() 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._dialButton = self._widgetTree.get_widget("dialButton") + self._dialButton.connect("clicked", self._on_dial) 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._messageViewport = self._widgetTree.get_widget("smsMessage_viewport") - self._scrollWindow = self._widgetTree.get_widget("smsMessage_scrolledwindow") + + self._messagemodel = gtk.ListStore(gobject.TYPE_STRING) + self._messagesView = self._widgetTree.get_widget("smsMessages") + + self._conversationView = self._messagesView.get_parent() + self._conversationViewPort = self._conversationView.get_parent() + self._scrollWindow = self._conversationViewPort.get_parent() + + self._phoneButton = self._widgetTree.get_widget("phoneTypeSelection") self._smsEntry = self._widgetTree.get_widget("smsEntry") - self._smsEntry.get_buffer().connect("changed", self._on_entry_changed) - def run(self, number, message = "", parent = None): - 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() + self._action = self.ACTION_CANCEL - if parent is not None: - self._dialog.set_transient_for(parent) + self._numberIndex = -1 + self._contactDetails = [] + def run(self, contactDetails, messages = (), parent = None, defaultIndex = -1): + entryConnectId = self._smsEntry.get_buffer().connect("changed", self._on_entry_changed) + phoneConnectId = self._phoneButton.connect("clicked", self._on_phone) + keyConnectId = self._keyPressEventId = self._dialog.connect("key-press-event", self._on_key_press) try: - self._dialog.show() - adjustment = self._scrollWindow.get_vadjustment() - dx = self._message.get_allocation().height - self._messageViewport.get_allocation().height - dx = max(dx, 0) - adjustment.value = dx + # Setup the phone selection button + del self._contactDetails[:] + for phoneType, phoneNumber in contactDetails: + display = " - ".join((make_pretty(phoneNumber), phoneType)) + row = (phoneNumber, display) + self._contactDetails.append(row) + if 0 < len(self._contactDetails): + self._numberIndex = defaultIndex if defaultIndex != -1 else 0 + self._phoneButton.set_label(self._contactDetails[self._numberIndex][1]) + else: + self._numberIndex = -1 + self._phoneButton.set_label("Error: No Number Available") - userResponse = self._dialog.run() - finally: - self._dialog.hide() + # Add the column to the messages tree view + self._messagemodel.clear() + self._messagesView.set_model(self._messagemodel) + self._messagesView.set_fixed_height_mode(False) - 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 = "" + textrenderer = gtk.CellRendererText() + textrenderer.set_property("wrap-mode", pango.WRAP_WORD) + textrenderer.set_property("wrap-width", 450) + messageColumn = gtk.TreeViewColumn("") + messageColumn.pack_start(textrenderer, expand=True) + messageColumn.add_attribute(textrenderer, "markup", 0) + messageColumn.set_sizing(gtk.TREE_VIEW_COLUMN_FIXED) + self._messagesView.append_column(messageColumn) + self._messagesView.set_headers_visible(False) + + if messages: + for message in messages: + row = (message, ) + self._messagemodel.append(row) + self._messagesView.show() + self._scrollWindow.show() + messagesSelection = self._messagesView.get_selection() + messagesSelection.select_path((len(messages)-1, )) + else: + self._messagesView.hide() + self._scrollWindow.hide() + + self._smsEntry.get_buffer().set_text("") + self._update_letter_count() + + if parent is not None: + self._dialog.set_transient_for(parent) + parentSize = parent.get_size() + self._dialog.resize(parentSize[0], max(parentSize[1]-10, 100)) + + # Run + try: + self._dialog.show_all() + self._smsEntry.grab_focus() + adjustment = self._scrollWindow.get_vadjustment() + dx = self._conversationView.get_allocation().height - self._conversationViewPort.get_allocation().height + dx = max(dx, 0) + adjustment.value = dx + + if 1 < len(self._contactDetails): + if defaultIndex == -1: + self._request_number() + self._phoneButton.set_sensitive(True) + else: + self._phoneButton.set_sensitive(False) + + userResponse = self._dialog.run() + finally: + self._dialog.hide_all() + + # Process the users response + if userResponse == gtk.RESPONSE_OK and 0 <= self._numberIndex: + phoneNumber = self._contactDetails[self._numberIndex][0] + phoneNumber = make_ugly(phoneNumber) + else: + phoneNumber = "" + if not phoneNumber: + self._action = self.ACTION_CANCEL + if self._action == self.ACTION_SEND_SMS: + entryBuffer = self._smsEntry.get_buffer() + enteredMessage = entryBuffer.get_text(entryBuffer.get_start_iter(), entryBuffer.get_end_iter()) + enteredMessage = enteredMessage[0:self.MAX_CHAR].strip() + if not enteredMessage: + phoneNumber = "" + self._action = self.ACTION_CANCEL + else: + enteredMessage = "" + + self._messagesView.remove_column(messageColumn) + self._messagesView.set_model(None) - return enteredMessage.strip() + return self._action, phoneNumber, enteredMessage + finally: + self._smsEntry.get_buffer().disconnect(entryConnectId) + self._phoneButton.disconnect(phoneConnectId) + self._keyPressEventId = self._dialog.disconnect(keyConnectId) def _update_letter_count(self, *args): entryLength = self._smsEntry.get_buffer().get_char_count() + charsLeft = self.MAX_CHAR - entryLength self._letterCountLabel.set_text(str(charsLeft)) - if charsLeft < 0: + if charsLeft < 0 or charsLeft == self.MAX_CHAR: self._smsButton.set_sensitive(False) else: self._smsButton.set_sensitive(True) + if entryLength == 0: + self._dialButton.set_sensitive(True) + else: + self._dialButton.set_sensitive(False) + + def _request_number(self): + try: + assert 0 <= self._numberIndex, "%r" % self._numberIndex + + self._numberIndex = hildonize.touch_selector( + self._dialog, + "Phone Numbers", + (description for (number, description) in self._contactDetails), + self._numberIndex, + ) + self._phoneButton.set_label(self._contactDetails[self._numberIndex][1]) + except Exception, e: + _moduleLogger.exception("%s" % str(e)) + + def _on_phone(self, *args): + self._request_number() + def _on_entry_changed(self, *args): self._update_letter_count() def _on_send(self, *args): self._dialog.response(gtk.RESPONSE_OK) + self._action = self.ACTION_SEND_SMS + + def _on_dial(self, *args): + self._dialog.response(gtk.RESPONSE_OK) + self._action = self.ACTION_DIAL def _on_cancel(self, *args): self._dialog.response(gtk.RESPONSE_CANCEL) + self._action = self.ACTION_CANCEL + + def _on_key_press(self, widget, event): + try: + if event.keyval == ord("c") and event.get_state() & gtk.gdk.CONTROL_MASK: + message = "\n".join( + messagePart[0] + for messagePart in self._messagemodel + ) + # For some reason this kills clipboard stuff + #self._clipboard.set_text(message) + except Exception, e: + _moduleLogger.exception(str(e)) class Dialpad(object): def __init__(self, widgetTree, errorDisplay): + self._clipboard = gtk.clipboard_get() self._errorDisplay = errorDisplay self._smsDialog = SmsEntryDialog(widgetTree) self._numberdisplay = widgetTree.get_widget("numberdisplay") + self._smsButton = widgetTree.get_widget("sms") self._dialButton = widgetTree.get_widget("dial") self._backButton = widgetTree.get_widget("back") self._phonenumber = "" self._prettynumber = "" callbackMapping = { - "on_dial_clicked": self._on_dial_clicked, - "on_sms_clicked": self._on_sms_clicked, "on_digit_clicked": self._on_digit_clicked, - "on_clear_number": self._on_clear_number, } widgetTree.signal_autoconnect(callbackMapping) + self._dialButton.connect("clicked", self._on_dial_clicked) + self._smsButton.connect("clicked", self._on_sms_clicked) self._originalLabel = self._backButton.get_label() self._backTapHandler = gtk_toolbox.TapOrHold(self._backButton) @@ -493,12 +543,16 @@ class Dialpad(object): self._backTapHandler.on_cancel = self._reset_back_button self._window = gtk_toolbox.find_parent_window(self._numberdisplay) + self._keyPressEventId = 0 def enable(self): self._dialButton.grab_focus() self._backTapHandler.enable() + self._keyPressEventId = self._window.connect("key-press-event", self._on_key_press) def disable(self): + self._window.disconnect(self._keyPressEventId) + self._keyPressEventId = 0 self._reset_back_button() self._backTapHandler.disable() @@ -538,45 +592,67 @@ class Dialpad(object): """ pass - def _on_sms_clicked(self, widget): - action = PhoneTypeSelector.ACTION_SEND_SMS - phoneNumber = self.get_number() + def _on_key_press(self, widget, event): + try: + if event.keyval == ord("v") and event.get_state() & gtk.gdk.CONTROL_MASK: + contents = self._clipboard.wait_for_text() + if contents is not None: + self.set_number(contents) + except Exception, e: + self._errorDisplay.push_exception() - message = self._smsDialog.run(phoneNumber, "", self._window) - if not message: - phoneNumber = "" - action = PhoneTypeSelector.ACTION_CANCEL + def _on_sms_clicked(self, widget): + try: + phoneNumber = self.get_number() + action, phoneNumber, message = self._smsDialog.run([("Dialer", phoneNumber)], (), self._window) - if action == PhoneTypeSelector.ACTION_CANCEL: - return - self.number_selected(action, phoneNumber, message) + if action == SmsEntryDialog.ACTION_CANCEL: + return + self.number_selected(action, phoneNumber, message) + except Exception, e: + self._errorDisplay.push_exception() def _on_dial_clicked(self, widget): - action = PhoneTypeSelector.ACTION_DIAL - phoneNumber = self.get_number() - message = "" - self.number_selected(action, phoneNumber, message) - - def _on_clear_number(self, *args): - self.clear() + try: + action = SmsEntryDialog.ACTION_DIAL + phoneNumber = self.get_number() + message = "" + self.number_selected(action, phoneNumber, message) + except Exception, e: + self._errorDisplay.push_exception() def _on_digit_clicked(self, widget): - self.set_number(self._phonenumber + widget.get_name()[-1]) + try: + self.set_number(self._phonenumber + widget.get_name()[-1]) + except Exception, e: + self._errorDisplay.push_exception() def _on_backspace(self, taps): - self.set_number(self._phonenumber[:-taps]) - self._reset_back_button() + try: + self.set_number(self._phonenumber[:-taps]) + self._reset_back_button() + except Exception, e: + self._errorDisplay.push_exception() def _on_clearall(self, taps): - self.clear() - self._reset_back_button() + try: + self.clear() + self._reset_back_button() + except Exception, e: + self._errorDisplay.push_exception() return False def _set_clear_button(self): - self._backButton.set_label("gtk-clear") + try: + self._backButton.set_label("gtk-clear") + except Exception, e: + self._errorDisplay.push_exception() def _reset_back_button(self): - self._backButton.set_label(self._originalLabel) + try: + self._backButton.set_label(self._originalLabel) + except Exception, e: + self._errorDisplay.push_exception() class AccountInfo(object): @@ -590,10 +666,10 @@ class AccountInfo(object): self._notifyOnVoicemail = False self._notifyOnSms = False - self._callbackList = gtk.ListStore(gobject.TYPE_STRING) + self._callbackList = [] self._accountViewNumberDisplay = widgetTree.get_widget("gcnumber_display") - self._callbackCombo = widgetTree.get_widget("callbackcombo") - self._onCallbackentryChangedId = 0 + self._callbackSelectButton = widgetTree.get_widget("callbackSelectButton") + self._onCallbackSelectChangedId = 0 self._notifyCheckbox = widgetTree.get_widget("notifyCheckbox") self._minutesEntryButton = widgetTree.get_widget("minutesEntryButton") @@ -608,7 +684,7 @@ class AccountInfo(object): self._applyAlarmTimeoutId = None self._window = gtk_toolbox.find_parent_window(self._minutesEntryButton) - self._defaultCallback = "" + self._callbackNumber = "" def enable(self): assert self._backend.is_authed(), "Attempting to enable backend while not logged in" @@ -616,8 +692,9 @@ class AccountInfo(object): self._accountViewNumberDisplay.set_use_markup(True) self.set_account_number("") - self._callbackList.clear() - self._onCallbackentryChangedId = self._callbackCombo.get_child().connect("changed", self._on_callbackentry_changed) + del self._callbackList[:] + self._onCallbackSelectChangedId = self._callbackSelectButton.connect("clicked", self._on_callbackentry_clicked) + self._set_callback_label("") if self._alarmHandler is not None: self._notifyCheckbox.set_active(self._alarmHandler.isEnabled) @@ -627,7 +704,7 @@ class AccountInfo(object): self._smsCheckbox.set_active(self._notifyOnSms) self._onNotifyToggled = self._notifyCheckbox.connect("toggled", self._on_notify_toggled) - self._onMinutesChanged = self._minutesEntryButton.connect("clicked", self._on_minutes_changed) + self._onMinutesChanged = self._minutesEntryButton.connect("clicked", self._on_minutes_clicked) self._onMissedToggled = self._missedCheckbox.connect("toggled", self._on_missed_toggled) self._onVoicemailToggled = self._voicemailCheckbox.connect("toggled", self._on_voicemail_toggled) self._onSmsToggled = self._smsCheckbox.connect("toggled", self._on_sms_toggled) @@ -641,8 +718,9 @@ class AccountInfo(object): self.update(force=True) def disable(self): - self._callbackCombo.get_child().disconnect(self._onCallbackentryChangedId) - self._onCallbackentryChangedId = 0 + self._callbackSelectButton.disconnect(self._onCallbackSelectChangedId) + self._onCallbackSelectChangedId = 0 + self._set_callback_label("") if self._alarmHandler is not None: self._notifyCheckbox.disconnect(self._onNotifyToggled) @@ -663,10 +741,7 @@ class AccountInfo(object): self._smsCheckbox.set_sensitive(True) self.clear() - self._callbackList.clear() - - def get_selected_callback_number(self): - return make_ugly(self._callbackCombo.get_child().get_text()) + del self._callbackList[:] def set_account_number(self, number): """ @@ -682,7 +757,7 @@ class AccountInfo(object): return True def clear(self): - self._callbackCombo.get_child().set_text("") + self._set_callback_label("") self.set_account_number("") self._isPopulated = False @@ -694,7 +769,7 @@ class AccountInfo(object): return "Account Info" def load_settings(self, config, section): - self._defaultCallback = config.get(section, "callback") + self._callbackNumber = make_ugly(config.get(section, "callback")) self._notifyOnMissed = config.getboolean(section, "notifyOnMissed") self._notifyOnVoicemail = config.getboolean(section, "notifyOnVoicemail") self._notifyOnSms = config.getboolean(section, "notifyOnSms") @@ -703,57 +778,61 @@ class AccountInfo(object): """ @note Thread Agnostic """ - callback = self.get_selected_callback_number() - config.set(section, "callback", callback) + config.set(section, "callback", self._callbackNumber) config.set(section, "notifyOnMissed", repr(self._notifyOnMissed)) config.set(section, "notifyOnVoicemail", repr(self._notifyOnVoicemail)) config.set(section, "notifyOnSms", repr(self._notifyOnSms)) def _populate_callback_combo(self): self._isPopulated = True - self._callbackList.clear() + del self._callbackList[:] try: callbackNumbers = self._backend.get_callback_numbers() - except StandardError, e: + except Exception, e: self._errorDisplay.push_exception() self._isPopulated = False return + if len(callbackNumbers) == 0: + callbackNumbers = {"": "No callback numbers available"} + for number, description in callbackNumbers.iteritems(): - self._callbackList.append((make_pretty(number),)) + self._callbackList.append((make_pretty(number), description)) - self._callbackCombo.set_model(self._callbackList) - self._callbackCombo.set_text_column(0) - #callbackNumber = self._backend.get_callback_number() - callbackNumber = self._defaultCallback - self._callbackCombo.get_child().set_text(make_pretty(callbackNumber)) + self._set_callback_number(self._callbackNumber) def _set_callback_number(self, number): try: - if not self._backend.is_valid_syntax(number): + if not self._backend.is_valid_syntax(number) and 0 < len(number): self._errorDisplay.push_message("%s is not a valid callback number" % number) - elif number == self._backend.get_callback_number(): - warnings.warn( + elif number == self._backend.get_callback_number() and 0 < len(number): + _moduleLogger.warning( "Callback number already is %s" % ( self._backend.get_callback_number(), ), - UserWarning, - 2 ) + self._set_callback_label(number) else: self._backend.set_callback_number(number) assert make_ugly(number) == make_ugly(self._backend.get_callback_number()), "Callback number should be %s but instead is %s" % ( make_pretty(number), make_pretty(self._backend.get_callback_number()) ) - warnings.warn( + self._callbackNumber = make_ugly(number) + self._set_callback_label(number) + _moduleLogger.info( "Callback number set to %s" % ( self._backend.get_callback_number(), ), - UserWarning, 2 ) - except StandardError, e: + except Exception, e: self._errorDisplay.push_exception() + def _set_callback_label(self, uglyNumber): + prettyNumber = make_pretty(uglyNumber) + if len(prettyNumber) == 0: + prettyNumber = "No Callback Number" + self._callbackSelectButton.set_label(prettyNumber) + def _update_alarm_settings(self, recurrence): try: isEnabled = self._notifyCheckbox.get_active() @@ -764,40 +843,109 @@ class AccountInfo(object): self._notifyCheckbox.set_active(self._alarmHandler.isEnabled) self._minutesEntryButton.set_label("%d Minutes" % self._alarmHandler.recurrence) - def _on_callbackentry_changed(self, *args): - text = self.get_selected_callback_number() - number = make_ugly(text) - self._set_callback_number(number) + def _on_callbackentry_clicked(self, *args): + try: + actualSelection = make_pretty(self._callbackNumber) + + userOptions = dict( + (number, "%s (%s)" % (number, description)) + for (number, description) in self._callbackList + ) + defaultSelection = userOptions.get(actualSelection, actualSelection) + + userSelection = hildonize.touch_selector_entry( + self._window, + "Callback Number", + list(userOptions.itervalues()), + defaultSelection, + ) + reversedUserOptions = dict( + itertools.izip(userOptions.itervalues(), userOptions.iterkeys()) + ) + selectedNumber = reversedUserOptions.get(userSelection, userSelection) + + number = make_ugly(selectedNumber) + self._set_callback_number(number) + except RuntimeError, e: + _moduleLogger.exception("%s" % str(e)) + except Exception, e: + self._errorDisplay.push_exception() def _on_notify_toggled(self, *args): - if self._applyAlarmTimeoutId is not None: - gobject.source_remove(self._applyAlarmTimeoutId) - self._applyAlarmTimeoutId = None - self._applyAlarmTimeoutId = gobject.timeout_add(500, self._on_apply_timeout) + try: + if self._applyAlarmTimeoutId is not None: + gobject.source_remove(self._applyAlarmTimeoutId) + self._applyAlarmTimeoutId = None + self._applyAlarmTimeoutId = gobject.timeout_add(500, self._on_apply_timeout) + except Exception, e: + self._errorDisplay.push_exception() - def _on_minutes_changed(self, *args): - recurrence = hildonize.request_number( - self._window, "Minutes", (1, 50), self._alarmHandler.recurrence - ) - self._update_alarm_settings(recurrence) + def _on_minutes_clicked(self, *args): + recurrenceChoices = [ + (1, "1 minute"), + (2, "2 minutes"), + (3, "3 minutes"), + (5, "5 minutes"), + (8, "8 minutes"), + (10, "10 minutes"), + (15, "15 minutes"), + (30, "30 minutes"), + (45, "45 minutes"), + (60, "1 hour"), + (3*60, "3 hours"), + (6*60, "6 hours"), + (12*60, "12 hours"), + ] + try: + actualSelection = self._alarmHandler.recurrence + + closestSelectionIndex = 0 + for i, possible in enumerate(recurrenceChoices): + if possible[0] <= actualSelection: + closestSelectionIndex = i + recurrenceIndex = hildonize.touch_selector( + self._window, + "Minutes", + (("%s" % m[1]) for m in recurrenceChoices), + closestSelectionIndex, + ) + recurrence = recurrenceChoices[recurrenceIndex][0] + + self._update_alarm_settings(recurrence) + except RuntimeError, e: + _moduleLogger.exception("%s" % str(e)) + except Exception, e: + self._errorDisplay.push_exception() def _on_apply_timeout(self, *args): - self._applyAlarmTimeoutId = None + try: + self._applyAlarmTimeoutId = None - self._update_alarm_settings(self._alarmHandler.recurrence) + self._update_alarm_settings(self._alarmHandler.recurrence) + except Exception, e: + self._errorDisplay.push_exception() return False def _on_missed_toggled(self, *args): - self._notifyOnMissed = self._missedCheckbox.get_active() - self.save_everything() + try: + self._notifyOnMissed = self._missedCheckbox.get_active() + self.save_everything() + except Exception, e: + self._errorDisplay.push_exception() def _on_voicemail_toggled(self, *args): - self._notifyOnVoicemail = self._voicemailCheckbox.get_active() - self.save_everything() + try: + self._notifyOnVoicemail = self._voicemailCheckbox.get_active() + self.save_everything() + except Exception, e: + self._errorDisplay.push_exception() def _on_sms_toggled(self, *args): - self._notifyOnSms = self._smsCheckbox.get_active() - self.save_everything() + try: + self._notifyOnSms = self._smsCheckbox.get_active() + self.save_everything() + except Exception, e: + self._errorDisplay.push_exception() class RecentCallsView(object): @@ -806,6 +954,7 @@ class RecentCallsView(object): DATE_IDX = 1 ACTION_IDX = 2 FROM_IDX = 3 + FROM_ID_IDX = 4 def __init__(self, widgetTree, backend, errorDisplay): self._errorDisplay = errorDisplay @@ -817,6 +966,7 @@ class RecentCallsView(object): gobject.TYPE_STRING, # date gobject.TYPE_STRING, # action gobject.TYPE_STRING, # from + gobject.TYPE_STRING, # from id ) self._recentview = widgetTree.get_widget("recentview") self._recentviewselection = None @@ -836,21 +986,22 @@ class RecentCallsView(object): textrenderer = gtk.CellRendererText() textrenderer.set_property("yalign", 0) + textrenderer.set_property("ellipsize", pango.ELLIPSIZE_END) + textrenderer.set_property("width-chars", len("1 (555) 555-1234")) + self._numberColumn = gtk.TreeViewColumn("Number") + self._numberColumn.pack_start(textrenderer, expand=True) + self._numberColumn.add_attribute(textrenderer, "text", self.NUMBER_IDX) + + textrenderer = gtk.CellRendererText() + textrenderer.set_property("yalign", 0) hildonize.set_cell_thumb_selectable(textrenderer) self._nameColumn = gtk.TreeViewColumn("From") self._nameColumn.pack_start(textrenderer, expand=True) self._nameColumn.add_attribute(textrenderer, "text", self.FROM_IDX) self._nameColumn.set_sizing(gtk.TREE_VIEW_COLUMN_FIXED) - textrenderer = gtk.CellRendererText() - textrenderer.set_property("yalign", 0) - hildonize.set_cell_thumb_selectable(textrenderer) - self._numberColumn = gtk.TreeViewColumn("Number") - self._numberColumn.pack_start(textrenderer, expand=True) - self._numberColumn.add_attribute(textrenderer, "text", self.NUMBER_IDX) - self._window = gtk_toolbox.find_parent_window(self._recentview) - self._phoneTypeSelector = PhoneTypeSelector(widgetTree, self._backend) + self._phoneTypeSelector = SmsEntryDialog(widgetTree) self._updateSink = gtk_toolbox.threaded_stage( gtk_toolbox.comap( @@ -862,6 +1013,7 @@ class RecentCallsView(object): def enable(self): assert self._backend.is_authed(), "Attempting to enable backend while not logged in" self._recentview.set_model(self._recentmodel) + self._recentview.set_fixed_height_mode(False) self._recentview.append_column(self._dateColumn) self._recentview.append_column(self._actionColumn) @@ -913,49 +1065,85 @@ class RecentCallsView(object): pass def _idly_populate_recentview(self): - self._recentmodel.clear() - self._isPopulated = True - + with gtk_toolbox.gtk_lock(): + banner = hildonize.show_busy_banner_start(self._window, "Loading Recent History") try: - recentItems = self._backend.get_recent() - except StandardError, e: + self._recentmodel.clear() + self._isPopulated = True + + try: + recentItems = self._backend.get_recent() + except Exception, e: + self._errorDisplay.push_exception_with_lock() + self._isPopulated = False + recentItems = [] + + recentItems = ( + gv_backend.decorate_recent(data) + for data in gv_backend.sort_messages(recentItems) + ) + + for contactId, personName, phoneNumber, date, action in recentItems: + if not personName: + personName = "Unknown" + date = abbrev_relative_date(date) + prettyNumber = phoneNumber[2:] if phoneNumber.startswith("+1") else phoneNumber + prettyNumber = make_pretty(prettyNumber) + item = (prettyNumber, date, action.capitalize(), personName, contactId) + with gtk_toolbox.gtk_lock(): + self._recentmodel.append(item) + except Exception, e: self._errorDisplay.push_exception_with_lock() - self._isPopulated = False - recentItems = [] - - for personName, phoneNumber, date, action in recentItems: - if not personName: - personName = "Unknown" - date = abbrev_relative_date(date) - prettyNumber = phoneNumber[2:] if phoneNumber.startswith("+1") else phoneNumber - prettyNumber = make_pretty(prettyNumber) - item = (prettyNumber, date, action.capitalize(), personName) + finally: with gtk_toolbox.gtk_lock(): - self._recentmodel.append(item) + hildonize.show_busy_banner_end(banner) return False def _on_recentview_row_activated(self, treeview, path, view_column): - model, itr = self._recentviewselection.get_selected() - if not itr: - return - - number = self._recentmodel.get_value(itr, self.NUMBER_IDX) - number = make_ugly(number) - contactPhoneNumbers = [("Phone", number)] - description = self._recentmodel.get_value(itr, self.FROM_IDX) + try: + itr = self._recentmodel.get_iter(path) + if not itr: + return - action, phoneNumber, message = self._phoneTypeSelector.run( - contactPhoneNumbers, - message = description, - parent = self._window, - ) - if action == PhoneTypeSelector.ACTION_CANCEL: - return - assert phoneNumber, "A lack of phone number exists" + number = self._recentmodel.get_value(itr, self.NUMBER_IDX) + number = make_ugly(number) + description = self._recentmodel.get_value(itr, self.FROM_IDX) + contactId = self._recentmodel.get_value(itr, self.FROM_ID_IDX) + if contactId: + contactPhoneNumbers = list(self._backend.get_contact_details(contactId)) + defaultMatches = [ + (number == make_ugly(contactNumber) or number[1:] == make_ugly(contactNumber)) + for (numberDescription, contactNumber) in contactPhoneNumbers + ] + try: + defaultIndex = defaultMatches.index(True) + except ValueError: + contactPhoneNumbers.append(("Other", number)) + defaultIndex = len(contactPhoneNumbers)-1 + _moduleLogger.warn( + "Could not find contact %r's number %s among %r" % ( + contactId, number, contactPhoneNumbers + ) + ) + else: + contactPhoneNumbers = [("Phone", number)] + defaultIndex = -1 + + action, phoneNumber, message = self._phoneTypeSelector.run( + contactPhoneNumbers, + messages = (description, ), + parent = self._window, + defaultIndex = defaultIndex, + ) + if action == SmsEntryDialog.ACTION_CANCEL: + return + assert phoneNumber, "A lack of phone number exists" - self.number_selected(action, phoneNumber, message) - self._recentviewselection.unselect_all() + self.number_selected(action, phoneNumber, message) + self._recentviewselection.unselect_all() + except Exception, e: + self._errorDisplay.push_exception() class MessagesView(object): @@ -964,6 +1152,19 @@ class MessagesView(object): DATE_IDX = 1 HEADER_IDX = 2 MESSAGE_IDX = 3 + MESSAGES_IDX = 4 + FROM_ID_IDX = 5 + + NO_MESSAGES = "None" + VOICEMAIL_MESSAGES = "Voicemail" + TEXT_MESSAGES = "Texts" + ALL_MESSAGES = "All Messages" + MESSAGE_TYPES = [NO_MESSAGES, VOICEMAIL_MESSAGES, TEXT_MESSAGES, ALL_MESSAGES] + + UNREAD_STATUS = "Unread" + UNARCHIVED_STATUS = "Unarchived" + ALL_STATUS = "Any" + MESSAGE_STATUSES = [UNREAD_STATUS, UNARCHIVED_STATUS, ALL_STATUS] def __init__(self, widgetTree, backend, errorDisplay): self._errorDisplay = errorDisplay @@ -975,6 +1176,8 @@ class MessagesView(object): gobject.TYPE_STRING, # date gobject.TYPE_STRING, # header gobject.TYPE_STRING, # message + object, # messages + gobject.TYPE_STRING, # from id ) self._messageview = widgetTree.get_widget("messages_view") self._messageviewselection = None @@ -989,7 +1192,14 @@ class MessagesView(object): self._messageColumn.set_sizing(gtk.TREE_VIEW_COLUMN_FIXED) self._window = gtk_toolbox.find_parent_window(self._messageview) - self._phoneTypeSelector = PhoneTypeSelector(widgetTree, self._backend) + self._phoneTypeSelector = SmsEntryDialog(widgetTree) + + self._messageTypeButton = widgetTree.get_widget("messageTypeButton") + self._onMessageTypeClickedId = 0 + self._messageType = self.ALL_MESSAGES + self._messageStatusButton = widgetTree.get_widget("messageStatusButton") + self._onMessageStatusClickedId = 0 + self._messageStatus = self.ALL_STATUS self._updateSink = gtk_toolbox.threaded_stage( gtk_toolbox.comap( @@ -1001,15 +1211,30 @@ class MessagesView(object): def enable(self): assert self._backend.is_authed(), "Attempting to enable backend while not logged in" self._messageview.set_model(self._messagemodel) + self._messageview.set_headers_visible(False) + self._messageview.set_fixed_height_mode(False) self._messageview.append_column(self._messageColumn) 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) + self._messageTypeButton.set_label(self._messageType) + self._messageStatusButton.set_label(self._messageStatus) + + self._onMessageviewRowActivatedId = self._messageview.connect( + "row-activated", self._on_messageview_row_activated + ) + self._onMessageTypeClickedId = self._messageTypeButton.connect( + "clicked", self._on_message_type_clicked + ) + self._onMessageStatusClickedId = self._messageStatusButton.connect( + "clicked", self._on_message_status_clicked + ) def disable(self): self._messageview.disconnect(self._onMessageviewRowActivatedId) + self._messageTypeButton.disconnect(self._onMessageTypeClickedId) + self._messageStatusButton.disconnect(self._onMessageStatusClickedId) self.clear() @@ -1036,56 +1261,182 @@ class MessagesView(object): def name(): return "Messages" - def load_settings(self, config, section): - pass + def load_settings(self, config, sectionName): + try: + self._messageStatus = config.get(sectionName, "status") + self._messageType = config.get(sectionName, "type") + except ConfigParser.NoOptionError: + pass - def save_settings(self, config, section): + def save_settings(self, config, sectionName): """ @note Thread Agnostic """ - pass + config.set(sectionName, "status", self._messageStatus) + config.set(sectionName, "type", self._messageType) - def _idly_populate_messageview(self): - self._messagemodel.clear() - self._isPopulated = True + _MIN_MESSAGES_SHOWN = 4 + + @classmethod + def _filter_messages(cls, message, type, status): + if type == cls.ALL_MESSAGES: + isType = True + else: + messageType = message["type"] + isType = messageType == type + if status == cls.ALL_STATUS: + isStatus = True + else: + isUnarchived = not message["isArchived"] + isUnread = not message["isRead"] + if status == cls.UNREAD_STATUS: + isStatus = isUnarchived and isUnread + elif status == cls.UNARCHIVED_STATUS: + isStatus = isUnarchived + else: + assert "Status %s is bad for %r" % (status, message) + + return isType and isStatus + + def _idly_populate_messageview(self): + with gtk_toolbox.gtk_lock(): + banner = hildonize.show_busy_banner_start(self._window, "Loading Messages") try: - messageItems = self._backend.get_messages() - except StandardError, e: - self._errorDisplay.push_exception_with_lock() - self._isPopulated = False - messageItems = [] + self._messagemodel.clear() + self._isPopulated = True - for header, number, relativeDate, message in messageItems: - prettyNumber = number[2:] if number.startswith("+1") else number - prettyNumber = make_pretty(prettyNumber) - message = "%s - %s (%s)\n\n%s" % (header, prettyNumber, relativeDate, message) - number = make_ugly(number) - row = (number, relativeDate, header, message) + if self._messageType == self.NO_MESSAGES: + messageItems = [] + else: + try: + messageItems = self._backend.get_messages() + except Exception, e: + self._errorDisplay.push_exception_with_lock() + self._isPopulated = False + messageItems = [] + + messageItems = ( + gv_backend.decorate_message(message) + for message in gv_backend.sort_messages(messageItems) + if self._filter_messages(message, self._messageType, self._messageStatus) + ) + + for contactId, header, number, relativeDate, messages in messageItems: + prettyNumber = number[2:] if number.startswith("+1") else number + prettyNumber = make_pretty(prettyNumber) + + firstMessage = "%s - %s (%s)" % (header, prettyNumber, relativeDate) + expandedMessages = [firstMessage] + expandedMessages.extend(messages) + if (self._MIN_MESSAGES_SHOWN + 1) < len(messages): + firstMessage = "%s - %s (%s)" % (header, prettyNumber, relativeDate) + secondMessage = "%d Messages Hidden..." % (len(messages) - self._MIN_MESSAGES_SHOWN, ) + collapsedMessages = [firstMessage, secondMessage] + collapsedMessages.extend(messages[-(self._MIN_MESSAGES_SHOWN+0):]) + else: + collapsedMessages = expandedMessages + #collapsedMessages = _collapse_message(collapsedMessages, 60, self._MIN_MESSAGES_SHOWN) + + number = make_ugly(number) + + row = number, relativeDate, header, "\n".join(collapsedMessages), expandedMessages, contactId + with gtk_toolbox.gtk_lock(): + self._messagemodel.append(row) + except Exception, e: + self._errorDisplay.push_exception_with_lock() + finally: with gtk_toolbox.gtk_lock(): - self._messagemodel.append(row) + hildonize.show_busy_banner_end(banner) return False def _on_messageview_row_activated(self, treeview, path, view_column): - model, itr = self._messageviewselection.get_selected() - if not itr: - return + try: + itr = self._messagemodel.get_iter(path) + if not itr: + return + + number = make_ugly(self._messagemodel.get_value(itr, self.NUMBER_IDX)) + description = self._messagemodel.get_value(itr, self.MESSAGES_IDX) + + contactId = self._messagemodel.get_value(itr, self.FROM_ID_IDX) + if contactId: + contactPhoneNumbers = list(self._backend.get_contact_details(contactId)) + defaultMatches = [ + (number == make_ugly(contactNumber) or number[1:] == make_ugly(contactNumber)) + for (numberDescription, contactNumber) in contactPhoneNumbers + ] + try: + defaultIndex = defaultMatches.index(True) + except ValueError: + contactPhoneNumbers.append(("Other", number)) + defaultIndex = len(contactPhoneNumbers)-1 + _moduleLogger.warn( + "Could not find contact %r's number %s among %r" % ( + contactId, number, contactPhoneNumbers + ) + ) + else: + contactPhoneNumbers = [("Phone", number)] + defaultIndex = -1 + + action, phoneNumber, message = self._phoneTypeSelector.run( + contactPhoneNumbers, + messages = description, + parent = self._window, + defaultIndex = defaultIndex, + ) + if action == SmsEntryDialog.ACTION_CANCEL: + return + assert phoneNumber, "A lock of phone number exists" - contactPhoneNumbers = [("Phone", self._messagemodel.get_value(itr, self.NUMBER_IDX))] - description = self._messagemodel.get_value(itr, self.MESSAGE_IDX) + self.number_selected(action, phoneNumber, message) + self._messageviewselection.unselect_all() + except Exception, e: + self._errorDisplay.push_exception() - action, phoneNumber, message = self._phoneTypeSelector.run( - contactPhoneNumbers, - message = description, - parent = self._window, - ) - if action == PhoneTypeSelector.ACTION_CANCEL: - return - assert phoneNumber, "A lock of phone number exists" + def _on_message_type_clicked(self, *args, **kwds): + try: + selectedIndex = self.MESSAGE_TYPES.index(self._messageType) + + try: + newSelectedIndex = hildonize.touch_selector( + self._window, + "Message Type", + self.MESSAGE_TYPES, + selectedIndex, + ) + except RuntimeError: + return + + if selectedIndex != newSelectedIndex: + self._messageType = self.MESSAGE_TYPES[newSelectedIndex] + self._messageTypeButton.set_label(self._messageType) + self.update(True) + except Exception, e: + self._errorDisplay.push_exception() - self.number_selected(action, phoneNumber, message) - self._messageviewselection.unselect_all() + def _on_message_status_clicked(self, *args, **kwds): + try: + selectedIndex = self.MESSAGE_STATUSES.index(self._messageStatus) + + try: + newSelectedIndex = hildonize.touch_selector( + self._window, + "Message Status", + self.MESSAGE_STATUSES, + selectedIndex, + ) + except RuntimeError: + return + + if selectedIndex != newSelectedIndex: + self._messageStatus = self.MESSAGE_STATUSES[newSelectedIndex] + self._messageStatusButton.set_label(self._messageStatus) + self.update(True) + except Exception, e: + self._errorDisplay.push_exception() class ContactsView(object): @@ -1098,8 +1449,8 @@ class ContactsView(object): self._selectedComboIndex = 0 self._addressBookFactories = [null_backend.NullAddressBook()] - self._booksList = gtk.ListStore(gobject.TYPE_STRING, gobject.TYPE_STRING, gobject.TYPE_STRING) - self._booksSelectionBox = widgetTree.get_widget("addressbook_combo") + self._booksList = [] + self._bookSelectionButton = widgetTree.get_widget("addressbookSelectButton") self._isPopulated = False self._contactsmodel = gtk.ListStore(gobject.TYPE_STRING, gobject.TYPE_STRING, gobject.TYPE_STRING, gobject.TYPE_STRING, gobject.TYPE_STRING) @@ -1124,9 +1475,9 @@ class ContactsView(object): self._contactColumn.set_visible(True) self._onContactsviewRowActivatedId = 0 - self._onAddressbookComboChangedId = 0 + self._onAddressbookButtonChangedId = 0 self._window = gtk_toolbox.find_parent_window(self._contactsview) - self._phoneTypeSelector = PhoneTypeSelector(widgetTree, self._backend) + self._phoneTypeSelector = SmsEntryDialog(widgetTree) self._updateSink = gtk_toolbox.threaded_stage( gtk_toolbox.comap( @@ -1139,11 +1490,12 @@ class ContactsView(object): assert self._backend.is_authed(), "Attempting to enable backend while not logged in" self._contactsview.set_model(self._contactsmodel) + self._contactsview.set_fixed_height_mode(False) self._contactsview.append_column(self._contactColumn) self._contactsviewselection = self._contactsview.get_selection() self._contactsviewselection.set_mode(gtk.SELECTION_SINGLE) - self._booksList.clear() + del self._booksList[:] for (factoryId, bookId), (factoryName, bookName) in self.get_addressbooks(): if factoryName and bookName: entryName = "%s: %s" % (factoryName, bookName) @@ -1156,26 +1508,24 @@ class ContactsView(object): row = (str(factoryId), bookId, entryName) self._booksList.append(row) - self._booksSelectionBox.set_model(self._booksList) - cell = gtk.CellRendererText() - self._booksSelectionBox.pack_start(cell, True) - self._booksSelectionBox.add_attribute(cell, 'text', 2) - self._onContactsviewRowActivatedId = self._contactsview.connect("row-activated", self._on_contactsview_row_activated) - self._onAddressbookComboChangedId = self._booksSelectionBox.connect("changed", self._on_addressbook_combo_changed) + self._onAddressbookButtonChangedId = self._bookSelectionButton.connect("clicked", self._on_addressbook_button_changed) if len(self._booksList) <= self._selectedComboIndex: self._selectedComboIndex = 0 - self._booksSelectionBox.set_active(self._selectedComboIndex) + self._bookSelectionButton.set_label(self._booksList[self._selectedComboIndex][2]) + + selectedFactoryId = self._booksList[self._selectedComboIndex][0] + selectedBookId = self._booksList[self._selectedComboIndex][1] + self.open_addressbook(selectedFactoryId, selectedBookId) def disable(self): self._contactsview.disconnect(self._onContactsviewRowActivatedId) - self._booksSelectionBox.disconnect(self._onAddressbookComboChangedId) + self._bookSelectionButton.disconnect(self._onAddressbookButtonChangedId) self.clear() - self._booksSelectionBox.clear() - self._booksSelectionBox.set_model(None) + self._bookSelectionButton.set_label("") self._contactsview.set_model(None) self._contactsview.remove_column(self._contactColumn) @@ -1196,11 +1546,7 @@ class ContactsView(object): def open_addressbook(self, bookFactoryId, bookId): bookFactoryIndex = int(bookFactoryId) addressBook = self._addressBookFactories[bookFactoryIndex].open_addressbook(bookId) - - forceUpdate = True if addressBook is not self._addressBook else False - self._addressBook = addressBook - self.update(force=forceUpdate) def update(self, force = False): if not force and self._isPopulated: @@ -1235,63 +1581,90 @@ class ContactsView(object): config.set(sectionName, "selectedAddressbook", str(self._selectedComboIndex)) def _idly_populate_contactsview(self): - addressBook = None - while addressBook is not self._addressBook: - addressBook = self._addressBook + with gtk_toolbox.gtk_lock(): + banner = hildonize.show_busy_banner_start(self._window, "Loading Contacts") + try: + addressBook = None + while addressBook is not self._addressBook: + addressBook = self._addressBook + with gtk_toolbox.gtk_lock(): + self._contactsview.set_model(None) + self.clear() + + try: + contacts = addressBook.get_contacts() + except Exception, e: + contacts = [] + self._isPopulated = False + self._errorDisplay.push_exception_with_lock() + for contactId, contactName in contacts: + contactType = (addressBook.contact_source_short_name(contactId), ) + self._contactsmodel.append(contactType + (contactName, "", contactId) + ("", )) + + with gtk_toolbox.gtk_lock(): + self._contactsview.set_model(self._contactsmodel) + + self._isPopulated = True + except Exception, e: + self._errorDisplay.push_exception_with_lock() + finally: with gtk_toolbox.gtk_lock(): - self._contactsview.set_model(None) - self.clear() + hildonize.show_busy_banner_end(banner) + return False + def _on_addressbook_button_changed(self, *args, **kwds): + try: try: - contacts = addressBook.get_contacts() - except StandardError, e: - contacts = [] - self._isPopulated = False - self._errorDisplay.push_exception_with_lock() - for contactId, contactName in contacts: - contactType = (addressBook.contact_source_short_name(contactId), ) - self._contactsmodel.append(contactType + (contactName, "", contactId) + ("", )) + newSelectedComboIndex = hildonize.touch_selector( + self._window, + "Addressbook", + (("%s" % m[2]) for m in self._booksList), + self._selectedComboIndex, + ) + except RuntimeError: + return - with gtk_toolbox.gtk_lock(): - self._contactsview.set_model(self._contactsmodel) + selectedFactoryId = self._booksList[newSelectedComboIndex][0] + selectedBookId = self._booksList[newSelectedComboIndex][1] - self._isPopulated = True - return False + oldAddressbook = self._addressBook + self.open_addressbook(selectedFactoryId, selectedBookId) + forceUpdate = True if oldAddressbook is not self._addressBook else False + self.update(force=forceUpdate) - def _on_addressbook_combo_changed(self, *args, **kwds): - itr = self._booksSelectionBox.get_active_iter() - if itr is None: - return - self._selectedComboIndex = self._booksSelectionBox.get_active() - selectedFactoryId = self._booksList.get_value(itr, 0) - selectedBookId = self._booksList.get_value(itr, 1) - self.open_addressbook(selectedFactoryId, selectedBookId) + self._selectedComboIndex = newSelectedComboIndex + self._bookSelectionButton.set_label(self._booksList[self._selectedComboIndex][2]) + except Exception, e: + self._errorDisplay.push_exception() def _on_contactsview_row_activated(self, treeview, path, view_column): - model, itr = self._contactsviewselection.get_selected() - if not itr: - return - - contactId = self._contactsmodel.get_value(itr, 3) - contactName = self._contactsmodel.get_value(itr, 1) try: - contactDetails = self._addressBook.get_contact_details(contactId) - except StandardError, e: - contactDetails = [] - self._errorDisplay.push_exception() - contactPhoneNumbers = [phoneNumber for phoneNumber in contactDetails] - - if len(contactPhoneNumbers) == 0: - return + itr = self._contactsmodel.get_iter(path) + if not itr: + return - action, phoneNumber, message = self._phoneTypeSelector.run( - contactPhoneNumbers, - message = contactName, - parent = self._window, - ) - if action == PhoneTypeSelector.ACTION_CANCEL: - return - assert phoneNumber, "A lack of phone number exists" + contactId = self._contactsmodel.get_value(itr, 3) + contactName = self._contactsmodel.get_value(itr, 1) + try: + contactDetails = self._addressBook.get_contact_details(contactId) + except Exception, e: + contactDetails = [] + self._errorDisplay.push_exception() + contactPhoneNumbers = [phoneNumber for phoneNumber in contactDetails] + + if len(contactPhoneNumbers) == 0: + return + + action, phoneNumber, message = self._phoneTypeSelector.run( + contactPhoneNumbers, + messages = (contactName, ), + parent = self._window, + ) + if action == SmsEntryDialog.ACTION_CANCEL: + return + assert phoneNumber, "A lack of phone number exists" - self.number_selected(action, phoneNumber, message) - self._contactsviewselection.unselect_all() + self.number_selected(action, phoneNumber, message) + self._contactsviewselection.unselect_all() + except Exception, e: + self._errorDisplay.push_exception()