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
24 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")
52 return normalize_number(prettynumber)
55 def normalize_number(prettynumber):
57 function to take a phone number and strip out all non-numeric
60 >>> normalize_number("+012-(345)-678-90")
62 >>> normalize_number("1-(345)-678-9000")
64 >>> normalize_number("+1-(345)-678-9000")
67 uglynumber = re.sub('[^0-9+]', '', prettynumber)
68 if uglynumber.startswith("+"):
70 elif uglynumber.startswith("1") and len(uglynumber) == 11:
71 uglynumber = "+"+uglynumber
72 elif len(uglynumber) == 10:
73 uglynumber = "+1"+uglynumber
77 #validateRe = re.compile("^\+?[0-9]{10,}$")
78 #assert validateRe.match(uglynumber) is not None
83 def _make_pretty_with_areacodde(phonenumber):
84 prettynumber = "(%s)" % (phonenumber[0:3], )
85 if 3 < len(phonenumber):
86 prettynumber += " %s" % (phonenumber[3:6], )
87 if 6 < len(phonenumber):
88 prettynumber += "-%s" % (phonenumber[6:], )
92 def _make_pretty_local(phonenumber):
93 prettynumber = "%s" % (phonenumber[0:3], )
94 if 3 < len(phonenumber):
95 prettynumber += "-%s" % (phonenumber[3:], )
99 def _make_pretty_international(phonenumber):
100 prettynumber = phonenumber
101 if phonenumber.startswith("0"):
102 prettynumber = "+%s " % (phonenumber[0:3], )
103 if 3 < len(phonenumber):
104 prettynumber += _make_pretty_with_areacodde(phonenumber[3:])
105 if phonenumber.startswith("1"):
107 prettynumber += _make_pretty_with_areacodde(phonenumber[1:])
111 def make_pretty(phonenumber):
113 Function to take a phone number and return the pretty version
115 if phonenumber begins with 0:
117 if phonenumber begins with 1: ( for gizmo callback numbers )
119 if phonenumber is 13 digits:
121 if phonenumber is 10 digits:
123 >>> make_pretty("12")
125 >>> make_pretty("1234567")
127 >>> make_pretty("2345678901")
129 >>> make_pretty("12345678901")
131 >>> make_pretty("01234567890")
133 >>> make_pretty("+01234567890")
135 >>> make_pretty("+12")
137 >>> make_pretty("+123")
139 >>> make_pretty("+1234")
142 if phonenumber is None or phonenumber is "":
145 phonenumber = normalize_number(phonenumber)
147 if phonenumber[0] == "+":
148 prettynumber = _make_pretty_international(phonenumber[1:])
149 if not prettynumber.startswith("+"):
150 prettynumber = "+"+prettynumber
151 elif 8 < len(phonenumber) and phonenumber[0] in ("0", "1"):
152 prettynumber = _make_pretty_international(phonenumber)
153 elif 7 < len(phonenumber):
154 prettynumber = _make_pretty_with_areacodde(phonenumber)
155 elif 3 < len(phonenumber):
156 prettynumber = _make_pretty_local(phonenumber)
158 prettynumber = phonenumber
159 return prettynumber.strip()
162 def abbrev_relative_date(date):
164 >>> abbrev_relative_date("42 hours ago")
166 >>> abbrev_relative_date("2 days ago")
168 >>> abbrev_relative_date("4 weeks ago")
171 parts = date.split(" ")
172 return "%s %s" % (parts[0], parts[1][0])
175 def _collapse_message(messageLines, maxCharsPerLine, maxLines):
178 numLines = len(messageLines)
179 for line in messageLines[0:min(maxLines, numLines)]:
180 linesPerLine = max(1, int(len(line) / maxCharsPerLine))
181 allowedLines = maxLines - lines
182 acceptedLines = min(allowedLines, linesPerLine)
183 acceptedChars = acceptedLines * maxCharsPerLine
185 if acceptedChars < (len(line) + 3):
188 acceptedChars = len(line) # eh, might as well complete the line
190 abbrevMessage = "%s%s" % (line[0:acceptedChars], suffix)
193 lines += acceptedLines
194 if maxLines <= lines:
198 def collapse_message(message, maxCharsPerLine, maxLines):
200 >>> collapse_message("Hello", 60, 2)
202 >>> collapse_message("Hello world how are you doing today? 01234567890123456789012345678901234567890123456789012345678901234567890123456789", 60, 2)
203 'Hello world how are you doing today? 01234567890123456789012...'
204 >>> collapse_message('''Hello world how are you doing today?
205 ... 01234567890123456789
206 ... 01234567890123456789
207 ... 01234567890123456789
208 ... 01234567890123456789''', 60, 2)
209 'Hello world how are you doing today?\n01234567890123456789'
210 >>> collapse_message('''
211 ... Hello world how are you doing today? 01234567890123456789012345678901234567890123456789012345678901234567890123456789
212 ... Hello world how are you doing today? 01234567890123456789012345678901234567890123456789012345678901234567890123456789
213 ... Hello world how are you doing today? 01234567890123456789012345678901234567890123456789012345678901234567890123456789
214 ... Hello world how are you doing today? 01234567890123456789012345678901234567890123456789012345678901234567890123456789
215 ... Hello world how are you doing today? 01234567890123456789012345678901234567890123456789012345678901234567890123456789
216 ... Hello world how are you doing today? 01234567890123456789012345678901234567890123456789012345678901234567890123456789''', 60, 2)
217 '\nHello world how are you doing today? 01234567890123456789012...'
219 messageLines = message.split("\n")
220 return "\n".join(_collapse_message(messageLines, maxCharsPerLine, maxLines))
223 class SmsEntryWindow(object):
227 def __init__(self, widgetTree):
228 self._clipboard = gtk.clipboard_get()
229 self._widgetTree = widgetTree
230 self._window = self._widgetTree.get_widget("smsWindow")
231 self._window.connect("delete-event", self._on_delete)
232 self._window.connect("key-press-event", self._on_key_press)
234 self._smsButton = self._widgetTree.get_widget("sendSmsButton")
235 self._smsButton.connect("clicked", self._on_send)
236 self._dialButton = self._widgetTree.get_widget("dialButton")
237 self._dialButton.connect("clicked", self._on_dial)
239 self._letterCountLabel = self._widgetTree.get_widget("smsLetterCount")
241 self._messagemodel = gtk.ListStore(gobject.TYPE_STRING)
242 self._messagesView = self._widgetTree.get_widget("smsMessages")
244 textrenderer = gtk.CellRendererText()
245 textrenderer.set_property("wrap-mode", pango.WRAP_WORD)
246 textrenderer.set_property("wrap-width", 450)
247 messageColumn = gtk.TreeViewColumn("")
248 messageColumn.pack_start(textrenderer, expand=True)
249 messageColumn.add_attribute(textrenderer, "markup", 0)
250 messageColumn.set_sizing(gtk.TREE_VIEW_COLUMN_FIXED)
251 self._messagesView.append_column(messageColumn)
252 self._messagesView.set_headers_visible(False)
253 self._messagesView.set_model(self._messagemodel)
254 self._messagesView.set_fixed_height_mode(False)
256 self._conversationView = self._messagesView.get_parent()
257 self._conversationViewPort = self._conversationView.get_parent()
258 self._scrollWindow = self._conversationViewPort.get_parent()
260 self._targetList = self._widgetTree.get_widget("smsTargetList")
261 self._phoneButton = self._widgetTree.get_widget("phoneTypeSelection")
262 self._phoneButton.connect("clicked", self._on_phone)
263 self._smsEntry = self._widgetTree.get_widget("smsEntry")
264 self._smsEntry.get_buffer().connect("changed", self._on_entry_changed)
265 self._smsEntrySize = None
269 def add_contact(self, contactDetails, messages = (), parent = None, defaultIndex = -1):
270 contactNumbers = list(self._to_contact_numbers(contactDetails))
271 assert contactNumbers
272 contactIndex = defaultIndex if defaultIndex != -1 else 0
273 contact = contactNumbers, contactIndex, messages
274 self._contacts.append(contact)
276 selector = gtk.Button(contactNumbers[0][1])
277 removeContact = gtk.Button(stock="gtk-delete")
279 row.pack_start(selector, True, True)
280 row.pack_start(removeContact, False, False)
282 self._targetList.pack_start(row)
283 selector.connect("clicked", self._on_choose_phone_n, row)
284 removeContact.connect("clicked", self._on_remove_phone_n, row)
285 self._update_button_state()
286 self._update_context()
288 if parent is not None:
289 parentSize = parent.get_size()
290 self._window.resize(parentSize[0], max(parentSize[1]-10, 100))
292 self._window.present()
294 self._smsEntry.grab_focus()
295 dx = self._conversationView.get_allocation().height - self._conversationViewPort.get_allocation().height
297 adjustment = self._scrollWindow.get_vadjustment()
298 adjustment.value = dx
301 del self._contacts[:]
303 for contactNumberSelector in list(self._targetList.get_children()):
304 self._targetList.remove(contactNumberSelector)
305 self._smsEntry.get_buffer().set_text("")
306 self._update_letter_count()
307 self._update_context()
309 def _remove_contact(self, contactIndex):
310 del self._contacts[contactIndex]
312 contactNumberSelector = list(self._targetList.get_children())[contactIndex]
313 self._targetList.remove(contactNumberSelector)
314 self._update_button_state()
315 self._update_context()
317 def _update_letter_count(self):
318 if self._smsEntrySize is None:
319 self._smsEntrySize = self._smsEntry.size_request()
321 self._smsEntry.set_size_request(*self._smsEntrySize)
322 entryLength = self._smsEntry.get_buffer().get_char_count()
324 numTexts, numCharInText = divmod(entryLength, self.MAX_CHAR)
326 self._letterCountLabel.set_text("%s.%s" % (numTexts, numCharInText))
328 self._letterCountLabel.set_text("%s" % (numCharInText, ))
330 self._update_button_state()
332 def _update_context(self):
333 self._messagemodel.clear()
334 if len(self._contacts) == 0:
335 self._messagesView.hide()
336 self._targetList.hide()
337 self._phoneButton.hide()
338 self._phoneButton.set_label("Error: You shouldn't see this")
339 elif len(self._contacts) == 1:
340 contactNumbers, index, messages = self._contacts[0]
342 self._messagesView.show()
343 for message in messages:
345 self._messagemodel.append(row)
346 messagesSelection = self._messagesView.get_selection()
347 messagesSelection.select_path((len(messages)-1, ))
349 self._messagesView.hide()
350 self._targetList.hide()
351 self._phoneButton.show()
352 self._phoneButton.set_label(contactNumbers[index][1])
353 if 1 < len(contactNumbers):
354 self._phoneButton.set_sensitive(True)
356 self._phoneButton.set_sensitive(False)
358 self._messagesView.hide()
359 self._targetList.show()
360 self._phoneButton.hide()
361 self._phoneButton.set_label("Error: You shouldn't see this")
363 def _update_button_state(self):
364 if len(self._contacts) == 0:
365 self._dialButton.set_sensitive(False)
366 self._smsButton.set_sensitive(False)
367 elif len(self._contacts) == 1:
368 entryLength = self._smsEntry.get_buffer().get_char_count()
370 self._dialButton.set_sensitive(True)
371 self._smsButton.set_sensitive(False)
373 self._dialButton.set_sensitive(False)
374 self._smsButton.set_sensitive(True)
376 self._dialButton.set_sensitive(False)
377 self._smsButton.set_sensitive(True)
379 def _to_contact_numbers(self, contactDetails):
380 for phoneType, phoneNumber in contactDetails:
381 display = " - ".join((make_pretty(phoneNumber), phoneType))
382 yield (phoneNumber, display)
388 def _request_number(self, contactIndex):
389 contactNumbers, index, messages = self._contacts[contactIndex]
390 assert 0 <= index, "%r" % index
392 index = hildonize.touch_selector(
395 (description for (number, description) in contactNumbers),
398 self._contacts[contactIndex] = contactNumbers, index, messages
400 def _on_phone(self, *args):
402 assert len(self._contacts) == 1
403 self._request_number(0)
405 contactNumbers, numberIndex, messages = self._contacts[0]
406 self._phoneButton.set_label(contactNumbers[numberIndex][1])
407 row = list(self._targetList.get_children())[0]
408 phoneButton = list(row.get_children())[0]
409 phoneButton.set_label(contactNumbers[numberIndex][1])
411 _moduleLogger.exception("%s" % str(e))
413 def _on_choose_phone_n(self, button, row):
415 assert 1 < len(self._contacts)
416 targetList = list(self._targetList.get_children())
417 index = targetList.index(row)
418 self._request_number(index)
420 contactNumbers, numberIndex, messages = self._contacts[0]
421 phoneButton = list(row.get_children())[0]
422 phoneButton.set_label(contactNumbers[numberIndex][1])
424 _moduleLogger.exception("%s" % str(e))
426 def _on_remove_phone_n(self, button, row):
428 assert 1 < len(self._contacts)
429 targetList = list(self._targetList.get_children())
430 index = targetList.index(row)
432 del self._contacts[index]
433 self._targetList.remove(row)
434 self._update_context()
435 self._update_button_state()
437 _moduleLogger.exception("%s" % str(e))
439 def _on_entry_changed(self, *args):
440 self._update_letter_count()
442 def _on_send(self, *args):
443 assert 0 < len(self._contacts), "%r" % self._contacts
445 make_ugly(contact[0][contact[1]][0])
446 for contact in self._contacts
449 entryBuffer = self._smsEntry.get_buffer()
450 enteredMessage = entryBuffer.get_text(entryBuffer.get_start_iter(), entryBuffer.get_end_iter())
451 enteredMessage = enteredMessage.strip()
452 assert enteredMessage
456 def _on_dial(self, *args):
457 assert len(self._contacts) == 1, "%r" % self._contacts
458 contact = self._contacts[0]
459 contactNumber = contact[0][contact[1]][0]
460 phoneNumber = make_ugly(contactNumber)
464 def _on_delete(self, *args):
465 self._window.emit_stop_by_name("delete-event")
469 def _on_key_press(self, widget, event):
471 if event.keyval == ord("c") and event.get_state() & gtk.gdk.CONTROL_MASK:
474 for messagePart in self._messagemodel
476 self._clipboard.set_text(str(message))
478 _moduleLogger.exception(str(e))
481 class Dialpad(object):
483 def __init__(self, widgetTree, errorDisplay):
484 self._clipboard = gtk.clipboard_get()
485 self._errorDisplay = errorDisplay
487 self._numberdisplay = widgetTree.get_widget("numberdisplay")
488 self._smsButton = widgetTree.get_widget("sms")
489 self._dialButton = widgetTree.get_widget("dial")
490 self._backButton = widgetTree.get_widget("back")
491 self._zeroOrPlusButton = widgetTree.get_widget("digit0")
492 self._phonenumber = ""
493 self._prettynumber = ""
496 "on_digit_clicked": self._on_digit_clicked,
498 widgetTree.signal_autoconnect(callbackMapping)
499 self._dialButton.connect("clicked", self._on_dial_clicked)
500 self._smsButton.connect("clicked", self._on_sms_clicked)
502 self._originalLabel = self._backButton.get_label()
503 self._backTapHandler = gtk_toolbox.TapOrHold(self._backButton)
504 self._backTapHandler.on_tap = self._on_backspace
505 self._backTapHandler.on_hold = self._on_clearall
506 self._backTapHandler.on_holding = self._set_clear_button
507 self._backTapHandler.on_cancel = self._reset_back_button
508 self._zeroOrPlusTapHandler = gtk_toolbox.TapOrHold(self._zeroOrPlusButton)
509 self._zeroOrPlusTapHandler.on_tap = self._on_zero
510 self._zeroOrPlusTapHandler.on_hold = self._on_plus
512 self._window = gtk_toolbox.find_parent_window(self._numberdisplay)
513 self._keyPressEventId = 0
516 self._dialButton.grab_focus()
517 self._backTapHandler.enable()
518 self._zeroOrPlusTapHandler.enable()
519 self._keyPressEventId = self._window.connect("key-press-event", self._on_key_press)
522 self._window.disconnect(self._keyPressEventId)
523 self._keyPressEventId = 0
524 self._reset_back_button()
525 self._backTapHandler.disable()
526 self._zeroOrPlusTapHandler.disable()
528 def add_contact(self, *args, **kwds):
530 @note Actual dial function is patched in later
532 raise NotImplementedError("Horrible unknown error has occurred")
534 def get_number(self):
535 return self._phonenumber
537 def set_number(self, number):
539 Set the number to dial
542 self._phonenumber = make_ugly(number)
543 self._prettynumber = make_pretty(self._phonenumber)
544 self._numberdisplay.set_label("<span size='30000' weight='bold'>%s</span>" % (self._prettynumber))
546 self._errorDisplay.push_exception()
555 def load_settings(self, config, section):
558 def save_settings(self, config, section):
560 @note Thread Agnostic
564 def _on_key_press(self, widget, event):
566 if event.keyval == ord("v") and event.get_state() & gtk.gdk.CONTROL_MASK:
567 contents = self._clipboard.wait_for_text()
568 if contents is not None:
569 self.set_number(contents)
571 self._errorDisplay.push_exception()
573 def _on_sms_clicked(self, widget):
575 phoneNumber = self.get_number()
577 [("Dialer", phoneNumber)], (), self._window
580 self._errorDisplay.push_exception()
582 def _on_dial_clicked(self, widget):
584 #self.number_selected(action, phoneNumbers, message) TODO
587 self._errorDisplay.push_exception()
589 def _on_digit_clicked(self, widget):
591 self.set_number(self._phonenumber + widget.get_name()[-1])
593 self._errorDisplay.push_exception()
595 def _on_zero(self, *args):
597 self.set_number(self._phonenumber + "0")
599 self._errorDisplay.push_exception()
601 def _on_plus(self, *args):
603 self.set_number(self._phonenumber + "+")
605 self._errorDisplay.push_exception()
607 def _on_backspace(self, taps):
609 self.set_number(self._phonenumber[:-taps])
610 self._reset_back_button()
612 self._errorDisplay.push_exception()
614 def _on_clearall(self, taps):
617 self._reset_back_button()
619 self._errorDisplay.push_exception()
622 def _set_clear_button(self):
624 self._backButton.set_label("gtk-clear")
626 self._errorDisplay.push_exception()
628 def _reset_back_button(self):
630 self._backButton.set_label(self._originalLabel)
632 self._errorDisplay.push_exception()
635 class AccountInfo(object):
637 def __init__(self, widgetTree, backend, alarmHandler, errorDisplay):
638 self._errorDisplay = errorDisplay
639 self._backend = backend
640 self._isPopulated = False
641 self._alarmHandler = alarmHandler
642 self._notifyOnMissed = False
643 self._notifyOnVoicemail = False
644 self._notifyOnSms = False
646 self._callbackList = []
647 self._accountViewNumberDisplay = widgetTree.get_widget("gcnumber_display")
648 self._callbackSelectButton = widgetTree.get_widget("callbackSelectButton")
649 self._onCallbackSelectChangedId = 0
651 self._notifyCheckbox = widgetTree.get_widget("notifyCheckbox")
652 self._minutesEntryButton = widgetTree.get_widget("minutesEntryButton")
653 self._missedCheckbox = widgetTree.get_widget("missedCheckbox")
654 self._voicemailCheckbox = widgetTree.get_widget("voicemailCheckbox")
655 self._smsCheckbox = widgetTree.get_widget("smsCheckbox")
656 self._onNotifyToggled = 0
657 self._onMinutesChanged = 0
658 self._onMissedToggled = 0
659 self._onVoicemailToggled = 0
660 self._onSmsToggled = 0
661 self._applyAlarmTimeoutId = None
663 self._window = gtk_toolbox.find_parent_window(self._minutesEntryButton)
664 self._callbackNumber = ""
667 assert self._backend.is_authed(), "Attempting to enable backend while not logged in"
669 self._accountViewNumberDisplay.set_use_markup(True)
670 self.set_account_number("")
672 del self._callbackList[:]
673 self._onCallbackSelectChangedId = self._callbackSelectButton.connect("clicked", self._on_callbackentry_clicked)
674 self._set_callback_label("")
676 if self._alarmHandler is not None:
677 self._notifyCheckbox.set_active(self._alarmHandler.isEnabled)
678 self._minutesEntryButton.set_label("%d minutes" % self._alarmHandler.recurrence)
679 self._missedCheckbox.set_active(self._notifyOnMissed)
680 self._voicemailCheckbox.set_active(self._notifyOnVoicemail)
681 self._smsCheckbox.set_active(self._notifyOnSms)
683 self._onNotifyToggled = self._notifyCheckbox.connect("toggled", self._on_notify_toggled)
684 self._onMinutesChanged = self._minutesEntryButton.connect("clicked", self._on_minutes_clicked)
685 self._onMissedToggled = self._missedCheckbox.connect("toggled", self._on_missed_toggled)
686 self._onVoicemailToggled = self._voicemailCheckbox.connect("toggled", self._on_voicemail_toggled)
687 self._onSmsToggled = self._smsCheckbox.connect("toggled", self._on_sms_toggled)
689 self._notifyCheckbox.set_sensitive(False)
690 self._minutesEntryButton.set_sensitive(False)
691 self._missedCheckbox.set_sensitive(False)
692 self._voicemailCheckbox.set_sensitive(False)
693 self._smsCheckbox.set_sensitive(False)
695 self.update(force=True)
698 self._callbackSelectButton.disconnect(self._onCallbackSelectChangedId)
699 self._onCallbackSelectChangedId = 0
700 self._set_callback_label("")
702 if self._alarmHandler is not None:
703 self._notifyCheckbox.disconnect(self._onNotifyToggled)
704 self._minutesEntryButton.disconnect(self._onMinutesChanged)
705 self._missedCheckbox.disconnect(self._onNotifyToggled)
706 self._voicemailCheckbox.disconnect(self._onNotifyToggled)
707 self._smsCheckbox.disconnect(self._onNotifyToggled)
708 self._onNotifyToggled = 0
709 self._onMinutesChanged = 0
710 self._onMissedToggled = 0
711 self._onVoicemailToggled = 0
712 self._onSmsToggled = 0
714 self._notifyCheckbox.set_sensitive(True)
715 self._minutesEntryButton.set_sensitive(True)
716 self._missedCheckbox.set_sensitive(True)
717 self._voicemailCheckbox.set_sensitive(True)
718 self._smsCheckbox.set_sensitive(True)
721 del self._callbackList[:]
723 def set_account_number(self, number):
725 Displays current account number
727 self._accountViewNumberDisplay.set_label("<span size='23000' weight='bold'>%s</span>" % (number))
729 def update(self, force = False):
730 if not force and self._isPopulated:
732 self._populate_callback_combo()
733 self.set_account_number(self._backend.get_account_number())
737 self._set_callback_label("")
738 self.set_account_number("")
739 self._isPopulated = False
741 def save_everything(self):
742 raise NotImplementedError
746 return "Account Info"
748 def load_settings(self, config, section):
749 self._callbackNumber = make_ugly(config.get(section, "callback"))
750 self._notifyOnMissed = config.getboolean(section, "notifyOnMissed")
751 self._notifyOnVoicemail = config.getboolean(section, "notifyOnVoicemail")
752 self._notifyOnSms = config.getboolean(section, "notifyOnSms")
754 def save_settings(self, config, section):
756 @note Thread Agnostic
758 config.set(section, "callback", self._callbackNumber)
759 config.set(section, "notifyOnMissed", repr(self._notifyOnMissed))
760 config.set(section, "notifyOnVoicemail", repr(self._notifyOnVoicemail))
761 config.set(section, "notifyOnSms", repr(self._notifyOnSms))
763 def _populate_callback_combo(self):
764 self._isPopulated = True
765 del self._callbackList[:]
767 callbackNumbers = self._backend.get_callback_numbers()
769 self._errorDisplay.push_exception()
770 self._isPopulated = False
773 if len(callbackNumbers) == 0:
774 callbackNumbers = {"": "No callback numbers available"}
776 for number, description in callbackNumbers.iteritems():
777 self._callbackList.append((make_pretty(number), description))
779 self._set_callback_number(self._callbackNumber)
781 def _set_callback_number(self, number):
783 if not self._backend.is_valid_syntax(number) and 0 < len(number):
784 self._errorDisplay.push_message("%s is not a valid callback number" % number)
785 elif number == self._backend.get_callback_number() and 0 < len(number):
786 _moduleLogger.warning(
787 "Callback number already is %s" % (
788 self._backend.get_callback_number(),
791 self._set_callback_label(number)
793 if number.startswith("1747"): number = "+" + number
794 self._backend.set_callback_number(number)
795 assert make_ugly(number) == make_ugly(self._backend.get_callback_number()), "Callback number should be %s but instead is %s" % (
796 make_pretty(number), make_pretty(self._backend.get_callback_number())
798 self._callbackNumber = make_ugly(number)
799 self._set_callback_label(number)
801 "Callback number set to %s" % (
802 self._backend.get_callback_number(),
806 self._errorDisplay.push_exception()
808 def _set_callback_label(self, uglyNumber):
809 prettyNumber = make_pretty(uglyNumber)
810 if len(prettyNumber) == 0:
811 prettyNumber = "No Callback Number"
812 self._callbackSelectButton.set_label(prettyNumber)
814 def _update_alarm_settings(self, recurrence):
816 isEnabled = self._notifyCheckbox.get_active()
817 if isEnabled != self._alarmHandler.isEnabled or recurrence != self._alarmHandler.recurrence:
818 self._alarmHandler.apply_settings(isEnabled, recurrence)
820 self.save_everything()
821 self._notifyCheckbox.set_active(self._alarmHandler.isEnabled)
822 self._minutesEntryButton.set_label("%d Minutes" % self._alarmHandler.recurrence)
824 def _on_callbackentry_clicked(self, *args):
826 actualSelection = make_pretty(self._callbackNumber)
829 (number, "%s (%s)" % (number, description))
830 for (number, description) in self._callbackList
832 defaultSelection = userOptions.get(actualSelection, actualSelection)
834 userSelection = hildonize.touch_selector_entry(
837 list(userOptions.itervalues()),
840 reversedUserOptions = dict(
841 itertools.izip(userOptions.itervalues(), userOptions.iterkeys())
843 selectedNumber = reversedUserOptions.get(userSelection, userSelection)
845 number = make_ugly(selectedNumber)
846 self._set_callback_number(number)
847 except RuntimeError, e:
848 _moduleLogger.exception("%s" % str(e))
850 self._errorDisplay.push_exception()
852 def _on_notify_toggled(self, *args):
854 if self._applyAlarmTimeoutId is not None:
855 gobject.source_remove(self._applyAlarmTimeoutId)
856 self._applyAlarmTimeoutId = None
857 self._applyAlarmTimeoutId = gobject.timeout_add(500, self._on_apply_timeout)
859 self._errorDisplay.push_exception()
861 def _on_minutes_clicked(self, *args):
862 recurrenceChoices = [
878 actualSelection = self._alarmHandler.recurrence
880 closestSelectionIndex = 0
881 for i, possible in enumerate(recurrenceChoices):
882 if possible[0] <= actualSelection:
883 closestSelectionIndex = i
884 recurrenceIndex = hildonize.touch_selector(
887 (("%s" % m[1]) for m in recurrenceChoices),
888 closestSelectionIndex,
890 recurrence = recurrenceChoices[recurrenceIndex][0]
892 self._update_alarm_settings(recurrence)
893 except RuntimeError, e:
894 _moduleLogger.exception("%s" % str(e))
896 self._errorDisplay.push_exception()
898 def _on_apply_timeout(self, *args):
900 self._applyAlarmTimeoutId = None
902 self._update_alarm_settings(self._alarmHandler.recurrence)
904 self._errorDisplay.push_exception()
907 def _on_missed_toggled(self, *args):
909 self._notifyOnMissed = self._missedCheckbox.get_active()
910 self.save_everything()
912 self._errorDisplay.push_exception()
914 def _on_voicemail_toggled(self, *args):
916 self._notifyOnVoicemail = self._voicemailCheckbox.get_active()
917 self.save_everything()
919 self._errorDisplay.push_exception()
921 def _on_sms_toggled(self, *args):
923 self._notifyOnSms = self._smsCheckbox.get_active()
924 self.save_everything()
926 self._errorDisplay.push_exception()
929 class CallHistoryView(object):
937 HISTORY_ITEM_TYPES = ["All", "Received", "Missed", "Placed"]
939 def __init__(self, widgetTree, backend, errorDisplay):
940 self._errorDisplay = errorDisplay
941 self._backend = backend
943 self._isPopulated = False
944 self._historymodel = gtk.ListStore(
945 gobject.TYPE_STRING, # number
946 gobject.TYPE_STRING, # date
947 gobject.TYPE_STRING, # action
948 gobject.TYPE_STRING, # from
949 gobject.TYPE_STRING, # from id
951 self._historymodelfiltered = self._historymodel.filter_new()
952 self._historymodelfiltered.set_visible_func(self._is_history_visible)
953 self._historyview = widgetTree.get_widget("historyview")
954 self._historyviewselection = None
955 self._onRecentviewRowActivatedId = 0
957 textrenderer = gtk.CellRendererText()
958 textrenderer.set_property("yalign", 0)
959 self._dateColumn = gtk.TreeViewColumn("Date")
960 self._dateColumn.pack_start(textrenderer, expand=True)
961 self._dateColumn.add_attribute(textrenderer, "text", self.DATE_IDX)
963 textrenderer = gtk.CellRendererText()
964 textrenderer.set_property("yalign", 0)
965 self._actionColumn = gtk.TreeViewColumn("Action")
966 self._actionColumn.pack_start(textrenderer, expand=True)
967 self._actionColumn.add_attribute(textrenderer, "text", self.ACTION_IDX)
969 textrenderer = gtk.CellRendererText()
970 textrenderer.set_property("yalign", 0)
971 textrenderer.set_property("ellipsize", pango.ELLIPSIZE_END)
972 textrenderer.set_property("width-chars", len("1 (555) 555-1234"))
973 self._numberColumn = gtk.TreeViewColumn("Number")
974 self._numberColumn.pack_start(textrenderer, expand=True)
975 self._numberColumn.add_attribute(textrenderer, "text", self.NUMBER_IDX)
977 textrenderer = gtk.CellRendererText()
978 textrenderer.set_property("yalign", 0)
979 hildonize.set_cell_thumb_selectable(textrenderer)
980 self._nameColumn = gtk.TreeViewColumn("From")
981 self._nameColumn.pack_start(textrenderer, expand=True)
982 self._nameColumn.add_attribute(textrenderer, "text", self.FROM_IDX)
983 self._nameColumn.set_sizing(gtk.TREE_VIEW_COLUMN_FIXED)
985 self._window = gtk_toolbox.find_parent_window(self._historyview)
987 self._historyFilterSelector = widgetTree.get_widget("historyFilterSelector")
988 self._historyFilterSelector.connect("clicked", self._on_history_filter_clicked)
989 self._selectedFilter = "All"
991 self._updateSink = gtk_toolbox.threaded_stage(
993 self._idly_populate_historyview,
994 gtk_toolbox.null_sink(),
999 assert self._backend.is_authed(), "Attempting to enable backend while not logged in"
1000 self._historyFilterSelector.set_label(self._selectedFilter)
1002 self._historyview.set_model(self._historymodelfiltered)
1003 self._historyview.set_fixed_height_mode(False)
1005 self._historyview.append_column(self._dateColumn)
1006 self._historyview.append_column(self._actionColumn)
1007 self._historyview.append_column(self._numberColumn)
1008 self._historyview.append_column(self._nameColumn)
1009 self._historyviewselection = self._historyview.get_selection()
1010 self._historyviewselection.set_mode(gtk.SELECTION_SINGLE)
1012 self._onRecentviewRowActivatedId = self._historyview.connect("row-activated", self._on_historyview_row_activated)
1015 self._historyview.disconnect(self._onRecentviewRowActivatedId)
1019 self._historyview.remove_column(self._dateColumn)
1020 self._historyview.remove_column(self._actionColumn)
1021 self._historyview.remove_column(self._nameColumn)
1022 self._historyview.remove_column(self._numberColumn)
1023 self._historyview.set_model(None)
1025 def add_contact(self, *args, **kwds):
1027 @note Actual dial function is patched in later
1029 raise NotImplementedError("Horrible unknown error has occurred")
1031 def update(self, force = False):
1032 if not force and self._isPopulated:
1034 self._updateSink.send(())
1038 self._isPopulated = False
1039 self._historymodel.clear()
1043 return "Recent Calls"
1045 def load_settings(self, config, sectionName):
1047 self._selectedFilter = config.get(sectionName, "filter")
1048 if self._selectedFilter not in self.HISTORY_ITEM_TYPES:
1049 self._messageType = self.HISTORY_ITEM_TYPES[0]
1050 except ConfigParser.NoOptionError:
1053 def save_settings(self, config, sectionName):
1055 @note Thread Agnostic
1057 config.set(sectionName, "filter", self._selectedFilter)
1059 def _is_history_visible(self, model, iter):
1061 action = model.get_value(iter, self.ACTION_IDX)
1063 return False # this seems weird but oh well
1065 if self._selectedFilter in [action, "All"]:
1069 except Exception, e:
1070 self._errorDisplay.push_exception()
1072 def _idly_populate_historyview(self):
1073 with gtk_toolbox.gtk_lock():
1074 banner = hildonize.show_busy_banner_start(self._window, "Loading Call History")
1076 self._historymodel.clear()
1077 self._isPopulated = True
1080 historyItems = self._backend.get_recent()
1081 except Exception, e:
1082 self._errorDisplay.push_exception_with_lock()
1083 self._isPopulated = False
1087 gv_backend.decorate_recent(data)
1088 for data in gv_backend.sort_messages(historyItems)
1091 for contactId, personName, phoneNumber, date, action in historyItems:
1093 personName = "Unknown"
1094 date = abbrev_relative_date(date)
1095 prettyNumber = phoneNumber[2:] if phoneNumber.startswith("+1") else phoneNumber
1096 prettyNumber = make_pretty(prettyNumber)
1097 item = (prettyNumber, date, action.capitalize(), personName, contactId)
1098 with gtk_toolbox.gtk_lock():
1099 self._historymodel.append(item)
1100 except Exception, e:
1101 self._errorDisplay.push_exception_with_lock()
1103 with gtk_toolbox.gtk_lock():
1104 hildonize.show_busy_banner_end(banner)
1108 def _on_history_filter_clicked(self, *args, **kwds):
1110 selectedComboIndex = self.HISTORY_ITEM_TYPES.index(self._selectedFilter)
1113 newSelectedComboIndex = hildonize.touch_selector(
1116 self.HISTORY_ITEM_TYPES,
1119 except RuntimeError:
1122 option = self.HISTORY_ITEM_TYPES[newSelectedComboIndex]
1123 self._selectedFilter = option
1124 self._historyFilterSelector.set_label(self._selectedFilter)
1125 self._historymodelfiltered.refilter()
1126 except Exception, e:
1127 self._errorDisplay.push_exception()
1129 def _on_historyview_row_activated(self, treeview, path, view_column):
1131 childPath = self._historymodelfiltered.convert_path_to_child_path(path)
1132 itr = self._historymodel.get_iter(childPath)
1136 number = self._historymodel.get_value(itr, self.NUMBER_IDX)
1137 number = make_ugly(number)
1138 description = self._historymodel.get_value(itr, self.FROM_IDX)
1139 contactId = self._historymodel.get_value(itr, self.FROM_ID_IDX)
1141 contactPhoneNumbers = list(self._backend.get_contact_details(contactId))
1143 (number == make_ugly(contactNumber) or number[1:] == make_ugly(contactNumber))
1144 for (numberDescription, contactNumber) in contactPhoneNumbers
1147 defaultIndex = defaultMatches.index(True)
1149 contactPhoneNumbers.append(("Other", number))
1150 defaultIndex = len(contactPhoneNumbers)-1
1152 "Could not find contact %r's number %s among %r" % (
1153 contactId, number, contactPhoneNumbers
1157 contactPhoneNumbers = [("Phone", number)]
1161 contactPhoneNumbers,
1162 messages = (description, ),
1163 parent = self._window,
1164 defaultIndex = defaultIndex,
1166 self._historyviewselection.unselect_all()
1167 except Exception, e:
1168 self._errorDisplay.push_exception()
1171 class MessagesView(object):
1179 MESSAGE_DATA_IDX = 6
1181 NO_MESSAGES = "None"
1182 VOICEMAIL_MESSAGES = "Voicemail"
1183 TEXT_MESSAGES = "Texts"
1184 ALL_TYPES = "All Messages"
1185 MESSAGE_TYPES = [NO_MESSAGES, VOICEMAIL_MESSAGES, TEXT_MESSAGES, ALL_TYPES]
1187 UNREAD_STATUS = "Unread"
1188 UNARCHIVED_STATUS = "Inbox"
1190 MESSAGE_STATUSES = [UNREAD_STATUS, UNARCHIVED_STATUS, ALL_STATUS]
1192 def __init__(self, widgetTree, backend, errorDisplay):
1193 self._errorDisplay = errorDisplay
1194 self._backend = backend
1196 self._isPopulated = False
1197 self._messagemodel = gtk.ListStore(
1198 gobject.TYPE_STRING, # number
1199 gobject.TYPE_STRING, # date
1200 gobject.TYPE_STRING, # header
1201 gobject.TYPE_STRING, # message
1203 gobject.TYPE_STRING, # from id
1204 object, # message data
1206 self._messagemodelfiltered = self._messagemodel.filter_new()
1207 self._messagemodelfiltered.set_visible_func(self._is_message_visible)
1208 self._messageview = widgetTree.get_widget("messages_view")
1209 self._messageviewselection = None
1210 self._onMessageviewRowActivatedId = 0
1212 self._messageRenderer = gtk.CellRendererText()
1213 self._messageRenderer.set_property("wrap-mode", pango.WRAP_WORD)
1214 self._messageRenderer.set_property("wrap-width", 500)
1215 self._messageColumn = gtk.TreeViewColumn("Messages")
1216 self._messageColumn.pack_start(self._messageRenderer, expand=True)
1217 self._messageColumn.add_attribute(self._messageRenderer, "markup", self.MESSAGE_IDX)
1218 self._messageColumn.set_sizing(gtk.TREE_VIEW_COLUMN_FIXED)
1220 self._window = gtk_toolbox.find_parent_window(self._messageview)
1222 self._messageTypeButton = widgetTree.get_widget("messageTypeButton")
1223 self._onMessageTypeClickedId = 0
1224 self._messageType = self.ALL_TYPES
1225 self._messageStatusButton = widgetTree.get_widget("messageStatusButton")
1226 self._onMessageStatusClickedId = 0
1227 self._messageStatus = self.ALL_STATUS
1229 self._updateSink = gtk_toolbox.threaded_stage(
1231 self._idly_populate_messageview,
1232 gtk_toolbox.null_sink(),
1237 assert self._backend.is_authed(), "Attempting to enable backend while not logged in"
1238 self._messageview.set_model(self._messagemodelfiltered)
1239 self._messageview.set_headers_visible(False)
1240 self._messageview.set_fixed_height_mode(False)
1242 self._messageview.append_column(self._messageColumn)
1243 self._messageviewselection = self._messageview.get_selection()
1244 self._messageviewselection.set_mode(gtk.SELECTION_SINGLE)
1246 self._messageTypeButton.set_label(self._messageType)
1247 self._messageStatusButton.set_label(self._messageStatus)
1249 self._onMessageviewRowActivatedId = self._messageview.connect(
1250 "row-activated", self._on_messageview_row_activated
1252 self._onMessageTypeClickedId = self._messageTypeButton.connect(
1253 "clicked", self._on_message_type_clicked
1255 self._onMessageStatusClickedId = self._messageStatusButton.connect(
1256 "clicked", self._on_message_status_clicked
1260 self._messageview.disconnect(self._onMessageviewRowActivatedId)
1261 self._messageTypeButton.disconnect(self._onMessageTypeClickedId)
1262 self._messageStatusButton.disconnect(self._onMessageStatusClickedId)
1266 self._messageview.remove_column(self._messageColumn)
1267 self._messageview.set_model(None)
1269 def add_contact(self, *args, **kwds):
1271 @note Actual dial function is patched in later
1273 raise NotImplementedError("Horrible unknown error has occurred")
1275 def update(self, force = False):
1276 if not force and self._isPopulated:
1278 self._updateSink.send(())
1282 self._isPopulated = False
1283 self._messagemodel.clear()
1289 def load_settings(self, config, sectionName):
1291 self._messageType = config.get(sectionName, "type")
1292 if self._messageType not in self.MESSAGE_TYPES:
1293 self._messageType = self.ALL_TYPES
1294 self._messageStatus = config.get(sectionName, "status")
1295 if self._messageStatus not in self.MESSAGE_STATUSES:
1296 self._messageStatus = self.ALL_STATUS
1297 except ConfigParser.NoOptionError:
1300 def save_settings(self, config, sectionName):
1302 @note Thread Agnostic
1304 config.set(sectionName, "status", self._messageStatus)
1305 config.set(sectionName, "type", self._messageType)
1307 def _is_message_visible(self, model, iter):
1309 message = model.get_value(iter, self.MESSAGE_DATA_IDX)
1311 return False # this seems weird but oh well
1312 return self._filter_messages(message, self._messageType, self._messageStatus)
1313 except Exception, e:
1314 self._errorDisplay.push_exception()
1317 def _filter_messages(cls, message, type, status):
1318 if type == cls.ALL_TYPES:
1321 messageType = message["type"]
1322 isType = messageType == type
1324 if status == cls.ALL_STATUS:
1327 isUnarchived = not message["isArchived"]
1328 isUnread = not message["isRead"]
1329 if status == cls.UNREAD_STATUS:
1330 isStatus = isUnarchived and isUnread
1331 elif status == cls.UNARCHIVED_STATUS:
1332 isStatus = isUnarchived
1334 assert "Status %s is bad for %r" % (status, message)
1336 return isType and isStatus
1338 _MIN_MESSAGES_SHOWN = 4
1340 def _idly_populate_messageview(self):
1341 with gtk_toolbox.gtk_lock():
1342 banner = hildonize.show_busy_banner_start(self._window, "Loading Messages")
1344 self._messagemodel.clear()
1345 self._isPopulated = True
1347 if self._messageType == self.NO_MESSAGES:
1351 messageItems = self._backend.get_messages()
1352 except Exception, e:
1353 self._errorDisplay.push_exception_with_lock()
1354 self._isPopulated = False
1358 (gv_backend.decorate_message(message), message)
1359 for message in gv_backend.sort_messages(messageItems)
1362 for (contactId, header, number, relativeDate, messages), messageData in messageItems:
1363 prettyNumber = number[2:] if number.startswith("+1") else number
1364 prettyNumber = make_pretty(prettyNumber)
1366 firstMessage = "<b>%s - %s</b> <i>(%s)</i>" % (header, prettyNumber, relativeDate)
1367 expandedMessages = [firstMessage]
1368 expandedMessages.extend(messages)
1369 if (self._MIN_MESSAGES_SHOWN + 1) < len(messages):
1370 firstMessage = "<b>%s - %s</b> <i>(%s)</i>" % (header, prettyNumber, relativeDate)
1371 secondMessage = "<i>%d Messages Hidden...</i>" % (len(messages) - self._MIN_MESSAGES_SHOWN, )
1372 collapsedMessages = [firstMessage, secondMessage]
1373 collapsedMessages.extend(messages[-(self._MIN_MESSAGES_SHOWN+0):])
1375 collapsedMessages = expandedMessages
1376 #collapsedMessages = _collapse_message(collapsedMessages, 60, self._MIN_MESSAGES_SHOWN)
1378 number = make_ugly(number)
1380 row = number, relativeDate, header, "\n".join(collapsedMessages), expandedMessages, contactId, messageData
1381 with gtk_toolbox.gtk_lock():
1382 self._messagemodel.append(row)
1383 except Exception, e:
1384 self._errorDisplay.push_exception_with_lock()
1386 with gtk_toolbox.gtk_lock():
1387 hildonize.show_busy_banner_end(banner)
1388 self._messagemodelfiltered.refilter()
1392 def _on_messageview_row_activated(self, treeview, path, view_column):
1394 childPath = self._messagemodelfiltered.convert_path_to_child_path(path)
1395 itr = self._messagemodel.get_iter(childPath)
1399 number = make_ugly(self._messagemodel.get_value(itr, self.NUMBER_IDX))
1400 description = self._messagemodel.get_value(itr, self.MESSAGES_IDX)
1402 contactId = self._messagemodel.get_value(itr, self.FROM_ID_IDX)
1404 contactPhoneNumbers = list(self._backend.get_contact_details(contactId))
1406 (number == make_ugly(contactNumber) or number[1:] == make_ugly(contactNumber))
1407 for (numberDescription, contactNumber) in contactPhoneNumbers
1410 defaultIndex = defaultMatches.index(True)
1412 contactPhoneNumbers.append(("Other", number))
1413 defaultIndex = len(contactPhoneNumbers)-1
1415 "Could not find contact %r's number %s among %r" % (
1416 contactId, number, contactPhoneNumbers
1420 contactPhoneNumbers = [("Phone", number)]
1424 contactPhoneNumbers,
1425 messages = description,
1426 parent = self._window,
1427 defaultIndex = defaultIndex,
1429 self._messageviewselection.unselect_all()
1430 except Exception, e:
1431 self._errorDisplay.push_exception()
1433 def _on_message_type_clicked(self, *args, **kwds):
1435 selectedIndex = self.MESSAGE_TYPES.index(self._messageType)
1438 newSelectedIndex = hildonize.touch_selector(
1444 except RuntimeError:
1447 if selectedIndex != newSelectedIndex:
1448 self._messageType = self.MESSAGE_TYPES[newSelectedIndex]
1449 self._messageTypeButton.set_label(self._messageType)
1450 self._messagemodelfiltered.refilter()
1451 except Exception, e:
1452 self._errorDisplay.push_exception()
1454 def _on_message_status_clicked(self, *args, **kwds):
1456 selectedIndex = self.MESSAGE_STATUSES.index(self._messageStatus)
1459 newSelectedIndex = hildonize.touch_selector(
1462 self.MESSAGE_STATUSES,
1465 except RuntimeError:
1468 if selectedIndex != newSelectedIndex:
1469 self._messageStatus = self.MESSAGE_STATUSES[newSelectedIndex]
1470 self._messageStatusButton.set_label(self._messageStatus)
1471 self._messagemodelfiltered.refilter()
1472 except Exception, e:
1473 self._errorDisplay.push_exception()
1476 class ContactsView(object):
1478 CONTACT_TYPE_IDX = 0
1479 CONTACT_NAME_IDX = 1
1482 def __init__(self, widgetTree, backend, errorDisplay):
1483 self._errorDisplay = errorDisplay
1484 self._backend = backend
1486 self._addressBook = None
1487 self._selectedComboIndex = 0
1488 self._addressBookFactories = [null_backend.NullAddressBook()]
1490 self._booksList = []
1491 self._bookSelectionButton = widgetTree.get_widget("addressbookSelectButton")
1493 self._isPopulated = False
1494 self._contactsmodel = gtk.ListStore(
1495 gobject.TYPE_STRING, # Contact Type
1496 gobject.TYPE_STRING, # Contact Name
1497 gobject.TYPE_STRING, # Contact ID
1499 self._contactsviewselection = None
1500 self._contactsview = widgetTree.get_widget("contactsview")
1502 self._contactColumn = gtk.TreeViewColumn("Contact")
1503 displayContactSource = False
1504 if displayContactSource:
1505 textrenderer = gtk.CellRendererText()
1506 self._contactColumn.pack_start(textrenderer, expand=False)
1507 self._contactColumn.add_attribute(textrenderer, 'text', self.CONTACT_TYPE_IDX)
1508 textrenderer = gtk.CellRendererText()
1509 hildonize.set_cell_thumb_selectable(textrenderer)
1510 self._contactColumn.pack_start(textrenderer, expand=True)
1511 self._contactColumn.add_attribute(textrenderer, 'text', self.CONTACT_NAME_IDX)
1512 self._contactColumn.set_sizing(gtk.TREE_VIEW_COLUMN_FIXED)
1513 self._contactColumn.set_sort_column_id(1)
1514 self._contactColumn.set_visible(True)
1516 self._onContactsviewRowActivatedId = 0
1517 self._onAddressbookButtonChangedId = 0
1518 self._window = gtk_toolbox.find_parent_window(self._contactsview)
1520 self._updateSink = gtk_toolbox.threaded_stage(
1522 self._idly_populate_contactsview,
1523 gtk_toolbox.null_sink(),
1528 assert self._backend.is_authed(), "Attempting to enable backend while not logged in"
1530 self._contactsview.set_model(self._contactsmodel)
1531 self._contactsview.set_fixed_height_mode(False)
1532 self._contactsview.append_column(self._contactColumn)
1533 self._contactsviewselection = self._contactsview.get_selection()
1534 self._contactsviewselection.set_mode(gtk.SELECTION_SINGLE)
1536 del self._booksList[:]
1537 for (factoryId, bookId), (factoryName, bookName) in self.get_addressbooks():
1538 if factoryName and bookName:
1539 entryName = "%s: %s" % (factoryName, bookName)
1541 entryName = factoryName
1543 entryName = bookName
1545 entryName = "Bad name (%d)" % factoryId
1546 row = (str(factoryId), bookId, entryName)
1547 self._booksList.append(row)
1549 self._onContactsviewRowActivatedId = self._contactsview.connect("row-activated", self._on_contactsview_row_activated)
1550 self._onAddressbookButtonChangedId = self._bookSelectionButton.connect("clicked", self._on_addressbook_button_changed)
1552 if len(self._booksList) <= self._selectedComboIndex:
1553 self._selectedComboIndex = 0
1554 self._bookSelectionButton.set_label(self._booksList[self._selectedComboIndex][2])
1556 selectedFactoryId = self._booksList[self._selectedComboIndex][0]
1557 selectedBookId = self._booksList[self._selectedComboIndex][1]
1558 self.open_addressbook(selectedFactoryId, selectedBookId)
1561 self._contactsview.disconnect(self._onContactsviewRowActivatedId)
1562 self._bookSelectionButton.disconnect(self._onAddressbookButtonChangedId)
1566 self._bookSelectionButton.set_label("")
1567 self._contactsview.set_model(None)
1568 self._contactsview.remove_column(self._contactColumn)
1570 def add_contact(self, *args, **kwds):
1572 @note Actual dial function is patched in later
1574 raise NotImplementedError("Horrible unknown error has occurred")
1576 def get_addressbooks(self):
1578 @returns Iterable of ((Factory Id, Book Id), (Factory Name, Book Name))
1580 for i, factory in enumerate(self._addressBookFactories):
1581 for bookFactory, bookId, bookName in factory.get_addressbooks():
1582 yield (str(i), bookId), (factory.factory_name(), bookName)
1584 def open_addressbook(self, bookFactoryId, bookId):
1585 bookFactoryIndex = int(bookFactoryId)
1586 addressBook = self._addressBookFactories[bookFactoryIndex].open_addressbook(bookId)
1587 self._addressBook = addressBook
1589 def update(self, force = False):
1590 if not force and self._isPopulated:
1592 self._updateSink.send(())
1596 self._isPopulated = False
1597 self._contactsmodel.clear()
1598 for factory in self._addressBookFactories:
1599 factory.clear_caches()
1600 self._addressBook.clear_caches()
1602 def append(self, book):
1603 self._addressBookFactories.append(book)
1605 def extend(self, books):
1606 self._addressBookFactories.extend(books)
1612 def load_settings(self, config, sectionName):
1614 self._selectedComboIndex = config.getint(sectionName, "selectedAddressbook")
1615 except ConfigParser.NoOptionError:
1616 self._selectedComboIndex = 0
1618 def save_settings(self, config, sectionName):
1619 config.set(sectionName, "selectedAddressbook", str(self._selectedComboIndex))
1621 def _idly_populate_contactsview(self):
1622 with gtk_toolbox.gtk_lock():
1623 banner = hildonize.show_busy_banner_start(self._window, "Loading Contacts")
1626 while addressBook is not self._addressBook:
1627 addressBook = self._addressBook
1628 with gtk_toolbox.gtk_lock():
1629 self._contactsview.set_model(None)
1633 contacts = addressBook.get_contacts()
1634 except Exception, e:
1636 self._isPopulated = False
1637 self._errorDisplay.push_exception_with_lock()
1638 for contactId, contactName in contacts:
1639 contactType = addressBook.contact_source_short_name(contactId)
1640 row = contactType, contactName, contactId
1641 self._contactsmodel.append(row)
1643 with gtk_toolbox.gtk_lock():
1644 self._contactsview.set_model(self._contactsmodel)
1646 self._isPopulated = True
1647 except Exception, e:
1648 self._errorDisplay.push_exception_with_lock()
1650 with gtk_toolbox.gtk_lock():
1651 hildonize.show_busy_banner_end(banner)
1654 def _on_addressbook_button_changed(self, *args, **kwds):
1657 newSelectedComboIndex = hildonize.touch_selector(
1660 (("%s" % m[2]) for m in self._booksList),
1661 self._selectedComboIndex,
1663 except RuntimeError:
1666 selectedFactoryId = self._booksList[newSelectedComboIndex][0]
1667 selectedBookId = self._booksList[newSelectedComboIndex][1]
1669 oldAddressbook = self._addressBook
1670 self.open_addressbook(selectedFactoryId, selectedBookId)
1671 forceUpdate = True if oldAddressbook is not self._addressBook else False
1672 self.update(force=forceUpdate)
1674 self._selectedComboIndex = newSelectedComboIndex
1675 self._bookSelectionButton.set_label(self._booksList[self._selectedComboIndex][2])
1676 except Exception, e:
1677 self._errorDisplay.push_exception()
1679 def _on_contactsview_row_activated(self, treeview, path, view_column):
1681 itr = self._contactsmodel.get_iter(path)
1685 contactId = self._contactsmodel.get_value(itr, self.CONTACT_ID_IDX)
1686 contactName = self._contactsmodel.get_value(itr, self.CONTACT_NAME_IDX)
1688 contactDetails = self._addressBook.get_contact_details(contactId)
1689 except Exception, e:
1691 self._errorDisplay.push_exception()
1692 contactPhoneNumbers = [phoneNumber for phoneNumber in contactDetails]
1694 if len(contactPhoneNumbers) == 0:
1698 contactPhoneNumbers,
1699 messages = (contactName, ),
1700 parent = self._window,
1702 self._contactsviewselection.unselect_all()
1703 except Exception, e:
1704 self._errorDisplay.push_exception()