4 DialCentral - Front end for Google's GoogleVoice service.
5 Copyright (C) 2008 Mark Bergman bergman AT merctech DOT com
7 This library is free software; you can redistribute it and/or
8 modify it under the terms of the GNU Lesser General Public
9 License as published by the Free Software Foundation; either
10 version 2.1 of the License, or (at your option) any later version.
12 This library is distributed in the hope that it will be useful,
13 but WITHOUT ANY WARRANTY; without even the implied warranty of
14 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
15 Lesser General Public License for more details.
17 You should have received a copy of the GNU Lesser General Public
18 License along with this library; if not, write to the Free Software
19 Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
21 @todo Collapse voicemails
22 @todo Alternate UI for dialogs (stackables)
25 from __future__ import with_statement
41 _moduleLogger = logging.getLogger("gv_views")
44 def make_ugly(prettynumber):
46 function to take a phone number and strip out all non-numeric
49 >>> make_ugly("+012-(345)-678-90")
53 uglynumber = re.sub('\D', '', prettynumber)
57 def make_pretty(phonenumber):
59 Function to take a phone number and return the pretty version
61 if phonenumber begins with 0:
63 if phonenumber begins with 1: ( for gizmo callback numbers )
65 if phonenumber is 13 digits:
67 if phonenumber is 10 digits:
71 >>> make_pretty("1234567")
73 >>> make_pretty("2345678901")
75 >>> make_pretty("12345678901")
77 >>> make_pretty("01234567890")
80 if phonenumber is None or phonenumber is "":
83 phonenumber = make_ugly(phonenumber)
85 if len(phonenumber) < 3:
88 if phonenumber[0] == "0":
90 prettynumber += "+%s" % phonenumber[0:3]
91 if 3 < len(phonenumber):
92 prettynumber += "-(%s)" % phonenumber[3:6]
93 if 6 < len(phonenumber):
94 prettynumber += "-%s" % phonenumber[6:9]
95 if 9 < len(phonenumber):
96 prettynumber += "-%s" % phonenumber[9:]
98 elif len(phonenumber) <= 7:
99 prettynumber = "%s-%s" % (phonenumber[0:3], phonenumber[3:])
100 elif len(phonenumber) > 8 and phonenumber[0] == "1":
101 prettynumber = "1 (%s)-%s-%s" % (phonenumber[1:4], phonenumber[4:7], phonenumber[7:])
102 elif len(phonenumber) > 7:
103 prettynumber = "(%s)-%s-%s" % (phonenumber[0:3], phonenumber[3:6], phonenumber[6:])
107 def abbrev_relative_date(date):
109 >>> abbrev_relative_date("42 hours ago")
111 >>> abbrev_relative_date("2 days ago")
113 >>> abbrev_relative_date("4 weeks ago")
116 parts = date.split(" ")
117 return "%s %s" % (parts[0], parts[1][0])
120 def _collapse_message(messageLines, maxCharsPerLine, maxLines):
123 numLines = len(messageLines)
124 for line in messageLines[0:min(maxLines, numLines)]:
125 linesPerLine = max(1, int(len(line) / maxCharsPerLine))
126 allowedLines = maxLines - lines
127 acceptedLines = min(allowedLines, linesPerLine)
128 acceptedChars = acceptedLines * maxCharsPerLine
130 if acceptedChars < (len(line) + 3):
133 acceptedChars = len(line) # eh, might as well complete the line
135 abbrevMessage = "%s%s" % (line[0:acceptedChars], suffix)
138 lines += acceptedLines
139 if maxLines <= lines:
143 def collapse_message(message, maxCharsPerLine, maxLines):
145 >>> collapse_message("Hello", 60, 2)
147 >>> collapse_message("Hello world how are you doing today? 01234567890123456789012345678901234567890123456789012345678901234567890123456789", 60, 2)
148 'Hello world how are you doing today? 01234567890123456789012...'
149 >>> collapse_message('''Hello world how are you doing today?
150 ... 01234567890123456789
151 ... 01234567890123456789
152 ... 01234567890123456789
153 ... 01234567890123456789''', 60, 2)
154 'Hello world how are you doing today?\n01234567890123456789'
155 >>> collapse_message('''
156 ... Hello world how are you doing today? 01234567890123456789012345678901234567890123456789012345678901234567890123456789
157 ... Hello world how are you doing today? 01234567890123456789012345678901234567890123456789012345678901234567890123456789
158 ... Hello world how are you doing today? 01234567890123456789012345678901234567890123456789012345678901234567890123456789
159 ... Hello world how are you doing today? 01234567890123456789012345678901234567890123456789012345678901234567890123456789
160 ... Hello world how are you doing today? 01234567890123456789012345678901234567890123456789012345678901234567890123456789
161 ... Hello world how are you doing today? 01234567890123456789012345678901234567890123456789012345678901234567890123456789''', 60, 2)
162 '\nHello world how are you doing today? 01234567890123456789012...'
164 messageLines = message.split("\n")
165 return "\n".join(_collapse_message(messageLines, maxCharsPerLine, maxLines))
168 class MergedAddressBook(object):
170 Merger of all addressbooks
173 def __init__(self, addressbookFactories, sorter = None):
174 self.__addressbookFactories = addressbookFactories
175 self.__addressbooks = None
176 self.__sort_contacts = sorter if sorter is not None else self.null_sorter
178 def clear_caches(self):
179 self.__addressbooks = None
180 for factory in self.__addressbookFactories:
181 factory.clear_caches()
183 def get_addressbooks(self):
185 @returns Iterable of (Address Book Factory, Book Id, Book Name)
189 def open_addressbook(self, bookId):
192 def contact_source_short_name(self, contactId):
193 if self.__addressbooks is None:
195 bookIndex, originalId = contactId.split("-", 1)
196 return self.__addressbooks[int(bookIndex)].contact_source_short_name(originalId)
200 return "All Contacts"
202 def get_contacts(self):
204 @returns Iterable of (contact id, contact name)
206 if self.__addressbooks is None:
207 self.__addressbooks = list(
208 factory.open_addressbook(id)
209 for factory in self.__addressbookFactories
210 for (f, id, name) in factory.get_addressbooks()
213 ("-".join([str(bookIndex), contactId]), contactName)
214 for (bookIndex, addressbook) in enumerate(self.__addressbooks)
215 for (contactId, contactName) in addressbook.get_contacts()
217 sortedContacts = self.__sort_contacts(contacts)
218 return sortedContacts
220 def get_contact_details(self, contactId):
222 @returns Iterable of (Phone Type, Phone Number)
224 if self.__addressbooks is None:
226 bookIndex, originalId = contactId.split("-", 1)
227 return self.__addressbooks[int(bookIndex)].get_contact_details(originalId)
230 def null_sorter(contacts):
232 Good for speed/low memory
237 def basic_firtname_sorter(contacts):
239 Expects names in "First Last" format
242 (contactName.rsplit(" ", 1)[0], (contactId, contactName))
243 for (contactId, contactName) in contacts
245 contactsWithKey.sort()
246 return (contactData for (lastName, contactData) in contactsWithKey)
249 def basic_lastname_sorter(contacts):
251 Expects names in "First Last" format
254 (contactName.rsplit(" ", 1)[-1], (contactId, contactName))
255 for (contactId, contactName) in contacts
257 contactsWithKey.sort()
258 return (contactData for (lastName, contactData) in contactsWithKey)
261 def reversed_firtname_sorter(contacts):
263 Expects names in "Last, First" format
266 (contactName.split(", ", 1)[-1], (contactId, contactName))
267 for (contactId, contactName) in contacts
269 contactsWithKey.sort()
270 return (contactData for (lastName, contactData) in contactsWithKey)
273 def reversed_lastname_sorter(contacts):
275 Expects names in "Last, First" format
278 (contactName.split(", ", 1)[0], (contactId, contactName))
279 for (contactId, contactName) in contacts
281 contactsWithKey.sort()
282 return (contactData for (lastName, contactData) in contactsWithKey)
285 def guess_firstname(name):
287 return name.split(", ", 1)[-1]
289 return name.rsplit(" ", 1)[0]
292 def guess_lastname(name):
294 return name.split(", ", 1)[0]
296 return name.rsplit(" ", 1)[-1]
299 def advanced_firstname_sorter(cls, contacts):
301 (cls.guess_firstname(contactName), (contactId, contactName))
302 for (contactId, contactName) in contacts
304 contactsWithKey.sort()
305 return (contactData for (lastName, contactData) in contactsWithKey)
308 def advanced_lastname_sorter(cls, contacts):
310 (cls.guess_lastname(contactName), (contactId, contactName))
311 for (contactId, contactName) in contacts
313 contactsWithKey.sort()
314 return (contactData for (lastName, contactData) in contactsWithKey)
317 class SmsEntryDialog(object):
319 @todo Add multi-SMS messages like GoogleVoice
322 ACTION_CANCEL = "cancel"
324 ACTION_SEND_SMS = "sms"
328 def __init__(self, widgetTree):
329 self._clipboard = gtk.clipboard_get()
330 self._widgetTree = widgetTree
331 self._dialog = self._widgetTree.get_widget("smsDialog")
333 self._smsButton = self._widgetTree.get_widget("sendSmsButton")
334 self._smsButton.connect("clicked", self._on_send)
335 self._dialButton = self._widgetTree.get_widget("dialButton")
336 self._dialButton.connect("clicked", self._on_dial)
337 self._cancelButton = self._widgetTree.get_widget("cancelSmsButton")
338 self._cancelButton.connect("clicked", self._on_cancel)
340 self._letterCountLabel = self._widgetTree.get_widget("smsLetterCount")
342 self._messagemodel = gtk.ListStore(gobject.TYPE_STRING)
343 self._messagesView = self._widgetTree.get_widget("smsMessages")
345 self._conversationView = self._messagesView.get_parent()
346 self._conversationViewPort = self._conversationView.get_parent()
347 self._scrollWindow = self._conversationViewPort.get_parent()
349 self._phoneButton = self._widgetTree.get_widget("phoneTypeSelection")
350 self._smsEntry = self._widgetTree.get_widget("smsEntry")
352 self._action = self.ACTION_CANCEL
354 self._numberIndex = -1
355 self._contactDetails = []
357 def run(self, contactDetails, messages = (), parent = None, defaultIndex = -1):
358 entryConnectId = self._smsEntry.get_buffer().connect("changed", self._on_entry_changed)
359 phoneConnectId = self._phoneButton.connect("clicked", self._on_phone)
360 keyConnectId = self._keyPressEventId = self._dialog.connect("key-press-event", self._on_key_press)
362 # Setup the phone selection button
363 del self._contactDetails[:]
364 for phoneType, phoneNumber in contactDetails:
365 display = " - ".join((make_pretty(phoneNumber), phoneType))
366 row = (phoneNumber, display)
367 self._contactDetails.append(row)
368 if 0 < len(self._contactDetails):
369 self._numberIndex = defaultIndex if defaultIndex != -1 else 0
370 self._phoneButton.set_label(self._contactDetails[self._numberIndex][1])
372 self._numberIndex = -1
373 self._phoneButton.set_label("Error: No Number Available")
375 # Add the column to the messages tree view
376 self._messagemodel.clear()
377 self._messagesView.set_model(self._messagemodel)
378 self._messagesView.set_fixed_height_mode(False)
380 textrenderer = gtk.CellRendererText()
381 textrenderer.set_property("wrap-mode", pango.WRAP_WORD)
382 textrenderer.set_property("wrap-width", 450)
383 messageColumn = gtk.TreeViewColumn("")
384 messageColumn.pack_start(textrenderer, expand=True)
385 messageColumn.add_attribute(textrenderer, "markup", 0)
386 messageColumn.set_sizing(gtk.TREE_VIEW_COLUMN_FIXED)
387 self._messagesView.append_column(messageColumn)
388 self._messagesView.set_headers_visible(False)
391 for message in messages:
393 self._messagemodel.append(row)
394 self._messagesView.show()
395 self._scrollWindow.show()
396 messagesSelection = self._messagesView.get_selection()
397 messagesSelection.select_path((len(messages)-1, ))
399 self._messagesView.hide()
400 self._scrollWindow.hide()
402 self._smsEntry.get_buffer().set_text("")
403 self._update_letter_count()
405 if parent is not None:
406 self._dialog.set_transient_for(parent)
407 parentSize = parent.get_size()
408 self._dialog.resize(parentSize[0], max(parentSize[1]-10, 100))
412 self._dialog.show_all()
413 self._smsEntry.grab_focus()
414 adjustment = self._scrollWindow.get_vadjustment()
415 dx = self._conversationView.get_allocation().height - self._conversationViewPort.get_allocation().height
417 adjustment.value = dx
419 if 1 < len(self._contactDetails):
420 if defaultIndex == -1:
421 self._request_number()
422 self._phoneButton.set_sensitive(True)
424 self._phoneButton.set_sensitive(False)
426 userResponse = self._dialog.run()
428 self._dialog.hide_all()
430 # Process the users response
431 if userResponse == gtk.RESPONSE_OK and 0 <= self._numberIndex:
432 phoneNumber = self._contactDetails[self._numberIndex][0]
433 phoneNumber = make_ugly(phoneNumber)
437 self._action = self.ACTION_CANCEL
438 if self._action == self.ACTION_SEND_SMS:
439 entryBuffer = self._smsEntry.get_buffer()
440 enteredMessage = entryBuffer.get_text(entryBuffer.get_start_iter(), entryBuffer.get_end_iter())
441 enteredMessage = enteredMessage[0:self.MAX_CHAR].strip()
442 if not enteredMessage:
444 self._action = self.ACTION_CANCEL
448 self._messagesView.remove_column(messageColumn)
449 self._messagesView.set_model(None)
451 return self._action, phoneNumber, enteredMessage
453 self._smsEntry.get_buffer().disconnect(entryConnectId)
454 self._phoneButton.disconnect(phoneConnectId)
455 self._keyPressEventId = self._dialog.disconnect(keyConnectId)
457 def _update_letter_count(self, *args):
458 entryLength = self._smsEntry.get_buffer().get_char_count()
460 charsLeft = self.MAX_CHAR - entryLength
461 self._letterCountLabel.set_text(str(charsLeft))
462 if charsLeft < 0 or charsLeft == self.MAX_CHAR:
463 self._smsButton.set_sensitive(False)
465 self._smsButton.set_sensitive(True)
468 self._dialButton.set_sensitive(True)
470 self._dialButton.set_sensitive(False)
472 def _request_number(self):
474 assert 0 <= self._numberIndex, "%r" % self._numberIndex
476 self._numberIndex = hildonize.touch_selector(
479 (description for (number, description) in self._contactDetails),
482 self._phoneButton.set_label(self._contactDetails[self._numberIndex][1])
484 _moduleLogger.exception("%s" % str(e))
486 def _on_phone(self, *args):
487 self._request_number()
489 def _on_entry_changed(self, *args):
490 self._update_letter_count()
492 def _on_send(self, *args):
493 self._dialog.response(gtk.RESPONSE_OK)
494 self._action = self.ACTION_SEND_SMS
496 def _on_dial(self, *args):
497 self._dialog.response(gtk.RESPONSE_OK)
498 self._action = self.ACTION_DIAL
500 def _on_cancel(self, *args):
501 self._dialog.response(gtk.RESPONSE_CANCEL)
502 self._action = self.ACTION_CANCEL
504 def _on_key_press(self, widget, event):
506 if event.keyval == ord("c") and event.get_state() & gtk.gdk.CONTROL_MASK:
509 for messagePart in self._messagemodel
511 # For some reason this kills clipboard stuff
512 #self._clipboard.set_text(message)
514 _moduleLogger.exception(str(e))
517 class Dialpad(object):
519 def __init__(self, widgetTree, errorDisplay):
520 self._clipboard = gtk.clipboard_get()
521 self._errorDisplay = errorDisplay
522 self._smsDialog = SmsEntryDialog(widgetTree)
524 self._numberdisplay = widgetTree.get_widget("numberdisplay")
525 self._smsButton = widgetTree.get_widget("sms")
526 self._dialButton = widgetTree.get_widget("dial")
527 self._backButton = widgetTree.get_widget("back")
528 self._phonenumber = ""
529 self._prettynumber = ""
532 "on_digit_clicked": self._on_digit_clicked,
534 widgetTree.signal_autoconnect(callbackMapping)
535 self._dialButton.connect("clicked", self._on_dial_clicked)
536 self._smsButton.connect("clicked", self._on_sms_clicked)
538 self._originalLabel = self._backButton.get_label()
539 self._backTapHandler = gtk_toolbox.TapOrHold(self._backButton)
540 self._backTapHandler.on_tap = self._on_backspace
541 self._backTapHandler.on_hold = self._on_clearall
542 self._backTapHandler.on_holding = self._set_clear_button
543 self._backTapHandler.on_cancel = self._reset_back_button
545 self._window = gtk_toolbox.find_parent_window(self._numberdisplay)
546 self._keyPressEventId = 0
549 self._dialButton.grab_focus()
550 self._backTapHandler.enable()
551 self._keyPressEventId = self._window.connect("key-press-event", self._on_key_press)
554 self._window.disconnect(self._keyPressEventId)
555 self._keyPressEventId = 0
556 self._reset_back_button()
557 self._backTapHandler.disable()
559 def number_selected(self, action, number, message):
561 @note Actual dial function is patched in later
563 raise NotImplementedError("Horrible unknown error has occurred")
565 def get_number(self):
566 return self._phonenumber
568 def set_number(self, number):
570 Set the number to dial
573 self._phonenumber = make_ugly(number)
574 self._prettynumber = make_pretty(self._phonenumber)
575 self._numberdisplay.set_label("<span size='30000' weight='bold'>%s</span>" % (self._prettynumber))
577 self._errorDisplay.push_exception()
586 def load_settings(self, config, section):
589 def save_settings(self, config, section):
591 @note Thread Agnostic
595 def _on_key_press(self, widget, event):
597 if event.keyval == ord("v") and event.get_state() & gtk.gdk.CONTROL_MASK:
598 contents = self._clipboard.wait_for_text()
599 if contents is not None:
600 self.set_number(contents)
602 self._errorDisplay.push_exception()
604 def _on_sms_clicked(self, widget):
606 phoneNumber = self.get_number()
607 action, phoneNumber, message = self._smsDialog.run([("Dialer", phoneNumber)], (), self._window)
609 if action == SmsEntryDialog.ACTION_CANCEL:
611 self.number_selected(action, phoneNumber, message)
613 self._errorDisplay.push_exception()
615 def _on_dial_clicked(self, widget):
617 action = SmsEntryDialog.ACTION_DIAL
618 phoneNumber = self.get_number()
620 self.number_selected(action, phoneNumber, message)
622 self._errorDisplay.push_exception()
624 def _on_digit_clicked(self, widget):
626 self.set_number(self._phonenumber + widget.get_name()[-1])
628 self._errorDisplay.push_exception()
630 def _on_backspace(self, taps):
632 self.set_number(self._phonenumber[:-taps])
633 self._reset_back_button()
635 self._errorDisplay.push_exception()
637 def _on_clearall(self, taps):
640 self._reset_back_button()
642 self._errorDisplay.push_exception()
645 def _set_clear_button(self):
647 self._backButton.set_label("gtk-clear")
649 self._errorDisplay.push_exception()
651 def _reset_back_button(self):
653 self._backButton.set_label(self._originalLabel)
655 self._errorDisplay.push_exception()
658 class AccountInfo(object):
660 def __init__(self, widgetTree, backend, alarmHandler, errorDisplay):
661 self._errorDisplay = errorDisplay
662 self._backend = backend
663 self._isPopulated = False
664 self._alarmHandler = alarmHandler
665 self._notifyOnMissed = False
666 self._notifyOnVoicemail = False
667 self._notifyOnSms = False
669 self._callbackList = []
670 self._accountViewNumberDisplay = widgetTree.get_widget("gcnumber_display")
671 self._callbackSelectButton = widgetTree.get_widget("callbackSelectButton")
672 self._onCallbackSelectChangedId = 0
674 self._notifyCheckbox = widgetTree.get_widget("notifyCheckbox")
675 self._minutesEntryButton = widgetTree.get_widget("minutesEntryButton")
676 self._missedCheckbox = widgetTree.get_widget("missedCheckbox")
677 self._voicemailCheckbox = widgetTree.get_widget("voicemailCheckbox")
678 self._smsCheckbox = widgetTree.get_widget("smsCheckbox")
679 self._onNotifyToggled = 0
680 self._onMinutesChanged = 0
681 self._onMissedToggled = 0
682 self._onVoicemailToggled = 0
683 self._onSmsToggled = 0
684 self._applyAlarmTimeoutId = None
686 self._window = gtk_toolbox.find_parent_window(self._minutesEntryButton)
687 self._callbackNumber = ""
690 assert self._backend.is_authed(), "Attempting to enable backend while not logged in"
692 self._accountViewNumberDisplay.set_use_markup(True)
693 self.set_account_number("")
695 del self._callbackList[:]
696 self._onCallbackSelectChangedId = self._callbackSelectButton.connect("clicked", self._on_callbackentry_clicked)
697 self._set_callback_label("")
699 if self._alarmHandler is not None:
700 self._notifyCheckbox.set_active(self._alarmHandler.isEnabled)
701 self._minutesEntryButton.set_label("%d minutes" % self._alarmHandler.recurrence)
702 self._missedCheckbox.set_active(self._notifyOnMissed)
703 self._voicemailCheckbox.set_active(self._notifyOnVoicemail)
704 self._smsCheckbox.set_active(self._notifyOnSms)
706 self._onNotifyToggled = self._notifyCheckbox.connect("toggled", self._on_notify_toggled)
707 self._onMinutesChanged = self._minutesEntryButton.connect("clicked", self._on_minutes_clicked)
708 self._onMissedToggled = self._missedCheckbox.connect("toggled", self._on_missed_toggled)
709 self._onVoicemailToggled = self._voicemailCheckbox.connect("toggled", self._on_voicemail_toggled)
710 self._onSmsToggled = self._smsCheckbox.connect("toggled", self._on_sms_toggled)
712 self._notifyCheckbox.set_sensitive(False)
713 self._minutesEntryButton.set_sensitive(False)
714 self._missedCheckbox.set_sensitive(False)
715 self._voicemailCheckbox.set_sensitive(False)
716 self._smsCheckbox.set_sensitive(False)
718 self.update(force=True)
721 self._callbackSelectButton.disconnect(self._onCallbackSelectChangedId)
722 self._onCallbackSelectChangedId = 0
723 self._set_callback_label("")
725 if self._alarmHandler is not None:
726 self._notifyCheckbox.disconnect(self._onNotifyToggled)
727 self._minutesEntryButton.disconnect(self._onMinutesChanged)
728 self._missedCheckbox.disconnect(self._onNotifyToggled)
729 self._voicemailCheckbox.disconnect(self._onNotifyToggled)
730 self._smsCheckbox.disconnect(self._onNotifyToggled)
731 self._onNotifyToggled = 0
732 self._onMinutesChanged = 0
733 self._onMissedToggled = 0
734 self._onVoicemailToggled = 0
735 self._onSmsToggled = 0
737 self._notifyCheckbox.set_sensitive(True)
738 self._minutesEntryButton.set_sensitive(True)
739 self._missedCheckbox.set_sensitive(True)
740 self._voicemailCheckbox.set_sensitive(True)
741 self._smsCheckbox.set_sensitive(True)
744 del self._callbackList[:]
746 def set_account_number(self, number):
748 Displays current account number
750 self._accountViewNumberDisplay.set_label("<span size='23000' weight='bold'>%s</span>" % (number))
752 def update(self, force = False):
753 if not force and self._isPopulated:
755 self._populate_callback_combo()
756 self.set_account_number(self._backend.get_account_number())
760 self._set_callback_label("")
761 self.set_account_number("")
762 self._isPopulated = False
764 def save_everything(self):
765 raise NotImplementedError
769 return "Account Info"
771 def load_settings(self, config, section):
772 self._callbackNumber = make_ugly(config.get(section, "callback"))
773 self._notifyOnMissed = config.getboolean(section, "notifyOnMissed")
774 self._notifyOnVoicemail = config.getboolean(section, "notifyOnVoicemail")
775 self._notifyOnSms = config.getboolean(section, "notifyOnSms")
777 def save_settings(self, config, section):
779 @note Thread Agnostic
781 config.set(section, "callback", self._callbackNumber)
782 config.set(section, "notifyOnMissed", repr(self._notifyOnMissed))
783 config.set(section, "notifyOnVoicemail", repr(self._notifyOnVoicemail))
784 config.set(section, "notifyOnSms", repr(self._notifyOnSms))
786 def _populate_callback_combo(self):
787 self._isPopulated = True
788 del self._callbackList[:]
790 callbackNumbers = self._backend.get_callback_numbers()
792 self._errorDisplay.push_exception()
793 self._isPopulated = False
796 if len(callbackNumbers) == 0:
797 callbackNumbers = {"": "No callback numbers available"}
799 for number, description in callbackNumbers.iteritems():
800 self._callbackList.append((make_pretty(number), description))
802 self._set_callback_number(self._callbackNumber)
804 def _set_callback_number(self, number):
806 if not self._backend.is_valid_syntax(number) and 0 < len(number):
807 self._errorDisplay.push_message("%s is not a valid callback number" % number)
808 elif number == self._backend.get_callback_number() and 0 < len(number):
809 _moduleLogger.warning(
810 "Callback number already is %s" % (
811 self._backend.get_callback_number(),
814 self._set_callback_label(number)
816 self._backend.set_callback_number(number)
817 assert make_ugly(number) == make_ugly(self._backend.get_callback_number()), "Callback number should be %s but instead is %s" % (
818 make_pretty(number), make_pretty(self._backend.get_callback_number())
820 self._callbackNumber = make_ugly(number)
821 self._set_callback_label(number)
823 "Callback number set to %s" % (
824 self._backend.get_callback_number(),
828 self._errorDisplay.push_exception()
830 def _set_callback_label(self, uglyNumber):
831 prettyNumber = make_pretty(uglyNumber)
832 if len(prettyNumber) == 0:
833 prettyNumber = "No Callback Number"
834 self._callbackSelectButton.set_label(prettyNumber)
836 def _update_alarm_settings(self, recurrence):
838 isEnabled = self._notifyCheckbox.get_active()
839 if isEnabled != self._alarmHandler.isEnabled or recurrence != self._alarmHandler.recurrence:
840 self._alarmHandler.apply_settings(isEnabled, recurrence)
842 self.save_everything()
843 self._notifyCheckbox.set_active(self._alarmHandler.isEnabled)
844 self._minutesEntryButton.set_label("%d Minutes" % self._alarmHandler.recurrence)
846 def _on_callbackentry_clicked(self, *args):
848 actualSelection = make_pretty(self._callbackNumber)
851 (number, "%s (%s)" % (number, description))
852 for (number, description) in self._callbackList
854 defaultSelection = userOptions.get(actualSelection, actualSelection)
856 userSelection = hildonize.touch_selector_entry(
859 list(userOptions.itervalues()),
862 reversedUserOptions = dict(
863 itertools.izip(userOptions.itervalues(), userOptions.iterkeys())
865 selectedNumber = reversedUserOptions.get(userSelection, userSelection)
867 number = make_ugly(selectedNumber)
868 self._set_callback_number(number)
869 except RuntimeError, e:
870 _moduleLogger.exception("%s" % str(e))
872 self._errorDisplay.push_exception()
874 def _on_notify_toggled(self, *args):
876 if self._applyAlarmTimeoutId is not None:
877 gobject.source_remove(self._applyAlarmTimeoutId)
878 self._applyAlarmTimeoutId = None
879 self._applyAlarmTimeoutId = gobject.timeout_add(500, self._on_apply_timeout)
881 self._errorDisplay.push_exception()
883 def _on_minutes_clicked(self, *args):
884 recurrenceChoices = [
900 actualSelection = self._alarmHandler.recurrence
902 closestSelectionIndex = 0
903 for i, possible in enumerate(recurrenceChoices):
904 if possible[0] <= actualSelection:
905 closestSelectionIndex = i
906 recurrenceIndex = hildonize.touch_selector(
909 (("%s" % m[1]) for m in recurrenceChoices),
910 closestSelectionIndex,
912 recurrence = recurrenceChoices[recurrenceIndex][0]
914 self._update_alarm_settings(recurrence)
915 except RuntimeError, e:
916 _moduleLogger.exception("%s" % str(e))
918 self._errorDisplay.push_exception()
920 def _on_apply_timeout(self, *args):
922 self._applyAlarmTimeoutId = None
924 self._update_alarm_settings(self._alarmHandler.recurrence)
926 self._errorDisplay.push_exception()
929 def _on_missed_toggled(self, *args):
931 self._notifyOnMissed = self._missedCheckbox.get_active()
932 self.save_everything()
934 self._errorDisplay.push_exception()
936 def _on_voicemail_toggled(self, *args):
938 self._notifyOnVoicemail = self._voicemailCheckbox.get_active()
939 self.save_everything()
941 self._errorDisplay.push_exception()
943 def _on_sms_toggled(self, *args):
945 self._notifyOnSms = self._smsCheckbox.get_active()
946 self.save_everything()
948 self._errorDisplay.push_exception()
951 class RecentCallsView(object):
959 def __init__(self, widgetTree, backend, errorDisplay):
960 self._errorDisplay = errorDisplay
961 self._backend = backend
963 self._isPopulated = False
964 self._recentmodel = gtk.ListStore(
965 gobject.TYPE_STRING, # number
966 gobject.TYPE_STRING, # date
967 gobject.TYPE_STRING, # action
968 gobject.TYPE_STRING, # from
969 gobject.TYPE_STRING, # from id
971 self._recentview = widgetTree.get_widget("recentview")
972 self._recentviewselection = None
973 self._onRecentviewRowActivatedId = 0
975 textrenderer = gtk.CellRendererText()
976 textrenderer.set_property("yalign", 0)
977 self._dateColumn = gtk.TreeViewColumn("Date")
978 self._dateColumn.pack_start(textrenderer, expand=True)
979 self._dateColumn.add_attribute(textrenderer, "text", self.DATE_IDX)
981 textrenderer = gtk.CellRendererText()
982 textrenderer.set_property("yalign", 0)
983 self._actionColumn = gtk.TreeViewColumn("Action")
984 self._actionColumn.pack_start(textrenderer, expand=True)
985 self._actionColumn.add_attribute(textrenderer, "text", self.ACTION_IDX)
987 textrenderer = gtk.CellRendererText()
988 textrenderer.set_property("yalign", 0)
989 textrenderer.set_property("ellipsize", pango.ELLIPSIZE_END)
990 textrenderer.set_property("width-chars", len("1 (555) 555-1234"))
991 self._numberColumn = gtk.TreeViewColumn("Number")
992 self._numberColumn.pack_start(textrenderer, expand=True)
993 self._numberColumn.add_attribute(textrenderer, "text", self.NUMBER_IDX)
995 textrenderer = gtk.CellRendererText()
996 textrenderer.set_property("yalign", 0)
997 hildonize.set_cell_thumb_selectable(textrenderer)
998 self._nameColumn = gtk.TreeViewColumn("From")
999 self._nameColumn.pack_start(textrenderer, expand=True)
1000 self._nameColumn.add_attribute(textrenderer, "text", self.FROM_IDX)
1001 self._nameColumn.set_sizing(gtk.TREE_VIEW_COLUMN_FIXED)
1003 self._window = gtk_toolbox.find_parent_window(self._recentview)
1004 self._phoneTypeSelector = SmsEntryDialog(widgetTree)
1006 self._updateSink = gtk_toolbox.threaded_stage(
1008 self._idly_populate_recentview,
1009 gtk_toolbox.null_sink(),
1014 assert self._backend.is_authed(), "Attempting to enable backend while not logged in"
1015 self._recentview.set_model(self._recentmodel)
1016 self._recentview.set_fixed_height_mode(False)
1018 self._recentview.append_column(self._dateColumn)
1019 self._recentview.append_column(self._actionColumn)
1020 self._recentview.append_column(self._numberColumn)
1021 self._recentview.append_column(self._nameColumn)
1022 self._recentviewselection = self._recentview.get_selection()
1023 self._recentviewselection.set_mode(gtk.SELECTION_SINGLE)
1025 self._onRecentviewRowActivatedId = self._recentview.connect("row-activated", self._on_recentview_row_activated)
1028 self._recentview.disconnect(self._onRecentviewRowActivatedId)
1032 self._recentview.remove_column(self._dateColumn)
1033 self._recentview.remove_column(self._actionColumn)
1034 self._recentview.remove_column(self._nameColumn)
1035 self._recentview.remove_column(self._numberColumn)
1036 self._recentview.set_model(None)
1038 def number_selected(self, action, number, message):
1040 @note Actual dial function is patched in later
1042 raise NotImplementedError("Horrible unknown error has occurred")
1044 def update(self, force = False):
1045 if not force and self._isPopulated:
1047 self._updateSink.send(())
1051 self._isPopulated = False
1052 self._recentmodel.clear()
1056 return "Recent Calls"
1058 def load_settings(self, config, section):
1061 def save_settings(self, config, section):
1063 @note Thread Agnostic
1067 def _idly_populate_recentview(self):
1068 with gtk_toolbox.gtk_lock():
1069 banner = hildonize.show_busy_banner_start(self._window, "Loading Recent History")
1071 self._recentmodel.clear()
1072 self._isPopulated = True
1075 recentItems = self._backend.get_recent()
1076 except Exception, e:
1077 self._errorDisplay.push_exception_with_lock()
1078 self._isPopulated = False
1082 gv_backend.decorate_recent(data)
1083 for data in gv_backend.sort_messages(recentItems)
1086 for contactId, personName, phoneNumber, date, action in recentItems:
1088 personName = "Unknown"
1089 date = abbrev_relative_date(date)
1090 prettyNumber = phoneNumber[2:] if phoneNumber.startswith("+1") else phoneNumber
1091 prettyNumber = make_pretty(prettyNumber)
1092 item = (prettyNumber, date, action.capitalize(), personName, contactId)
1093 with gtk_toolbox.gtk_lock():
1094 self._recentmodel.append(item)
1095 except Exception, e:
1096 self._errorDisplay.push_exception_with_lock()
1098 with gtk_toolbox.gtk_lock():
1099 hildonize.show_busy_banner_end(banner)
1103 def _on_recentview_row_activated(self, treeview, path, view_column):
1105 itr = self._recentmodel.get_iter(path)
1109 number = self._recentmodel.get_value(itr, self.NUMBER_IDX)
1110 number = make_ugly(number)
1111 description = self._recentmodel.get_value(itr, self.FROM_IDX)
1112 contactId = self._recentmodel.get_value(itr, self.FROM_ID_IDX)
1114 contactPhoneNumbers = list(self._backend.get_contact_details(contactId))
1116 (number == make_ugly(contactNumber) or number[1:] == make_ugly(contactNumber))
1117 for (numberDescription, contactNumber) in contactPhoneNumbers
1120 defaultIndex = defaultMatches.index(True)
1122 contactPhoneNumbers.append(("Other", number))
1123 defaultIndex = len(contactPhoneNumbers)-1
1125 "Could not find contact %r's number %s among %r" % (
1126 contactId, number, contactPhoneNumbers
1130 contactPhoneNumbers = [("Phone", number)]
1133 action, phoneNumber, message = self._phoneTypeSelector.run(
1134 contactPhoneNumbers,
1135 messages = (description, ),
1136 parent = self._window,
1137 defaultIndex = defaultIndex,
1139 if action == SmsEntryDialog.ACTION_CANCEL:
1141 assert phoneNumber, "A lack of phone number exists"
1143 self.number_selected(action, phoneNumber, message)
1144 self._recentviewselection.unselect_all()
1145 except Exception, e:
1146 self._errorDisplay.push_exception()
1149 class MessagesView(object):
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
1182 self._messageview = widgetTree.get_widget("messages_view")
1183 self._messageviewselection = None
1184 self._onMessageviewRowActivatedId = 0
1186 self._messageRenderer = gtk.CellRendererText()
1187 self._messageRenderer.set_property("wrap-mode", pango.WRAP_WORD)
1188 self._messageRenderer.set_property("wrap-width", 500)
1189 self._messageColumn = gtk.TreeViewColumn("Messages")
1190 self._messageColumn.pack_start(self._messageRenderer, expand=True)
1191 self._messageColumn.add_attribute(self._messageRenderer, "markup", self.MESSAGE_IDX)
1192 self._messageColumn.set_sizing(gtk.TREE_VIEW_COLUMN_FIXED)
1194 self._window = gtk_toolbox.find_parent_window(self._messageview)
1195 self._phoneTypeSelector = SmsEntryDialog(widgetTree)
1197 self._messageTypeButton = widgetTree.get_widget("messageTypeButton")
1198 self._onMessageTypeClickedId = 0
1199 self._messageType = self.ALL_TYPES
1200 self._messageStatusButton = widgetTree.get_widget("messageStatusButton")
1201 self._onMessageStatusClickedId = 0
1202 self._messageStatus = self.ALL_STATUS
1204 self._updateSink = gtk_toolbox.threaded_stage(
1206 self._idly_populate_messageview,
1207 gtk_toolbox.null_sink(),
1212 assert self._backend.is_authed(), "Attempting to enable backend while not logged in"
1213 self._messageview.set_model(self._messagemodel)
1214 self._messageview.set_headers_visible(False)
1215 self._messageview.set_fixed_height_mode(False)
1217 self._messageview.append_column(self._messageColumn)
1218 self._messageviewselection = self._messageview.get_selection()
1219 self._messageviewselection.set_mode(gtk.SELECTION_SINGLE)
1221 self._messageTypeButton.set_label(self._messageType)
1222 self._messageStatusButton.set_label(self._messageStatus)
1224 self._onMessageviewRowActivatedId = self._messageview.connect(
1225 "row-activated", self._on_messageview_row_activated
1227 self._onMessageTypeClickedId = self._messageTypeButton.connect(
1228 "clicked", self._on_message_type_clicked
1230 self._onMessageStatusClickedId = self._messageStatusButton.connect(
1231 "clicked", self._on_message_status_clicked
1235 self._messageview.disconnect(self._onMessageviewRowActivatedId)
1236 self._messageTypeButton.disconnect(self._onMessageTypeClickedId)
1237 self._messageStatusButton.disconnect(self._onMessageStatusClickedId)
1241 self._messageview.remove_column(self._messageColumn)
1242 self._messageview.set_model(None)
1244 def number_selected(self, action, number, message):
1246 @note Actual dial function is patched in later
1248 raise NotImplementedError("Horrible unknown error has occurred")
1250 def update(self, force = False):
1251 if not force and self._isPopulated:
1253 self._updateSink.send(())
1257 self._isPopulated = False
1258 self._messagemodel.clear()
1264 def load_settings(self, config, sectionName):
1266 self._messageType = config.get(sectionName, "type")
1267 if self._messageType not in self.MESSAGE_TYPES:
1268 self._messageType = self.ALL_TYPES
1269 self._messageStatus = config.get(sectionName, "status")
1270 if self._messageStatus not in self.MESSAGE_STATUSES:
1271 self._messageStatus = self.ALL_STATUS
1272 except ConfigParser.NoOptionError:
1275 def save_settings(self, config, sectionName):
1277 @note Thread Agnostic
1279 config.set(sectionName, "status", self._messageStatus)
1280 config.set(sectionName, "type", self._messageType)
1282 _MIN_MESSAGES_SHOWN = 4
1285 def _filter_messages(cls, message, type, status):
1286 if type == cls.ALL_TYPES:
1289 messageType = message["type"]
1290 isType = messageType == type
1292 if status == cls.ALL_STATUS:
1295 isUnarchived = not message["isArchived"]
1296 isUnread = not message["isRead"]
1297 if status == cls.UNREAD_STATUS:
1298 isStatus = isUnarchived and isUnread
1299 elif status == cls.UNARCHIVED_STATUS:
1300 isStatus = isUnarchived
1302 assert "Status %s is bad for %r" % (status, message)
1304 return isType and isStatus
1306 def _idly_populate_messageview(self):
1307 with gtk_toolbox.gtk_lock():
1308 banner = hildonize.show_busy_banner_start(self._window, "Loading Messages")
1310 self._messagemodel.clear()
1311 self._isPopulated = True
1313 if self._messageType == self.NO_MESSAGES:
1317 messageItems = self._backend.get_messages()
1318 except Exception, e:
1319 self._errorDisplay.push_exception_with_lock()
1320 self._isPopulated = False
1324 gv_backend.decorate_message(message)
1325 for message in gv_backend.sort_messages(messageItems)
1326 if self._filter_messages(message, self._messageType, self._messageStatus)
1329 for contactId, header, number, relativeDate, messages in messageItems:
1330 prettyNumber = number[2:] if number.startswith("+1") else number
1331 prettyNumber = make_pretty(prettyNumber)
1333 firstMessage = "<b>%s - %s</b> <i>(%s)</i>" % (header, prettyNumber, relativeDate)
1334 expandedMessages = [firstMessage]
1335 expandedMessages.extend(messages)
1336 if (self._MIN_MESSAGES_SHOWN + 1) < len(messages):
1337 firstMessage = "<b>%s - %s</b> <i>(%s)</i>" % (header, prettyNumber, relativeDate)
1338 secondMessage = "<i>%d Messages Hidden...</i>" % (len(messages) - self._MIN_MESSAGES_SHOWN, )
1339 collapsedMessages = [firstMessage, secondMessage]
1340 collapsedMessages.extend(messages[-(self._MIN_MESSAGES_SHOWN+0):])
1342 collapsedMessages = expandedMessages
1343 #collapsedMessages = _collapse_message(collapsedMessages, 60, self._MIN_MESSAGES_SHOWN)
1345 number = make_ugly(number)
1347 row = number, relativeDate, header, "\n".join(collapsedMessages), expandedMessages, contactId
1348 with gtk_toolbox.gtk_lock():
1349 self._messagemodel.append(row)
1350 except Exception, e:
1351 self._errorDisplay.push_exception_with_lock()
1353 with gtk_toolbox.gtk_lock():
1354 hildonize.show_busy_banner_end(banner)
1358 def _on_messageview_row_activated(self, treeview, path, view_column):
1360 itr = self._messagemodel.get_iter(path)
1364 number = make_ugly(self._messagemodel.get_value(itr, self.NUMBER_IDX))
1365 description = self._messagemodel.get_value(itr, self.MESSAGES_IDX)
1367 contactId = self._messagemodel.get_value(itr, self.FROM_ID_IDX)
1369 contactPhoneNumbers = list(self._backend.get_contact_details(contactId))
1371 (number == make_ugly(contactNumber) or number[1:] == make_ugly(contactNumber))
1372 for (numberDescription, contactNumber) in contactPhoneNumbers
1375 defaultIndex = defaultMatches.index(True)
1377 contactPhoneNumbers.append(("Other", number))
1378 defaultIndex = len(contactPhoneNumbers)-1
1380 "Could not find contact %r's number %s among %r" % (
1381 contactId, number, contactPhoneNumbers
1385 contactPhoneNumbers = [("Phone", number)]
1388 action, phoneNumber, message = self._phoneTypeSelector.run(
1389 contactPhoneNumbers,
1390 messages = description,
1391 parent = self._window,
1392 defaultIndex = defaultIndex,
1394 if action == SmsEntryDialog.ACTION_CANCEL:
1396 assert phoneNumber, "A lock of phone number exists"
1398 self.number_selected(action, phoneNumber, message)
1399 self._messageviewselection.unselect_all()
1400 except Exception, e:
1401 self._errorDisplay.push_exception()
1403 def _on_message_type_clicked(self, *args, **kwds):
1405 selectedIndex = self.MESSAGE_TYPES.index(self._messageType)
1408 newSelectedIndex = hildonize.touch_selector(
1414 except RuntimeError:
1417 if selectedIndex != newSelectedIndex:
1418 self._messageType = self.MESSAGE_TYPES[newSelectedIndex]
1419 self._messageTypeButton.set_label(self._messageType)
1421 except Exception, e:
1422 self._errorDisplay.push_exception()
1424 def _on_message_status_clicked(self, *args, **kwds):
1426 selectedIndex = self.MESSAGE_STATUSES.index(self._messageStatus)
1429 newSelectedIndex = hildonize.touch_selector(
1432 self.MESSAGE_STATUSES,
1435 except RuntimeError:
1438 if selectedIndex != newSelectedIndex:
1439 self._messageStatus = self.MESSAGE_STATUSES[newSelectedIndex]
1440 self._messageStatusButton.set_label(self._messageStatus)
1442 except Exception, e:
1443 self._errorDisplay.push_exception()
1446 class ContactsView(object):
1448 def __init__(self, widgetTree, backend, errorDisplay):
1449 self._errorDisplay = errorDisplay
1450 self._backend = backend
1452 self._addressBook = None
1453 self._selectedComboIndex = 0
1454 self._addressBookFactories = [null_backend.NullAddressBook()]
1456 self._booksList = []
1457 self._bookSelectionButton = widgetTree.get_widget("addressbookSelectButton")
1459 self._isPopulated = False
1460 self._contactsmodel = gtk.ListStore(gobject.TYPE_STRING, gobject.TYPE_STRING, gobject.TYPE_STRING, gobject.TYPE_STRING, gobject.TYPE_STRING)
1461 self._contactsviewselection = None
1462 self._contactsview = widgetTree.get_widget("contactsview")
1464 self._contactColumn = gtk.TreeViewColumn("Contact")
1465 displayContactSource = False
1466 if displayContactSource:
1467 textrenderer = gtk.CellRendererText()
1468 self._contactColumn.pack_start(textrenderer, expand=False)
1469 self._contactColumn.add_attribute(textrenderer, 'text', 0)
1470 textrenderer = gtk.CellRendererText()
1471 hildonize.set_cell_thumb_selectable(textrenderer)
1472 self._contactColumn.pack_start(textrenderer, expand=True)
1473 self._contactColumn.add_attribute(textrenderer, 'text', 1)
1474 textrenderer = gtk.CellRendererText()
1475 self._contactColumn.pack_start(textrenderer, expand=True)
1476 self._contactColumn.add_attribute(textrenderer, 'text', 4)
1477 self._contactColumn.set_sizing(gtk.TREE_VIEW_COLUMN_FIXED)
1478 self._contactColumn.set_sort_column_id(1)
1479 self._contactColumn.set_visible(True)
1481 self._onContactsviewRowActivatedId = 0
1482 self._onAddressbookButtonChangedId = 0
1483 self._window = gtk_toolbox.find_parent_window(self._contactsview)
1484 self._phoneTypeSelector = SmsEntryDialog(widgetTree)
1486 self._updateSink = gtk_toolbox.threaded_stage(
1488 self._idly_populate_contactsview,
1489 gtk_toolbox.null_sink(),
1494 assert self._backend.is_authed(), "Attempting to enable backend while not logged in"
1496 self._contactsview.set_model(self._contactsmodel)
1497 self._contactsview.set_fixed_height_mode(False)
1498 self._contactsview.append_column(self._contactColumn)
1499 self._contactsviewselection = self._contactsview.get_selection()
1500 self._contactsviewselection.set_mode(gtk.SELECTION_SINGLE)
1502 del self._booksList[:]
1503 for (factoryId, bookId), (factoryName, bookName) in self.get_addressbooks():
1504 if factoryName and bookName:
1505 entryName = "%s: %s" % (factoryName, bookName)
1507 entryName = factoryName
1509 entryName = bookName
1511 entryName = "Bad name (%d)" % factoryId
1512 row = (str(factoryId), bookId, entryName)
1513 self._booksList.append(row)
1515 self._onContactsviewRowActivatedId = self._contactsview.connect("row-activated", self._on_contactsview_row_activated)
1516 self._onAddressbookButtonChangedId = self._bookSelectionButton.connect("clicked", self._on_addressbook_button_changed)
1518 if len(self._booksList) <= self._selectedComboIndex:
1519 self._selectedComboIndex = 0
1520 self._bookSelectionButton.set_label(self._booksList[self._selectedComboIndex][2])
1522 selectedFactoryId = self._booksList[self._selectedComboIndex][0]
1523 selectedBookId = self._booksList[self._selectedComboIndex][1]
1524 self.open_addressbook(selectedFactoryId, selectedBookId)
1527 self._contactsview.disconnect(self._onContactsviewRowActivatedId)
1528 self._bookSelectionButton.disconnect(self._onAddressbookButtonChangedId)
1532 self._bookSelectionButton.set_label("")
1533 self._contactsview.set_model(None)
1534 self._contactsview.remove_column(self._contactColumn)
1536 def number_selected(self, action, number, message):
1538 @note Actual dial function is patched in later
1540 raise NotImplementedError("Horrible unknown error has occurred")
1542 def get_addressbooks(self):
1544 @returns Iterable of ((Factory Id, Book Id), (Factory Name, Book Name))
1546 for i, factory in enumerate(self._addressBookFactories):
1547 for bookFactory, bookId, bookName in factory.get_addressbooks():
1548 yield (str(i), bookId), (factory.factory_name(), bookName)
1550 def open_addressbook(self, bookFactoryId, bookId):
1551 bookFactoryIndex = int(bookFactoryId)
1552 addressBook = self._addressBookFactories[bookFactoryIndex].open_addressbook(bookId)
1553 self._addressBook = addressBook
1555 def update(self, force = False):
1556 if not force and self._isPopulated:
1558 self._updateSink.send(())
1562 self._isPopulated = False
1563 self._contactsmodel.clear()
1564 for factory in self._addressBookFactories:
1565 factory.clear_caches()
1566 self._addressBook.clear_caches()
1568 def append(self, book):
1569 self._addressBookFactories.append(book)
1571 def extend(self, books):
1572 self._addressBookFactories.extend(books)
1578 def load_settings(self, config, sectionName):
1580 self._selectedComboIndex = config.getint(sectionName, "selectedAddressbook")
1581 except ConfigParser.NoOptionError:
1582 self._selectedComboIndex = 0
1584 def save_settings(self, config, sectionName):
1585 config.set(sectionName, "selectedAddressbook", str(self._selectedComboIndex))
1587 def _idly_populate_contactsview(self):
1588 with gtk_toolbox.gtk_lock():
1589 banner = hildonize.show_busy_banner_start(self._window, "Loading Contacts")
1592 while addressBook is not self._addressBook:
1593 addressBook = self._addressBook
1594 with gtk_toolbox.gtk_lock():
1595 self._contactsview.set_model(None)
1599 contacts = addressBook.get_contacts()
1600 except Exception, e:
1602 self._isPopulated = False
1603 self._errorDisplay.push_exception_with_lock()
1604 for contactId, contactName in contacts:
1605 contactType = (addressBook.contact_source_short_name(contactId), )
1606 self._contactsmodel.append(contactType + (contactName, "", contactId) + ("", ))
1608 with gtk_toolbox.gtk_lock():
1609 self._contactsview.set_model(self._contactsmodel)
1611 self._isPopulated = True
1612 except Exception, e:
1613 self._errorDisplay.push_exception_with_lock()
1615 with gtk_toolbox.gtk_lock():
1616 hildonize.show_busy_banner_end(banner)
1619 def _on_addressbook_button_changed(self, *args, **kwds):
1622 newSelectedComboIndex = hildonize.touch_selector(
1625 (("%s" % m[2]) for m in self._booksList),
1626 self._selectedComboIndex,
1628 except RuntimeError:
1631 selectedFactoryId = self._booksList[newSelectedComboIndex][0]
1632 selectedBookId = self._booksList[newSelectedComboIndex][1]
1634 oldAddressbook = self._addressBook
1635 self.open_addressbook(selectedFactoryId, selectedBookId)
1636 forceUpdate = True if oldAddressbook is not self._addressBook else False
1637 self.update(force=forceUpdate)
1639 self._selectedComboIndex = newSelectedComboIndex
1640 self._bookSelectionButton.set_label(self._booksList[self._selectedComboIndex][2])
1641 except Exception, e:
1642 self._errorDisplay.push_exception()
1644 def _on_contactsview_row_activated(self, treeview, path, view_column):
1646 itr = self._contactsmodel.get_iter(path)
1650 contactId = self._contactsmodel.get_value(itr, 3)
1651 contactName = self._contactsmodel.get_value(itr, 1)
1653 contactDetails = self._addressBook.get_contact_details(contactId)
1654 except Exception, e:
1656 self._errorDisplay.push_exception()
1657 contactPhoneNumbers = [phoneNumber for phoneNumber in contactDetails]
1659 if len(contactPhoneNumbers) == 0:
1662 action, phoneNumber, message = self._phoneTypeSelector.run(
1663 contactPhoneNumbers,
1664 messages = (contactName, ),
1665 parent = self._window,
1667 if action == SmsEntryDialog.ACTION_CANCEL:
1669 assert phoneNumber, "A lack of phone number exists"
1671 self.number_selected(action, phoneNumber, message)
1672 self._contactsviewselection.unselect_all()
1673 except Exception, e:
1674 self._errorDisplay.push_exception()