4 DialCentral - Front end for Google's GoogleVoice service.
5 Copyright (C) 2008 Mark Bergman bergman AT merctech DOT com
7 This library is free software; you can redistribute it and/or
8 modify it under the terms of the GNU Lesser General Public
9 License as published by the Free Software Foundation; either
10 version 2.1 of the License, or (at your option) any later version.
12 This library is distributed in the hope that it will be useful,
13 but WITHOUT ANY WARRANTY; without even the implied warranty of
14 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
15 Lesser General Public License for more details.
17 You should have received a copy of the GNU Lesser General Public
18 License along with this library; if not, write to the Free Software
19 Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
21 @todo Collapse voicemails
22 @todo Alternate UI for dialogs (stackables)
25 from __future__ import with_statement
37 from backends import gv_backend
38 from backends import null_backend
41 _moduleLogger = logging.getLogger("gv_views")
44 def make_ugly(prettynumber):
46 function to take a phone number and strip out all non-numeric
49 >>> make_ugly("+012-(345)-678-90")
53 uglynumber = re.sub('\D', '', prettynumber)
57 def make_pretty(phonenumber):
59 Function to take a phone number and return the pretty version
61 if phonenumber begins with 0:
63 if phonenumber begins with 1: ( for gizmo callback numbers )
65 if phonenumber is 13 digits:
67 if phonenumber is 10 digits:
71 >>> make_pretty("1234567")
73 >>> make_pretty("2345678901")
75 >>> make_pretty("12345678901")
77 >>> make_pretty("01234567890")
80 if phonenumber is None or phonenumber is "":
83 phonenumber = make_ugly(phonenumber)
85 if len(phonenumber) < 3:
88 if phonenumber[0] == "0":
90 prettynumber += "+%s" % phonenumber[0:3]
91 if 3 < len(phonenumber):
92 prettynumber += "-(%s)" % phonenumber[3:6]
93 if 6 < len(phonenumber):
94 prettynumber += "-%s" % phonenumber[6:9]
95 if 9 < len(phonenumber):
96 prettynumber += "-%s" % phonenumber[9:]
98 elif len(phonenumber) <= 7:
99 prettynumber = "%s-%s" % (phonenumber[0:3], phonenumber[3:])
100 elif len(phonenumber) > 8 and phonenumber[0] == "1":
101 prettynumber = "1 (%s)-%s-%s" % (phonenumber[1:4], phonenumber[4:7], phonenumber[7:])
102 elif len(phonenumber) > 7:
103 prettynumber = "(%s)-%s-%s" % (phonenumber[0:3], phonenumber[3:6], phonenumber[6:])
107 def abbrev_relative_date(date):
109 >>> abbrev_relative_date("42 hours ago")
111 >>> abbrev_relative_date("2 days ago")
113 >>> abbrev_relative_date("4 weeks ago")
116 parts = date.split(" ")
117 return "%s %s" % (parts[0], parts[1][0])
120 def _collapse_message(messageLines, maxCharsPerLine, maxLines):
123 numLines = len(messageLines)
124 for line in messageLines[0:min(maxLines, numLines)]:
125 linesPerLine = max(1, int(len(line) / maxCharsPerLine))
126 allowedLines = maxLines - lines
127 acceptedLines = min(allowedLines, linesPerLine)
128 acceptedChars = acceptedLines * maxCharsPerLine
130 if acceptedChars < (len(line) + 3):
133 acceptedChars = len(line) # eh, might as well complete the line
135 abbrevMessage = "%s%s" % (line[0:acceptedChars], suffix)
138 lines += acceptedLines
139 if maxLines <= lines:
143 def collapse_message(message, maxCharsPerLine, maxLines):
145 >>> collapse_message("Hello", 60, 2)
147 >>> collapse_message("Hello world how are you doing today? 01234567890123456789012345678901234567890123456789012345678901234567890123456789", 60, 2)
148 'Hello world how are you doing today? 01234567890123456789012...'
149 >>> collapse_message('''Hello world how are you doing today?
150 ... 01234567890123456789
151 ... 01234567890123456789
152 ... 01234567890123456789
153 ... 01234567890123456789''', 60, 2)
154 'Hello world how are you doing today?\n01234567890123456789'
155 >>> collapse_message('''
156 ... Hello world how are you doing today? 01234567890123456789012345678901234567890123456789012345678901234567890123456789
157 ... Hello world how are you doing today? 01234567890123456789012345678901234567890123456789012345678901234567890123456789
158 ... Hello world how are you doing today? 01234567890123456789012345678901234567890123456789012345678901234567890123456789
159 ... Hello world how are you doing today? 01234567890123456789012345678901234567890123456789012345678901234567890123456789
160 ... Hello world how are you doing today? 01234567890123456789012345678901234567890123456789012345678901234567890123456789
161 ... Hello world how are you doing today? 01234567890123456789012345678901234567890123456789012345678901234567890123456789''', 60, 2)
162 '\nHello world how are you doing today? 01234567890123456789012...'
164 messageLines = message.split("\n")
165 return "\n".join(_collapse_message(messageLines, maxCharsPerLine, maxLines))
168 class SmsEntryDialog(object):
170 @todo Add multi-SMS messages like GoogleVoice
173 ACTION_CANCEL = "cancel"
175 ACTION_SEND_SMS = "sms"
179 def __init__(self, widgetTree):
180 self._clipboard = gtk.clipboard_get()
181 self._widgetTree = widgetTree
182 self._dialog = self._widgetTree.get_widget("smsDialog")
184 self._smsButton = self._widgetTree.get_widget("sendSmsButton")
185 self._smsButton.connect("clicked", self._on_send)
186 self._dialButton = self._widgetTree.get_widget("dialButton")
187 self._dialButton.connect("clicked", self._on_dial)
188 self._cancelButton = self._widgetTree.get_widget("cancelSmsButton")
189 self._cancelButton.connect("clicked", self._on_cancel)
191 self._letterCountLabel = self._widgetTree.get_widget("smsLetterCount")
193 self._messagemodel = gtk.ListStore(gobject.TYPE_STRING)
194 self._messagesView = self._widgetTree.get_widget("smsMessages")
196 self._conversationView = self._messagesView.get_parent()
197 self._conversationViewPort = self._conversationView.get_parent()
198 self._scrollWindow = self._conversationViewPort.get_parent()
200 self._phoneButton = self._widgetTree.get_widget("phoneTypeSelection")
201 self._smsEntry = self._widgetTree.get_widget("smsEntry")
203 self._action = self.ACTION_CANCEL
205 self._numberIndex = -1
206 self._contactDetails = []
208 def run(self, contactDetails, messages = (), parent = None, defaultIndex = -1):
209 entryConnectId = self._smsEntry.get_buffer().connect("changed", self._on_entry_changed)
210 phoneConnectId = self._phoneButton.connect("clicked", self._on_phone)
211 keyConnectId = self._keyPressEventId = self._dialog.connect("key-press-event", self._on_key_press)
213 # Setup the phone selection button
214 del self._contactDetails[:]
215 for phoneType, phoneNumber in contactDetails:
216 display = " - ".join((make_pretty(phoneNumber), phoneType))
217 row = (phoneNumber, display)
218 self._contactDetails.append(row)
219 if 0 < len(self._contactDetails):
220 self._numberIndex = defaultIndex if defaultIndex != -1 else 0
221 self._phoneButton.set_label(self._contactDetails[self._numberIndex][1])
223 self._numberIndex = -1
224 self._phoneButton.set_label("Error: No Number Available")
226 # Add the column to the messages tree view
227 self._messagemodel.clear()
228 self._messagesView.set_model(self._messagemodel)
229 self._messagesView.set_fixed_height_mode(False)
231 textrenderer = gtk.CellRendererText()
232 textrenderer.set_property("wrap-mode", pango.WRAP_WORD)
233 textrenderer.set_property("wrap-width", 450)
234 messageColumn = gtk.TreeViewColumn("")
235 messageColumn.pack_start(textrenderer, expand=True)
236 messageColumn.add_attribute(textrenderer, "markup", 0)
237 messageColumn.set_sizing(gtk.TREE_VIEW_COLUMN_FIXED)
238 self._messagesView.append_column(messageColumn)
239 self._messagesView.set_headers_visible(False)
242 for message in messages:
244 self._messagemodel.append(row)
245 self._messagesView.show()
246 self._scrollWindow.show()
247 messagesSelection = self._messagesView.get_selection()
248 messagesSelection.select_path((len(messages)-1, ))
250 self._messagesView.hide()
251 self._scrollWindow.hide()
253 self._smsEntry.get_buffer().set_text("")
254 self._update_letter_count()
256 if parent is not None:
257 self._dialog.set_transient_for(parent)
258 parentSize = parent.get_size()
259 self._dialog.resize(parentSize[0], max(parentSize[1]-10, 100))
263 self._dialog.show_all()
264 self._smsEntry.grab_focus()
265 adjustment = self._scrollWindow.get_vadjustment()
266 dx = self._conversationView.get_allocation().height - self._conversationViewPort.get_allocation().height
268 adjustment.value = dx
270 if 1 < len(self._contactDetails):
271 if defaultIndex == -1:
272 self._request_number()
273 self._phoneButton.set_sensitive(True)
275 self._phoneButton.set_sensitive(False)
277 userResponse = self._dialog.run()
279 self._dialog.hide_all()
281 # Process the users response
282 if userResponse == gtk.RESPONSE_OK and 0 <= self._numberIndex:
283 phoneNumber = self._contactDetails[self._numberIndex][0]
284 phoneNumber = make_ugly(phoneNumber)
288 self._action = self.ACTION_CANCEL
289 if self._action == self.ACTION_SEND_SMS:
290 entryBuffer = self._smsEntry.get_buffer()
291 enteredMessage = entryBuffer.get_text(entryBuffer.get_start_iter(), entryBuffer.get_end_iter())
292 enteredMessage = enteredMessage[0:self.MAX_CHAR].strip()
293 if not enteredMessage:
295 self._action = self.ACTION_CANCEL
299 self._messagesView.remove_column(messageColumn)
300 self._messagesView.set_model(None)
302 return self._action, phoneNumber, enteredMessage
304 self._smsEntry.get_buffer().disconnect(entryConnectId)
305 self._phoneButton.disconnect(phoneConnectId)
306 self._keyPressEventId = self._dialog.disconnect(keyConnectId)
308 def _update_letter_count(self, *args):
309 entryLength = self._smsEntry.get_buffer().get_char_count()
311 charsLeft = self.MAX_CHAR - entryLength
312 self._letterCountLabel.set_text(str(charsLeft))
313 if charsLeft < 0 or charsLeft == self.MAX_CHAR:
314 self._smsButton.set_sensitive(False)
316 self._smsButton.set_sensitive(True)
319 self._dialButton.set_sensitive(True)
321 self._dialButton.set_sensitive(False)
323 def _request_number(self):
325 assert 0 <= self._numberIndex, "%r" % self._numberIndex
327 self._numberIndex = hildonize.touch_selector(
330 (description for (number, description) in self._contactDetails),
333 self._phoneButton.set_label(self._contactDetails[self._numberIndex][1])
335 _moduleLogger.exception("%s" % str(e))
337 def _on_phone(self, *args):
338 self._request_number()
340 def _on_entry_changed(self, *args):
341 self._update_letter_count()
343 def _on_send(self, *args):
344 self._dialog.response(gtk.RESPONSE_OK)
345 self._action = self.ACTION_SEND_SMS
347 def _on_dial(self, *args):
348 self._dialog.response(gtk.RESPONSE_OK)
349 self._action = self.ACTION_DIAL
351 def _on_cancel(self, *args):
352 self._dialog.response(gtk.RESPONSE_CANCEL)
353 self._action = self.ACTION_CANCEL
355 def _on_key_press(self, widget, event):
357 if event.keyval == ord("c") and event.get_state() & gtk.gdk.CONTROL_MASK:
360 for messagePart in self._messagemodel
362 self._clipboard.set_text(str(message))
364 _moduleLogger.exception(str(e))
367 class Dialpad(object):
369 def __init__(self, widgetTree, errorDisplay):
370 self._clipboard = gtk.clipboard_get()
371 self._errorDisplay = errorDisplay
372 self._smsDialog = SmsEntryDialog(widgetTree)
374 self._numberdisplay = widgetTree.get_widget("numberdisplay")
375 self._smsButton = widgetTree.get_widget("sms")
376 self._dialButton = widgetTree.get_widget("dial")
377 self._backButton = widgetTree.get_widget("back")
378 self._phonenumber = ""
379 self._prettynumber = ""
382 "on_digit_clicked": self._on_digit_clicked,
384 widgetTree.signal_autoconnect(callbackMapping)
385 self._dialButton.connect("clicked", self._on_dial_clicked)
386 self._smsButton.connect("clicked", self._on_sms_clicked)
388 self._originalLabel = self._backButton.get_label()
389 self._backTapHandler = gtk_toolbox.TapOrHold(self._backButton)
390 self._backTapHandler.on_tap = self._on_backspace
391 self._backTapHandler.on_hold = self._on_clearall
392 self._backTapHandler.on_holding = self._set_clear_button
393 self._backTapHandler.on_cancel = self._reset_back_button
395 self._window = gtk_toolbox.find_parent_window(self._numberdisplay)
396 self._keyPressEventId = 0
399 self._dialButton.grab_focus()
400 self._backTapHandler.enable()
401 self._keyPressEventId = self._window.connect("key-press-event", self._on_key_press)
404 self._window.disconnect(self._keyPressEventId)
405 self._keyPressEventId = 0
406 self._reset_back_button()
407 self._backTapHandler.disable()
409 def number_selected(self, action, number, message):
411 @note Actual dial function is patched in later
413 raise NotImplementedError("Horrible unknown error has occurred")
415 def get_number(self):
416 return self._phonenumber
418 def set_number(self, number):
420 Set the number to dial
423 self._phonenumber = make_ugly(number)
424 self._prettynumber = make_pretty(self._phonenumber)
425 self._numberdisplay.set_label("<span size='30000' weight='bold'>%s</span>" % (self._prettynumber))
427 self._errorDisplay.push_exception()
436 def load_settings(self, config, section):
439 def save_settings(self, config, section):
441 @note Thread Agnostic
445 def _on_key_press(self, widget, event):
447 if event.keyval == ord("v") and event.get_state() & gtk.gdk.CONTROL_MASK:
448 contents = self._clipboard.wait_for_text()
449 if contents is not None:
450 self.set_number(contents)
452 self._errorDisplay.push_exception()
454 def _on_sms_clicked(self, widget):
456 phoneNumber = self.get_number()
457 action, phoneNumber, message = self._smsDialog.run([("Dialer", phoneNumber)], (), self._window)
459 if action == SmsEntryDialog.ACTION_CANCEL:
461 self.number_selected(action, phoneNumber, message)
463 self._errorDisplay.push_exception()
465 def _on_dial_clicked(self, widget):
467 action = SmsEntryDialog.ACTION_DIAL
468 phoneNumber = self.get_number()
470 self.number_selected(action, phoneNumber, message)
472 self._errorDisplay.push_exception()
474 def _on_digit_clicked(self, widget):
476 self.set_number(self._phonenumber + widget.get_name()[-1])
478 self._errorDisplay.push_exception()
480 def _on_backspace(self, taps):
482 self.set_number(self._phonenumber[:-taps])
483 self._reset_back_button()
485 self._errorDisplay.push_exception()
487 def _on_clearall(self, taps):
490 self._reset_back_button()
492 self._errorDisplay.push_exception()
495 def _set_clear_button(self):
497 self._backButton.set_label("gtk-clear")
499 self._errorDisplay.push_exception()
501 def _reset_back_button(self):
503 self._backButton.set_label(self._originalLabel)
505 self._errorDisplay.push_exception()
508 class AccountInfo(object):
510 def __init__(self, widgetTree, backend, alarmHandler, errorDisplay):
511 self._errorDisplay = errorDisplay
512 self._backend = backend
513 self._isPopulated = False
514 self._alarmHandler = alarmHandler
515 self._notifyOnMissed = False
516 self._notifyOnVoicemail = False
517 self._notifyOnSms = False
519 self._callbackList = []
520 self._accountViewNumberDisplay = widgetTree.get_widget("gcnumber_display")
521 self._callbackSelectButton = widgetTree.get_widget("callbackSelectButton")
522 self._onCallbackSelectChangedId = 0
524 self._notifyCheckbox = widgetTree.get_widget("notifyCheckbox")
525 self._minutesEntryButton = widgetTree.get_widget("minutesEntryButton")
526 self._missedCheckbox = widgetTree.get_widget("missedCheckbox")
527 self._voicemailCheckbox = widgetTree.get_widget("voicemailCheckbox")
528 self._smsCheckbox = widgetTree.get_widget("smsCheckbox")
529 self._onNotifyToggled = 0
530 self._onMinutesChanged = 0
531 self._onMissedToggled = 0
532 self._onVoicemailToggled = 0
533 self._onSmsToggled = 0
534 self._applyAlarmTimeoutId = None
536 self._window = gtk_toolbox.find_parent_window(self._minutesEntryButton)
537 self._callbackNumber = ""
540 assert self._backend.is_authed(), "Attempting to enable backend while not logged in"
542 self._accountViewNumberDisplay.set_use_markup(True)
543 self.set_account_number("")
545 del self._callbackList[:]
546 self._onCallbackSelectChangedId = self._callbackSelectButton.connect("clicked", self._on_callbackentry_clicked)
547 self._set_callback_label("")
549 if self._alarmHandler is not None:
550 self._notifyCheckbox.set_active(self._alarmHandler.isEnabled)
551 self._minutesEntryButton.set_label("%d minutes" % self._alarmHandler.recurrence)
552 self._missedCheckbox.set_active(self._notifyOnMissed)
553 self._voicemailCheckbox.set_active(self._notifyOnVoicemail)
554 self._smsCheckbox.set_active(self._notifyOnSms)
556 self._onNotifyToggled = self._notifyCheckbox.connect("toggled", self._on_notify_toggled)
557 self._onMinutesChanged = self._minutesEntryButton.connect("clicked", self._on_minutes_clicked)
558 self._onMissedToggled = self._missedCheckbox.connect("toggled", self._on_missed_toggled)
559 self._onVoicemailToggled = self._voicemailCheckbox.connect("toggled", self._on_voicemail_toggled)
560 self._onSmsToggled = self._smsCheckbox.connect("toggled", self._on_sms_toggled)
562 self._notifyCheckbox.set_sensitive(False)
563 self._minutesEntryButton.set_sensitive(False)
564 self._missedCheckbox.set_sensitive(False)
565 self._voicemailCheckbox.set_sensitive(False)
566 self._smsCheckbox.set_sensitive(False)
568 self.update(force=True)
571 self._callbackSelectButton.disconnect(self._onCallbackSelectChangedId)
572 self._onCallbackSelectChangedId = 0
573 self._set_callback_label("")
575 if self._alarmHandler is not None:
576 self._notifyCheckbox.disconnect(self._onNotifyToggled)
577 self._minutesEntryButton.disconnect(self._onMinutesChanged)
578 self._missedCheckbox.disconnect(self._onNotifyToggled)
579 self._voicemailCheckbox.disconnect(self._onNotifyToggled)
580 self._smsCheckbox.disconnect(self._onNotifyToggled)
581 self._onNotifyToggled = 0
582 self._onMinutesChanged = 0
583 self._onMissedToggled = 0
584 self._onVoicemailToggled = 0
585 self._onSmsToggled = 0
587 self._notifyCheckbox.set_sensitive(True)
588 self._minutesEntryButton.set_sensitive(True)
589 self._missedCheckbox.set_sensitive(True)
590 self._voicemailCheckbox.set_sensitive(True)
591 self._smsCheckbox.set_sensitive(True)
594 del self._callbackList[:]
596 def set_account_number(self, number):
598 Displays current account number
600 self._accountViewNumberDisplay.set_label("<span size='23000' weight='bold'>%s</span>" % (number))
602 def update(self, force = False):
603 if not force and self._isPopulated:
605 self._populate_callback_combo()
606 self.set_account_number(self._backend.get_account_number())
610 self._set_callback_label("")
611 self.set_account_number("")
612 self._isPopulated = False
614 def save_everything(self):
615 raise NotImplementedError
619 return "Account Info"
621 def load_settings(self, config, section):
622 self._callbackNumber = make_ugly(config.get(section, "callback"))
623 self._notifyOnMissed = config.getboolean(section, "notifyOnMissed")
624 self._notifyOnVoicemail = config.getboolean(section, "notifyOnVoicemail")
625 self._notifyOnSms = config.getboolean(section, "notifyOnSms")
627 def save_settings(self, config, section):
629 @note Thread Agnostic
631 config.set(section, "callback", self._callbackNumber)
632 config.set(section, "notifyOnMissed", repr(self._notifyOnMissed))
633 config.set(section, "notifyOnVoicemail", repr(self._notifyOnVoicemail))
634 config.set(section, "notifyOnSms", repr(self._notifyOnSms))
636 def _populate_callback_combo(self):
637 self._isPopulated = True
638 del self._callbackList[:]
640 callbackNumbers = self._backend.get_callback_numbers()
642 self._errorDisplay.push_exception()
643 self._isPopulated = False
646 if len(callbackNumbers) == 0:
647 callbackNumbers = {"": "No callback numbers available"}
649 for number, description in callbackNumbers.iteritems():
650 self._callbackList.append((make_pretty(number), description))
652 self._set_callback_number(self._callbackNumber)
654 def _set_callback_number(self, number):
656 if not self._backend.is_valid_syntax(number) and 0 < len(number):
657 self._errorDisplay.push_message("%s is not a valid callback number" % number)
658 elif number == self._backend.get_callback_number() and 0 < len(number):
659 _moduleLogger.warning(
660 "Callback number already is %s" % (
661 self._backend.get_callback_number(),
664 self._set_callback_label(number)
666 self._backend.set_callback_number(number)
667 assert make_ugly(number) == make_ugly(self._backend.get_callback_number()), "Callback number should be %s but instead is %s" % (
668 make_pretty(number), make_pretty(self._backend.get_callback_number())
670 self._callbackNumber = make_ugly(number)
671 self._set_callback_label(number)
673 "Callback number set to %s" % (
674 self._backend.get_callback_number(),
678 self._errorDisplay.push_exception()
680 def _set_callback_label(self, uglyNumber):
681 prettyNumber = make_pretty(uglyNumber)
682 if len(prettyNumber) == 0:
683 prettyNumber = "No Callback Number"
684 self._callbackSelectButton.set_label(prettyNumber)
686 def _update_alarm_settings(self, recurrence):
688 isEnabled = self._notifyCheckbox.get_active()
689 if isEnabled != self._alarmHandler.isEnabled or recurrence != self._alarmHandler.recurrence:
690 self._alarmHandler.apply_settings(isEnabled, recurrence)
692 self.save_everything()
693 self._notifyCheckbox.set_active(self._alarmHandler.isEnabled)
694 self._minutesEntryButton.set_label("%d Minutes" % self._alarmHandler.recurrence)
696 def _on_callbackentry_clicked(self, *args):
698 actualSelection = make_pretty(self._callbackNumber)
701 (number, "%s (%s)" % (number, description))
702 for (number, description) in self._callbackList
704 defaultSelection = userOptions.get(actualSelection, actualSelection)
706 userSelection = hildonize.touch_selector_entry(
709 list(userOptions.itervalues()),
712 reversedUserOptions = dict(
713 itertools.izip(userOptions.itervalues(), userOptions.iterkeys())
715 selectedNumber = reversedUserOptions.get(userSelection, userSelection)
717 number = make_ugly(selectedNumber)
718 self._set_callback_number(number)
719 except RuntimeError, e:
720 _moduleLogger.exception("%s" % str(e))
722 self._errorDisplay.push_exception()
724 def _on_notify_toggled(self, *args):
726 if self._applyAlarmTimeoutId is not None:
727 gobject.source_remove(self._applyAlarmTimeoutId)
728 self._applyAlarmTimeoutId = None
729 self._applyAlarmTimeoutId = gobject.timeout_add(500, self._on_apply_timeout)
731 self._errorDisplay.push_exception()
733 def _on_minutes_clicked(self, *args):
734 recurrenceChoices = [
750 actualSelection = self._alarmHandler.recurrence
752 closestSelectionIndex = 0
753 for i, possible in enumerate(recurrenceChoices):
754 if possible[0] <= actualSelection:
755 closestSelectionIndex = i
756 recurrenceIndex = hildonize.touch_selector(
759 (("%s" % m[1]) for m in recurrenceChoices),
760 closestSelectionIndex,
762 recurrence = recurrenceChoices[recurrenceIndex][0]
764 self._update_alarm_settings(recurrence)
765 except RuntimeError, e:
766 _moduleLogger.exception("%s" % str(e))
768 self._errorDisplay.push_exception()
770 def _on_apply_timeout(self, *args):
772 self._applyAlarmTimeoutId = None
774 self._update_alarm_settings(self._alarmHandler.recurrence)
776 self._errorDisplay.push_exception()
779 def _on_missed_toggled(self, *args):
781 self._notifyOnMissed = self._missedCheckbox.get_active()
782 self.save_everything()
784 self._errorDisplay.push_exception()
786 def _on_voicemail_toggled(self, *args):
788 self._notifyOnVoicemail = self._voicemailCheckbox.get_active()
789 self.save_everything()
791 self._errorDisplay.push_exception()
793 def _on_sms_toggled(self, *args):
795 self._notifyOnSms = self._smsCheckbox.get_active()
796 self.save_everything()
798 self._errorDisplay.push_exception()
801 class RecentCallsView(object):
809 def __init__(self, widgetTree, backend, errorDisplay):
810 self._errorDisplay = errorDisplay
811 self._backend = backend
813 self._isPopulated = False
814 self._recentmodel = gtk.ListStore(
815 gobject.TYPE_STRING, # number
816 gobject.TYPE_STRING, # date
817 gobject.TYPE_STRING, # action
818 gobject.TYPE_STRING, # from
819 gobject.TYPE_STRING, # from id
821 self._recentview = widgetTree.get_widget("recentview")
822 self._recentviewselection = None
823 self._onRecentviewRowActivatedId = 0
825 textrenderer = gtk.CellRendererText()
826 textrenderer.set_property("yalign", 0)
827 self._dateColumn = gtk.TreeViewColumn("Date")
828 self._dateColumn.pack_start(textrenderer, expand=True)
829 self._dateColumn.add_attribute(textrenderer, "text", self.DATE_IDX)
831 textrenderer = gtk.CellRendererText()
832 textrenderer.set_property("yalign", 0)
833 self._actionColumn = gtk.TreeViewColumn("Action")
834 self._actionColumn.pack_start(textrenderer, expand=True)
835 self._actionColumn.add_attribute(textrenderer, "text", self.ACTION_IDX)
837 textrenderer = gtk.CellRendererText()
838 textrenderer.set_property("yalign", 0)
839 textrenderer.set_property("ellipsize", pango.ELLIPSIZE_END)
840 textrenderer.set_property("width-chars", len("1 (555) 555-1234"))
841 self._numberColumn = gtk.TreeViewColumn("Number")
842 self._numberColumn.pack_start(textrenderer, expand=True)
843 self._numberColumn.add_attribute(textrenderer, "text", self.NUMBER_IDX)
845 textrenderer = gtk.CellRendererText()
846 textrenderer.set_property("yalign", 0)
847 hildonize.set_cell_thumb_selectable(textrenderer)
848 self._nameColumn = gtk.TreeViewColumn("From")
849 self._nameColumn.pack_start(textrenderer, expand=True)
850 self._nameColumn.add_attribute(textrenderer, "text", self.FROM_IDX)
851 self._nameColumn.set_sizing(gtk.TREE_VIEW_COLUMN_FIXED)
853 self._window = gtk_toolbox.find_parent_window(self._recentview)
854 self._phoneTypeSelector = SmsEntryDialog(widgetTree)
856 self._updateSink = gtk_toolbox.threaded_stage(
858 self._idly_populate_recentview,
859 gtk_toolbox.null_sink(),
864 assert self._backend.is_authed(), "Attempting to enable backend while not logged in"
865 self._recentview.set_model(self._recentmodel)
866 self._recentview.set_fixed_height_mode(False)
868 self._recentview.append_column(self._dateColumn)
869 self._recentview.append_column(self._actionColumn)
870 self._recentview.append_column(self._numberColumn)
871 self._recentview.append_column(self._nameColumn)
872 self._recentviewselection = self._recentview.get_selection()
873 self._recentviewselection.set_mode(gtk.SELECTION_SINGLE)
875 self._onRecentviewRowActivatedId = self._recentview.connect("row-activated", self._on_recentview_row_activated)
878 self._recentview.disconnect(self._onRecentviewRowActivatedId)
882 self._recentview.remove_column(self._dateColumn)
883 self._recentview.remove_column(self._actionColumn)
884 self._recentview.remove_column(self._nameColumn)
885 self._recentview.remove_column(self._numberColumn)
886 self._recentview.set_model(None)
888 def number_selected(self, action, number, message):
890 @note Actual dial function is patched in later
892 raise NotImplementedError("Horrible unknown error has occurred")
894 def update(self, force = False):
895 if not force and self._isPopulated:
897 self._updateSink.send(())
901 self._isPopulated = False
902 self._recentmodel.clear()
906 return "Recent Calls"
908 def load_settings(self, config, section):
911 def save_settings(self, config, section):
913 @note Thread Agnostic
917 def _idly_populate_recentview(self):
918 with gtk_toolbox.gtk_lock():
919 banner = hildonize.show_busy_banner_start(self._window, "Loading Recent History")
921 self._recentmodel.clear()
922 self._isPopulated = True
925 recentItems = self._backend.get_recent()
927 self._errorDisplay.push_exception_with_lock()
928 self._isPopulated = False
932 gv_backend.decorate_recent(data)
933 for data in gv_backend.sort_messages(recentItems)
936 for contactId, personName, phoneNumber, date, action in recentItems:
938 personName = "Unknown"
939 date = abbrev_relative_date(date)
940 prettyNumber = phoneNumber[2:] if phoneNumber.startswith("+1") else phoneNumber
941 prettyNumber = make_pretty(prettyNumber)
942 item = (prettyNumber, date, action.capitalize(), personName, contactId)
943 with gtk_toolbox.gtk_lock():
944 self._recentmodel.append(item)
946 self._errorDisplay.push_exception_with_lock()
948 with gtk_toolbox.gtk_lock():
949 hildonize.show_busy_banner_end(banner)
953 def _on_recentview_row_activated(self, treeview, path, view_column):
955 itr = self._recentmodel.get_iter(path)
959 number = self._recentmodel.get_value(itr, self.NUMBER_IDX)
960 number = make_ugly(number)
961 description = self._recentmodel.get_value(itr, self.FROM_IDX)
962 contactId = self._recentmodel.get_value(itr, self.FROM_ID_IDX)
964 contactPhoneNumbers = list(self._backend.get_contact_details(contactId))
966 (number == make_ugly(contactNumber) or number[1:] == make_ugly(contactNumber))
967 for (numberDescription, contactNumber) in contactPhoneNumbers
970 defaultIndex = defaultMatches.index(True)
972 contactPhoneNumbers.append(("Other", number))
973 defaultIndex = len(contactPhoneNumbers)-1
975 "Could not find contact %r's number %s among %r" % (
976 contactId, number, contactPhoneNumbers
980 contactPhoneNumbers = [("Phone", number)]
983 action, phoneNumber, message = self._phoneTypeSelector.run(
985 messages = (description, ),
986 parent = self._window,
987 defaultIndex = defaultIndex,
989 if action == SmsEntryDialog.ACTION_CANCEL:
991 assert phoneNumber, "A lack of phone number exists"
993 self.number_selected(action, phoneNumber, message)
994 self._recentviewselection.unselect_all()
996 self._errorDisplay.push_exception()
999 class MessagesView(object):
1007 MESSAGE_DATA_IDX = 6
1009 NO_MESSAGES = "None"
1010 VOICEMAIL_MESSAGES = "Voicemail"
1011 TEXT_MESSAGES = "Texts"
1012 ALL_TYPES = "All Messages"
1013 MESSAGE_TYPES = [NO_MESSAGES, VOICEMAIL_MESSAGES, TEXT_MESSAGES, ALL_TYPES]
1015 UNREAD_STATUS = "Unread"
1016 UNARCHIVED_STATUS = "Inbox"
1018 MESSAGE_STATUSES = [UNREAD_STATUS, UNARCHIVED_STATUS, ALL_STATUS]
1020 def __init__(self, widgetTree, backend, errorDisplay):
1021 self._errorDisplay = errorDisplay
1022 self._backend = backend
1024 self._isPopulated = False
1025 self._messagemodel = gtk.ListStore(
1026 gobject.TYPE_STRING, # number
1027 gobject.TYPE_STRING, # date
1028 gobject.TYPE_STRING, # header
1029 gobject.TYPE_STRING, # message
1031 gobject.TYPE_STRING, # from id
1032 object, # message data
1034 self._messagemodelfiltered = self._messagemodel.filter_new()
1035 self._messagemodelfiltered.set_visible_func(self._is_message_visible)
1036 self._messageview = widgetTree.get_widget("messages_view")
1037 self._messageviewselection = None
1038 self._onMessageviewRowActivatedId = 0
1040 self._messageRenderer = gtk.CellRendererText()
1041 self._messageRenderer.set_property("wrap-mode", pango.WRAP_WORD)
1042 self._messageRenderer.set_property("wrap-width", 500)
1043 self._messageColumn = gtk.TreeViewColumn("Messages")
1044 self._messageColumn.pack_start(self._messageRenderer, expand=True)
1045 self._messageColumn.add_attribute(self._messageRenderer, "markup", self.MESSAGE_IDX)
1046 self._messageColumn.set_sizing(gtk.TREE_VIEW_COLUMN_FIXED)
1048 self._window = gtk_toolbox.find_parent_window(self._messageview)
1049 self._phoneTypeSelector = SmsEntryDialog(widgetTree)
1051 self._messageTypeButton = widgetTree.get_widget("messageTypeButton")
1052 self._onMessageTypeClickedId = 0
1053 self._messageType = self.ALL_TYPES
1054 self._messageStatusButton = widgetTree.get_widget("messageStatusButton")
1055 self._onMessageStatusClickedId = 0
1056 self._messageStatus = self.ALL_STATUS
1058 self._updateSink = gtk_toolbox.threaded_stage(
1060 self._idly_populate_messageview,
1061 gtk_toolbox.null_sink(),
1066 assert self._backend.is_authed(), "Attempting to enable backend while not logged in"
1067 self._messageview.set_model(self._messagemodelfiltered)
1068 self._messageview.set_headers_visible(False)
1069 self._messageview.set_fixed_height_mode(False)
1071 self._messageview.append_column(self._messageColumn)
1072 self._messageviewselection = self._messageview.get_selection()
1073 self._messageviewselection.set_mode(gtk.SELECTION_SINGLE)
1075 self._messageTypeButton.set_label(self._messageType)
1076 self._messageStatusButton.set_label(self._messageStatus)
1078 self._onMessageviewRowActivatedId = self._messageview.connect(
1079 "row-activated", self._on_messageview_row_activated
1081 self._onMessageTypeClickedId = self._messageTypeButton.connect(
1082 "clicked", self._on_message_type_clicked
1084 self._onMessageStatusClickedId = self._messageStatusButton.connect(
1085 "clicked", self._on_message_status_clicked
1089 self._messageview.disconnect(self._onMessageviewRowActivatedId)
1090 self._messageTypeButton.disconnect(self._onMessageTypeClickedId)
1091 self._messageStatusButton.disconnect(self._onMessageStatusClickedId)
1095 self._messageview.remove_column(self._messageColumn)
1096 self._messageview.set_model(None)
1098 def number_selected(self, action, number, message):
1100 @note Actual dial function is patched in later
1102 raise NotImplementedError("Horrible unknown error has occurred")
1104 def update(self, force = False):
1105 if not force and self._isPopulated:
1107 self._updateSink.send(())
1111 self._isPopulated = False
1112 self._messagemodel.clear()
1118 def load_settings(self, config, sectionName):
1120 self._messageType = config.get(sectionName, "type")
1121 if self._messageType not in self.MESSAGE_TYPES:
1122 self._messageType = self.ALL_TYPES
1123 self._messageStatus = config.get(sectionName, "status")
1124 if self._messageStatus not in self.MESSAGE_STATUSES:
1125 self._messageStatus = self.ALL_STATUS
1126 except ConfigParser.NoOptionError:
1129 def save_settings(self, config, sectionName):
1131 @note Thread Agnostic
1133 config.set(sectionName, "status", self._messageStatus)
1134 config.set(sectionName, "type", self._messageType)
1136 def _is_message_visible(self, model, iter):
1138 message = model.get_value(iter, self.MESSAGE_DATA_IDX)
1140 return False # this seems weird but oh well
1141 return self._filter_messages(message, self._messageType, self._messageStatus)
1142 except Exception, e:
1143 self._errorDisplay.push_exception()
1146 def _filter_messages(cls, message, type, status):
1147 if type == cls.ALL_TYPES:
1150 messageType = message["type"]
1151 isType = messageType == type
1153 if status == cls.ALL_STATUS:
1156 isUnarchived = not message["isArchived"]
1157 isUnread = not message["isRead"]
1158 if status == cls.UNREAD_STATUS:
1159 isStatus = isUnarchived and isUnread
1160 elif status == cls.UNARCHIVED_STATUS:
1161 isStatus = isUnarchived
1163 assert "Status %s is bad for %r" % (status, message)
1165 return isType and isStatus
1167 _MIN_MESSAGES_SHOWN = 4
1169 def _idly_populate_messageview(self):
1170 with gtk_toolbox.gtk_lock():
1171 banner = hildonize.show_busy_banner_start(self._window, "Loading Messages")
1173 self._messagemodel.clear()
1174 self._isPopulated = True
1176 if self._messageType == self.NO_MESSAGES:
1180 messageItems = self._backend.get_messages()
1181 except Exception, e:
1182 self._errorDisplay.push_exception_with_lock()
1183 self._isPopulated = False
1187 (gv_backend.decorate_message(message), message)
1188 for message in gv_backend.sort_messages(messageItems)
1191 for (contactId, header, number, relativeDate, messages), messageData in messageItems:
1192 prettyNumber = number[2:] if number.startswith("+1") else number
1193 prettyNumber = make_pretty(prettyNumber)
1195 firstMessage = "<b>%s - %s</b> <i>(%s)</i>" % (header, prettyNumber, relativeDate)
1196 expandedMessages = [firstMessage]
1197 expandedMessages.extend(messages)
1198 if (self._MIN_MESSAGES_SHOWN + 1) < len(messages):
1199 firstMessage = "<b>%s - %s</b> <i>(%s)</i>" % (header, prettyNumber, relativeDate)
1200 secondMessage = "<i>%d Messages Hidden...</i>" % (len(messages) - self._MIN_MESSAGES_SHOWN, )
1201 collapsedMessages = [firstMessage, secondMessage]
1202 collapsedMessages.extend(messages[-(self._MIN_MESSAGES_SHOWN+0):])
1204 collapsedMessages = expandedMessages
1205 #collapsedMessages = _collapse_message(collapsedMessages, 60, self._MIN_MESSAGES_SHOWN)
1207 number = make_ugly(number)
1209 row = number, relativeDate, header, "\n".join(collapsedMessages), expandedMessages, contactId, messageData
1210 with gtk_toolbox.gtk_lock():
1211 self._messagemodel.append(row)
1212 except Exception, e:
1213 self._errorDisplay.push_exception_with_lock()
1215 with gtk_toolbox.gtk_lock():
1216 hildonize.show_busy_banner_end(banner)
1217 self._messagemodelfiltered.refilter()
1221 def _on_messageview_row_activated(self, treeview, path, view_column):
1223 itr = self._messagemodel.get_iter(path)
1227 number = make_ugly(self._messagemodel.get_value(itr, self.NUMBER_IDX))
1228 description = self._messagemodel.get_value(itr, self.MESSAGES_IDX)
1230 contactId = self._messagemodel.get_value(itr, self.FROM_ID_IDX)
1232 contactPhoneNumbers = list(self._backend.get_contact_details(contactId))
1234 (number == make_ugly(contactNumber) or number[1:] == make_ugly(contactNumber))
1235 for (numberDescription, contactNumber) in contactPhoneNumbers
1238 defaultIndex = defaultMatches.index(True)
1240 contactPhoneNumbers.append(("Other", number))
1241 defaultIndex = len(contactPhoneNumbers)-1
1243 "Could not find contact %r's number %s among %r" % (
1244 contactId, number, contactPhoneNumbers
1248 contactPhoneNumbers = [("Phone", number)]
1251 action, phoneNumber, message = self._phoneTypeSelector.run(
1252 contactPhoneNumbers,
1253 messages = description,
1254 parent = self._window,
1255 defaultIndex = defaultIndex,
1257 if action == SmsEntryDialog.ACTION_CANCEL:
1259 assert phoneNumber, "A lock of phone number exists"
1261 self.number_selected(action, phoneNumber, message)
1262 self._messageviewselection.unselect_all()
1263 except Exception, e:
1264 self._errorDisplay.push_exception()
1266 def _on_message_type_clicked(self, *args, **kwds):
1268 selectedIndex = self.MESSAGE_TYPES.index(self._messageType)
1271 newSelectedIndex = hildonize.touch_selector(
1277 except RuntimeError:
1280 if selectedIndex != newSelectedIndex:
1281 self._messageType = self.MESSAGE_TYPES[newSelectedIndex]
1282 self._messageTypeButton.set_label(self._messageType)
1283 self._messagemodelfiltered.refilter()
1284 except Exception, e:
1285 self._errorDisplay.push_exception()
1287 def _on_message_status_clicked(self, *args, **kwds):
1289 selectedIndex = self.MESSAGE_STATUSES.index(self._messageStatus)
1292 newSelectedIndex = hildonize.touch_selector(
1295 self.MESSAGE_STATUSES,
1298 except RuntimeError:
1301 if selectedIndex != newSelectedIndex:
1302 self._messageStatus = self.MESSAGE_STATUSES[newSelectedIndex]
1303 self._messageStatusButton.set_label(self._messageStatus)
1304 self._messagemodelfiltered.refilter()
1305 except Exception, e:
1306 self._errorDisplay.push_exception()
1309 class ContactsView(object):
1311 CONTACT_TYPE_IDX = 0
1312 CONTACT_NAME_IDX = 1
1315 def __init__(self, widgetTree, backend, errorDisplay):
1316 self._errorDisplay = errorDisplay
1317 self._backend = backend
1319 self._addressBook = None
1320 self._selectedComboIndex = 0
1321 self._addressBookFactories = [null_backend.NullAddressBook()]
1323 self._booksList = []
1324 self._bookSelectionButton = widgetTree.get_widget("addressbookSelectButton")
1326 self._isPopulated = False
1327 self._contactsmodel = gtk.ListStore(
1328 gobject.TYPE_STRING, # Contact Type
1329 gobject.TYPE_STRING, # Contact Name
1330 gobject.TYPE_STRING, # Contact ID
1332 self._contactsviewselection = None
1333 self._contactsview = widgetTree.get_widget("contactsview")
1335 self._contactColumn = gtk.TreeViewColumn("Contact")
1336 displayContactSource = False
1337 if displayContactSource:
1338 textrenderer = gtk.CellRendererText()
1339 self._contactColumn.pack_start(textrenderer, expand=False)
1340 self._contactColumn.add_attribute(textrenderer, 'text', self.CONTACT_TYPE_IDX)
1341 textrenderer = gtk.CellRendererText()
1342 hildonize.set_cell_thumb_selectable(textrenderer)
1343 self._contactColumn.pack_start(textrenderer, expand=True)
1344 self._contactColumn.add_attribute(textrenderer, 'text', self.CONTACT_NAME_IDX)
1345 self._contactColumn.set_sizing(gtk.TREE_VIEW_COLUMN_FIXED)
1346 self._contactColumn.set_sort_column_id(1)
1347 self._contactColumn.set_visible(True)
1349 self._onContactsviewRowActivatedId = 0
1350 self._onAddressbookButtonChangedId = 0
1351 self._window = gtk_toolbox.find_parent_window(self._contactsview)
1352 self._phoneTypeSelector = SmsEntryDialog(widgetTree)
1354 self._updateSink = gtk_toolbox.threaded_stage(
1356 self._idly_populate_contactsview,
1357 gtk_toolbox.null_sink(),
1362 assert self._backend.is_authed(), "Attempting to enable backend while not logged in"
1364 self._contactsview.set_model(self._contactsmodel)
1365 self._contactsview.set_fixed_height_mode(False)
1366 self._contactsview.append_column(self._contactColumn)
1367 self._contactsviewselection = self._contactsview.get_selection()
1368 self._contactsviewselection.set_mode(gtk.SELECTION_SINGLE)
1370 del self._booksList[:]
1371 for (factoryId, bookId), (factoryName, bookName) in self.get_addressbooks():
1372 if factoryName and bookName:
1373 entryName = "%s: %s" % (factoryName, bookName)
1375 entryName = factoryName
1377 entryName = bookName
1379 entryName = "Bad name (%d)" % factoryId
1380 row = (str(factoryId), bookId, entryName)
1381 self._booksList.append(row)
1383 self._onContactsviewRowActivatedId = self._contactsview.connect("row-activated", self._on_contactsview_row_activated)
1384 self._onAddressbookButtonChangedId = self._bookSelectionButton.connect("clicked", self._on_addressbook_button_changed)
1386 if len(self._booksList) <= self._selectedComboIndex:
1387 self._selectedComboIndex = 0
1388 self._bookSelectionButton.set_label(self._booksList[self._selectedComboIndex][2])
1390 selectedFactoryId = self._booksList[self._selectedComboIndex][0]
1391 selectedBookId = self._booksList[self._selectedComboIndex][1]
1392 self.open_addressbook(selectedFactoryId, selectedBookId)
1395 self._contactsview.disconnect(self._onContactsviewRowActivatedId)
1396 self._bookSelectionButton.disconnect(self._onAddressbookButtonChangedId)
1400 self._bookSelectionButton.set_label("")
1401 self._contactsview.set_model(None)
1402 self._contactsview.remove_column(self._contactColumn)
1404 def number_selected(self, action, number, message):
1406 @note Actual dial function is patched in later
1408 raise NotImplementedError("Horrible unknown error has occurred")
1410 def get_addressbooks(self):
1412 @returns Iterable of ((Factory Id, Book Id), (Factory Name, Book Name))
1414 for i, factory in enumerate(self._addressBookFactories):
1415 for bookFactory, bookId, bookName in factory.get_addressbooks():
1416 yield (str(i), bookId), (factory.factory_name(), bookName)
1418 def open_addressbook(self, bookFactoryId, bookId):
1419 bookFactoryIndex = int(bookFactoryId)
1420 addressBook = self._addressBookFactories[bookFactoryIndex].open_addressbook(bookId)
1421 self._addressBook = addressBook
1423 def update(self, force = False):
1424 if not force and self._isPopulated:
1426 self._updateSink.send(())
1430 self._isPopulated = False
1431 self._contactsmodel.clear()
1432 for factory in self._addressBookFactories:
1433 factory.clear_caches()
1434 self._addressBook.clear_caches()
1436 def append(self, book):
1437 self._addressBookFactories.append(book)
1439 def extend(self, books):
1440 self._addressBookFactories.extend(books)
1446 def load_settings(self, config, sectionName):
1448 self._selectedComboIndex = config.getint(sectionName, "selectedAddressbook")
1449 except ConfigParser.NoOptionError:
1450 self._selectedComboIndex = 0
1452 def save_settings(self, config, sectionName):
1453 config.set(sectionName, "selectedAddressbook", str(self._selectedComboIndex))
1455 def _idly_populate_contactsview(self):
1456 with gtk_toolbox.gtk_lock():
1457 banner = hildonize.show_busy_banner_start(self._window, "Loading Contacts")
1460 while addressBook is not self._addressBook:
1461 addressBook = self._addressBook
1462 with gtk_toolbox.gtk_lock():
1463 self._contactsview.set_model(None)
1467 contacts = addressBook.get_contacts()
1468 except Exception, e:
1470 self._isPopulated = False
1471 self._errorDisplay.push_exception_with_lock()
1472 for contactId, contactName in contacts:
1473 contactType = addressBook.contact_source_short_name(contactId)
1474 row = contactType, contactName, contactId
1475 self._contactsmodel.append(row)
1477 with gtk_toolbox.gtk_lock():
1478 self._contactsview.set_model(self._contactsmodel)
1480 self._isPopulated = True
1481 except Exception, e:
1482 self._errorDisplay.push_exception_with_lock()
1484 with gtk_toolbox.gtk_lock():
1485 hildonize.show_busy_banner_end(banner)
1488 def _on_addressbook_button_changed(self, *args, **kwds):
1491 newSelectedComboIndex = hildonize.touch_selector(
1494 (("%s" % m[2]) for m in self._booksList),
1495 self._selectedComboIndex,
1497 except RuntimeError:
1500 selectedFactoryId = self._booksList[newSelectedComboIndex][0]
1501 selectedBookId = self._booksList[newSelectedComboIndex][1]
1503 oldAddressbook = self._addressBook
1504 self.open_addressbook(selectedFactoryId, selectedBookId)
1505 forceUpdate = True if oldAddressbook is not self._addressBook else False
1506 self.update(force=forceUpdate)
1508 self._selectedComboIndex = newSelectedComboIndex
1509 self._bookSelectionButton.set_label(self._booksList[self._selectedComboIndex][2])
1510 except Exception, e:
1511 self._errorDisplay.push_exception()
1513 def _on_contactsview_row_activated(self, treeview, path, view_column):
1515 itr = self._contactsmodel.get_iter(path)
1519 contactId = self._contactsmodel.get_value(itr, self.CONTACT_ID_IDX)
1520 contactName = self._contactsmodel.get_value(itr, self.CONTACT_NAME_IDX)
1522 contactDetails = self._addressBook.get_contact_details(contactId)
1523 except Exception, e:
1525 self._errorDisplay.push_exception()
1526 contactPhoneNumbers = [phoneNumber for phoneNumber in contactDetails]
1528 if len(contactPhoneNumbers) == 0:
1531 action, phoneNumber, message = self._phoneTypeSelector.run(
1532 contactPhoneNumbers,
1533 messages = (contactName, ),
1534 parent = self._window,
1536 if action == SmsEntryDialog.ACTION_CANCEL:
1538 assert phoneNumber, "A lack of phone number exists"
1540 self.number_selected(action, phoneNumber, message)
1541 self._contactsviewselection.unselect_all()
1542 except Exception, e:
1543 self._errorDisplay.push_exception()