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)
72 def _make_pretty_with_areacodde(phonenumber):
73 prettynumber = "(%s)" % (phonenumber[0:3], )
74 if 3 < len(phonenumber):
75 prettynumber += " %s" % (phonenumber[3:6], )
76 if 6 < len(phonenumber):
77 prettynumber += "-%s" % (phonenumber[6:], )
81 def _make_pretty_local(phonenumber):
82 prettynumber = "%s" % (phonenumber[0:3], )
83 if 3 < len(phonenumber):
84 prettynumber += "-%s" % (phonenumber[3:], )
88 def _make_pretty_international(phonenumber):
89 prettynumber = phonenumber
90 if phonenumber.startswith("0"):
91 prettynumber = "+%s " % (phonenumber[0:3], )
92 if 3 < len(phonenumber):
93 prettynumber += _make_pretty_with_areacodde(phonenumber[3:])
94 if phonenumber.startswith("1"):
96 prettynumber += _make_pretty_with_areacodde(phonenumber[1:])
100 def make_pretty(phonenumber):
102 Function to take a phone number and return the pretty version
104 if phonenumber begins with 0:
106 if phonenumber begins with 1: ( for gizmo callback numbers )
108 if phonenumber is 13 digits:
110 if phonenumber is 10 digits:
112 >>> make_pretty("12")
114 >>> make_pretty("1234567")
116 >>> make_pretty("2345678901")
118 >>> make_pretty("12345678901")
120 >>> make_pretty("01234567890")
122 >>> make_pretty("+01234567890")
124 >>> make_pretty("+12")
126 >>> make_pretty("+123")
128 >>> make_pretty("+1234")
131 if phonenumber is None or phonenumber is "":
134 phonenumber = normalize_number(phonenumber)
136 if phonenumber[0] == "+":
137 prettynumber = _make_pretty_international(phonenumber[1:])
138 if not prettynumber.startswith("+"):
139 prettynumber = "+"+prettynumber
140 elif 8 < len(phonenumber) and phonenumber[0] in ("0", "1"):
141 prettynumber = _make_pretty_international(phonenumber)
142 elif 7 < len(phonenumber):
143 prettynumber = _make_pretty_with_areacodde(phonenumber)
144 elif 3 < len(phonenumber):
145 prettynumber = _make_pretty_local(phonenumber)
147 prettynumber = phonenumber
148 return prettynumber.strip()
151 def abbrev_relative_date(date):
153 >>> abbrev_relative_date("42 hours ago")
155 >>> abbrev_relative_date("2 days ago")
157 >>> abbrev_relative_date("4 weeks ago")
160 parts = date.split(" ")
161 return "%s %s" % (parts[0], parts[1][0])
164 def _collapse_message(messageLines, maxCharsPerLine, maxLines):
167 numLines = len(messageLines)
168 for line in messageLines[0:min(maxLines, numLines)]:
169 linesPerLine = max(1, int(len(line) / maxCharsPerLine))
170 allowedLines = maxLines - lines
171 acceptedLines = min(allowedLines, linesPerLine)
172 acceptedChars = acceptedLines * maxCharsPerLine
174 if acceptedChars < (len(line) + 3):
177 acceptedChars = len(line) # eh, might as well complete the line
179 abbrevMessage = "%s%s" % (line[0:acceptedChars], suffix)
182 lines += acceptedLines
183 if maxLines <= lines:
187 def collapse_message(message, maxCharsPerLine, maxLines):
189 >>> collapse_message("Hello", 60, 2)
191 >>> collapse_message("Hello world how are you doing today? 01234567890123456789012345678901234567890123456789012345678901234567890123456789", 60, 2)
192 'Hello world how are you doing today? 01234567890123456789012...'
193 >>> collapse_message('''Hello world how are you doing today?
194 ... 01234567890123456789
195 ... 01234567890123456789
196 ... 01234567890123456789
197 ... 01234567890123456789''', 60, 2)
198 'Hello world how are you doing today?\n01234567890123456789'
199 >>> collapse_message('''
200 ... Hello world how are you doing today? 01234567890123456789012345678901234567890123456789012345678901234567890123456789
201 ... Hello world how are you doing today? 01234567890123456789012345678901234567890123456789012345678901234567890123456789
202 ... Hello world how are you doing today? 01234567890123456789012345678901234567890123456789012345678901234567890123456789
203 ... Hello world how are you doing today? 01234567890123456789012345678901234567890123456789012345678901234567890123456789
204 ... Hello world how are you doing today? 01234567890123456789012345678901234567890123456789012345678901234567890123456789
205 ... Hello world how are you doing today? 01234567890123456789012345678901234567890123456789012345678901234567890123456789''', 60, 2)
206 '\nHello world how are you doing today? 01234567890123456789012...'
208 messageLines = message.split("\n")
209 return "\n".join(_collapse_message(messageLines, maxCharsPerLine, maxLines))
212 class SmsEntryWindow(object):
216 def __init__(self, widgetTree):
217 self._clipboard = gtk.clipboard_get()
218 self._widgetTree = widgetTree
219 self._window = self._widgetTree.get_widget("smsWindow")
220 self._window.connect("delete-event", self._on_delete)
221 self._window.connect("key-press-event", self._on_key_press)
223 self._smsButton = self._widgetTree.get_widget("sendSmsButton")
224 self._smsButton.connect("clicked", self._on_send)
225 self._dialButton = self._widgetTree.get_widget("dialButton")
226 self._dialButton.connect("clicked", self._on_dial)
228 self._letterCountLabel = self._widgetTree.get_widget("smsLetterCount")
230 self._messagemodel = gtk.ListStore(gobject.TYPE_STRING)
231 self._messagesView = self._widgetTree.get_widget("smsMessages")
233 textrenderer = gtk.CellRendererText()
234 textrenderer.set_property("wrap-mode", pango.WRAP_WORD)
235 textrenderer.set_property("wrap-width", 450)
236 messageColumn = gtk.TreeViewColumn("")
237 messageColumn.pack_start(textrenderer, expand=True)
238 messageColumn.add_attribute(textrenderer, "markup", 0)
239 messageColumn.set_sizing(gtk.TREE_VIEW_COLUMN_FIXED)
240 self._messagesView.append_column(messageColumn)
241 self._messagesView.set_headers_visible(False)
242 self._messagesView.set_model(self._messagemodel)
243 self._messagesView.set_fixed_height_mode(False)
245 self._conversationView = self._messagesView.get_parent()
246 self._conversationViewPort = self._conversationView.get_parent()
247 self._scrollWindow = self._conversationViewPort.get_parent()
249 self._targetList = self._widgetTree.get_widget("smsTargetList")
250 self._phoneButton = self._widgetTree.get_widget("phoneTypeSelection")
251 self._phoneButton.connect("clicked", self._on_phone)
252 self._smsEntry = self._widgetTree.get_widget("smsEntry")
253 self._smsEntry.get_buffer().connect("changed", self._on_entry_changed)
254 self._smsEntrySize = None
258 def add_contact(self, contactDetails, messages = (), parent = None, defaultIndex = -1):
259 contactNumbers = list(self._to_contact_numbers(contactDetails))
260 assert contactNumbers
261 contactIndex = defaultIndex if defaultIndex != -1 else 0
262 contact = contactNumbers, contactIndex, messages
263 self._contacts.append(contact)
265 selector = gtk.Button(contactNumbers[0][1])
266 removeContact = gtk.Button(stock="gtk-delete")
268 row.pack_start(selector, True, True)
269 row.pack_start(removeContact, False, False)
271 self._targetList.pack_start(row)
272 selector.connect("clicked", self._on_choose_phone_n, row)
273 removeContact.connect("clicked", self._on_remove_phone_n, row)
274 self._update_button_state()
275 self._update_context()
277 if parent is not None:
278 parentSize = parent.get_size()
279 self._window.resize(parentSize[0], max(parentSize[1]-10, 100))
281 self._window.present()
283 self._smsEntry.grab_focus()
284 dx = self._conversationView.get_allocation().height - self._conversationViewPort.get_allocation().height
286 adjustment = self._scrollWindow.get_vadjustment()
287 adjustment.value = dx
290 del self._contacts[:]
292 for contactNumberSelector in list(self._targetList.get_children()):
293 self._targetList.remove(contactNumberSelector)
294 self._smsEntry.get_buffer().set_text("")
295 self._update_letter_count()
296 self._update_context()
298 def _remove_contact(self, contactIndex):
299 del self._contacts[contactIndex]
301 contactNumberSelector = list(self._targetList.get_children())[contactIndex]
302 self._targetList.remove(contactNumberSelector)
303 self._update_button_state()
304 self._update_context()
306 def _update_letter_count(self):
307 if self._smsEntrySize is None:
308 self._smsEntrySize = self._smsEntry.size_request()
310 self._smsEntry.set_size_request(*self._smsEntrySize)
311 entryLength = self._smsEntry.get_buffer().get_char_count()
313 numTexts, numCharInText = divmod(entryLength, self.MAX_CHAR)
315 self._letterCountLabel.set_text("%s.%s" % (numTexts, numCharInText))
317 self._letterCountLabel.set_text("%s" % (numCharInText, ))
319 self._update_button_state()
321 def _update_context(self):
322 self._messagemodel.clear()
323 if len(self._contacts) == 0:
324 self._messagesView.hide()
325 self._targetList.hide()
326 self._phoneButton.hide()
327 self._phoneButton.set_label("Error: You shouldn't see this")
328 elif len(self._contacts) == 1:
329 contactNumbers, index, messages = self._contacts[0]
331 self._messagesView.show()
332 for message in messages:
334 self._messagemodel.append(row)
335 messagesSelection = self._messagesView.get_selection()
336 messagesSelection.select_path((len(messages)-1, ))
338 self._messagesView.hide()
339 self._targetList.hide()
340 self._phoneButton.show()
341 self._phoneButton.set_label(contactNumbers[index][1])
342 if 1 < len(contactNumbers):
343 self._phoneButton.set_sensitive(True)
345 self._phoneButton.set_sensitive(False)
347 self._messagesView.hide()
348 self._targetList.show()
349 self._phoneButton.hide()
350 self._phoneButton.set_label("Error: You shouldn't see this")
352 def _update_button_state(self):
353 if len(self._contacts) == 0:
354 self._dialButton.set_sensitive(False)
355 self._smsButton.set_sensitive(False)
356 elif len(self._contacts) == 1:
357 entryLength = self._smsEntry.get_buffer().get_char_count()
359 self._dialButton.set_sensitive(True)
360 self._smsButton.set_sensitive(False)
362 self._dialButton.set_sensitive(False)
363 self._smsButton.set_sensitive(True)
365 self._dialButton.set_sensitive(False)
366 self._smsButton.set_sensitive(True)
368 def _to_contact_numbers(self, contactDetails):
369 for phoneType, phoneNumber in contactDetails:
370 display = " - ".join((make_pretty(phoneNumber), phoneType))
371 yield (phoneNumber, display)
377 def _request_number(self, contactIndex):
378 contactNumbers, index, messages = self._contacts[contactIndex]
379 assert 0 <= index, "%r" % index
381 index = hildonize.touch_selector(
384 (description for (number, description) in contactNumbers),
387 self._contacts[contactIndex] = contactNumbers, index, messages
389 def send_sms(self, numbers, message):
390 raise NotImplementedError()
392 def dial(self, number):
393 raise NotImplementedError()
395 def _on_phone(self, *args):
397 assert len(self._contacts) == 1
398 self._request_number(0)
400 contactNumbers, numberIndex, messages = self._contacts[0]
401 self._phoneButton.set_label(contactNumbers[numberIndex][1])
402 row = list(self._targetList.get_children())[0]
403 phoneButton = list(row.get_children())[0]
404 phoneButton.set_label(contactNumbers[numberIndex][1])
406 _moduleLogger.exception("%s" % str(e))
408 def _on_choose_phone_n(self, button, row):
410 assert 1 < len(self._contacts)
411 targetList = list(self._targetList.get_children())
412 index = targetList.index(row)
413 self._request_number(index)
415 contactNumbers, numberIndex, messages = self._contacts[0]
416 phoneButton = list(row.get_children())[0]
417 phoneButton.set_label(contactNumbers[numberIndex][1])
419 _moduleLogger.exception("%s" % str(e))
421 def _on_remove_phone_n(self, button, row):
423 assert 1 < len(self._contacts)
424 targetList = list(self._targetList.get_children())
425 index = targetList.index(row)
427 del self._contacts[index]
428 self._targetList.remove(row)
429 self._update_context()
430 self._update_button_state()
432 _moduleLogger.exception("%s" % str(e))
434 def _on_entry_changed(self, *args):
435 self._update_letter_count()
437 def _on_send(self, *args):
438 assert 0 < len(self._contacts), "%r" % self._contacts
440 make_ugly(contact[0][contact[1]][0])
441 for contact in self._contacts
444 entryBuffer = self._smsEntry.get_buffer()
445 enteredMessage = entryBuffer.get_text(entryBuffer.get_start_iter(), entryBuffer.get_end_iter())
446 enteredMessage = enteredMessage.strip()
447 assert enteredMessage
448 self.send_sms(phoneNumbers, enteredMessage)
451 def _on_dial(self, *args):
452 assert len(self._contacts) == 1, "%r" % self._contacts
453 contact = self._contacts[0]
454 contactNumber = contact[0][contact[1]][0]
455 phoneNumber = make_ugly(contactNumber)
456 self.dial(phoneNumber)
459 def _on_delete(self, *args):
460 self._window.emit_stop_by_name("delete-event")
464 def _on_key_press(self, widget, event):
466 if event.keyval == ord("c") and event.get_state() & gtk.gdk.CONTROL_MASK:
469 for messagePart in self._messagemodel
471 self._clipboard.set_text(str(message))
473 _moduleLogger.exception(str(e))
476 class Dialpad(object):
478 def __init__(self, widgetTree, errorDisplay):
479 self._clipboard = gtk.clipboard_get()
480 self._errorDisplay = errorDisplay
482 self._numberdisplay = widgetTree.get_widget("numberdisplay")
483 self._okButton = widgetTree.get_widget("dialpadOk")
484 self._backButton = widgetTree.get_widget("back")
485 self._plusButton = widgetTree.get_widget("plus")
486 self._phonenumber = ""
487 self._prettynumber = ""
490 "on_digit_clicked": self._on_digit_clicked,
492 widgetTree.signal_autoconnect(callbackMapping)
493 self._okButton.connect("clicked", self._on_ok_clicked)
494 self._plusButton.connect("clicked", self._on_plus)
496 self._originalLabel = self._backButton.get_label()
497 self._backTapHandler = gtk_toolbox.TapOrHold(self._backButton)
498 self._backTapHandler.on_tap = self._on_backspace
499 self._backTapHandler.on_hold = self._on_clearall
500 self._backTapHandler.on_holding = self._set_clear_button
501 self._backTapHandler.on_cancel = self._reset_back_button
503 self._window = gtk_toolbox.find_parent_window(self._numberdisplay)
504 self._keyPressEventId = 0
507 self._okButton.grab_focus()
508 self._backTapHandler.enable()
509 self._keyPressEventId = self._window.connect("key-press-event", self._on_key_press)
512 self._window.disconnect(self._keyPressEventId)
513 self._keyPressEventId = 0
514 self._reset_back_button()
515 self._backTapHandler.disable()
517 def add_contact(self, *args, **kwds):
519 @note Actual dial function is patched in later
521 raise NotImplementedError("Horrible unknown error has occurred")
523 def get_number(self):
524 return self._phonenumber
526 def set_number(self, number):
528 Set the number to dial
531 self._phonenumber = make_ugly(number)
532 self._prettynumber = make_pretty(self._phonenumber)
533 self._numberdisplay.set_label("<span size='30000' weight='bold'>%s</span>" % (self._prettynumber))
535 self._errorDisplay.push_exception()
544 def load_settings(self, config, section):
547 def save_settings(self, config, section):
549 @note Thread Agnostic
553 def _on_key_press(self, widget, event):
555 if event.keyval == ord("v") and event.get_state() & gtk.gdk.CONTROL_MASK:
556 contents = self._clipboard.wait_for_text()
557 if contents is not None:
558 self.set_number(contents)
560 self._errorDisplay.push_exception()
562 def _on_ok_clicked(self, widget):
564 phoneNumber = self.get_number()
566 [("Dialer", phoneNumber)], (), self._window
570 self._errorDisplay.push_exception()
572 def _on_digit_clicked(self, widget):
574 self.set_number(self._phonenumber + widget.get_name()[-1])
576 self._errorDisplay.push_exception()
578 def _on_plus(self, *args):
580 self.set_number(self._phonenumber + "+")
582 self._errorDisplay.push_exception()
584 def _on_backspace(self, taps):
586 self.set_number(self._phonenumber[:-taps])
587 self._reset_back_button()
589 self._errorDisplay.push_exception()
591 def _on_clearall(self, taps):
594 self._reset_back_button()
596 self._errorDisplay.push_exception()
599 def _set_clear_button(self):
601 self._backButton.set_label("gtk-clear")
603 self._errorDisplay.push_exception()
605 def _reset_back_button(self):
607 self._backButton.set_label(self._originalLabel)
609 self._errorDisplay.push_exception()
612 class AccountInfo(object):
614 def __init__(self, widgetTree, backend, alarmHandler, errorDisplay):
615 self._errorDisplay = errorDisplay
616 self._backend = backend
617 self._isPopulated = False
618 self._alarmHandler = alarmHandler
619 self._notifyOnMissed = False
620 self._notifyOnVoicemail = False
621 self._notifyOnSms = False
623 self._callbackList = []
624 self._accountViewNumberDisplay = widgetTree.get_widget("gcnumber_display")
625 self._callbackSelectButton = widgetTree.get_widget("callbackSelectButton")
626 self._onCallbackSelectChangedId = 0
628 self._notifyCheckbox = widgetTree.get_widget("notifyCheckbox")
629 self._minutesEntryButton = widgetTree.get_widget("minutesEntryButton")
630 self._missedCheckbox = widgetTree.get_widget("missedCheckbox")
631 self._voicemailCheckbox = widgetTree.get_widget("voicemailCheckbox")
632 self._smsCheckbox = widgetTree.get_widget("smsCheckbox")
633 self._onNotifyToggled = 0
634 self._onMinutesChanged = 0
635 self._onMissedToggled = 0
636 self._onVoicemailToggled = 0
637 self._onSmsToggled = 0
638 self._applyAlarmTimeoutId = None
640 self._window = gtk_toolbox.find_parent_window(self._minutesEntryButton)
641 self._callbackNumber = ""
644 assert self._backend.is_authed(), "Attempting to enable backend while not logged in"
646 self._accountViewNumberDisplay.set_use_markup(True)
647 self.set_account_number("")
649 del self._callbackList[:]
650 self._onCallbackSelectChangedId = self._callbackSelectButton.connect("clicked", self._on_callbackentry_clicked)
651 self._set_callback_label("")
653 if self._alarmHandler is not None:
654 self._notifyCheckbox.set_active(self._alarmHandler.isEnabled)
655 self._minutesEntryButton.set_label("%d minutes" % self._alarmHandler.recurrence)
656 self._missedCheckbox.set_active(self._notifyOnMissed)
657 self._voicemailCheckbox.set_active(self._notifyOnVoicemail)
658 self._smsCheckbox.set_active(self._notifyOnSms)
660 self._onNotifyToggled = self._notifyCheckbox.connect("toggled", self._on_notify_toggled)
661 self._onMinutesChanged = self._minutesEntryButton.connect("clicked", self._on_minutes_clicked)
662 self._onMissedToggled = self._missedCheckbox.connect("toggled", self._on_missed_toggled)
663 self._onVoicemailToggled = self._voicemailCheckbox.connect("toggled", self._on_voicemail_toggled)
664 self._onSmsToggled = self._smsCheckbox.connect("toggled", self._on_sms_toggled)
666 self._notifyCheckbox.set_sensitive(False)
667 self._minutesEntryButton.set_sensitive(False)
668 self._missedCheckbox.set_sensitive(False)
669 self._voicemailCheckbox.set_sensitive(False)
670 self._smsCheckbox.set_sensitive(False)
672 self.update(force=True)
675 self._callbackSelectButton.disconnect(self._onCallbackSelectChangedId)
676 self._onCallbackSelectChangedId = 0
677 self._set_callback_label("")
679 if self._alarmHandler is not None:
680 self._notifyCheckbox.disconnect(self._onNotifyToggled)
681 self._minutesEntryButton.disconnect(self._onMinutesChanged)
682 self._missedCheckbox.disconnect(self._onNotifyToggled)
683 self._voicemailCheckbox.disconnect(self._onNotifyToggled)
684 self._smsCheckbox.disconnect(self._onNotifyToggled)
685 self._onNotifyToggled = 0
686 self._onMinutesChanged = 0
687 self._onMissedToggled = 0
688 self._onVoicemailToggled = 0
689 self._onSmsToggled = 0
691 self._notifyCheckbox.set_sensitive(True)
692 self._minutesEntryButton.set_sensitive(True)
693 self._missedCheckbox.set_sensitive(True)
694 self._voicemailCheckbox.set_sensitive(True)
695 self._smsCheckbox.set_sensitive(True)
698 del self._callbackList[:]
700 def set_account_number(self, number):
702 Displays current account number
704 self._accountViewNumberDisplay.set_label("<span size='23000' weight='bold'>%s</span>" % (number))
706 def update(self, force = False):
707 if not force and self._isPopulated:
709 self._populate_callback_combo()
710 self.set_account_number(self._backend.get_account_number())
714 self._set_callback_label("")
715 self.set_account_number("")
716 self._isPopulated = False
718 def save_everything(self):
719 raise NotImplementedError
723 return "Account Info"
725 def load_settings(self, config, section):
726 self._callbackNumber = make_ugly(config.get(section, "callback"))
727 self._notifyOnMissed = config.getboolean(section, "notifyOnMissed")
728 self._notifyOnVoicemail = config.getboolean(section, "notifyOnVoicemail")
729 self._notifyOnSms = config.getboolean(section, "notifyOnSms")
731 def save_settings(self, config, section):
733 @note Thread Agnostic
735 config.set(section, "callback", self._callbackNumber)
736 config.set(section, "notifyOnMissed", repr(self._notifyOnMissed))
737 config.set(section, "notifyOnVoicemail", repr(self._notifyOnVoicemail))
738 config.set(section, "notifyOnSms", repr(self._notifyOnSms))
740 def _populate_callback_combo(self):
741 self._isPopulated = True
742 del self._callbackList[:]
744 callbackNumbers = self._backend.get_callback_numbers()
746 self._errorDisplay.push_exception()
747 self._isPopulated = False
750 if len(callbackNumbers) == 0:
751 callbackNumbers = {"": "No callback numbers available"}
753 for number, description in callbackNumbers.iteritems():
754 self._callbackList.append((make_pretty(number), description))
756 self._set_callback_number(self._callbackNumber)
758 def _set_callback_number(self, number):
760 if not self._backend.is_valid_syntax(number) and 0 < len(number):
761 self._errorDisplay.push_message("%s is not a valid callback number" % number)
762 elif number == self._backend.get_callback_number() and 0 < len(number):
763 _moduleLogger.warning(
764 "Callback number already is %s" % (
765 self._backend.get_callback_number(),
768 self._set_callback_label(number)
770 if number.startswith("1747"): number = "+" + number
771 self._backend.set_callback_number(number)
772 assert make_ugly(number) == make_ugly(self._backend.get_callback_number()), "Callback number should be %s but instead is %s" % (
773 make_pretty(number), make_pretty(self._backend.get_callback_number())
775 self._callbackNumber = make_ugly(number)
776 self._set_callback_label(number)
778 "Callback number set to %s" % (
779 self._backend.get_callback_number(),
783 self._errorDisplay.push_exception()
785 def _set_callback_label(self, uglyNumber):
786 prettyNumber = make_pretty(uglyNumber)
787 if len(prettyNumber) == 0:
788 prettyNumber = "No Callback Number"
789 self._callbackSelectButton.set_label(prettyNumber)
791 def _update_alarm_settings(self, recurrence):
793 isEnabled = self._notifyCheckbox.get_active()
794 if isEnabled != self._alarmHandler.isEnabled or recurrence != self._alarmHandler.recurrence:
795 self._alarmHandler.apply_settings(isEnabled, recurrence)
797 self.save_everything()
798 self._notifyCheckbox.set_active(self._alarmHandler.isEnabled)
799 self._minutesEntryButton.set_label("%d Minutes" % self._alarmHandler.recurrence)
801 def _on_callbackentry_clicked(self, *args):
803 actualSelection = make_pretty(self._callbackNumber)
806 (number, "%s (%s)" % (number, description))
807 for (number, description) in self._callbackList
809 defaultSelection = userOptions.get(actualSelection, actualSelection)
811 userSelection = hildonize.touch_selector_entry(
814 list(userOptions.itervalues()),
817 reversedUserOptions = dict(
818 itertools.izip(userOptions.itervalues(), userOptions.iterkeys())
820 selectedNumber = reversedUserOptions.get(userSelection, userSelection)
822 number = make_ugly(selectedNumber)
823 self._set_callback_number(number)
824 except RuntimeError, e:
825 _moduleLogger.exception("%s" % str(e))
827 self._errorDisplay.push_exception()
829 def _on_notify_toggled(self, *args):
831 if self._applyAlarmTimeoutId is not None:
832 gobject.source_remove(self._applyAlarmTimeoutId)
833 self._applyAlarmTimeoutId = None
834 self._applyAlarmTimeoutId = gobject.timeout_add(500, self._on_apply_timeout)
836 self._errorDisplay.push_exception()
838 def _on_minutes_clicked(self, *args):
839 recurrenceChoices = [
855 actualSelection = self._alarmHandler.recurrence
857 closestSelectionIndex = 0
858 for i, possible in enumerate(recurrenceChoices):
859 if possible[0] <= actualSelection:
860 closestSelectionIndex = i
861 recurrenceIndex = hildonize.touch_selector(
864 (("%s" % m[1]) for m in recurrenceChoices),
865 closestSelectionIndex,
867 recurrence = recurrenceChoices[recurrenceIndex][0]
869 self._update_alarm_settings(recurrence)
870 except RuntimeError, e:
871 _moduleLogger.exception("%s" % str(e))
873 self._errorDisplay.push_exception()
875 def _on_apply_timeout(self, *args):
877 self._applyAlarmTimeoutId = None
879 self._update_alarm_settings(self._alarmHandler.recurrence)
881 self._errorDisplay.push_exception()
884 def _on_missed_toggled(self, *args):
886 self._notifyOnMissed = self._missedCheckbox.get_active()
887 self.save_everything()
889 self._errorDisplay.push_exception()
891 def _on_voicemail_toggled(self, *args):
893 self._notifyOnVoicemail = self._voicemailCheckbox.get_active()
894 self.save_everything()
896 self._errorDisplay.push_exception()
898 def _on_sms_toggled(self, *args):
900 self._notifyOnSms = self._smsCheckbox.get_active()
901 self.save_everything()
903 self._errorDisplay.push_exception()
906 class CallHistoryView(object):
914 HISTORY_ITEM_TYPES = ["All", "Received", "Missed", "Placed"]
916 def __init__(self, widgetTree, backend, errorDisplay):
917 self._errorDisplay = errorDisplay
918 self._backend = backend
920 self._isPopulated = False
921 self._historymodel = gtk.ListStore(
922 gobject.TYPE_STRING, # number
923 gobject.TYPE_STRING, # date
924 gobject.TYPE_STRING, # action
925 gobject.TYPE_STRING, # from
926 gobject.TYPE_STRING, # from id
928 self._historymodelfiltered = self._historymodel.filter_new()
929 self._historymodelfiltered.set_visible_func(self._is_history_visible)
930 self._historyview = widgetTree.get_widget("historyview")
931 self._historyviewselection = None
932 self._onRecentviewRowActivatedId = 0
934 textrenderer = gtk.CellRendererText()
935 textrenderer.set_property("yalign", 0)
936 self._dateColumn = gtk.TreeViewColumn("Date")
937 self._dateColumn.pack_start(textrenderer, expand=True)
938 self._dateColumn.add_attribute(textrenderer, "text", self.DATE_IDX)
940 textrenderer = gtk.CellRendererText()
941 textrenderer.set_property("yalign", 0)
942 self._actionColumn = gtk.TreeViewColumn("Action")
943 self._actionColumn.pack_start(textrenderer, expand=True)
944 self._actionColumn.add_attribute(textrenderer, "text", self.ACTION_IDX)
946 textrenderer = gtk.CellRendererText()
947 textrenderer.set_property("yalign", 0)
948 textrenderer.set_property("ellipsize", pango.ELLIPSIZE_END)
949 textrenderer.set_property("width-chars", len("1 (555) 555-1234"))
950 self._numberColumn = gtk.TreeViewColumn("Number")
951 self._numberColumn.pack_start(textrenderer, expand=True)
952 self._numberColumn.add_attribute(textrenderer, "text", self.NUMBER_IDX)
954 textrenderer = gtk.CellRendererText()
955 textrenderer.set_property("yalign", 0)
956 hildonize.set_cell_thumb_selectable(textrenderer)
957 self._nameColumn = gtk.TreeViewColumn("From")
958 self._nameColumn.pack_start(textrenderer, expand=True)
959 self._nameColumn.add_attribute(textrenderer, "text", self.FROM_IDX)
960 self._nameColumn.set_sizing(gtk.TREE_VIEW_COLUMN_FIXED)
962 self._window = gtk_toolbox.find_parent_window(self._historyview)
964 self._historyFilterSelector = widgetTree.get_widget("historyFilterSelector")
965 self._historyFilterSelector.connect("clicked", self._on_history_filter_clicked)
966 self._selectedFilter = "All"
968 self._updateSink = gtk_toolbox.threaded_stage(
970 self._idly_populate_historyview,
971 gtk_toolbox.null_sink(),
976 assert self._backend.is_authed(), "Attempting to enable backend while not logged in"
977 self._historyFilterSelector.set_label(self._selectedFilter)
979 self._historyview.set_model(self._historymodelfiltered)
980 self._historyview.set_fixed_height_mode(False)
982 self._historyview.append_column(self._dateColumn)
983 self._historyview.append_column(self._actionColumn)
984 self._historyview.append_column(self._numberColumn)
985 self._historyview.append_column(self._nameColumn)
986 self._historyviewselection = self._historyview.get_selection()
987 self._historyviewselection.set_mode(gtk.SELECTION_SINGLE)
989 self._onRecentviewRowActivatedId = self._historyview.connect("row-activated", self._on_historyview_row_activated)
992 self._historyview.disconnect(self._onRecentviewRowActivatedId)
996 self._historyview.remove_column(self._dateColumn)
997 self._historyview.remove_column(self._actionColumn)
998 self._historyview.remove_column(self._nameColumn)
999 self._historyview.remove_column(self._numberColumn)
1000 self._historyview.set_model(None)
1002 def add_contact(self, *args, **kwds):
1004 @note Actual dial function is patched in later
1006 raise NotImplementedError("Horrible unknown error has occurred")
1008 def update(self, force = False):
1009 if not force and self._isPopulated:
1011 self._updateSink.send(())
1015 self._isPopulated = False
1016 self._historymodel.clear()
1020 return "Recent Calls"
1022 def load_settings(self, config, sectionName):
1024 self._selectedFilter = config.get(sectionName, "filter")
1025 if self._selectedFilter not in self.HISTORY_ITEM_TYPES:
1026 self._messageType = self.HISTORY_ITEM_TYPES[0]
1027 except ConfigParser.NoOptionError:
1030 def save_settings(self, config, sectionName):
1032 @note Thread Agnostic
1034 config.set(sectionName, "filter", self._selectedFilter)
1036 def _is_history_visible(self, model, iter):
1038 action = model.get_value(iter, self.ACTION_IDX)
1040 return False # this seems weird but oh well
1042 if self._selectedFilter in [action, "All"]:
1046 except Exception, e:
1047 self._errorDisplay.push_exception()
1049 def _idly_populate_historyview(self):
1050 with gtk_toolbox.gtk_lock():
1051 banner = hildonize.show_busy_banner_start(self._window, "Loading Call History")
1053 self._historymodel.clear()
1054 self._isPopulated = True
1057 historyItems = self._backend.get_recent()
1058 except Exception, e:
1059 self._errorDisplay.push_exception_with_lock()
1060 self._isPopulated = False
1064 gv_backend.decorate_recent(data)
1065 for data in gv_backend.sort_messages(historyItems)
1068 for contactId, personName, phoneNumber, date, action in historyItems:
1070 personName = "Unknown"
1071 date = abbrev_relative_date(date)
1072 prettyNumber = phoneNumber[2:] if phoneNumber.startswith("+1") else phoneNumber
1073 prettyNumber = make_pretty(prettyNumber)
1074 item = (prettyNumber, date, action.capitalize(), personName, contactId)
1075 with gtk_toolbox.gtk_lock():
1076 self._historymodel.append(item)
1077 except Exception, e:
1078 self._errorDisplay.push_exception_with_lock()
1080 with gtk_toolbox.gtk_lock():
1081 hildonize.show_busy_banner_end(banner)
1085 def _on_history_filter_clicked(self, *args, **kwds):
1087 selectedComboIndex = self.HISTORY_ITEM_TYPES.index(self._selectedFilter)
1090 newSelectedComboIndex = hildonize.touch_selector(
1093 self.HISTORY_ITEM_TYPES,
1096 except RuntimeError:
1099 option = self.HISTORY_ITEM_TYPES[newSelectedComboIndex]
1100 self._selectedFilter = option
1101 self._historyFilterSelector.set_label(self._selectedFilter)
1102 self._historymodelfiltered.refilter()
1103 except Exception, e:
1104 self._errorDisplay.push_exception()
1106 def _on_historyview_row_activated(self, treeview, path, view_column):
1108 childPath = self._historymodelfiltered.convert_path_to_child_path(path)
1109 itr = self._historymodel.get_iter(childPath)
1113 number = self._historymodel.get_value(itr, self.NUMBER_IDX)
1114 number = make_ugly(number)
1115 description = self._historymodel.get_value(itr, self.FROM_IDX)
1116 contactId = self._historymodel.get_value(itr, self.FROM_ID_IDX)
1118 contactPhoneNumbers = list(self._backend.get_contact_details(contactId))
1120 (number == make_ugly(contactNumber) or number[1:] == make_ugly(contactNumber))
1121 for (numberDescription, contactNumber) in contactPhoneNumbers
1124 defaultIndex = defaultMatches.index(True)
1126 contactPhoneNumbers.append(("Other", number))
1127 defaultIndex = len(contactPhoneNumbers)-1
1129 "Could not find contact %r's number %s among %r" % (
1130 contactId, number, contactPhoneNumbers
1134 contactPhoneNumbers = [("Phone", number)]
1138 contactPhoneNumbers,
1139 messages = (description, ),
1140 parent = self._window,
1141 defaultIndex = defaultIndex,
1143 self._historyviewselection.unselect_all()
1144 except Exception, e:
1145 self._errorDisplay.push_exception()
1148 class MessagesView(object):
1156 MESSAGE_DATA_IDX = 6
1158 NO_MESSAGES = "None"
1159 VOICEMAIL_MESSAGES = "Voicemail"
1160 TEXT_MESSAGES = "Texts"
1161 ALL_TYPES = "All Messages"
1162 MESSAGE_TYPES = [NO_MESSAGES, VOICEMAIL_MESSAGES, TEXT_MESSAGES, ALL_TYPES]
1164 UNREAD_STATUS = "Unread"
1165 UNARCHIVED_STATUS = "Inbox"
1167 MESSAGE_STATUSES = [UNREAD_STATUS, UNARCHIVED_STATUS, ALL_STATUS]
1169 def __init__(self, widgetTree, backend, errorDisplay):
1170 self._errorDisplay = errorDisplay
1171 self._backend = backend
1173 self._isPopulated = False
1174 self._messagemodel = gtk.ListStore(
1175 gobject.TYPE_STRING, # number
1176 gobject.TYPE_STRING, # date
1177 gobject.TYPE_STRING, # header
1178 gobject.TYPE_STRING, # message
1180 gobject.TYPE_STRING, # from id
1181 object, # message data
1183 self._messagemodelfiltered = self._messagemodel.filter_new()
1184 self._messagemodelfiltered.set_visible_func(self._is_message_visible)
1185 self._messageview = widgetTree.get_widget("messages_view")
1186 self._messageviewselection = None
1187 self._onMessageviewRowActivatedId = 0
1189 self._messageRenderer = gtk.CellRendererText()
1190 self._messageRenderer.set_property("wrap-mode", pango.WRAP_WORD)
1191 self._messageRenderer.set_property("wrap-width", 500)
1192 self._messageColumn = gtk.TreeViewColumn("Messages")
1193 self._messageColumn.pack_start(self._messageRenderer, expand=True)
1194 self._messageColumn.add_attribute(self._messageRenderer, "markup", self.MESSAGE_IDX)
1195 self._messageColumn.set_sizing(gtk.TREE_VIEW_COLUMN_FIXED)
1197 self._window = gtk_toolbox.find_parent_window(self._messageview)
1199 self._messageTypeButton = widgetTree.get_widget("messageTypeButton")
1200 self._onMessageTypeClickedId = 0
1201 self._messageType = self.ALL_TYPES
1202 self._messageStatusButton = widgetTree.get_widget("messageStatusButton")
1203 self._onMessageStatusClickedId = 0
1204 self._messageStatus = self.ALL_STATUS
1206 self._updateSink = gtk_toolbox.threaded_stage(
1208 self._idly_populate_messageview,
1209 gtk_toolbox.null_sink(),
1214 assert self._backend.is_authed(), "Attempting to enable backend while not logged in"
1215 self._messageview.set_model(self._messagemodelfiltered)
1216 self._messageview.set_headers_visible(False)
1217 self._messageview.set_fixed_height_mode(False)
1219 self._messageview.append_column(self._messageColumn)
1220 self._messageviewselection = self._messageview.get_selection()
1221 self._messageviewselection.set_mode(gtk.SELECTION_SINGLE)
1223 self._messageTypeButton.set_label(self._messageType)
1224 self._messageStatusButton.set_label(self._messageStatus)
1226 self._onMessageviewRowActivatedId = self._messageview.connect(
1227 "row-activated", self._on_messageview_row_activated
1229 self._onMessageTypeClickedId = self._messageTypeButton.connect(
1230 "clicked", self._on_message_type_clicked
1232 self._onMessageStatusClickedId = self._messageStatusButton.connect(
1233 "clicked", self._on_message_status_clicked
1237 self._messageview.disconnect(self._onMessageviewRowActivatedId)
1238 self._messageTypeButton.disconnect(self._onMessageTypeClickedId)
1239 self._messageStatusButton.disconnect(self._onMessageStatusClickedId)
1243 self._messageview.remove_column(self._messageColumn)
1244 self._messageview.set_model(None)
1246 def add_contact(self, *args, **kwds):
1248 @note Actual dial function is patched in later
1250 raise NotImplementedError("Horrible unknown error has occurred")
1252 def update(self, force = False):
1253 if not force and self._isPopulated:
1255 self._updateSink.send(())
1259 self._isPopulated = False
1260 self._messagemodel.clear()
1266 def load_settings(self, config, sectionName):
1268 self._messageType = config.get(sectionName, "type")
1269 if self._messageType not in self.MESSAGE_TYPES:
1270 self._messageType = self.ALL_TYPES
1271 self._messageStatus = config.get(sectionName, "status")
1272 if self._messageStatus not in self.MESSAGE_STATUSES:
1273 self._messageStatus = self.ALL_STATUS
1274 except ConfigParser.NoOptionError:
1277 def save_settings(self, config, sectionName):
1279 @note Thread Agnostic
1281 config.set(sectionName, "status", self._messageStatus)
1282 config.set(sectionName, "type", self._messageType)
1284 def _is_message_visible(self, model, iter):
1286 message = model.get_value(iter, self.MESSAGE_DATA_IDX)
1288 return False # this seems weird but oh well
1289 return self._filter_messages(message, self._messageType, self._messageStatus)
1290 except Exception, e:
1291 self._errorDisplay.push_exception()
1294 def _filter_messages(cls, message, type, status):
1295 if type == cls.ALL_TYPES:
1298 messageType = message["type"]
1299 isType = messageType == type
1301 if status == cls.ALL_STATUS:
1304 isUnarchived = not message["isArchived"]
1305 isUnread = not message["isRead"]
1306 if status == cls.UNREAD_STATUS:
1307 isStatus = isUnarchived and isUnread
1308 elif status == cls.UNARCHIVED_STATUS:
1309 isStatus = isUnarchived
1311 assert "Status %s is bad for %r" % (status, message)
1313 return isType and isStatus
1315 _MIN_MESSAGES_SHOWN = 4
1317 def _idly_populate_messageview(self):
1318 with gtk_toolbox.gtk_lock():
1319 banner = hildonize.show_busy_banner_start(self._window, "Loading Messages")
1321 self._messagemodel.clear()
1322 self._isPopulated = True
1324 if self._messageType == self.NO_MESSAGES:
1328 messageItems = self._backend.get_messages()
1329 except Exception, e:
1330 self._errorDisplay.push_exception_with_lock()
1331 self._isPopulated = False
1335 (gv_backend.decorate_message(message), message)
1336 for message in gv_backend.sort_messages(messageItems)
1339 for (contactId, header, number, relativeDate, messages), messageData in messageItems:
1340 prettyNumber = number[2:] if number.startswith("+1") else number
1341 prettyNumber = make_pretty(prettyNumber)
1343 firstMessage = "<b>%s - %s</b> <i>(%s)</i>" % (header, prettyNumber, relativeDate)
1344 expandedMessages = [firstMessage]
1345 expandedMessages.extend(messages)
1346 if (self._MIN_MESSAGES_SHOWN + 1) < len(messages):
1347 firstMessage = "<b>%s - %s</b> <i>(%s)</i>" % (header, prettyNumber, relativeDate)
1348 secondMessage = "<i>%d Messages Hidden...</i>" % (len(messages) - self._MIN_MESSAGES_SHOWN, )
1349 collapsedMessages = [firstMessage, secondMessage]
1350 collapsedMessages.extend(messages[-(self._MIN_MESSAGES_SHOWN+0):])
1352 collapsedMessages = expandedMessages
1353 #collapsedMessages = _collapse_message(collapsedMessages, 60, self._MIN_MESSAGES_SHOWN)
1355 number = make_ugly(number)
1357 row = number, relativeDate, header, "\n".join(collapsedMessages), expandedMessages, contactId, messageData
1358 with gtk_toolbox.gtk_lock():
1359 self._messagemodel.append(row)
1360 except Exception, e:
1361 self._errorDisplay.push_exception_with_lock()
1363 with gtk_toolbox.gtk_lock():
1364 hildonize.show_busy_banner_end(banner)
1365 self._messagemodelfiltered.refilter()
1369 def _on_messageview_row_activated(self, treeview, path, view_column):
1371 childPath = self._messagemodelfiltered.convert_path_to_child_path(path)
1372 itr = self._messagemodel.get_iter(childPath)
1376 number = make_ugly(self._messagemodel.get_value(itr, self.NUMBER_IDX))
1377 description = self._messagemodel.get_value(itr, self.MESSAGES_IDX)
1379 contactId = self._messagemodel.get_value(itr, self.FROM_ID_IDX)
1381 contactPhoneNumbers = list(self._backend.get_contact_details(contactId))
1383 (number == make_ugly(contactNumber) or number[1:] == make_ugly(contactNumber))
1384 for (numberDescription, contactNumber) in contactPhoneNumbers
1387 defaultIndex = defaultMatches.index(True)
1389 contactPhoneNumbers.append(("Other", number))
1390 defaultIndex = len(contactPhoneNumbers)-1
1392 "Could not find contact %r's number %s among %r" % (
1393 contactId, number, contactPhoneNumbers
1397 contactPhoneNumbers = [("Phone", number)]
1401 contactPhoneNumbers,
1402 messages = description,
1403 parent = self._window,
1404 defaultIndex = defaultIndex,
1406 self._messageviewselection.unselect_all()
1407 except Exception, e:
1408 self._errorDisplay.push_exception()
1410 def _on_message_type_clicked(self, *args, **kwds):
1412 selectedIndex = self.MESSAGE_TYPES.index(self._messageType)
1415 newSelectedIndex = hildonize.touch_selector(
1421 except RuntimeError:
1424 if selectedIndex != newSelectedIndex:
1425 self._messageType = self.MESSAGE_TYPES[newSelectedIndex]
1426 self._messageTypeButton.set_label(self._messageType)
1427 self._messagemodelfiltered.refilter()
1428 except Exception, e:
1429 self._errorDisplay.push_exception()
1431 def _on_message_status_clicked(self, *args, **kwds):
1433 selectedIndex = self.MESSAGE_STATUSES.index(self._messageStatus)
1436 newSelectedIndex = hildonize.touch_selector(
1439 self.MESSAGE_STATUSES,
1442 except RuntimeError:
1445 if selectedIndex != newSelectedIndex:
1446 self._messageStatus = self.MESSAGE_STATUSES[newSelectedIndex]
1447 self._messageStatusButton.set_label(self._messageStatus)
1448 self._messagemodelfiltered.refilter()
1449 except Exception, e:
1450 self._errorDisplay.push_exception()
1453 class ContactsView(object):
1455 CONTACT_TYPE_IDX = 0
1456 CONTACT_NAME_IDX = 1
1459 def __init__(self, widgetTree, backend, errorDisplay):
1460 self._errorDisplay = errorDisplay
1461 self._backend = backend
1463 self._addressBook = None
1464 self._selectedComboIndex = 0
1465 self._addressBookFactories = [null_backend.NullAddressBook()]
1467 self._booksList = []
1468 self._bookSelectionButton = widgetTree.get_widget("addressbookSelectButton")
1470 self._isPopulated = False
1471 self._contactsmodel = gtk.ListStore(
1472 gobject.TYPE_STRING, # Contact Type
1473 gobject.TYPE_STRING, # Contact Name
1474 gobject.TYPE_STRING, # Contact ID
1476 self._contactsviewselection = None
1477 self._contactsview = widgetTree.get_widget("contactsview")
1479 self._contactColumn = gtk.TreeViewColumn("Contact")
1480 displayContactSource = False
1481 if displayContactSource:
1482 textrenderer = gtk.CellRendererText()
1483 self._contactColumn.pack_start(textrenderer, expand=False)
1484 self._contactColumn.add_attribute(textrenderer, 'text', self.CONTACT_TYPE_IDX)
1485 textrenderer = gtk.CellRendererText()
1486 hildonize.set_cell_thumb_selectable(textrenderer)
1487 self._contactColumn.pack_start(textrenderer, expand=True)
1488 self._contactColumn.add_attribute(textrenderer, 'text', self.CONTACT_NAME_IDX)
1489 self._contactColumn.set_sizing(gtk.TREE_VIEW_COLUMN_FIXED)
1490 self._contactColumn.set_sort_column_id(1)
1491 self._contactColumn.set_visible(True)
1493 self._onContactsviewRowActivatedId = 0
1494 self._onAddressbookButtonChangedId = 0
1495 self._window = gtk_toolbox.find_parent_window(self._contactsview)
1497 self._updateSink = gtk_toolbox.threaded_stage(
1499 self._idly_populate_contactsview,
1500 gtk_toolbox.null_sink(),
1505 assert self._backend.is_authed(), "Attempting to enable backend while not logged in"
1507 self._contactsview.set_model(self._contactsmodel)
1508 self._contactsview.set_fixed_height_mode(False)
1509 self._contactsview.append_column(self._contactColumn)
1510 self._contactsviewselection = self._contactsview.get_selection()
1511 self._contactsviewselection.set_mode(gtk.SELECTION_SINGLE)
1513 del self._booksList[:]
1514 for (factoryId, bookId), (factoryName, bookName) in self.get_addressbooks():
1515 if factoryName and bookName:
1516 entryName = "%s: %s" % (factoryName, bookName)
1518 entryName = factoryName
1520 entryName = bookName
1522 entryName = "Bad name (%d)" % factoryId
1523 row = (str(factoryId), bookId, entryName)
1524 self._booksList.append(row)
1526 self._onContactsviewRowActivatedId = self._contactsview.connect("row-activated", self._on_contactsview_row_activated)
1527 self._onAddressbookButtonChangedId = self._bookSelectionButton.connect("clicked", self._on_addressbook_button_changed)
1529 if len(self._booksList) <= self._selectedComboIndex:
1530 self._selectedComboIndex = 0
1531 self._bookSelectionButton.set_label(self._booksList[self._selectedComboIndex][2])
1533 selectedFactoryId = self._booksList[self._selectedComboIndex][0]
1534 selectedBookId = self._booksList[self._selectedComboIndex][1]
1535 self.open_addressbook(selectedFactoryId, selectedBookId)
1538 self._contactsview.disconnect(self._onContactsviewRowActivatedId)
1539 self._bookSelectionButton.disconnect(self._onAddressbookButtonChangedId)
1543 self._bookSelectionButton.set_label("")
1544 self._contactsview.set_model(None)
1545 self._contactsview.remove_column(self._contactColumn)
1547 def add_contact(self, *args, **kwds):
1549 @note Actual dial function is patched in later
1551 raise NotImplementedError("Horrible unknown error has occurred")
1553 def get_addressbooks(self):
1555 @returns Iterable of ((Factory Id, Book Id), (Factory Name, Book Name))
1557 for i, factory in enumerate(self._addressBookFactories):
1558 for bookFactory, bookId, bookName in factory.get_addressbooks():
1559 yield (str(i), bookId), (factory.factory_name(), bookName)
1561 def open_addressbook(self, bookFactoryId, bookId):
1562 bookFactoryIndex = int(bookFactoryId)
1563 addressBook = self._addressBookFactories[bookFactoryIndex].open_addressbook(bookId)
1564 self._addressBook = addressBook
1566 def update(self, force = False):
1567 if not force and self._isPopulated:
1569 self._updateSink.send(())
1573 self._isPopulated = False
1574 self._contactsmodel.clear()
1575 for factory in self._addressBookFactories:
1576 factory.clear_caches()
1577 self._addressBook.clear_caches()
1579 def append(self, book):
1580 self._addressBookFactories.append(book)
1582 def extend(self, books):
1583 self._addressBookFactories.extend(books)
1589 def load_settings(self, config, sectionName):
1591 self._selectedComboIndex = config.getint(sectionName, "selectedAddressbook")
1592 except ConfigParser.NoOptionError:
1593 self._selectedComboIndex = 0
1595 def save_settings(self, config, sectionName):
1596 config.set(sectionName, "selectedAddressbook", str(self._selectedComboIndex))
1598 def _idly_populate_contactsview(self):
1599 with gtk_toolbox.gtk_lock():
1600 banner = hildonize.show_busy_banner_start(self._window, "Loading Contacts")
1603 while addressBook is not self._addressBook:
1604 addressBook = self._addressBook
1605 with gtk_toolbox.gtk_lock():
1606 self._contactsview.set_model(None)
1610 contacts = addressBook.get_contacts()
1611 except Exception, e:
1613 self._isPopulated = False
1614 self._errorDisplay.push_exception_with_lock()
1615 for contactId, contactName in contacts:
1616 contactType = addressBook.contact_source_short_name(contactId)
1617 row = contactType, contactName, contactId
1618 self._contactsmodel.append(row)
1620 with gtk_toolbox.gtk_lock():
1621 self._contactsview.set_model(self._contactsmodel)
1623 self._isPopulated = True
1624 except Exception, e:
1625 self._errorDisplay.push_exception_with_lock()
1627 with gtk_toolbox.gtk_lock():
1628 hildonize.show_busy_banner_end(banner)
1631 def _on_addressbook_button_changed(self, *args, **kwds):
1634 newSelectedComboIndex = hildonize.touch_selector(
1637 (("%s" % m[2]) for m in self._booksList),
1638 self._selectedComboIndex,
1640 except RuntimeError:
1643 selectedFactoryId = self._booksList[newSelectedComboIndex][0]
1644 selectedBookId = self._booksList[newSelectedComboIndex][1]
1646 oldAddressbook = self._addressBook
1647 self.open_addressbook(selectedFactoryId, selectedBookId)
1648 forceUpdate = True if oldAddressbook is not self._addressBook else False
1649 self.update(force=forceUpdate)
1651 self._selectedComboIndex = newSelectedComboIndex
1652 self._bookSelectionButton.set_label(self._booksList[self._selectedComboIndex][2])
1653 except Exception, e:
1654 self._errorDisplay.push_exception()
1656 def _on_contactsview_row_activated(self, treeview, path, view_column):
1658 itr = self._contactsmodel.get_iter(path)
1662 contactId = self._contactsmodel.get_value(itr, self.CONTACT_ID_IDX)
1663 contactName = self._contactsmodel.get_value(itr, self.CONTACT_NAME_IDX)
1665 contactDetails = self._addressBook.get_contact_details(contactId)
1666 except Exception, e:
1668 self._errorDisplay.push_exception()
1669 contactPhoneNumbers = [phoneNumber for phoneNumber in contactDetails]
1671 if len(contactPhoneNumbers) == 0:
1675 contactPhoneNumbers,
1676 messages = (contactName, ),
1677 parent = self._window,
1679 self._contactsviewselection.unselect_all()
1680 except Exception, e:
1681 self._errorDisplay.push_exception()