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 if number.startswith("1747"): number = "+" + number
667 self._backend.set_callback_number(number)
668 assert make_ugly(number) == make_ugly(self._backend.get_callback_number()), "Callback number should be %s but instead is %s" % (
669 make_pretty(number), make_pretty(self._backend.get_callback_number())
671 self._callbackNumber = make_ugly(number)
672 self._set_callback_label(number)
674 "Callback number set to %s" % (
675 self._backend.get_callback_number(),
679 self._errorDisplay.push_exception()
681 def _set_callback_label(self, uglyNumber):
682 prettyNumber = make_pretty(uglyNumber)
683 if len(prettyNumber) == 0:
684 prettyNumber = "No Callback Number"
685 self._callbackSelectButton.set_label(prettyNumber)
687 def _update_alarm_settings(self, recurrence):
689 isEnabled = self._notifyCheckbox.get_active()
690 if isEnabled != self._alarmHandler.isEnabled or recurrence != self._alarmHandler.recurrence:
691 self._alarmHandler.apply_settings(isEnabled, recurrence)
693 self.save_everything()
694 self._notifyCheckbox.set_active(self._alarmHandler.isEnabled)
695 self._minutesEntryButton.set_label("%d Minutes" % self._alarmHandler.recurrence)
697 def _on_callbackentry_clicked(self, *args):
699 actualSelection = make_pretty(self._callbackNumber)
702 (number, "%s (%s)" % (number, description))
703 for (number, description) in self._callbackList
705 defaultSelection = userOptions.get(actualSelection, actualSelection)
707 userSelection = hildonize.touch_selector_entry(
710 list(userOptions.itervalues()),
713 reversedUserOptions = dict(
714 itertools.izip(userOptions.itervalues(), userOptions.iterkeys())
716 selectedNumber = reversedUserOptions.get(userSelection, userSelection)
718 number = make_ugly(selectedNumber)
719 self._set_callback_number(number)
720 except RuntimeError, e:
721 _moduleLogger.exception("%s" % str(e))
723 self._errorDisplay.push_exception()
725 def _on_notify_toggled(self, *args):
727 if self._applyAlarmTimeoutId is not None:
728 gobject.source_remove(self._applyAlarmTimeoutId)
729 self._applyAlarmTimeoutId = None
730 self._applyAlarmTimeoutId = gobject.timeout_add(500, self._on_apply_timeout)
732 self._errorDisplay.push_exception()
734 def _on_minutes_clicked(self, *args):
735 recurrenceChoices = [
751 actualSelection = self._alarmHandler.recurrence
753 closestSelectionIndex = 0
754 for i, possible in enumerate(recurrenceChoices):
755 if possible[0] <= actualSelection:
756 closestSelectionIndex = i
757 recurrenceIndex = hildonize.touch_selector(
760 (("%s" % m[1]) for m in recurrenceChoices),
761 closestSelectionIndex,
763 recurrence = recurrenceChoices[recurrenceIndex][0]
765 self._update_alarm_settings(recurrence)
766 except RuntimeError, e:
767 _moduleLogger.exception("%s" % str(e))
769 self._errorDisplay.push_exception()
771 def _on_apply_timeout(self, *args):
773 self._applyAlarmTimeoutId = None
775 self._update_alarm_settings(self._alarmHandler.recurrence)
777 self._errorDisplay.push_exception()
780 def _on_missed_toggled(self, *args):
782 self._notifyOnMissed = self._missedCheckbox.get_active()
783 self.save_everything()
785 self._errorDisplay.push_exception()
787 def _on_voicemail_toggled(self, *args):
789 self._notifyOnVoicemail = self._voicemailCheckbox.get_active()
790 self.save_everything()
792 self._errorDisplay.push_exception()
794 def _on_sms_toggled(self, *args):
796 self._notifyOnSms = self._smsCheckbox.get_active()
797 self.save_everything()
799 self._errorDisplay.push_exception()
802 class CallHistoryView(object):
810 def __init__(self, widgetTree, backend, errorDisplay):
811 self._errorDisplay = errorDisplay
812 self._backend = backend
814 self._isPopulated = False
815 self._historymodel = gtk.ListStore(
816 gobject.TYPE_STRING, # number
817 gobject.TYPE_STRING, # date
818 gobject.TYPE_STRING, # action
819 gobject.TYPE_STRING, # from
820 gobject.TYPE_STRING, # from id
822 self._historyview = widgetTree.get_widget("historyview")
823 self._historyviewselection = None
824 self._onRecentviewRowActivatedId = 0
826 textrenderer = gtk.CellRendererText()
827 textrenderer.set_property("yalign", 0)
828 self._dateColumn = gtk.TreeViewColumn("Date")
829 self._dateColumn.pack_start(textrenderer, expand=True)
830 self._dateColumn.add_attribute(textrenderer, "text", self.DATE_IDX)
832 textrenderer = gtk.CellRendererText()
833 textrenderer.set_property("yalign", 0)
834 self._actionColumn = gtk.TreeViewColumn("Action")
835 self._actionColumn.pack_start(textrenderer, expand=True)
836 self._actionColumn.add_attribute(textrenderer, "text", self.ACTION_IDX)
838 textrenderer = gtk.CellRendererText()
839 textrenderer.set_property("yalign", 0)
840 textrenderer.set_property("ellipsize", pango.ELLIPSIZE_END)
841 textrenderer.set_property("width-chars", len("1 (555) 555-1234"))
842 self._numberColumn = gtk.TreeViewColumn("Number")
843 self._numberColumn.pack_start(textrenderer, expand=True)
844 self._numberColumn.add_attribute(textrenderer, "text", self.NUMBER_IDX)
846 textrenderer = gtk.CellRendererText()
847 textrenderer.set_property("yalign", 0)
848 hildonize.set_cell_thumb_selectable(textrenderer)
849 self._nameColumn = gtk.TreeViewColumn("From")
850 self._nameColumn.pack_start(textrenderer, expand=True)
851 self._nameColumn.add_attribute(textrenderer, "text", self.FROM_IDX)
852 self._nameColumn.set_sizing(gtk.TREE_VIEW_COLUMN_FIXED)
854 self._window = gtk_toolbox.find_parent_window(self._historyview)
855 self._phoneTypeSelector = SmsEntryDialog(widgetTree)
857 self._updateSink = gtk_toolbox.threaded_stage(
859 self._idly_populate_historyview,
860 gtk_toolbox.null_sink(),
865 assert self._backend.is_authed(), "Attempting to enable backend while not logged in"
866 self._historyview.set_model(self._historymodel)
867 self._historyview.set_fixed_height_mode(False)
869 self._historyview.append_column(self._dateColumn)
870 self._historyview.append_column(self._actionColumn)
871 self._historyview.append_column(self._numberColumn)
872 self._historyview.append_column(self._nameColumn)
873 self._historyviewselection = self._historyview.get_selection()
874 self._historyviewselection.set_mode(gtk.SELECTION_SINGLE)
876 self._onRecentviewRowActivatedId = self._historyview.connect("row-activated", self._on_historyview_row_activated)
879 self._historyview.disconnect(self._onRecentviewRowActivatedId)
883 self._historyview.remove_column(self._dateColumn)
884 self._historyview.remove_column(self._actionColumn)
885 self._historyview.remove_column(self._nameColumn)
886 self._historyview.remove_column(self._numberColumn)
887 self._historyview.set_model(None)
889 def number_selected(self, action, number, message):
891 @note Actual dial function is patched in later
893 raise NotImplementedError("Horrible unknown error has occurred")
895 def update(self, force = False):
896 if not force and self._isPopulated:
898 self._updateSink.send(())
902 self._isPopulated = False
903 self._historymodel.clear()
907 return "Recent Calls"
909 def load_settings(self, config, section):
912 def save_settings(self, config, section):
914 @note Thread Agnostic
918 def _idly_populate_historyview(self):
919 with gtk_toolbox.gtk_lock():
920 banner = hildonize.show_busy_banner_start(self._window, "Loading Call History")
922 self._historymodel.clear()
923 self._isPopulated = True
926 historyItems = self._backend.get_recent()
928 self._errorDisplay.push_exception_with_lock()
929 self._isPopulated = False
933 gv_backend.decorate_recent(data)
934 for data in gv_backend.sort_messages(historyItems)
937 for contactId, personName, phoneNumber, date, action in historyItems:
939 personName = "Unknown"
940 date = abbrev_relative_date(date)
941 prettyNumber = phoneNumber[2:] if phoneNumber.startswith("+1") else phoneNumber
942 prettyNumber = make_pretty(prettyNumber)
943 item = (prettyNumber, date, action.capitalize(), personName, contactId)
944 with gtk_toolbox.gtk_lock():
945 self._historymodel.append(item)
947 self._errorDisplay.push_exception_with_lock()
949 with gtk_toolbox.gtk_lock():
950 hildonize.show_busy_banner_end(banner)
954 def _on_historyview_row_activated(self, treeview, path, view_column):
956 itr = self._historymodel.get_iter(path)
960 number = self._historymodel.get_value(itr, self.NUMBER_IDX)
961 number = make_ugly(number)
962 description = self._historymodel.get_value(itr, self.FROM_IDX)
963 contactId = self._historymodel.get_value(itr, self.FROM_ID_IDX)
965 contactPhoneNumbers = list(self._backend.get_contact_details(contactId))
967 (number == make_ugly(contactNumber) or number[1:] == make_ugly(contactNumber))
968 for (numberDescription, contactNumber) in contactPhoneNumbers
971 defaultIndex = defaultMatches.index(True)
973 contactPhoneNumbers.append(("Other", number))
974 defaultIndex = len(contactPhoneNumbers)-1
976 "Could not find contact %r's number %s among %r" % (
977 contactId, number, contactPhoneNumbers
981 contactPhoneNumbers = [("Phone", number)]
984 action, phoneNumber, message = self._phoneTypeSelector.run(
986 messages = (description, ),
987 parent = self._window,
988 defaultIndex = defaultIndex,
990 if action == SmsEntryDialog.ACTION_CANCEL:
992 assert phoneNumber, "A lack of phone number exists"
994 self.number_selected(action, phoneNumber, message)
995 self._historyviewselection.unselect_all()
997 self._errorDisplay.push_exception()
1000 class MessagesView(object):
1008 MESSAGE_DATA_IDX = 6
1010 NO_MESSAGES = "None"
1011 VOICEMAIL_MESSAGES = "Voicemail"
1012 TEXT_MESSAGES = "Texts"
1013 ALL_TYPES = "All Messages"
1014 MESSAGE_TYPES = [NO_MESSAGES, VOICEMAIL_MESSAGES, TEXT_MESSAGES, ALL_TYPES]
1016 UNREAD_STATUS = "Unread"
1017 UNARCHIVED_STATUS = "Inbox"
1019 MESSAGE_STATUSES = [UNREAD_STATUS, UNARCHIVED_STATUS, ALL_STATUS]
1021 def __init__(self, widgetTree, backend, errorDisplay):
1022 self._errorDisplay = errorDisplay
1023 self._backend = backend
1025 self._isPopulated = False
1026 self._messagemodel = gtk.ListStore(
1027 gobject.TYPE_STRING, # number
1028 gobject.TYPE_STRING, # date
1029 gobject.TYPE_STRING, # header
1030 gobject.TYPE_STRING, # message
1032 gobject.TYPE_STRING, # from id
1033 object, # message data
1035 self._messagemodelfiltered = self._messagemodel.filter_new()
1036 self._messagemodelfiltered.set_visible_func(self._is_message_visible)
1037 self._messageview = widgetTree.get_widget("messages_view")
1038 self._messageviewselection = None
1039 self._onMessageviewRowActivatedId = 0
1041 self._messageRenderer = gtk.CellRendererText()
1042 self._messageRenderer.set_property("wrap-mode", pango.WRAP_WORD)
1043 self._messageRenderer.set_property("wrap-width", 500)
1044 self._messageColumn = gtk.TreeViewColumn("Messages")
1045 self._messageColumn.pack_start(self._messageRenderer, expand=True)
1046 self._messageColumn.add_attribute(self._messageRenderer, "markup", self.MESSAGE_IDX)
1047 self._messageColumn.set_sizing(gtk.TREE_VIEW_COLUMN_FIXED)
1049 self._window = gtk_toolbox.find_parent_window(self._messageview)
1050 self._phoneTypeSelector = SmsEntryDialog(widgetTree)
1052 self._messageTypeButton = widgetTree.get_widget("messageTypeButton")
1053 self._onMessageTypeClickedId = 0
1054 self._messageType = self.ALL_TYPES
1055 self._messageStatusButton = widgetTree.get_widget("messageStatusButton")
1056 self._onMessageStatusClickedId = 0
1057 self._messageStatus = self.ALL_STATUS
1059 self._updateSink = gtk_toolbox.threaded_stage(
1061 self._idly_populate_messageview,
1062 gtk_toolbox.null_sink(),
1067 assert self._backend.is_authed(), "Attempting to enable backend while not logged in"
1068 self._messageview.set_model(self._messagemodelfiltered)
1069 self._messageview.set_headers_visible(False)
1070 self._messageview.set_fixed_height_mode(False)
1072 self._messageview.append_column(self._messageColumn)
1073 self._messageviewselection = self._messageview.get_selection()
1074 self._messageviewselection.set_mode(gtk.SELECTION_SINGLE)
1076 self._messageTypeButton.set_label(self._messageType)
1077 self._messageStatusButton.set_label(self._messageStatus)
1079 self._onMessageviewRowActivatedId = self._messageview.connect(
1080 "row-activated", self._on_messageview_row_activated
1082 self._onMessageTypeClickedId = self._messageTypeButton.connect(
1083 "clicked", self._on_message_type_clicked
1085 self._onMessageStatusClickedId = self._messageStatusButton.connect(
1086 "clicked", self._on_message_status_clicked
1090 self._messageview.disconnect(self._onMessageviewRowActivatedId)
1091 self._messageTypeButton.disconnect(self._onMessageTypeClickedId)
1092 self._messageStatusButton.disconnect(self._onMessageStatusClickedId)
1096 self._messageview.remove_column(self._messageColumn)
1097 self._messageview.set_model(None)
1099 def number_selected(self, action, number, message):
1101 @note Actual dial function is patched in later
1103 raise NotImplementedError("Horrible unknown error has occurred")
1105 def update(self, force = False):
1106 if not force and self._isPopulated:
1108 self._updateSink.send(())
1112 self._isPopulated = False
1113 self._messagemodel.clear()
1119 def load_settings(self, config, sectionName):
1121 self._messageType = config.get(sectionName, "type")
1122 if self._messageType not in self.MESSAGE_TYPES:
1123 self._messageType = self.ALL_TYPES
1124 self._messageStatus = config.get(sectionName, "status")
1125 if self._messageStatus not in self.MESSAGE_STATUSES:
1126 self._messageStatus = self.ALL_STATUS
1127 except ConfigParser.NoOptionError:
1130 def save_settings(self, config, sectionName):
1132 @note Thread Agnostic
1134 config.set(sectionName, "status", self._messageStatus)
1135 config.set(sectionName, "type", self._messageType)
1137 def _is_message_visible(self, model, iter):
1139 message = model.get_value(iter, self.MESSAGE_DATA_IDX)
1141 return False # this seems weird but oh well
1142 return self._filter_messages(message, self._messageType, self._messageStatus)
1143 except Exception, e:
1144 self._errorDisplay.push_exception()
1147 def _filter_messages(cls, message, type, status):
1148 if type == cls.ALL_TYPES:
1151 messageType = message["type"]
1152 isType = messageType == type
1154 if status == cls.ALL_STATUS:
1157 isUnarchived = not message["isArchived"]
1158 isUnread = not message["isRead"]
1159 if status == cls.UNREAD_STATUS:
1160 isStatus = isUnarchived and isUnread
1161 elif status == cls.UNARCHIVED_STATUS:
1162 isStatus = isUnarchived
1164 assert "Status %s is bad for %r" % (status, message)
1166 return isType and isStatus
1168 _MIN_MESSAGES_SHOWN = 4
1170 def _idly_populate_messageview(self):
1171 with gtk_toolbox.gtk_lock():
1172 banner = hildonize.show_busy_banner_start(self._window, "Loading Messages")
1174 self._messagemodel.clear()
1175 self._isPopulated = True
1177 if self._messageType == self.NO_MESSAGES:
1181 messageItems = self._backend.get_messages()
1182 except Exception, e:
1183 self._errorDisplay.push_exception_with_lock()
1184 self._isPopulated = False
1188 (gv_backend.decorate_message(message), message)
1189 for message in gv_backend.sort_messages(messageItems)
1192 for (contactId, header, number, relativeDate, messages), messageData in messageItems:
1193 prettyNumber = number[2:] if number.startswith("+1") else number
1194 prettyNumber = make_pretty(prettyNumber)
1196 firstMessage = "<b>%s - %s</b> <i>(%s)</i>" % (header, prettyNumber, relativeDate)
1197 expandedMessages = [firstMessage]
1198 expandedMessages.extend(messages)
1199 if (self._MIN_MESSAGES_SHOWN + 1) < len(messages):
1200 firstMessage = "<b>%s - %s</b> <i>(%s)</i>" % (header, prettyNumber, relativeDate)
1201 secondMessage = "<i>%d Messages Hidden...</i>" % (len(messages) - self._MIN_MESSAGES_SHOWN, )
1202 collapsedMessages = [firstMessage, secondMessage]
1203 collapsedMessages.extend(messages[-(self._MIN_MESSAGES_SHOWN+0):])
1205 collapsedMessages = expandedMessages
1206 #collapsedMessages = _collapse_message(collapsedMessages, 60, self._MIN_MESSAGES_SHOWN)
1208 number = make_ugly(number)
1210 row = number, relativeDate, header, "\n".join(collapsedMessages), expandedMessages, contactId, messageData
1211 with gtk_toolbox.gtk_lock():
1212 self._messagemodel.append(row)
1213 except Exception, e:
1214 self._errorDisplay.push_exception_with_lock()
1216 with gtk_toolbox.gtk_lock():
1217 hildonize.show_busy_banner_end(banner)
1218 self._messagemodelfiltered.refilter()
1222 def _on_messageview_row_activated(self, treeview, path, view_column):
1224 childPath = self._messagemodelfiltered.convert_path_to_child_path(path)
1225 itr = self._messagemodel.get_iter(childPath)
1229 number = make_ugly(self._messagemodel.get_value(itr, self.NUMBER_IDX))
1230 description = self._messagemodel.get_value(itr, self.MESSAGES_IDX)
1232 contactId = self._messagemodel.get_value(itr, self.FROM_ID_IDX)
1234 contactPhoneNumbers = list(self._backend.get_contact_details(contactId))
1236 (number == make_ugly(contactNumber) or number[1:] == make_ugly(contactNumber))
1237 for (numberDescription, contactNumber) in contactPhoneNumbers
1240 defaultIndex = defaultMatches.index(True)
1242 contactPhoneNumbers.append(("Other", number))
1243 defaultIndex = len(contactPhoneNumbers)-1
1245 "Could not find contact %r's number %s among %r" % (
1246 contactId, number, contactPhoneNumbers
1250 contactPhoneNumbers = [("Phone", number)]
1253 action, phoneNumber, message = self._phoneTypeSelector.run(
1254 contactPhoneNumbers,
1255 messages = description,
1256 parent = self._window,
1257 defaultIndex = defaultIndex,
1259 if action == SmsEntryDialog.ACTION_CANCEL:
1261 assert phoneNumber, "A lock of phone number exists"
1263 self.number_selected(action, phoneNumber, message)
1264 self._messageviewselection.unselect_all()
1265 except Exception, e:
1266 self._errorDisplay.push_exception()
1268 def _on_message_type_clicked(self, *args, **kwds):
1270 selectedIndex = self.MESSAGE_TYPES.index(self._messageType)
1273 newSelectedIndex = hildonize.touch_selector(
1279 except RuntimeError:
1282 if selectedIndex != newSelectedIndex:
1283 self._messageType = self.MESSAGE_TYPES[newSelectedIndex]
1284 self._messageTypeButton.set_label(self._messageType)
1285 self._messagemodelfiltered.refilter()
1286 except Exception, e:
1287 self._errorDisplay.push_exception()
1289 def _on_message_status_clicked(self, *args, **kwds):
1291 selectedIndex = self.MESSAGE_STATUSES.index(self._messageStatus)
1294 newSelectedIndex = hildonize.touch_selector(
1297 self.MESSAGE_STATUSES,
1300 except RuntimeError:
1303 if selectedIndex != newSelectedIndex:
1304 self._messageStatus = self.MESSAGE_STATUSES[newSelectedIndex]
1305 self._messageStatusButton.set_label(self._messageStatus)
1306 self._messagemodelfiltered.refilter()
1307 except Exception, e:
1308 self._errorDisplay.push_exception()
1311 class ContactsView(object):
1313 CONTACT_TYPE_IDX = 0
1314 CONTACT_NAME_IDX = 1
1317 def __init__(self, widgetTree, backend, errorDisplay):
1318 self._errorDisplay = errorDisplay
1319 self._backend = backend
1321 self._addressBook = None
1322 self._selectedComboIndex = 0
1323 self._addressBookFactories = [null_backend.NullAddressBook()]
1325 self._booksList = []
1326 self._bookSelectionButton = widgetTree.get_widget("addressbookSelectButton")
1328 self._isPopulated = False
1329 self._contactsmodel = gtk.ListStore(
1330 gobject.TYPE_STRING, # Contact Type
1331 gobject.TYPE_STRING, # Contact Name
1332 gobject.TYPE_STRING, # Contact ID
1334 self._contactsviewselection = None
1335 self._contactsview = widgetTree.get_widget("contactsview")
1337 self._contactColumn = gtk.TreeViewColumn("Contact")
1338 displayContactSource = False
1339 if displayContactSource:
1340 textrenderer = gtk.CellRendererText()
1341 self._contactColumn.pack_start(textrenderer, expand=False)
1342 self._contactColumn.add_attribute(textrenderer, 'text', self.CONTACT_TYPE_IDX)
1343 textrenderer = gtk.CellRendererText()
1344 hildonize.set_cell_thumb_selectable(textrenderer)
1345 self._contactColumn.pack_start(textrenderer, expand=True)
1346 self._contactColumn.add_attribute(textrenderer, 'text', self.CONTACT_NAME_IDX)
1347 self._contactColumn.set_sizing(gtk.TREE_VIEW_COLUMN_FIXED)
1348 self._contactColumn.set_sort_column_id(1)
1349 self._contactColumn.set_visible(True)
1351 self._onContactsviewRowActivatedId = 0
1352 self._onAddressbookButtonChangedId = 0
1353 self._window = gtk_toolbox.find_parent_window(self._contactsview)
1354 self._phoneTypeSelector = SmsEntryDialog(widgetTree)
1356 self._updateSink = gtk_toolbox.threaded_stage(
1358 self._idly_populate_contactsview,
1359 gtk_toolbox.null_sink(),
1364 assert self._backend.is_authed(), "Attempting to enable backend while not logged in"
1366 self._contactsview.set_model(self._contactsmodel)
1367 self._contactsview.set_fixed_height_mode(False)
1368 self._contactsview.append_column(self._contactColumn)
1369 self._contactsviewselection = self._contactsview.get_selection()
1370 self._contactsviewselection.set_mode(gtk.SELECTION_SINGLE)
1372 del self._booksList[:]
1373 for (factoryId, bookId), (factoryName, bookName) in self.get_addressbooks():
1374 if factoryName and bookName:
1375 entryName = "%s: %s" % (factoryName, bookName)
1377 entryName = factoryName
1379 entryName = bookName
1381 entryName = "Bad name (%d)" % factoryId
1382 row = (str(factoryId), bookId, entryName)
1383 self._booksList.append(row)
1385 self._onContactsviewRowActivatedId = self._contactsview.connect("row-activated", self._on_contactsview_row_activated)
1386 self._onAddressbookButtonChangedId = self._bookSelectionButton.connect("clicked", self._on_addressbook_button_changed)
1388 if len(self._booksList) <= self._selectedComboIndex:
1389 self._selectedComboIndex = 0
1390 self._bookSelectionButton.set_label(self._booksList[self._selectedComboIndex][2])
1392 selectedFactoryId = self._booksList[self._selectedComboIndex][0]
1393 selectedBookId = self._booksList[self._selectedComboIndex][1]
1394 self.open_addressbook(selectedFactoryId, selectedBookId)
1397 self._contactsview.disconnect(self._onContactsviewRowActivatedId)
1398 self._bookSelectionButton.disconnect(self._onAddressbookButtonChangedId)
1402 self._bookSelectionButton.set_label("")
1403 self._contactsview.set_model(None)
1404 self._contactsview.remove_column(self._contactColumn)
1406 def number_selected(self, action, number, message):
1408 @note Actual dial function is patched in later
1410 raise NotImplementedError("Horrible unknown error has occurred")
1412 def get_addressbooks(self):
1414 @returns Iterable of ((Factory Id, Book Id), (Factory Name, Book Name))
1416 for i, factory in enumerate(self._addressBookFactories):
1417 for bookFactory, bookId, bookName in factory.get_addressbooks():
1418 yield (str(i), bookId), (factory.factory_name(), bookName)
1420 def open_addressbook(self, bookFactoryId, bookId):
1421 bookFactoryIndex = int(bookFactoryId)
1422 addressBook = self._addressBookFactories[bookFactoryIndex].open_addressbook(bookId)
1423 self._addressBook = addressBook
1425 def update(self, force = False):
1426 if not force and self._isPopulated:
1428 self._updateSink.send(())
1432 self._isPopulated = False
1433 self._contactsmodel.clear()
1434 for factory in self._addressBookFactories:
1435 factory.clear_caches()
1436 self._addressBook.clear_caches()
1438 def append(self, book):
1439 self._addressBookFactories.append(book)
1441 def extend(self, books):
1442 self._addressBookFactories.extend(books)
1448 def load_settings(self, config, sectionName):
1450 self._selectedComboIndex = config.getint(sectionName, "selectedAddressbook")
1451 except ConfigParser.NoOptionError:
1452 self._selectedComboIndex = 0
1454 def save_settings(self, config, sectionName):
1455 config.set(sectionName, "selectedAddressbook", str(self._selectedComboIndex))
1457 def _idly_populate_contactsview(self):
1458 with gtk_toolbox.gtk_lock():
1459 banner = hildonize.show_busy_banner_start(self._window, "Loading Contacts")
1462 while addressBook is not self._addressBook:
1463 addressBook = self._addressBook
1464 with gtk_toolbox.gtk_lock():
1465 self._contactsview.set_model(None)
1469 contacts = addressBook.get_contacts()
1470 except Exception, e:
1472 self._isPopulated = False
1473 self._errorDisplay.push_exception_with_lock()
1474 for contactId, contactName in contacts:
1475 contactType = addressBook.contact_source_short_name(contactId)
1476 row = contactType, contactName, contactId
1477 self._contactsmodel.append(row)
1479 with gtk_toolbox.gtk_lock():
1480 self._contactsview.set_model(self._contactsmodel)
1482 self._isPopulated = True
1483 except Exception, e:
1484 self._errorDisplay.push_exception_with_lock()
1486 with gtk_toolbox.gtk_lock():
1487 hildonize.show_busy_banner_end(banner)
1490 def _on_addressbook_button_changed(self, *args, **kwds):
1493 newSelectedComboIndex = hildonize.touch_selector(
1496 (("%s" % m[2]) for m in self._booksList),
1497 self._selectedComboIndex,
1499 except RuntimeError:
1502 selectedFactoryId = self._booksList[newSelectedComboIndex][0]
1503 selectedBookId = self._booksList[newSelectedComboIndex][1]
1505 oldAddressbook = self._addressBook
1506 self.open_addressbook(selectedFactoryId, selectedBookId)
1507 forceUpdate = True if oldAddressbook is not self._addressBook else False
1508 self.update(force=forceUpdate)
1510 self._selectedComboIndex = newSelectedComboIndex
1511 self._bookSelectionButton.set_label(self._booksList[self._selectedComboIndex][2])
1512 except Exception, e:
1513 self._errorDisplay.push_exception()
1515 def _on_contactsview_row_activated(self, treeview, path, view_column):
1517 itr = self._contactsmodel.get_iter(path)
1521 contactId = self._contactsmodel.get_value(itr, self.CONTACT_ID_IDX)
1522 contactName = self._contactsmodel.get_value(itr, self.CONTACT_NAME_IDX)
1524 contactDetails = self._addressBook.get_contact_details(contactId)
1525 except Exception, e:
1527 self._errorDisplay.push_exception()
1528 contactPhoneNumbers = [phoneNumber for phoneNumber in contactDetails]
1530 if len(contactPhoneNumbers) == 0:
1533 action, phoneNumber, message = self._phoneTypeSelector.run(
1534 contactPhoneNumbers,
1535 messages = (contactName, ),
1536 parent = self._window,
1538 if action == SmsEntryDialog.ACTION_CANCEL:
1540 assert phoneNumber, "A lack of phone number exists"
1542 self.number_selected(action, phoneNumber, message)
1543 self._contactsviewselection.unselect_all()
1544 except Exception, e:
1545 self._errorDisplay.push_exception()