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")
202 self._smsEntrySize = None
204 self._action = self.ACTION_CANCEL
206 self._numberIndex = -1
207 self._contactDetails = []
209 def run(self, contactDetails, messages = (), parent = None, defaultIndex = -1):
210 entryConnectId = self._smsEntry.get_buffer().connect("changed", self._on_entry_changed)
211 phoneConnectId = self._phoneButton.connect("clicked", self._on_phone)
212 keyConnectId = self._keyPressEventId = self._dialog.connect("key-press-event", self._on_key_press)
214 # Setup the phone selection button
215 del self._contactDetails[:]
216 for phoneType, phoneNumber in contactDetails:
217 display = " - ".join((make_pretty(phoneNumber), phoneType))
218 row = (phoneNumber, display)
219 self._contactDetails.append(row)
220 if 0 < len(self._contactDetails):
221 self._numberIndex = defaultIndex if defaultIndex != -1 else 0
222 self._phoneButton.set_label(self._contactDetails[self._numberIndex][1])
224 self._numberIndex = -1
225 self._phoneButton.set_label("Error: No Number Available")
227 # Add the column to the messages tree view
228 self._messagemodel.clear()
229 self._messagesView.set_model(self._messagemodel)
230 self._messagesView.set_fixed_height_mode(False)
232 textrenderer = gtk.CellRendererText()
233 textrenderer.set_property("wrap-mode", pango.WRAP_WORD)
234 textrenderer.set_property("wrap-width", 450)
235 messageColumn = gtk.TreeViewColumn("")
236 messageColumn.pack_start(textrenderer, expand=True)
237 messageColumn.add_attribute(textrenderer, "markup", 0)
238 messageColumn.set_sizing(gtk.TREE_VIEW_COLUMN_FIXED)
239 self._messagesView.append_column(messageColumn)
240 self._messagesView.set_headers_visible(False)
243 for message in messages:
245 self._messagemodel.append(row)
246 self._messagesView.show()
247 self._scrollWindow.show()
248 messagesSelection = self._messagesView.get_selection()
249 messagesSelection.select_path((len(messages)-1, ))
251 self._messagesView.hide()
252 self._scrollWindow.hide()
254 self._smsEntry.get_buffer().set_text("")
255 self._update_letter_count()
257 if parent is not None:
258 self._dialog.set_transient_for(parent)
259 parentSize = parent.get_size()
260 self._dialog.resize(parentSize[0], max(parentSize[1]-10, 100))
264 self._dialog.show_all()
265 self._smsEntry.grab_focus()
266 adjustment = self._scrollWindow.get_vadjustment()
267 dx = self._conversationView.get_allocation().height - self._conversationViewPort.get_allocation().height
269 adjustment.value = dx
271 if 1 < len(self._contactDetails):
272 if defaultIndex == -1:
273 self._request_number()
274 self._phoneButton.set_sensitive(True)
276 self._phoneButton.set_sensitive(False)
278 userResponse = self._dialog.run()
280 self._dialog.hide_all()
282 # Process the users response
283 if userResponse == gtk.RESPONSE_OK and 0 <= self._numberIndex:
284 phoneNumber = self._contactDetails[self._numberIndex][0]
285 phoneNumber = make_ugly(phoneNumber)
289 self._action = self.ACTION_CANCEL
290 if self._action == self.ACTION_SEND_SMS:
291 entryBuffer = self._smsEntry.get_buffer()
292 enteredMessage = entryBuffer.get_text(entryBuffer.get_start_iter(), entryBuffer.get_end_iter())
293 enteredMessage = enteredMessage[0:self.MAX_CHAR].strip()
294 if not enteredMessage:
296 self._action = self.ACTION_CANCEL
300 self._messagesView.remove_column(messageColumn)
301 self._messagesView.set_model(None)
303 return self._action, phoneNumber, enteredMessage
305 self._smsEntry.get_buffer().disconnect(entryConnectId)
306 self._phoneButton.disconnect(phoneConnectId)
307 self._keyPressEventId = self._dialog.disconnect(keyConnectId)
309 def _update_letter_count(self, *args):
310 if self._smsEntrySize is None:
311 self._smsEntrySize = self._smsEntry.size_request()
313 self._smsEntry.set_size_request(*self._smsEntrySize)
314 entryLength = self._smsEntry.get_buffer().get_char_count()
316 charsLeft = self.MAX_CHAR - entryLength
317 self._letterCountLabel.set_text(str(charsLeft))
318 if charsLeft < 0 or charsLeft == self.MAX_CHAR:
319 self._smsButton.set_sensitive(False)
321 self._smsButton.set_sensitive(True)
324 self._dialButton.set_sensitive(True)
326 self._dialButton.set_sensitive(False)
328 def _request_number(self):
330 assert 0 <= self._numberIndex, "%r" % self._numberIndex
332 self._numberIndex = hildonize.touch_selector(
335 (description for (number, description) in self._contactDetails),
338 self._phoneButton.set_label(self._contactDetails[self._numberIndex][1])
340 _moduleLogger.exception("%s" % str(e))
342 def _on_phone(self, *args):
343 self._request_number()
345 def _on_entry_changed(self, *args):
346 self._update_letter_count()
348 def _on_send(self, *args):
349 self._dialog.response(gtk.RESPONSE_OK)
350 self._action = self.ACTION_SEND_SMS
352 def _on_dial(self, *args):
353 self._dialog.response(gtk.RESPONSE_OK)
354 self._action = self.ACTION_DIAL
356 def _on_cancel(self, *args):
357 self._dialog.response(gtk.RESPONSE_CANCEL)
358 self._action = self.ACTION_CANCEL
360 def _on_key_press(self, widget, event):
362 if event.keyval == ord("c") and event.get_state() & gtk.gdk.CONTROL_MASK:
365 for messagePart in self._messagemodel
367 self._clipboard.set_text(str(message))
369 _moduleLogger.exception(str(e))
372 class Dialpad(object):
374 def __init__(self, widgetTree, errorDisplay):
375 self._clipboard = gtk.clipboard_get()
376 self._errorDisplay = errorDisplay
377 self._smsDialog = SmsEntryDialog(widgetTree)
379 self._numberdisplay = widgetTree.get_widget("numberdisplay")
380 self._smsButton = widgetTree.get_widget("sms")
381 self._dialButton = widgetTree.get_widget("dial")
382 self._backButton = widgetTree.get_widget("back")
383 self._phonenumber = ""
384 self._prettynumber = ""
387 "on_digit_clicked": self._on_digit_clicked,
389 widgetTree.signal_autoconnect(callbackMapping)
390 self._dialButton.connect("clicked", self._on_dial_clicked)
391 self._smsButton.connect("clicked", self._on_sms_clicked)
393 self._originalLabel = self._backButton.get_label()
394 self._backTapHandler = gtk_toolbox.TapOrHold(self._backButton)
395 self._backTapHandler.on_tap = self._on_backspace
396 self._backTapHandler.on_hold = self._on_clearall
397 self._backTapHandler.on_holding = self._set_clear_button
398 self._backTapHandler.on_cancel = self._reset_back_button
400 self._window = gtk_toolbox.find_parent_window(self._numberdisplay)
401 self._keyPressEventId = 0
404 self._dialButton.grab_focus()
405 self._backTapHandler.enable()
406 self._keyPressEventId = self._window.connect("key-press-event", self._on_key_press)
409 self._window.disconnect(self._keyPressEventId)
410 self._keyPressEventId = 0
411 self._reset_back_button()
412 self._backTapHandler.disable()
414 def number_selected(self, action, number, message):
416 @note Actual dial function is patched in later
418 raise NotImplementedError("Horrible unknown error has occurred")
420 def get_number(self):
421 return self._phonenumber
423 def set_number(self, number):
425 Set the number to dial
428 self._phonenumber = make_ugly(number)
429 self._prettynumber = make_pretty(self._phonenumber)
430 self._numberdisplay.set_label("<span size='30000' weight='bold'>%s</span>" % (self._prettynumber))
432 self._errorDisplay.push_exception()
441 def load_settings(self, config, section):
444 def save_settings(self, config, section):
446 @note Thread Agnostic
450 def _on_key_press(self, widget, event):
452 if event.keyval == ord("v") and event.get_state() & gtk.gdk.CONTROL_MASK:
453 contents = self._clipboard.wait_for_text()
454 if contents is not None:
455 self.set_number(contents)
457 self._errorDisplay.push_exception()
459 def _on_sms_clicked(self, widget):
461 phoneNumber = self.get_number()
462 action, phoneNumber, message = self._smsDialog.run([("Dialer", phoneNumber)], (), self._window)
464 if action == SmsEntryDialog.ACTION_CANCEL:
466 self.number_selected(action, phoneNumber, message)
468 self._errorDisplay.push_exception()
470 def _on_dial_clicked(self, widget):
472 action = SmsEntryDialog.ACTION_DIAL
473 phoneNumber = self.get_number()
475 self.number_selected(action, phoneNumber, message)
477 self._errorDisplay.push_exception()
479 def _on_digit_clicked(self, widget):
481 self.set_number(self._phonenumber + widget.get_name()[-1])
483 self._errorDisplay.push_exception()
485 def _on_backspace(self, taps):
487 self.set_number(self._phonenumber[:-taps])
488 self._reset_back_button()
490 self._errorDisplay.push_exception()
492 def _on_clearall(self, taps):
495 self._reset_back_button()
497 self._errorDisplay.push_exception()
500 def _set_clear_button(self):
502 self._backButton.set_label("gtk-clear")
504 self._errorDisplay.push_exception()
506 def _reset_back_button(self):
508 self._backButton.set_label(self._originalLabel)
510 self._errorDisplay.push_exception()
513 class AccountInfo(object):
515 def __init__(self, widgetTree, backend, alarmHandler, errorDisplay):
516 self._errorDisplay = errorDisplay
517 self._backend = backend
518 self._isPopulated = False
519 self._alarmHandler = alarmHandler
520 self._notifyOnMissed = False
521 self._notifyOnVoicemail = False
522 self._notifyOnSms = False
524 self._callbackList = []
525 self._accountViewNumberDisplay = widgetTree.get_widget("gcnumber_display")
526 self._callbackSelectButton = widgetTree.get_widget("callbackSelectButton")
527 self._onCallbackSelectChangedId = 0
529 self._notifyCheckbox = widgetTree.get_widget("notifyCheckbox")
530 self._minutesEntryButton = widgetTree.get_widget("minutesEntryButton")
531 self._missedCheckbox = widgetTree.get_widget("missedCheckbox")
532 self._voicemailCheckbox = widgetTree.get_widget("voicemailCheckbox")
533 self._smsCheckbox = widgetTree.get_widget("smsCheckbox")
534 self._onNotifyToggled = 0
535 self._onMinutesChanged = 0
536 self._onMissedToggled = 0
537 self._onVoicemailToggled = 0
538 self._onSmsToggled = 0
539 self._applyAlarmTimeoutId = None
541 self._window = gtk_toolbox.find_parent_window(self._minutesEntryButton)
542 self._callbackNumber = ""
545 assert self._backend.is_authed(), "Attempting to enable backend while not logged in"
547 self._accountViewNumberDisplay.set_use_markup(True)
548 self.set_account_number("")
550 del self._callbackList[:]
551 self._onCallbackSelectChangedId = self._callbackSelectButton.connect("clicked", self._on_callbackentry_clicked)
552 self._set_callback_label("")
554 if self._alarmHandler is not None:
555 self._notifyCheckbox.set_active(self._alarmHandler.isEnabled)
556 self._minutesEntryButton.set_label("%d minutes" % self._alarmHandler.recurrence)
557 self._missedCheckbox.set_active(self._notifyOnMissed)
558 self._voicemailCheckbox.set_active(self._notifyOnVoicemail)
559 self._smsCheckbox.set_active(self._notifyOnSms)
561 self._onNotifyToggled = self._notifyCheckbox.connect("toggled", self._on_notify_toggled)
562 self._onMinutesChanged = self._minutesEntryButton.connect("clicked", self._on_minutes_clicked)
563 self._onMissedToggled = self._missedCheckbox.connect("toggled", self._on_missed_toggled)
564 self._onVoicemailToggled = self._voicemailCheckbox.connect("toggled", self._on_voicemail_toggled)
565 self._onSmsToggled = self._smsCheckbox.connect("toggled", self._on_sms_toggled)
567 self._notifyCheckbox.set_sensitive(False)
568 self._minutesEntryButton.set_sensitive(False)
569 self._missedCheckbox.set_sensitive(False)
570 self._voicemailCheckbox.set_sensitive(False)
571 self._smsCheckbox.set_sensitive(False)
573 self.update(force=True)
576 self._callbackSelectButton.disconnect(self._onCallbackSelectChangedId)
577 self._onCallbackSelectChangedId = 0
578 self._set_callback_label("")
580 if self._alarmHandler is not None:
581 self._notifyCheckbox.disconnect(self._onNotifyToggled)
582 self._minutesEntryButton.disconnect(self._onMinutesChanged)
583 self._missedCheckbox.disconnect(self._onNotifyToggled)
584 self._voicemailCheckbox.disconnect(self._onNotifyToggled)
585 self._smsCheckbox.disconnect(self._onNotifyToggled)
586 self._onNotifyToggled = 0
587 self._onMinutesChanged = 0
588 self._onMissedToggled = 0
589 self._onVoicemailToggled = 0
590 self._onSmsToggled = 0
592 self._notifyCheckbox.set_sensitive(True)
593 self._minutesEntryButton.set_sensitive(True)
594 self._missedCheckbox.set_sensitive(True)
595 self._voicemailCheckbox.set_sensitive(True)
596 self._smsCheckbox.set_sensitive(True)
599 del self._callbackList[:]
601 def set_account_number(self, number):
603 Displays current account number
605 self._accountViewNumberDisplay.set_label("<span size='23000' weight='bold'>%s</span>" % (number))
607 def update(self, force = False):
608 if not force and self._isPopulated:
610 self._populate_callback_combo()
611 self.set_account_number(self._backend.get_account_number())
615 self._set_callback_label("")
616 self.set_account_number("")
617 self._isPopulated = False
619 def save_everything(self):
620 raise NotImplementedError
624 return "Account Info"
626 def load_settings(self, config, section):
627 self._callbackNumber = make_ugly(config.get(section, "callback"))
628 self._notifyOnMissed = config.getboolean(section, "notifyOnMissed")
629 self._notifyOnVoicemail = config.getboolean(section, "notifyOnVoicemail")
630 self._notifyOnSms = config.getboolean(section, "notifyOnSms")
632 def save_settings(self, config, section):
634 @note Thread Agnostic
636 config.set(section, "callback", self._callbackNumber)
637 config.set(section, "notifyOnMissed", repr(self._notifyOnMissed))
638 config.set(section, "notifyOnVoicemail", repr(self._notifyOnVoicemail))
639 config.set(section, "notifyOnSms", repr(self._notifyOnSms))
641 def _populate_callback_combo(self):
642 self._isPopulated = True
643 del self._callbackList[:]
645 callbackNumbers = self._backend.get_callback_numbers()
647 self._errorDisplay.push_exception()
648 self._isPopulated = False
651 if len(callbackNumbers) == 0:
652 callbackNumbers = {"": "No callback numbers available"}
654 for number, description in callbackNumbers.iteritems():
655 self._callbackList.append((make_pretty(number), description))
657 self._set_callback_number(self._callbackNumber)
659 def _set_callback_number(self, number):
661 if not self._backend.is_valid_syntax(number) and 0 < len(number):
662 self._errorDisplay.push_message("%s is not a valid callback number" % number)
663 elif number == self._backend.get_callback_number() and 0 < len(number):
664 _moduleLogger.warning(
665 "Callback number already is %s" % (
666 self._backend.get_callback_number(),
669 self._set_callback_label(number)
671 if number.startswith("1747"): number = "+" + number
672 self._backend.set_callback_number(number)
673 assert make_ugly(number) == make_ugly(self._backend.get_callback_number()), "Callback number should be %s but instead is %s" % (
674 make_pretty(number), make_pretty(self._backend.get_callback_number())
676 self._callbackNumber = make_ugly(number)
677 self._set_callback_label(number)
679 "Callback number set to %s" % (
680 self._backend.get_callback_number(),
684 self._errorDisplay.push_exception()
686 def _set_callback_label(self, uglyNumber):
687 prettyNumber = make_pretty(uglyNumber)
688 if len(prettyNumber) == 0:
689 prettyNumber = "No Callback Number"
690 self._callbackSelectButton.set_label(prettyNumber)
692 def _update_alarm_settings(self, recurrence):
694 isEnabled = self._notifyCheckbox.get_active()
695 if isEnabled != self._alarmHandler.isEnabled or recurrence != self._alarmHandler.recurrence:
696 self._alarmHandler.apply_settings(isEnabled, recurrence)
698 self.save_everything()
699 self._notifyCheckbox.set_active(self._alarmHandler.isEnabled)
700 self._minutesEntryButton.set_label("%d Minutes" % self._alarmHandler.recurrence)
702 def _on_callbackentry_clicked(self, *args):
704 actualSelection = make_pretty(self._callbackNumber)
707 (number, "%s (%s)" % (number, description))
708 for (number, description) in self._callbackList
710 defaultSelection = userOptions.get(actualSelection, actualSelection)
712 userSelection = hildonize.touch_selector_entry(
715 list(userOptions.itervalues()),
718 reversedUserOptions = dict(
719 itertools.izip(userOptions.itervalues(), userOptions.iterkeys())
721 selectedNumber = reversedUserOptions.get(userSelection, userSelection)
723 number = make_ugly(selectedNumber)
724 self._set_callback_number(number)
725 except RuntimeError, e:
726 _moduleLogger.exception("%s" % str(e))
728 self._errorDisplay.push_exception()
730 def _on_notify_toggled(self, *args):
732 if self._applyAlarmTimeoutId is not None:
733 gobject.source_remove(self._applyAlarmTimeoutId)
734 self._applyAlarmTimeoutId = None
735 self._applyAlarmTimeoutId = gobject.timeout_add(500, self._on_apply_timeout)
737 self._errorDisplay.push_exception()
739 def _on_minutes_clicked(self, *args):
740 recurrenceChoices = [
756 actualSelection = self._alarmHandler.recurrence
758 closestSelectionIndex = 0
759 for i, possible in enumerate(recurrenceChoices):
760 if possible[0] <= actualSelection:
761 closestSelectionIndex = i
762 recurrenceIndex = hildonize.touch_selector(
765 (("%s" % m[1]) for m in recurrenceChoices),
766 closestSelectionIndex,
768 recurrence = recurrenceChoices[recurrenceIndex][0]
770 self._update_alarm_settings(recurrence)
771 except RuntimeError, e:
772 _moduleLogger.exception("%s" % str(e))
774 self._errorDisplay.push_exception()
776 def _on_apply_timeout(self, *args):
778 self._applyAlarmTimeoutId = None
780 self._update_alarm_settings(self._alarmHandler.recurrence)
782 self._errorDisplay.push_exception()
785 def _on_missed_toggled(self, *args):
787 self._notifyOnMissed = self._missedCheckbox.get_active()
788 self.save_everything()
790 self._errorDisplay.push_exception()
792 def _on_voicemail_toggled(self, *args):
794 self._notifyOnVoicemail = self._voicemailCheckbox.get_active()
795 self.save_everything()
797 self._errorDisplay.push_exception()
799 def _on_sms_toggled(self, *args):
801 self._notifyOnSms = self._smsCheckbox.get_active()
802 self.save_everything()
804 self._errorDisplay.push_exception()
807 class CallHistoryView(object):
815 def __init__(self, widgetTree, backend, errorDisplay):
816 self._errorDisplay = errorDisplay
817 self._backend = backend
819 self._isPopulated = False
820 self._historymodel = gtk.ListStore(
821 gobject.TYPE_STRING, # number
822 gobject.TYPE_STRING, # date
823 gobject.TYPE_STRING, # action
824 gobject.TYPE_STRING, # from
825 gobject.TYPE_STRING, # from id
827 self._historyview = widgetTree.get_widget("historyview")
828 self._historyviewselection = None
829 self._onRecentviewRowActivatedId = 0
831 textrenderer = gtk.CellRendererText()
832 textrenderer.set_property("yalign", 0)
833 self._dateColumn = gtk.TreeViewColumn("Date")
834 self._dateColumn.pack_start(textrenderer, expand=True)
835 self._dateColumn.add_attribute(textrenderer, "text", self.DATE_IDX)
837 textrenderer = gtk.CellRendererText()
838 textrenderer.set_property("yalign", 0)
839 self._actionColumn = gtk.TreeViewColumn("Action")
840 self._actionColumn.pack_start(textrenderer, expand=True)
841 self._actionColumn.add_attribute(textrenderer, "text", self.ACTION_IDX)
843 textrenderer = gtk.CellRendererText()
844 textrenderer.set_property("yalign", 0)
845 textrenderer.set_property("ellipsize", pango.ELLIPSIZE_END)
846 textrenderer.set_property("width-chars", len("1 (555) 555-1234"))
847 self._numberColumn = gtk.TreeViewColumn("Number")
848 self._numberColumn.pack_start(textrenderer, expand=True)
849 self._numberColumn.add_attribute(textrenderer, "text", self.NUMBER_IDX)
851 textrenderer = gtk.CellRendererText()
852 textrenderer.set_property("yalign", 0)
853 hildonize.set_cell_thumb_selectable(textrenderer)
854 self._nameColumn = gtk.TreeViewColumn("From")
855 self._nameColumn.pack_start(textrenderer, expand=True)
856 self._nameColumn.add_attribute(textrenderer, "text", self.FROM_IDX)
857 self._nameColumn.set_sizing(gtk.TREE_VIEW_COLUMN_FIXED)
859 self._window = gtk_toolbox.find_parent_window(self._historyview)
860 self._phoneTypeSelector = SmsEntryDialog(widgetTree)
862 self._updateSink = gtk_toolbox.threaded_stage(
864 self._idly_populate_historyview,
865 gtk_toolbox.null_sink(),
870 assert self._backend.is_authed(), "Attempting to enable backend while not logged in"
871 self._historyview.set_model(self._historymodel)
872 self._historyview.set_fixed_height_mode(False)
874 self._historyview.append_column(self._dateColumn)
875 self._historyview.append_column(self._actionColumn)
876 self._historyview.append_column(self._numberColumn)
877 self._historyview.append_column(self._nameColumn)
878 self._historyviewselection = self._historyview.get_selection()
879 self._historyviewselection.set_mode(gtk.SELECTION_SINGLE)
881 self._onRecentviewRowActivatedId = self._historyview.connect("row-activated", self._on_historyview_row_activated)
884 self._historyview.disconnect(self._onRecentviewRowActivatedId)
888 self._historyview.remove_column(self._dateColumn)
889 self._historyview.remove_column(self._actionColumn)
890 self._historyview.remove_column(self._nameColumn)
891 self._historyview.remove_column(self._numberColumn)
892 self._historyview.set_model(None)
894 def number_selected(self, action, number, message):
896 @note Actual dial function is patched in later
898 raise NotImplementedError("Horrible unknown error has occurred")
900 def update(self, force = False):
901 if not force and self._isPopulated:
903 self._updateSink.send(())
907 self._isPopulated = False
908 self._historymodel.clear()
912 return "Recent Calls"
914 def load_settings(self, config, section):
917 def save_settings(self, config, section):
919 @note Thread Agnostic
923 def _idly_populate_historyview(self):
924 with gtk_toolbox.gtk_lock():
925 banner = hildonize.show_busy_banner_start(self._window, "Loading Call History")
927 self._historymodel.clear()
928 self._isPopulated = True
931 historyItems = self._backend.get_recent()
933 self._errorDisplay.push_exception_with_lock()
934 self._isPopulated = False
938 gv_backend.decorate_recent(data)
939 for data in gv_backend.sort_messages(historyItems)
942 for contactId, personName, phoneNumber, date, action in historyItems:
944 personName = "Unknown"
945 date = abbrev_relative_date(date)
946 prettyNumber = phoneNumber[2:] if phoneNumber.startswith("+1") else phoneNumber
947 prettyNumber = make_pretty(prettyNumber)
948 item = (prettyNumber, date, action.capitalize(), personName, contactId)
949 with gtk_toolbox.gtk_lock():
950 self._historymodel.append(item)
952 self._errorDisplay.push_exception_with_lock()
954 with gtk_toolbox.gtk_lock():
955 hildonize.show_busy_banner_end(banner)
959 def _on_historyview_row_activated(self, treeview, path, view_column):
961 itr = self._historymodel.get_iter(path)
965 number = self._historymodel.get_value(itr, self.NUMBER_IDX)
966 number = make_ugly(number)
967 description = self._historymodel.get_value(itr, self.FROM_IDX)
968 contactId = self._historymodel.get_value(itr, self.FROM_ID_IDX)
970 contactPhoneNumbers = list(self._backend.get_contact_details(contactId))
972 (number == make_ugly(contactNumber) or number[1:] == make_ugly(contactNumber))
973 for (numberDescription, contactNumber) in contactPhoneNumbers
976 defaultIndex = defaultMatches.index(True)
978 contactPhoneNumbers.append(("Other", number))
979 defaultIndex = len(contactPhoneNumbers)-1
981 "Could not find contact %r's number %s among %r" % (
982 contactId, number, contactPhoneNumbers
986 contactPhoneNumbers = [("Phone", number)]
989 action, phoneNumber, message = self._phoneTypeSelector.run(
991 messages = (description, ),
992 parent = self._window,
993 defaultIndex = defaultIndex,
995 if action == SmsEntryDialog.ACTION_CANCEL:
997 assert phoneNumber, "A lack of phone number exists"
999 self.number_selected(action, phoneNumber, message)
1000 self._historyviewselection.unselect_all()
1001 except Exception, e:
1002 self._errorDisplay.push_exception()
1005 class MessagesView(object):
1013 MESSAGE_DATA_IDX = 6
1015 NO_MESSAGES = "None"
1016 VOICEMAIL_MESSAGES = "Voicemail"
1017 TEXT_MESSAGES = "Texts"
1018 ALL_TYPES = "All Messages"
1019 MESSAGE_TYPES = [NO_MESSAGES, VOICEMAIL_MESSAGES, TEXT_MESSAGES, ALL_TYPES]
1021 UNREAD_STATUS = "Unread"
1022 UNARCHIVED_STATUS = "Inbox"
1024 MESSAGE_STATUSES = [UNREAD_STATUS, UNARCHIVED_STATUS, ALL_STATUS]
1026 def __init__(self, widgetTree, backend, errorDisplay):
1027 self._errorDisplay = errorDisplay
1028 self._backend = backend
1030 self._isPopulated = False
1031 self._messagemodel = gtk.ListStore(
1032 gobject.TYPE_STRING, # number
1033 gobject.TYPE_STRING, # date
1034 gobject.TYPE_STRING, # header
1035 gobject.TYPE_STRING, # message
1037 gobject.TYPE_STRING, # from id
1038 object, # message data
1040 self._messagemodelfiltered = self._messagemodel.filter_new()
1041 self._messagemodelfiltered.set_visible_func(self._is_message_visible)
1042 self._messageview = widgetTree.get_widget("messages_view")
1043 self._messageviewselection = None
1044 self._onMessageviewRowActivatedId = 0
1046 self._messageRenderer = gtk.CellRendererText()
1047 self._messageRenderer.set_property("wrap-mode", pango.WRAP_WORD)
1048 self._messageRenderer.set_property("wrap-width", 500)
1049 self._messageColumn = gtk.TreeViewColumn("Messages")
1050 self._messageColumn.pack_start(self._messageRenderer, expand=True)
1051 self._messageColumn.add_attribute(self._messageRenderer, "markup", self.MESSAGE_IDX)
1052 self._messageColumn.set_sizing(gtk.TREE_VIEW_COLUMN_FIXED)
1054 self._window = gtk_toolbox.find_parent_window(self._messageview)
1055 self._phoneTypeSelector = SmsEntryDialog(widgetTree)
1057 self._messageTypeButton = widgetTree.get_widget("messageTypeButton")
1058 self._onMessageTypeClickedId = 0
1059 self._messageType = self.ALL_TYPES
1060 self._messageStatusButton = widgetTree.get_widget("messageStatusButton")
1061 self._onMessageStatusClickedId = 0
1062 self._messageStatus = self.ALL_STATUS
1064 self._updateSink = gtk_toolbox.threaded_stage(
1066 self._idly_populate_messageview,
1067 gtk_toolbox.null_sink(),
1072 assert self._backend.is_authed(), "Attempting to enable backend while not logged in"
1073 self._messageview.set_model(self._messagemodelfiltered)
1074 self._messageview.set_headers_visible(False)
1075 self._messageview.set_fixed_height_mode(False)
1077 self._messageview.append_column(self._messageColumn)
1078 self._messageviewselection = self._messageview.get_selection()
1079 self._messageviewselection.set_mode(gtk.SELECTION_SINGLE)
1081 self._messageTypeButton.set_label(self._messageType)
1082 self._messageStatusButton.set_label(self._messageStatus)
1084 self._onMessageviewRowActivatedId = self._messageview.connect(
1085 "row-activated", self._on_messageview_row_activated
1087 self._onMessageTypeClickedId = self._messageTypeButton.connect(
1088 "clicked", self._on_message_type_clicked
1090 self._onMessageStatusClickedId = self._messageStatusButton.connect(
1091 "clicked", self._on_message_status_clicked
1095 self._messageview.disconnect(self._onMessageviewRowActivatedId)
1096 self._messageTypeButton.disconnect(self._onMessageTypeClickedId)
1097 self._messageStatusButton.disconnect(self._onMessageStatusClickedId)
1101 self._messageview.remove_column(self._messageColumn)
1102 self._messageview.set_model(None)
1104 def number_selected(self, action, number, message):
1106 @note Actual dial function is patched in later
1108 raise NotImplementedError("Horrible unknown error has occurred")
1110 def update(self, force = False):
1111 if not force and self._isPopulated:
1113 self._updateSink.send(())
1117 self._isPopulated = False
1118 self._messagemodel.clear()
1124 def load_settings(self, config, sectionName):
1126 self._messageType = config.get(sectionName, "type")
1127 if self._messageType not in self.MESSAGE_TYPES:
1128 self._messageType = self.ALL_TYPES
1129 self._messageStatus = config.get(sectionName, "status")
1130 if self._messageStatus not in self.MESSAGE_STATUSES:
1131 self._messageStatus = self.ALL_STATUS
1132 except ConfigParser.NoOptionError:
1135 def save_settings(self, config, sectionName):
1137 @note Thread Agnostic
1139 config.set(sectionName, "status", self._messageStatus)
1140 config.set(sectionName, "type", self._messageType)
1142 def _is_message_visible(self, model, iter):
1144 message = model.get_value(iter, self.MESSAGE_DATA_IDX)
1146 return False # this seems weird but oh well
1147 return self._filter_messages(message, self._messageType, self._messageStatus)
1148 except Exception, e:
1149 self._errorDisplay.push_exception()
1152 def _filter_messages(cls, message, type, status):
1153 if type == cls.ALL_TYPES:
1156 messageType = message["type"]
1157 isType = messageType == type
1159 if status == cls.ALL_STATUS:
1162 isUnarchived = not message["isArchived"]
1163 isUnread = not message["isRead"]
1164 if status == cls.UNREAD_STATUS:
1165 isStatus = isUnarchived and isUnread
1166 elif status == cls.UNARCHIVED_STATUS:
1167 isStatus = isUnarchived
1169 assert "Status %s is bad for %r" % (status, message)
1171 return isType and isStatus
1173 _MIN_MESSAGES_SHOWN = 4
1175 def _idly_populate_messageview(self):
1176 with gtk_toolbox.gtk_lock():
1177 banner = hildonize.show_busy_banner_start(self._window, "Loading Messages")
1179 self._messagemodel.clear()
1180 self._isPopulated = True
1182 if self._messageType == self.NO_MESSAGES:
1186 messageItems = self._backend.get_messages()
1187 except Exception, e:
1188 self._errorDisplay.push_exception_with_lock()
1189 self._isPopulated = False
1193 (gv_backend.decorate_message(message), message)
1194 for message in gv_backend.sort_messages(messageItems)
1197 for (contactId, header, number, relativeDate, messages), messageData in messageItems:
1198 prettyNumber = number[2:] if number.startswith("+1") else number
1199 prettyNumber = make_pretty(prettyNumber)
1201 firstMessage = "<b>%s - %s</b> <i>(%s)</i>" % (header, prettyNumber, relativeDate)
1202 expandedMessages = [firstMessage]
1203 expandedMessages.extend(messages)
1204 if (self._MIN_MESSAGES_SHOWN + 1) < len(messages):
1205 firstMessage = "<b>%s - %s</b> <i>(%s)</i>" % (header, prettyNumber, relativeDate)
1206 secondMessage = "<i>%d Messages Hidden...</i>" % (len(messages) - self._MIN_MESSAGES_SHOWN, )
1207 collapsedMessages = [firstMessage, secondMessage]
1208 collapsedMessages.extend(messages[-(self._MIN_MESSAGES_SHOWN+0):])
1210 collapsedMessages = expandedMessages
1211 #collapsedMessages = _collapse_message(collapsedMessages, 60, self._MIN_MESSAGES_SHOWN)
1213 number = make_ugly(number)
1215 row = number, relativeDate, header, "\n".join(collapsedMessages), expandedMessages, contactId, messageData
1216 with gtk_toolbox.gtk_lock():
1217 self._messagemodel.append(row)
1218 except Exception, e:
1219 self._errorDisplay.push_exception_with_lock()
1221 with gtk_toolbox.gtk_lock():
1222 hildonize.show_busy_banner_end(banner)
1223 self._messagemodelfiltered.refilter()
1227 def _on_messageview_row_activated(self, treeview, path, view_column):
1229 childPath = self._messagemodelfiltered.convert_path_to_child_path(path)
1230 itr = self._messagemodel.get_iter(childPath)
1234 number = make_ugly(self._messagemodel.get_value(itr, self.NUMBER_IDX))
1235 description = self._messagemodel.get_value(itr, self.MESSAGES_IDX)
1237 contactId = self._messagemodel.get_value(itr, self.FROM_ID_IDX)
1239 contactPhoneNumbers = list(self._backend.get_contact_details(contactId))
1241 (number == make_ugly(contactNumber) or number[1:] == make_ugly(contactNumber))
1242 for (numberDescription, contactNumber) in contactPhoneNumbers
1245 defaultIndex = defaultMatches.index(True)
1247 contactPhoneNumbers.append(("Other", number))
1248 defaultIndex = len(contactPhoneNumbers)-1
1250 "Could not find contact %r's number %s among %r" % (
1251 contactId, number, contactPhoneNumbers
1255 contactPhoneNumbers = [("Phone", number)]
1258 action, phoneNumber, message = self._phoneTypeSelector.run(
1259 contactPhoneNumbers,
1260 messages = description,
1261 parent = self._window,
1262 defaultIndex = defaultIndex,
1264 if action == SmsEntryDialog.ACTION_CANCEL:
1266 assert phoneNumber, "A lock of phone number exists"
1268 self.number_selected(action, phoneNumber, message)
1269 self._messageviewselection.unselect_all()
1270 except Exception, e:
1271 self._errorDisplay.push_exception()
1273 def _on_message_type_clicked(self, *args, **kwds):
1275 selectedIndex = self.MESSAGE_TYPES.index(self._messageType)
1278 newSelectedIndex = hildonize.touch_selector(
1284 except RuntimeError:
1287 if selectedIndex != newSelectedIndex:
1288 self._messageType = self.MESSAGE_TYPES[newSelectedIndex]
1289 self._messageTypeButton.set_label(self._messageType)
1290 self._messagemodelfiltered.refilter()
1291 except Exception, e:
1292 self._errorDisplay.push_exception()
1294 def _on_message_status_clicked(self, *args, **kwds):
1296 selectedIndex = self.MESSAGE_STATUSES.index(self._messageStatus)
1299 newSelectedIndex = hildonize.touch_selector(
1302 self.MESSAGE_STATUSES,
1305 except RuntimeError:
1308 if selectedIndex != newSelectedIndex:
1309 self._messageStatus = self.MESSAGE_STATUSES[newSelectedIndex]
1310 self._messageStatusButton.set_label(self._messageStatus)
1311 self._messagemodelfiltered.refilter()
1312 except Exception, e:
1313 self._errorDisplay.push_exception()
1316 class ContactsView(object):
1318 CONTACT_TYPE_IDX = 0
1319 CONTACT_NAME_IDX = 1
1322 def __init__(self, widgetTree, backend, errorDisplay):
1323 self._errorDisplay = errorDisplay
1324 self._backend = backend
1326 self._addressBook = None
1327 self._selectedComboIndex = 0
1328 self._addressBookFactories = [null_backend.NullAddressBook()]
1330 self._booksList = []
1331 self._bookSelectionButton = widgetTree.get_widget("addressbookSelectButton")
1333 self._isPopulated = False
1334 self._contactsmodel = gtk.ListStore(
1335 gobject.TYPE_STRING, # Contact Type
1336 gobject.TYPE_STRING, # Contact Name
1337 gobject.TYPE_STRING, # Contact ID
1339 self._contactsviewselection = None
1340 self._contactsview = widgetTree.get_widget("contactsview")
1342 self._contactColumn = gtk.TreeViewColumn("Contact")
1343 displayContactSource = False
1344 if displayContactSource:
1345 textrenderer = gtk.CellRendererText()
1346 self._contactColumn.pack_start(textrenderer, expand=False)
1347 self._contactColumn.add_attribute(textrenderer, 'text', self.CONTACT_TYPE_IDX)
1348 textrenderer = gtk.CellRendererText()
1349 hildonize.set_cell_thumb_selectable(textrenderer)
1350 self._contactColumn.pack_start(textrenderer, expand=True)
1351 self._contactColumn.add_attribute(textrenderer, 'text', self.CONTACT_NAME_IDX)
1352 self._contactColumn.set_sizing(gtk.TREE_VIEW_COLUMN_FIXED)
1353 self._contactColumn.set_sort_column_id(1)
1354 self._contactColumn.set_visible(True)
1356 self._onContactsviewRowActivatedId = 0
1357 self._onAddressbookButtonChangedId = 0
1358 self._window = gtk_toolbox.find_parent_window(self._contactsview)
1359 self._phoneTypeSelector = SmsEntryDialog(widgetTree)
1361 self._updateSink = gtk_toolbox.threaded_stage(
1363 self._idly_populate_contactsview,
1364 gtk_toolbox.null_sink(),
1369 assert self._backend.is_authed(), "Attempting to enable backend while not logged in"
1371 self._contactsview.set_model(self._contactsmodel)
1372 self._contactsview.set_fixed_height_mode(False)
1373 self._contactsview.append_column(self._contactColumn)
1374 self._contactsviewselection = self._contactsview.get_selection()
1375 self._contactsviewselection.set_mode(gtk.SELECTION_SINGLE)
1377 del self._booksList[:]
1378 for (factoryId, bookId), (factoryName, bookName) in self.get_addressbooks():
1379 if factoryName and bookName:
1380 entryName = "%s: %s" % (factoryName, bookName)
1382 entryName = factoryName
1384 entryName = bookName
1386 entryName = "Bad name (%d)" % factoryId
1387 row = (str(factoryId), bookId, entryName)
1388 self._booksList.append(row)
1390 self._onContactsviewRowActivatedId = self._contactsview.connect("row-activated", self._on_contactsview_row_activated)
1391 self._onAddressbookButtonChangedId = self._bookSelectionButton.connect("clicked", self._on_addressbook_button_changed)
1393 if len(self._booksList) <= self._selectedComboIndex:
1394 self._selectedComboIndex = 0
1395 self._bookSelectionButton.set_label(self._booksList[self._selectedComboIndex][2])
1397 selectedFactoryId = self._booksList[self._selectedComboIndex][0]
1398 selectedBookId = self._booksList[self._selectedComboIndex][1]
1399 self.open_addressbook(selectedFactoryId, selectedBookId)
1402 self._contactsview.disconnect(self._onContactsviewRowActivatedId)
1403 self._bookSelectionButton.disconnect(self._onAddressbookButtonChangedId)
1407 self._bookSelectionButton.set_label("")
1408 self._contactsview.set_model(None)
1409 self._contactsview.remove_column(self._contactColumn)
1411 def number_selected(self, action, number, message):
1413 @note Actual dial function is patched in later
1415 raise NotImplementedError("Horrible unknown error has occurred")
1417 def get_addressbooks(self):
1419 @returns Iterable of ((Factory Id, Book Id), (Factory Name, Book Name))
1421 for i, factory in enumerate(self._addressBookFactories):
1422 for bookFactory, bookId, bookName in factory.get_addressbooks():
1423 yield (str(i), bookId), (factory.factory_name(), bookName)
1425 def open_addressbook(self, bookFactoryId, bookId):
1426 bookFactoryIndex = int(bookFactoryId)
1427 addressBook = self._addressBookFactories[bookFactoryIndex].open_addressbook(bookId)
1428 self._addressBook = addressBook
1430 def update(self, force = False):
1431 if not force and self._isPopulated:
1433 self._updateSink.send(())
1437 self._isPopulated = False
1438 self._contactsmodel.clear()
1439 for factory in self._addressBookFactories:
1440 factory.clear_caches()
1441 self._addressBook.clear_caches()
1443 def append(self, book):
1444 self._addressBookFactories.append(book)
1446 def extend(self, books):
1447 self._addressBookFactories.extend(books)
1453 def load_settings(self, config, sectionName):
1455 self._selectedComboIndex = config.getint(sectionName, "selectedAddressbook")
1456 except ConfigParser.NoOptionError:
1457 self._selectedComboIndex = 0
1459 def save_settings(self, config, sectionName):
1460 config.set(sectionName, "selectedAddressbook", str(self._selectedComboIndex))
1462 def _idly_populate_contactsview(self):
1463 with gtk_toolbox.gtk_lock():
1464 banner = hildonize.show_busy_banner_start(self._window, "Loading Contacts")
1467 while addressBook is not self._addressBook:
1468 addressBook = self._addressBook
1469 with gtk_toolbox.gtk_lock():
1470 self._contactsview.set_model(None)
1474 contacts = addressBook.get_contacts()
1475 except Exception, e:
1477 self._isPopulated = False
1478 self._errorDisplay.push_exception_with_lock()
1479 for contactId, contactName in contacts:
1480 contactType = addressBook.contact_source_short_name(contactId)
1481 row = contactType, contactName, contactId
1482 self._contactsmodel.append(row)
1484 with gtk_toolbox.gtk_lock():
1485 self._contactsview.set_model(self._contactsmodel)
1487 self._isPopulated = True
1488 except Exception, e:
1489 self._errorDisplay.push_exception_with_lock()
1491 with gtk_toolbox.gtk_lock():
1492 hildonize.show_busy_banner_end(banner)
1495 def _on_addressbook_button_changed(self, *args, **kwds):
1498 newSelectedComboIndex = hildonize.touch_selector(
1501 (("%s" % m[2]) for m in self._booksList),
1502 self._selectedComboIndex,
1504 except RuntimeError:
1507 selectedFactoryId = self._booksList[newSelectedComboIndex][0]
1508 selectedBookId = self._booksList[newSelectedComboIndex][1]
1510 oldAddressbook = self._addressBook
1511 self.open_addressbook(selectedFactoryId, selectedBookId)
1512 forceUpdate = True if oldAddressbook is not self._addressBook else False
1513 self.update(force=forceUpdate)
1515 self._selectedComboIndex = newSelectedComboIndex
1516 self._bookSelectionButton.set_label(self._booksList[self._selectedComboIndex][2])
1517 except Exception, e:
1518 self._errorDisplay.push_exception()
1520 def _on_contactsview_row_activated(self, treeview, path, view_column):
1522 itr = self._contactsmodel.get_iter(path)
1526 contactId = self._contactsmodel.get_value(itr, self.CONTACT_ID_IDX)
1527 contactName = self._contactsmodel.get_value(itr, self.CONTACT_NAME_IDX)
1529 contactDetails = self._addressBook.get_contact_details(contactId)
1530 except Exception, e:
1532 self._errorDisplay.push_exception()
1533 contactPhoneNumbers = [phoneNumber for phoneNumber in contactDetails]
1535 if len(contactPhoneNumbers) == 0:
1538 action, phoneNumber, message = self._phoneTypeSelector.run(
1539 contactPhoneNumbers,
1540 messages = (contactName, ),
1541 parent = self._window,
1543 if action == SmsEntryDialog.ACTION_CANCEL:
1545 assert phoneNumber, "A lack of phone number exists"
1547 self.number_selected(action, phoneNumber, message)
1548 self._contactsviewselection.unselect_all()
1549 except Exception, e:
1550 self._errorDisplay.push_exception()