X-Git-Url: http://git.maemo.org/git/?a=blobdiff_plain;f=src%2Fgc_views.py;h=5f480978950a9dcd8af4ab682ca5569029fdb226;hb=d732b698f3b864c6643722d5f22c2449856e7049;hp=de7369b34680445b13e846f40c5776c425619ac4;hpb=a26e3d9b1842e59da1168d06f5ce5a4f5d219080;p=gc-dialer diff --git a/src/gc_views.py b/src/gc_views.py index de7369b..5f48097 100644 --- a/src/gc_views.py +++ b/src/gc_views.py @@ -17,6 +17,8 @@ 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 Feature request: The ability to go to relevant thing in web browser """ from __future__ import with_statement @@ -28,6 +30,7 @@ import gobject import gtk import gtk_toolbox +import null_backend def make_ugly(prettynumber): @@ -93,69 +96,6 @@ def make_pretty(phonenumber): return prettynumber -def make_idler(func): - """ - Decorator that makes a generator-function into a function that will continue execution on next call - """ - a = [] - - def decorated_func(*args, **kwds): - if not a: - a.append(func(*args, **kwds)) - try: - a[0].next() - return True - except StopIteration: - del a[:] - return False - - decorated_func.__name__ = func.__name__ - decorated_func.__doc__ = func.__doc__ - decorated_func.__dict__.update(func.__dict__) - - return decorated_func - - -class DummyAddressBook(object): - """ - Minimal example of both an addressbook factory and an addressbook - """ - - def clear_caches(self): - pass - - def get_addressbooks(self): - """ - @returns Iterable of (Address Book Factory, Book Id, Book Name) - """ - yield self, "", "None" - - def open_addressbook(self, bookId): - return self - - @staticmethod - def contact_source_short_name(contactId): - return "" - - @staticmethod - def factory_name(): - return "" - - @staticmethod - def get_contacts(): - """ - @returns Iterable of (contact id, contact name) - """ - return [] - - @staticmethod - def get_contact_details(contactId): - """ - @returns Iterable of (Phone Type, Phone Number) - """ - return [] - - class MergedAddressBook(object): """ Merger of all addressbooks @@ -317,7 +257,7 @@ class PhoneTypeSelector(object): self._widgetTree = widgetTree self._dialog = self._widgetTree.get_widget("phonetype_dialog") - self._smsDialog = SmsEntryDialog(self._widgetTree, self._gcBackend) + self._smsDialog = SmsEntryDialog(self._widgetTree) self._smsButton = self._widgetTree.get_widget("sms_button") self._smsButton.connect("clicked", self._on_phonetype_send_sms) @@ -340,7 +280,7 @@ class PhoneTypeSelector(object): self._action = self.ACTION_CANCEL - def run(self, contactDetails, message = ""): + def run(self, contactDetails, message = "", parent = None): self._action = self.ACTION_CANCEL self._typemodel.clear() self._typeview.set_model(self._typemodel) @@ -371,7 +311,13 @@ class PhoneTypeSelector(object): self._message.set_markup("") self._message.hide() - userResponse = self._dialog.run() + if parent is not None: + self._dialog.set_transient_for(parent) + + try: + userResponse = self._dialog.run() + finally: + self._dialog.hide() if userResponse == gtk.RESPONSE_OK: phoneNumber = self._get_number() @@ -382,7 +328,7 @@ class PhoneTypeSelector(object): self._action = self.ACTION_CANCEL if self._action == self.ACTION_SEND_SMS: - smsMessage = self._smsDialog.run(phoneNumber, message) + smsMessage = self._smsDialog.run(phoneNumber, message, parent) if not smsMessage: phoneNumber = "" self._action = self.ACTION_CANCEL @@ -393,7 +339,7 @@ class PhoneTypeSelector(object): self._typeview.remove_column(numberColumn) self._typeview.remove_column(typeColumn) self._typeview.set_model(None) - self._dialog.hide() + return self._action, phoneNumber, smsMessage def _get_number(self): @@ -423,10 +369,13 @@ class PhoneTypeSelector(object): class SmsEntryDialog(object): + """ + @todo Add multi-SMS messages like GoogleVoice + """ + MAX_CHAR = 160 - def __init__(self, widgetTree, gcBackend): - self._gcBackend = gcBackend + def __init__(self, widgetTree): self._widgetTree = widgetTree self._dialog = self._widgetTree.get_widget("smsDialog") @@ -441,7 +390,7 @@ class SmsEntryDialog(object): self._smsEntry = self._widgetTree.get_widget("smsEntry") self._smsEntry.get_buffer().connect("changed", self._on_entry_changed) - def run(self, number, message = ""): + def run(self, number, message = "", parent = None): if message: self._message.set_markup(message) self._message.show() @@ -451,7 +400,14 @@ class SmsEntryDialog(object): self._smsEntry.get_buffer().set_text("") self._update_letter_count() - userResponse = self._dialog.run() + if parent is not None: + self._dialog.set_transient_for(parent) + + try: + userResponse = self._dialog.run() + finally: + self._dialog.hide() + if userResponse == gtk.RESPONSE_OK: entryBuffer = self._smsEntry.get_buffer() enteredMessage = entryBuffer.get_text(entryBuffer.get_start_iter(), entryBuffer.get_end_iter()) @@ -459,8 +415,7 @@ class SmsEntryDialog(object): else: enteredMessage = "" - self._dialog.hide() - return enteredMessage + return enteredMessage.strip() def _update_letter_count(self, *args): entryLength = self._smsEntry.get_buffer().get_char_count() @@ -485,33 +440,44 @@ class Dialpad(object): def __init__(self, widgetTree, errorDisplay): self._errorDisplay = errorDisplay + self._smsDialog = SmsEntryDialog(widgetTree) + self._numberdisplay = widgetTree.get_widget("numberdisplay") self._dialButton = widgetTree.get_widget("dial") + self._backButton = widgetTree.get_widget("back") self._phonenumber = "" self._prettynumber = "" - self._clearall_id = None 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, - "on_back_clicked": self._on_backspace, - "on_back_pressed": self._on_back_pressed, - "on_back_released": self._on_back_released, } widgetTree.signal_autoconnect(callbackMapping) + self._originalLabel = self._backButton.get_label() + self._backTapHandler = gtk_toolbox.TapOrHold(self._backButton) + self._backTapHandler.on_tap = self._on_backspace + self._backTapHandler.on_hold = self._on_clearall + self._backTapHandler.on_holding = self._set_clear_button + self._backTapHandler.on_cancel = self._reset_back_button + + self._window = gtk_toolbox.find_parent_window(self._numberdisplay) + def enable(self): self._dialButton.grab_focus() + self._backTapHandler.enable() def disable(self): - pass + self._reset_back_button() + self._backTapHandler.disable() - def dial(self, number): + def number_selected(self, action, number, message): """ @note Actual dial function is patched in later """ - raise NotImplementedError + raise NotImplementedError("Horrible unknown error has occurred") def get_number(self): return self._phonenumber @@ -525,7 +491,7 @@ class Dialpad(object): self._prettynumber = make_pretty(self._phonenumber) self._numberdisplay.set_label("%s" % (self._prettynumber)) except TypeError, e: - self._errorDisplay.push_exception(e) + self._errorDisplay.push_exception() def clear(self): self.set_number("") @@ -543,8 +509,24 @@ class Dialpad(object): """ pass + def _on_sms_clicked(self, widget): + action = PhoneTypeSelector.ACTION_SEND_SMS + phoneNumber = self.get_number() + + message = self._smsDialog.run(phoneNumber, "", self._window) + if not message: + phoneNumber = "" + action = PhoneTypeSelector.ACTION_CANCEL + + if action == PhoneTypeSelector.ACTION_CANCEL: + return + self.number_selected(action, phoneNumber, message) + def _on_dial_clicked(self, widget): - self.dial(self.get_number()) + action = PhoneTypeSelector.ACTION_DIAL + phoneNumber = self.get_number() + message = "" + self.number_selected(action, phoneNumber, message) def _on_clear_number(self, *args): self.clear() @@ -552,49 +534,107 @@ class Dialpad(object): def _on_digit_clicked(self, widget): self.set_number(self._phonenumber + widget.get_name()[-1]) - def _on_backspace(self, widget): - self.set_number(self._phonenumber[:-1]) + def _on_backspace(self, taps): + self.set_number(self._phonenumber[:-taps]) + self._reset_back_button() - def _on_clearall(self): + def _on_clearall(self, taps): self.clear() + self._reset_back_button() return False - def _on_back_pressed(self, widget): - self._clearall_id = gobject.timeout_add(1000, self._on_clearall) + def _set_clear_button(self): + self._backButton.set_label("gtk-clear") - def _on_back_released(self, widget): - if self._clearall_id is not None: - gobject.source_remove(self._clearall_id) - self._clearall_id = None + def _reset_back_button(self): + self._backButton.set_label(self._originalLabel) class AccountInfo(object): - def __init__(self, widgetTree, backend, errorDisplay): + def __init__(self, widgetTree, backend, alarmHandler, errorDisplay): self._errorDisplay = errorDisplay self._backend = backend self._isPopulated = False + self._alarmHandler = alarmHandler + self._notifyOnMissed = False + self._notifyOnVoicemail = False + self._notifyOnSms = False self._callbackList = gtk.ListStore(gobject.TYPE_STRING) self._accountViewNumberDisplay = widgetTree.get_widget("gcnumber_display") self._callbackCombo = widgetTree.get_widget("callbackcombo") self._onCallbackentryChangedId = 0 + self._notifyCheckbox = widgetTree.get_widget("notifyCheckbox") + self._minutesEntry = widgetTree.get_widget("minutesEntry") + self._missedCheckbox = widgetTree.get_widget("missedCheckbox") + self._voicemailCheckbox = widgetTree.get_widget("voicemailCheckbox") + self._smsCheckbox = widgetTree.get_widget("smsCheckbox") + self._onNotifyToggled = 0 + self._onMinutesChanged = 0 + self._onMissedToggled = 0 + self._onVoicemailToggled = 0 + self._onSmsToggled = 0 + self._defaultCallback = "" def enable(self): - assert self._backend.is_authed() + assert self._backend.is_authed(), "Attempting to enable backend while not logged in" + 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) + + if self._alarmHandler is not None: + self._minutesEntry.set_range(0, 60) + self._minutesEntry.set_increments(1, 5) + + self._notifyCheckbox.set_active(self._alarmHandler.isEnabled) + self._minutesEntry.set_value(self._alarmHandler.recurrence) + self._missedCheckbox.set_active(self._notifyOnMissed) + self._voicemailCheckbox.set_active(self._notifyOnVoicemail) + self._smsCheckbox.set_active(self._notifyOnSms) + + self._onNotifyToggled = self._notifyCheckbox.connect("toggled", self._on_notify_toggled) + self._onMinutesChanged = self._minutesEntry.connect("value-changed", self._on_minutes_changed) + 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) + else: + self._notifyCheckbox.set_sensitive(False) + self._minutesEntry.set_sensitive(False) + self._missedCheckbox.set_sensitive(False) + self._voicemailCheckbox.set_sensitive(False) + self._smsCheckbox.set_sensitive(False) + self.update(force=True) def disable(self): self._callbackCombo.get_child().disconnect(self._onCallbackentryChangedId) + self._onCallbackentryChangedId = 0 - self.clear() + if self._alarmHandler is not None: + self._notifyCheckbox.disconnect(self._onNotifyToggled) + self._minutesEntry.disconnect(self._onMinutesChanged) + self._missedCheckbox.disconnect(self._onNotifyToggled) + self._voicemailCheckbox.disconnect(self._onNotifyToggled) + self._smsCheckbox.disconnect(self._onNotifyToggled) + self._onNotifyToggled = 0 + self._onMinutesChanged = 0 + self._onMissedToggled = 0 + self._onVoicemailToggled = 0 + self._onSmsToggled = 0 + else: + self._notifyCheckbox.set_sensitive(True) + self._minutesEntry.set_sensitive(True) + self._missedCheckbox.set_sensitive(True) + self._voicemailCheckbox.set_sensitive(True) + self._smsCheckbox.set_sensitive(True) + self.clear() self._callbackList.clear() def get_selected_callback_number(self): @@ -608,21 +648,28 @@ class AccountInfo(object): def update(self, force = False): if not force and self._isPopulated: - return + return False self._populate_callback_combo() self.set_account_number(self._backend.get_account_number()) + return True def clear(self): self._callbackCombo.get_child().set_text("") self.set_account_number("") self._isPopulated = False + def save_everything(self): + raise NotImplementedError + @staticmethod def name(): return "Account Info" def load_settings(self, config, section): self._defaultCallback = config.get(section, "callback") + self._notifyOnMissed = config.getboolean(section, "notifyOnMissed") + self._notifyOnVoicemail = config.getboolean(section, "notifyOnVoicemail") + self._notifyOnSms = config.getboolean(section, "notifyOnSms") def save_settings(self, config, section): """ @@ -630,14 +677,17 @@ class AccountInfo(object): """ callback = self.get_selected_callback_number() config.set(section, "callback", callback) + 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() try: callbackNumbers = self._backend.get_callback_numbers() - except RuntimeError, e: - self._errorDisplay.push_exception(e) + except StandardError, e: + self._errorDisplay.push_exception() self._isPopulated = False return @@ -651,54 +701,126 @@ class AccountInfo(object): self._callbackCombo.get_child().set_text(make_pretty(callbackNumber)) def _set_callback_number(self, number): - """ - @todo Potential blocking on web access, maybe we should defer this or put up a dialog? - """ try: if not self._backend.is_valid_syntax(number): self._errorDisplay.push_message("%s is not a valid callback number" % number) elif number == self._backend.get_callback_number(): - warnings.warn("Callback number already is %s" % self._backend.get_callback_number(), UserWarning, 2) + warnings.warn( + "Callback number already is %s" % ( + self._backend.get_callback_number(), + ), + UserWarning, + 2 + ) else: self._backend.set_callback_number(number) - warnings.warn("Callback number set to %s" % self._backend.get_callback_number(), UserWarning, 2) - except RuntimeError, e: - self._errorDisplay.push_exception(e) + 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( + "Callback number set to %s" % ( + self._backend.get_callback_number(), + ), + UserWarning, 2 + ) + except StandardError, e: + self._errorDisplay.push_exception() + + def _update_alarm_settings(self): + try: + isEnabled = self._notifyCheckbox.get_active() + recurrence = self._minutesEntry.get_value_as_int() + if isEnabled != self._alarmHandler.isEnabled or recurrence != self._alarmHandler.recurrence: + self._alarmHandler.apply_settings(isEnabled, recurrence) + finally: + self.save_everything() + self._notifyCheckbox.set_active(self._alarmHandler.isEnabled) + self._minutesEntry.set_value(self._alarmHandler.recurrence) def _on_callbackentry_changed(self, *args): - """ - @todo Potential blocking on web access, maybe we should defer this or put up a dialog? - """ text = self.get_selected_callback_number() - self._set_callback_number(text) + number = make_ugly(text) + self._set_callback_number(number) + + self.save_everything() + + def _on_notify_toggled(self, *args): + self._update_alarm_settings() + + def _on_minutes_changed(self, *args): + self._update_alarm_settings() + + def _on_missed_toggled(self, *args): + self._notifyOnMissed = self._missedCheckbox.get_active() + self.save_everything() + + def _on_voicemail_toggled(self, *args): + self._notifyOnVoicemail = self._voicemailCheckbox.get_active() + self.save_everything() + + def _on_sms_toggled(self, *args): + self._notifyOnSms = self._smsCheckbox.get_active() + self.save_everything() class RecentCallsView(object): + NUMBER_IDX = 0 + DATE_IDX = 1 + ACTION_IDX = 2 + FROM_IDX = 3 + def __init__(self, widgetTree, backend, errorDisplay): self._errorDisplay = errorDisplay self._backend = backend self._isPopulated = False - self._recentmodel = gtk.ListStore(gobject.TYPE_STRING, gobject.TYPE_STRING) + self._recentmodel = gtk.ListStore( + gobject.TYPE_STRING, # number + gobject.TYPE_STRING, # date + gobject.TYPE_STRING, # action + gobject.TYPE_STRING, # from + ) self._recentview = widgetTree.get_widget("recentview") self._recentviewselection = None self._onRecentviewRowActivatedId = 0 - # @todo Make seperate columns for each item in recent item payload textrenderer = gtk.CellRendererText() - 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) + textrenderer.set_property("yalign", 0) + self._dateColumn = gtk.TreeViewColumn("Date") + self._dateColumn.pack_start(textrenderer, expand=True) + self._dateColumn.add_attribute(textrenderer, "text", self.DATE_IDX) + + textrenderer = gtk.CellRendererText() + textrenderer.set_property("yalign", 0) + self._actionColumn = gtk.TreeViewColumn("Action") + self._actionColumn.pack_start(textrenderer, expand=True) + self._actionColumn.add_attribute(textrenderer, "text", self.ACTION_IDX) + textrenderer = gtk.CellRendererText() + textrenderer.set_property("yalign", 0) + self._fromColumn = gtk.TreeViewColumn("From") + self._fromColumn.pack_start(textrenderer, expand=True) + self._fromColumn.add_attribute(textrenderer, "text", self.FROM_IDX) + self._fromColumn.set_sizing(gtk.TREE_VIEW_COLUMN_FIXED) + + self._window = gtk_toolbox.find_parent_window(self._recentview) self._phoneTypeSelector = PhoneTypeSelector(widgetTree, self._backend) + self._updateSink = gtk_toolbox.threaded_stage( + gtk_toolbox.comap( + self._idly_populate_recentview, + gtk_toolbox.null_sink(), + ) + ) + def enable(self): - assert self._backend.is_authed() + assert self._backend.is_authed(), "Attempting to enable backend while not logged in" self._recentview.set_model(self._recentmodel) - self._recentview.append_column(self._recentviewColumn) + self._recentview.append_column(self._dateColumn) + self._recentview.append_column(self._actionColumn) + self._recentview.append_column(self._fromColumn) self._recentviewselection = self._recentview.get_selection() self._recentviewselection.set_mode(gtk.SELECTION_SINGLE) @@ -709,21 +831,22 @@ class RecentCallsView(object): self.clear() - self._recentview.remove_column(self._recentviewColumn) + self._recentview.remove_column(self._dateColumn) + self._recentview.remove_column(self._actionColumn) + self._recentview.remove_column(self._fromColumn) self._recentview.set_model(None) def number_selected(self, action, number, message): """ @note Actual dial function is patched in later """ - raise NotImplementedError + raise NotImplementedError("Horrible unknown error has occurred") def update(self, force = False): if not force and self._isPopulated: - return - backgroundPopulate = threading.Thread(target=self._idly_populate_recentview) - backgroundPopulate.setDaemon(True) - backgroundPopulate.start() + return False + self._updateSink.send(()) + return True def clear(self): self._isPopulated = False @@ -743,19 +866,23 @@ class RecentCallsView(object): pass def _idly_populate_recentview(self): - self._isPopulated = True self._recentmodel.clear() + self._isPopulated = True try: recentItems = self._backend.get_recent() - except RuntimeError, e: - self._errorDisplay.push_exception_with_lock(e) + except StandardError, e: + self._errorDisplay.push_exception_with_lock() self._isPopulated = False recentItems = [] - for personsName, phoneNumber, date, action in recentItems: - description = "%s on %s from/to %s - %s" % (action.capitalize(), date, personsName, phoneNumber) - item = (phoneNumber, description) + for personName, phoneNumber, date, action in recentItems: + if not personName: + personName = "Unknown" + prettyNumber = phoneNumber[2:] if phoneNumber.startswith("+1") else phoneNumber + prettyNumber = make_pretty(prettyNumber) + description = "%s - %s" % (personName, prettyNumber) + item = (phoneNumber, date, action.capitalize(), description) with gtk_toolbox.gtk_lock(): self._recentmodel.append(item) @@ -766,15 +893,19 @@ class RecentCallsView(object): if not itr: return - number = self._recentmodel.get_value(itr, 0) + number = self._recentmodel.get_value(itr, self.NUMBER_IDX) number = make_ugly(number) contactPhoneNumbers = [("Phone", number)] - description = self._recentmodel.get_value(itr, 1) + description = self._recentmodel.get_value(itr, self.FROM_IDX) - action, phoneNumber, message = self._phoneTypeSelector.run(contactPhoneNumbers, message = description) + action, phoneNumber, message = self._phoneTypeSelector.run( + contactPhoneNumbers, + message = description, + parent = self._window, + ) if action == PhoneTypeSelector.ACTION_CANCEL: return - assert phoneNumber + assert phoneNumber, "A lack of phone number exists" self.number_selected(action, phoneNumber, message) self._recentviewselection.unselect_all() @@ -782,30 +913,62 @@ class RecentCallsView(object): class MessagesView(object): + NUMBER_IDX = 0 + DATE_IDX = 1 + HEADER_IDX = 2 + MESSAGE_IDX = 3 + def __init__(self, widgetTree, backend, errorDisplay): self._errorDisplay = errorDisplay self._backend = backend self._isPopulated = False - self._messagemodel = gtk.ListStore(gobject.TYPE_STRING, gobject.TYPE_STRING) + self._messagemodel = gtk.ListStore( + gobject.TYPE_STRING, # number + gobject.TYPE_STRING, # date + gobject.TYPE_STRING, # header + gobject.TYPE_STRING, # message + ) 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) + textrenderer.set_property("yalign", 0) + self._dateColumn = gtk.TreeViewColumn("Date") + self._dateColumn.pack_start(textrenderer, expand=True) + self._dateColumn.add_attribute(textrenderer, "markup", self.DATE_IDX) + + textrenderer = gtk.CellRendererText() + textrenderer.set_property("yalign", 0) + self._headerColumn = gtk.TreeViewColumn("From") + self._headerColumn.pack_start(textrenderer, expand=True) + self._headerColumn.add_attribute(textrenderer, "markup", self.HEADER_IDX) + + textrenderer = gtk.CellRendererText() + textrenderer.set_property("yalign", 0) + self._messageColumn = gtk.TreeViewColumn("Messages") + self._messageColumn.pack_start(textrenderer, expand=True) + self._messageColumn.add_attribute(textrenderer, "markup", self.MESSAGE_IDX) + 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._updateSink = gtk_toolbox.threaded_stage( + gtk_toolbox.comap( + self._idly_populate_messageview, + gtk_toolbox.null_sink(), + ) + ) + def enable(self): - assert self._backend.is_authed() + assert self._backend.is_authed(), "Attempting to enable backend while not logged in" self._messageview.set_model(self._messagemodel) - self._messageview.append_column(self._messageviewColumn) + self._messageview.append_column(self._dateColumn) + self._messageview.append_column(self._headerColumn) + self._messageview.append_column(self._messageColumn) self._messageviewselection = self._messageview.get_selection() self._messageviewselection.set_mode(gtk.SELECTION_SINGLE) @@ -816,21 +979,22 @@ class MessagesView(object): self.clear() - self._messageview.remove_column(self._messageviewColumn) + self._messageview.remove_column(self._dateColumn) + self._messageview.remove_column(self._headerColumn) + self._messageview.remove_column(self._messageColumn) self._messageview.set_model(None) def number_selected(self, action, number, message): """ @note Actual dial function is patched in later """ - raise NotImplementedError + raise NotImplementedError("Horrible unknown error has occurred") def update(self, force = False): if not force and self._isPopulated: - return - backgroundPopulate = threading.Thread(target=self._idly_populate_messageview) - backgroundPopulate.setDaemon(True) - backgroundPopulate.start() + return False + self._updateSink.send(()) + return True def clear(self): self._isPopulated = False @@ -850,19 +1014,19 @@ class MessagesView(object): pass def _idly_populate_messageview(self): - self._isPopulated = True self._messagemodel.clear() + self._isPopulated = True try: messageItems = self._backend.get_messages() - except RuntimeError, e: - self._errorDisplay.push_exception_with_lock(e) + except StandardError, e: + self._errorDisplay.push_exception_with_lock() self._isPopulated = False messageItems = [] for header, number, relativeDate, message in messageItems: number = make_ugly(number) - row = (number, message) + row = (number, relativeDate, header, message) with gtk_toolbox.gtk_lock(): self._messagemodel.append(row) @@ -873,13 +1037,17 @@ class MessagesView(object): if not itr: return - contactPhoneNumbers = [("Phone", self._messagemodel.get_value(itr, 0))] - description = self._messagemodel.get_value(itr, 1) + contactPhoneNumbers = [("Phone", self._messagemodel.get_value(itr, self.NUMBER_IDX))] + description = self._messagemodel.get_value(itr, self.MESSAGE_IDX) - action, phoneNumber, message = self._phoneTypeSelector.run(contactPhoneNumbers, message = description) + action, phoneNumber, message = self._phoneTypeSelector.run( + contactPhoneNumbers, + message = description, + parent = self._window, + ) if action == PhoneTypeSelector.ACTION_CANCEL: return - assert phoneNumber + assert phoneNumber, "A lock of phone number exists" self.number_selected(action, phoneNumber, message) self._messageviewselection.unselect_all() @@ -892,7 +1060,7 @@ class ContactsView(object): self._backend = backend self._addressBook = None - self._addressBookFactories = [DummyAddressBook()] + 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") @@ -920,10 +1088,18 @@ class ContactsView(object): self._onContactsviewRowActivatedId = 0 self._onAddressbookComboChangedId = 0 + self._window = gtk_toolbox.find_parent_window(self._contactsview) self._phoneTypeSelector = PhoneTypeSelector(widgetTree, self._backend) + self._updateSink = gtk_toolbox.threaded_stage( + gtk_toolbox.comap( + self._idly_populate_contactsview, + gtk_toolbox.null_sink(), + ) + ) + def enable(self): - assert self._backend.is_authed() + assert self._backend.is_authed(), "Attempting to enable backend while not logged in" self._contactsview.set_model(self._contactsmodel) self._contactsview.append_column(self._contactColumn) @@ -967,7 +1143,7 @@ class ContactsView(object): """ @note Actual dial function is patched in later """ - raise NotImplementedError + raise NotImplementedError("Horrible unknown error has occurred") def get_addressbooks(self): """ @@ -983,16 +1159,13 @@ class ContactsView(object): def update(self, force = False): if not force and self._isPopulated: - return - backgroundPopulate = threading.Thread(target=self._idly_populate_contactsview) - backgroundPopulate.setDaemon(True) - backgroundPopulate.start() + return False + self._updateSink.send(()) + return True def clear(self): self._isPopulated = False self._contactsmodel.clear() - - def clear_caches(self): for factory in self._addressBookFactories: factory.clear_caches() self._addressBook.clear_caches() @@ -1017,27 +1190,29 @@ class ContactsView(object): pass def _idly_populate_contactsview(self): - self._isPopulated = True self.clear() + self._isPopulated = True # completely disable updating the treeview while we populate the data self._contactsview.freeze_child_notify() - self._contactsview.set_model(None) - - addressBook = self._addressBook try: - contacts = addressBook.get_contacts() - except RuntimeError, e: - contacts = [] - self._isPopulated = False - self._errorDisplay.push_exception_with_lock(e) - for contactId, contactName in contacts: - contactType = (addressBook.contact_source_short_name(contactId), ) - self._contactsmodel.append(contactType + (contactName, "", contactId) + ("", )) - - # restart the treeview data rendering - self._contactsview.set_model(self._contactsmodel) - self._contactsview.thaw_child_notify() + self._contactsview.set_model(None) + + addressBook = self._addressBook + 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) + ("", )) + + # restart the treeview data rendering + self._contactsview.set_model(self._contactsmodel) + finally: + self._contactsview.thaw_child_notify() return False def _on_addressbook_combo_changed(self, *args, **kwds): @@ -1057,18 +1232,22 @@ class ContactsView(object): contactName = self._contactsmodel.get_value(itr, 1) try: contactDetails = self._addressBook.get_contact_details(contactId) - except RuntimeError, e: + except StandardError, e: contactDetails = [] - self._errorDisplay.push_exception(e) + self._errorDisplay.push_exception() contactPhoneNumbers = [phoneNumber for phoneNumber in contactDetails] if len(contactPhoneNumbers) == 0: return - action, phoneNumber, message = self._phoneTypeSelector.run(contactPhoneNumbers, message = contactName) + action, phoneNumber, message = self._phoneTypeSelector.run( + contactPhoneNumbers, + message = contactName, + parent = self._window, + ) if action == PhoneTypeSelector.ACTION_CANCEL: return - assert phoneNumber + assert phoneNumber, "A lack of phone number exists" self.number_selected(action, phoneNumber, message) self._contactsviewselection.unselect_all()