4 DialCentral - Front end for Google's GoogleVoice service.
5 Copyright (C) 2008 Mark Bergman bergman AT merctech DOT com
7 This library is free software; you can redistribute it and/or
8 modify it under the terms of the GNU Lesser General Public
9 License as published by the Free Software Foundation; either
10 version 2.1 of the License, or (at your option) any later version.
12 This library is distributed in the hope that it will be useful,
13 but WITHOUT ANY WARRANTY; without even the implied warranty of
14 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
15 Lesser General Public License for more details.
17 You should have received a copy of the GNU Lesser General Public
18 License along with this library; if not, write to the Free Software
19 Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
21 @todo Collapse voicemails
24 from __future__ import with_statement
37 from backends import gv_backend
38 from backends import null_backend
41 _moduleLogger = logging.getLogger("gv_views")
44 def make_ugly(prettynumber):
46 function to take a phone number and strip out all non-numeric
49 >>> make_ugly("+012-(345)-678-90")
52 return normalize_number(prettynumber)
55 def normalize_number(prettynumber):
57 function to take a phone number and strip out all non-numeric
60 >>> normalize_number("+012-(345)-678-90")
62 >>> normalize_number("1-(345)-678-9000")
64 >>> normalize_number("+1-(345)-678-9000")
67 uglynumber = re.sub('[^0-9+]', '', prettynumber)
68 if uglynumber.startswith("+"):
70 elif uglynumber.startswith("1") and len(uglynumber) == 11:
71 uglynumber = "+"+uglynumber
72 elif len(uglynumber) == 10:
73 uglynumber = "+1"+uglynumber
77 #validateRe = re.compile("^\+?[0-9]{10,}$")
78 #assert validateRe.match(uglynumber) is not None
83 def _make_pretty_with_areacodde(phonenumber):
84 prettynumber = "(%s)" % (phonenumber[0:3], )
85 if 3 < len(phonenumber):
86 prettynumber += " %s" % (phonenumber[3:6], )
87 if 6 < len(phonenumber):
88 prettynumber += "-%s" % (phonenumber[6:], )
92 def _make_pretty_local(phonenumber):
93 prettynumber = "%s" % (phonenumber[0:3], )
94 if 3 < len(phonenumber):
95 prettynumber += "-%s" % (phonenumber[3:], )
99 def _make_pretty_international(phonenumber):
100 prettynumber = phonenumber
101 if phonenumber.startswith("0"):
102 prettynumber = "+%s " % (phonenumber[0:3], )
103 if 3 < len(phonenumber):
104 prettynumber += _make_pretty_with_areacodde(phonenumber[3:])
105 if phonenumber.startswith("1"):
107 prettynumber += _make_pretty_with_areacodde(phonenumber[1:])
111 def make_pretty(phonenumber):
113 Function to take a phone number and return the pretty version
115 if phonenumber begins with 0:
117 if phonenumber begins with 1: ( for gizmo callback numbers )
119 if phonenumber is 13 digits:
121 if phonenumber is 10 digits:
123 >>> make_pretty("12")
125 >>> make_pretty("1234567")
127 >>> make_pretty("2345678901")
129 >>> make_pretty("12345678901")
131 >>> make_pretty("01234567890")
133 >>> make_pretty("+01234567890")
135 >>> make_pretty("+12")
137 >>> make_pretty("+123")
139 >>> make_pretty("+1234")
142 if phonenumber is None or phonenumber is "":
145 phonenumber = normalize_number(phonenumber)
147 if phonenumber[0] == "+":
148 prettynumber = _make_pretty_international(phonenumber[1:])
149 if not prettynumber.startswith("+"):
150 prettynumber = "+"+prettynumber
151 elif 8 < len(phonenumber) and phonenumber[0] in ("0", "1"):
152 prettynumber = _make_pretty_international(phonenumber)
153 elif 7 < len(phonenumber):
154 prettynumber = _make_pretty_with_areacodde(phonenumber)
155 elif 3 < len(phonenumber):
156 prettynumber = _make_pretty_local(phonenumber)
158 prettynumber = phonenumber
159 return prettynumber.strip()
162 def abbrev_relative_date(date):
164 >>> abbrev_relative_date("42 hours ago")
166 >>> abbrev_relative_date("2 days ago")
168 >>> abbrev_relative_date("4 weeks ago")
171 parts = date.split(" ")
172 return "%s %s" % (parts[0], parts[1][0])
175 def _collapse_message(messageLines, maxCharsPerLine, maxLines):
178 numLines = len(messageLines)
179 for line in messageLines[0:min(maxLines, numLines)]:
180 linesPerLine = max(1, int(len(line) / maxCharsPerLine))
181 allowedLines = maxLines - lines
182 acceptedLines = min(allowedLines, linesPerLine)
183 acceptedChars = acceptedLines * maxCharsPerLine
185 if acceptedChars < (len(line) + 3):
188 acceptedChars = len(line) # eh, might as well complete the line
190 abbrevMessage = "%s%s" % (line[0:acceptedChars], suffix)
193 lines += acceptedLines
194 if maxLines <= lines:
198 def collapse_message(message, maxCharsPerLine, maxLines):
200 >>> collapse_message("Hello", 60, 2)
202 >>> collapse_message("Hello world how are you doing today? 01234567890123456789012345678901234567890123456789012345678901234567890123456789", 60, 2)
203 'Hello world how are you doing today? 01234567890123456789012...'
204 >>> collapse_message('''Hello world how are you doing today?
205 ... 01234567890123456789
206 ... 01234567890123456789
207 ... 01234567890123456789
208 ... 01234567890123456789''', 60, 2)
209 'Hello world how are you doing today?\n01234567890123456789'
210 >>> collapse_message('''
211 ... Hello world how are you doing today? 01234567890123456789012345678901234567890123456789012345678901234567890123456789
212 ... Hello world how are you doing today? 01234567890123456789012345678901234567890123456789012345678901234567890123456789
213 ... Hello world how are you doing today? 01234567890123456789012345678901234567890123456789012345678901234567890123456789
214 ... Hello world how are you doing today? 01234567890123456789012345678901234567890123456789012345678901234567890123456789
215 ... Hello world how are you doing today? 01234567890123456789012345678901234567890123456789012345678901234567890123456789
216 ... Hello world how are you doing today? 01234567890123456789012345678901234567890123456789012345678901234567890123456789''', 60, 2)
217 '\nHello world how are you doing today? 01234567890123456789012...'
219 messageLines = message.split("\n")
220 return "\n".join(_collapse_message(messageLines, maxCharsPerLine, maxLines))
223 class SmsEntryWindow(object):
227 def __init__(self, widgetTree):
228 self._clipboard = gtk.clipboard_get()
229 self._widgetTree = widgetTree
230 self._window = self._widgetTree.get_widget("smsWindow")
231 self._window.connect("delete-event", self._on_delete)
232 self._window.connect("key-press-event", self._on_key_press)
234 self._smsButton = self._widgetTree.get_widget("sendSmsButton")
235 self._smsButton.connect("clicked", self._on_send)
236 self._dialButton = self._widgetTree.get_widget("dialButton")
237 self._dialButton.connect("clicked", self._on_dial)
239 self._letterCountLabel = self._widgetTree.get_widget("smsLetterCount")
241 self._messagemodel = gtk.ListStore(gobject.TYPE_STRING)
242 self._messagesView = self._widgetTree.get_widget("smsMessages")
244 textrenderer = gtk.CellRendererText()
245 textrenderer.set_property("wrap-mode", pango.WRAP_WORD)
246 textrenderer.set_property("wrap-width", 450)
247 messageColumn = gtk.TreeViewColumn("")
248 messageColumn.pack_start(textrenderer, expand=True)
249 messageColumn.add_attribute(textrenderer, "markup", 0)
250 messageColumn.set_sizing(gtk.TREE_VIEW_COLUMN_FIXED)
251 self._messagesView.append_column(messageColumn)
252 self._messagesView.set_headers_visible(False)
253 self._messagesView.set_model(self._messagemodel)
254 self._messagesView.set_fixed_height_mode(False)
256 self._conversationView = self._messagesView.get_parent()
257 self._conversationViewPort = self._conversationView.get_parent()
258 self._scrollWindow = self._conversationViewPort.get_parent()
260 self._targetList = self._widgetTree.get_widget("smsTargetList")
261 self._phoneButton = self._widgetTree.get_widget("phoneTypeSelection")
262 self._phoneButton.connect("clicked", self._on_phone)
263 self._smsEntry = self._widgetTree.get_widget("smsEntry")
264 self._smsEntry.get_buffer().connect("changed", self._on_entry_changed)
265 self._smsEntrySize = None
269 def add_contact(self, contactDetails, messages = (), parent = None, defaultIndex = -1):
270 contactNumbers = list(self._to_contact_numbers(contactDetails))
271 assert contactNumbers
272 contactIndex = defaultIndex if defaultIndex != -1 else 0
273 contact = contactNumbers, contactIndex, messages
274 self._contacts.append(contact)
276 selector = gtk.Button(contactNumbers[0][1])
277 removeContact = gtk.Button(stock="gtk-delete")
279 row.pack_start(selector, True, True)
280 row.pack_start(removeContact, False, False)
282 self._targetList.pack_start(row)
283 selector.connect("clicked", self._on_choose_phone_n, row)
284 removeContact.connect("clicked", self._on_remove_phone_n, row)
285 self._update_button_state()
286 self._update_context()
288 if parent is not None:
289 parentSize = parent.get_size()
290 self._window.resize(parentSize[0], max(parentSize[1]-10, 100))
292 self._window.present()
294 self._smsEntry.grab_focus()
295 dx = self._conversationView.get_allocation().height - self._conversationViewPort.get_allocation().height
297 adjustment = self._scrollWindow.get_vadjustment()
298 adjustment.value = dx
301 del self._contacts[:]
303 for contactNumberSelector in list(self._targetList.get_children()):
304 self._targetList.remove(contactNumberSelector)
305 self._smsEntry.get_buffer().set_text("")
306 self._update_letter_count()
307 self._update_context()
309 def _remove_contact(self, contactIndex):
310 del self._contacts[contactIndex]
312 contactNumberSelector = list(self._targetList.get_children())[contactIndex]
313 self._targetList.remove(contactNumberSelector)
314 self._update_button_state()
315 self._update_context()
317 def _update_letter_count(self):
318 if self._smsEntrySize is None:
319 self._smsEntrySize = self._smsEntry.size_request()
321 self._smsEntry.set_size_request(*self._smsEntrySize)
322 entryLength = self._smsEntry.get_buffer().get_char_count()
324 numTexts, numCharInText = divmod(entryLength, self.MAX_CHAR)
326 self._letterCountLabel.set_text("%s.%s" % (numTexts, numCharInText))
328 self._letterCountLabel.set_text("%s" % (numCharInText, ))
330 self._update_button_state()
332 def _update_context(self):
333 self._messagemodel.clear()
334 if len(self._contacts) == 0:
335 self._messagesView.hide()
336 self._targetList.hide()
337 self._phoneButton.hide()
338 self._phoneButton.set_label("Error: You shouldn't see this")
339 elif len(self._contacts) == 1:
340 contactNumbers, index, messages = self._contacts[0]
342 self._messagesView.show()
343 for message in messages:
345 self._messagemodel.append(row)
346 messagesSelection = self._messagesView.get_selection()
347 messagesSelection.select_path((len(messages)-1, ))
349 self._messagesView.hide()
350 self._targetList.hide()
351 self._phoneButton.show()
352 self._phoneButton.set_label(contactNumbers[index][1])
353 if 1 < len(contactNumbers):
354 self._phoneButton.set_sensitive(True)
356 self._phoneButton.set_sensitive(False)
358 self._messagesView.hide()
359 self._targetList.show()
360 self._phoneButton.hide()
361 self._phoneButton.set_label("Error: You shouldn't see this")
363 def _update_button_state(self):
364 if len(self._contacts) == 0:
365 self._dialButton.set_sensitive(False)
366 self._smsButton.set_sensitive(False)
367 elif len(self._contacts) == 1:
368 entryLength = self._smsEntry.get_buffer().get_char_count()
370 self._dialButton.set_sensitive(True)
371 self._smsButton.set_sensitive(False)
373 self._dialButton.set_sensitive(False)
374 self._smsButton.set_sensitive(True)
376 self._dialButton.set_sensitive(False)
377 self._smsButton.set_sensitive(True)
379 def _to_contact_numbers(self, contactDetails):
380 for phoneType, phoneNumber in contactDetails:
381 display = " - ".join((make_pretty(phoneNumber), phoneType))
382 yield (phoneNumber, display)
388 def _request_number(self, contactIndex):
389 contactNumbers, index, messages = self._contacts[contactIndex]
390 assert 0 <= index, "%r" % index
392 index = hildonize.touch_selector(
395 (description for (number, description) in contactNumbers),
398 self._contacts[contactIndex] = contactNumbers, index, messages
400 def _on_phone(self, *args):
402 assert len(self._contacts) == 1
403 self._request_number(0)
405 contactNumbers, numberIndex, messages = self._contacts[0]
406 self._phoneButton.set_label(contactNumbers[numberIndex][1])
407 row = list(self._targetList.get_children())[0]
408 phoneButton = list(row.get_children())[0]
409 phoneButton.set_label(contactNumbers[numberIndex][1])
411 _moduleLogger.exception("%s" % str(e))
413 def _on_choose_phone_n(self, button, row):
415 assert 1 < len(self._contacts)
416 targetList = list(self._targetList.get_children())
417 index = targetList.index(row)
418 self._request_number(index)
420 contactNumbers, numberIndex, messages = self._contacts[0]
421 phoneButton = list(row.get_children())[0]
422 phoneButton.set_label(contactNumbers[numberIndex][1])
424 _moduleLogger.exception("%s" % str(e))
426 def _on_remove_phone_n(self, button, row):
428 assert 1 < len(self._contacts)
429 targetList = list(self._targetList.get_children())
430 index = targetList.index(row)
432 del self._contacts[index]
433 self._targetList.remove(row)
434 self._update_context()
435 self._update_button_state()
437 _moduleLogger.exception("%s" % str(e))
439 def _on_entry_changed(self, *args):
440 self._update_letter_count()
442 def _on_send(self, *args):
443 assert 0 < len(self._contacts), "%r" % self._contacts
445 make_ugly(contact[0][contact[1]][0])
446 for contact in self._contacts
449 entryBuffer = self._smsEntry.get_buffer()
450 enteredMessage = entryBuffer.get_text(entryBuffer.get_start_iter(), entryBuffer.get_end_iter())
451 enteredMessage = enteredMessage.strip()
452 assert enteredMessage
456 def _on_dial(self, *args):
457 assert len(self._contacts) == 1, "%r" % self._contacts
458 contact = self._contacts[0]
459 contactNumber = contact[0][contact[1]][0]
460 phoneNumber = make_ugly(contactNumber)
464 def _on_delete(self, *args):
465 self._window.emit_stop_by_name("delete-event")
469 def _on_key_press(self, widget, event):
471 if event.keyval == ord("c") and event.get_state() & gtk.gdk.CONTROL_MASK:
474 for messagePart in self._messagemodel
476 self._clipboard.set_text(str(message))
478 _moduleLogger.exception(str(e))
481 class Dialpad(object):
483 def __init__(self, widgetTree, errorDisplay):
484 self._clipboard = gtk.clipboard_get()
485 self._errorDisplay = errorDisplay
487 self._numberdisplay = widgetTree.get_widget("numberdisplay")
488 self._okButton = widgetTree.get_widget("dialpadOk")
489 self._backButton = widgetTree.get_widget("back")
490 self._plusButton = widgetTree.get_widget("plus")
491 self._phonenumber = ""
492 self._prettynumber = ""
495 "on_digit_clicked": self._on_digit_clicked,
497 widgetTree.signal_autoconnect(callbackMapping)
498 self._okButton.connect("clicked", self._on_ok_clicked)
499 self._plusButton.connect("clicked", self._on_plus)
501 self._originalLabel = self._backButton.get_label()
502 self._backTapHandler = gtk_toolbox.TapOrHold(self._backButton)
503 self._backTapHandler.on_tap = self._on_backspace
504 self._backTapHandler.on_hold = self._on_clearall
505 self._backTapHandler.on_holding = self._set_clear_button
506 self._backTapHandler.on_cancel = self._reset_back_button
508 self._window = gtk_toolbox.find_parent_window(self._numberdisplay)
509 self._keyPressEventId = 0
512 self._okButton.grab_focus()
513 self._backTapHandler.enable()
514 self._keyPressEventId = self._window.connect("key-press-event", self._on_key_press)
517 self._window.disconnect(self._keyPressEventId)
518 self._keyPressEventId = 0
519 self._reset_back_button()
520 self._backTapHandler.disable()
522 def add_contact(self, *args, **kwds):
524 @note Actual dial function is patched in later
526 raise NotImplementedError("Horrible unknown error has occurred")
528 def get_number(self):
529 return self._phonenumber
531 def set_number(self, number):
533 Set the number to dial
536 self._phonenumber = make_ugly(number)
537 self._prettynumber = make_pretty(self._phonenumber)
538 self._numberdisplay.set_label("<span size='30000' weight='bold'>%s</span>" % (self._prettynumber))
540 self._errorDisplay.push_exception()
549 def load_settings(self, config, section):
552 def save_settings(self, config, section):
554 @note Thread Agnostic
558 def _on_key_press(self, widget, event):
560 if event.keyval == ord("v") and event.get_state() & gtk.gdk.CONTROL_MASK:
561 contents = self._clipboard.wait_for_text()
562 if contents is not None:
563 self.set_number(contents)
565 self._errorDisplay.push_exception()
567 def _on_ok_clicked(self, widget):
569 phoneNumber = self.get_number()
571 [("Dialer", phoneNumber)], (), self._window
574 self._errorDisplay.push_exception()
576 def _on_digit_clicked(self, widget):
578 self.set_number(self._phonenumber + widget.get_name()[-1])
580 self._errorDisplay.push_exception()
582 def _on_plus(self, *args):
584 self.set_number(self._phonenumber + "+")
586 self._errorDisplay.push_exception()
588 def _on_backspace(self, taps):
590 self.set_number(self._phonenumber[:-taps])
591 self._reset_back_button()
593 self._errorDisplay.push_exception()
595 def _on_clearall(self, taps):
598 self._reset_back_button()
600 self._errorDisplay.push_exception()
603 def _set_clear_button(self):
605 self._backButton.set_label("gtk-clear")
607 self._errorDisplay.push_exception()
609 def _reset_back_button(self):
611 self._backButton.set_label(self._originalLabel)
613 self._errorDisplay.push_exception()
616 class AccountInfo(object):
618 def __init__(self, widgetTree, backend, alarmHandler, errorDisplay):
619 self._errorDisplay = errorDisplay
620 self._backend = backend
621 self._isPopulated = False
622 self._alarmHandler = alarmHandler
623 self._notifyOnMissed = False
624 self._notifyOnVoicemail = False
625 self._notifyOnSms = False
627 self._callbackList = []
628 self._accountViewNumberDisplay = widgetTree.get_widget("gcnumber_display")
629 self._callbackSelectButton = widgetTree.get_widget("callbackSelectButton")
630 self._onCallbackSelectChangedId = 0
632 self._notifyCheckbox = widgetTree.get_widget("notifyCheckbox")
633 self._minutesEntryButton = widgetTree.get_widget("minutesEntryButton")
634 self._missedCheckbox = widgetTree.get_widget("missedCheckbox")
635 self._voicemailCheckbox = widgetTree.get_widget("voicemailCheckbox")
636 self._smsCheckbox = widgetTree.get_widget("smsCheckbox")
637 self._onNotifyToggled = 0
638 self._onMinutesChanged = 0
639 self._onMissedToggled = 0
640 self._onVoicemailToggled = 0
641 self._onSmsToggled = 0
642 self._applyAlarmTimeoutId = None
644 self._window = gtk_toolbox.find_parent_window(self._minutesEntryButton)
645 self._callbackNumber = ""
648 assert self._backend.is_authed(), "Attempting to enable backend while not logged in"
650 self._accountViewNumberDisplay.set_use_markup(True)
651 self.set_account_number("")
653 del self._callbackList[:]
654 self._onCallbackSelectChangedId = self._callbackSelectButton.connect("clicked", self._on_callbackentry_clicked)
655 self._set_callback_label("")
657 if self._alarmHandler is not None:
658 self._notifyCheckbox.set_active(self._alarmHandler.isEnabled)
659 self._minutesEntryButton.set_label("%d minutes" % self._alarmHandler.recurrence)
660 self._missedCheckbox.set_active(self._notifyOnMissed)
661 self._voicemailCheckbox.set_active(self._notifyOnVoicemail)
662 self._smsCheckbox.set_active(self._notifyOnSms)
664 self._onNotifyToggled = self._notifyCheckbox.connect("toggled", self._on_notify_toggled)
665 self._onMinutesChanged = self._minutesEntryButton.connect("clicked", self._on_minutes_clicked)
666 self._onMissedToggled = self._missedCheckbox.connect("toggled", self._on_missed_toggled)
667 self._onVoicemailToggled = self._voicemailCheckbox.connect("toggled", self._on_voicemail_toggled)
668 self._onSmsToggled = self._smsCheckbox.connect("toggled", self._on_sms_toggled)
670 self._notifyCheckbox.set_sensitive(False)
671 self._minutesEntryButton.set_sensitive(False)
672 self._missedCheckbox.set_sensitive(False)
673 self._voicemailCheckbox.set_sensitive(False)
674 self._smsCheckbox.set_sensitive(False)
676 self.update(force=True)
679 self._callbackSelectButton.disconnect(self._onCallbackSelectChangedId)
680 self._onCallbackSelectChangedId = 0
681 self._set_callback_label("")
683 if self._alarmHandler is not None:
684 self._notifyCheckbox.disconnect(self._onNotifyToggled)
685 self._minutesEntryButton.disconnect(self._onMinutesChanged)
686 self._missedCheckbox.disconnect(self._onNotifyToggled)
687 self._voicemailCheckbox.disconnect(self._onNotifyToggled)
688 self._smsCheckbox.disconnect(self._onNotifyToggled)
689 self._onNotifyToggled = 0
690 self._onMinutesChanged = 0
691 self._onMissedToggled = 0
692 self._onVoicemailToggled = 0
693 self._onSmsToggled = 0
695 self._notifyCheckbox.set_sensitive(True)
696 self._minutesEntryButton.set_sensitive(True)
697 self._missedCheckbox.set_sensitive(True)
698 self._voicemailCheckbox.set_sensitive(True)
699 self._smsCheckbox.set_sensitive(True)
702 del self._callbackList[:]
704 def set_account_number(self, number):
706 Displays current account number
708 self._accountViewNumberDisplay.set_label("<span size='23000' weight='bold'>%s</span>" % (number))
710 def update(self, force = False):
711 if not force and self._isPopulated:
713 self._populate_callback_combo()
714 self.set_account_number(self._backend.get_account_number())
718 self._set_callback_label("")
719 self.set_account_number("")
720 self._isPopulated = False
722 def save_everything(self):
723 raise NotImplementedError
727 return "Account Info"
729 def load_settings(self, config, section):
730 self._callbackNumber = make_ugly(config.get(section, "callback"))
731 self._notifyOnMissed = config.getboolean(section, "notifyOnMissed")
732 self._notifyOnVoicemail = config.getboolean(section, "notifyOnVoicemail")
733 self._notifyOnSms = config.getboolean(section, "notifyOnSms")
735 def save_settings(self, config, section):
737 @note Thread Agnostic
739 config.set(section, "callback", self._callbackNumber)
740 config.set(section, "notifyOnMissed", repr(self._notifyOnMissed))
741 config.set(section, "notifyOnVoicemail", repr(self._notifyOnVoicemail))
742 config.set(section, "notifyOnSms", repr(self._notifyOnSms))
744 def _populate_callback_combo(self):
745 self._isPopulated = True
746 del self._callbackList[:]
748 callbackNumbers = self._backend.get_callback_numbers()
750 self._errorDisplay.push_exception()
751 self._isPopulated = False
754 if len(callbackNumbers) == 0:
755 callbackNumbers = {"": "No callback numbers available"}
757 for number, description in callbackNumbers.iteritems():
758 self._callbackList.append((make_pretty(number), description))
760 self._set_callback_number(self._callbackNumber)
762 def _set_callback_number(self, number):
764 if not self._backend.is_valid_syntax(number) and 0 < len(number):
765 self._errorDisplay.push_message("%s is not a valid callback number" % number)
766 elif number == self._backend.get_callback_number() and 0 < len(number):
767 _moduleLogger.warning(
768 "Callback number already is %s" % (
769 self._backend.get_callback_number(),
772 self._set_callback_label(number)
774 if number.startswith("1747"): number = "+" + number
775 self._backend.set_callback_number(number)
776 assert make_ugly(number) == make_ugly(self._backend.get_callback_number()), "Callback number should be %s but instead is %s" % (
777 make_pretty(number), make_pretty(self._backend.get_callback_number())
779 self._callbackNumber = make_ugly(number)
780 self._set_callback_label(number)
782 "Callback number set to %s" % (
783 self._backend.get_callback_number(),
787 self._errorDisplay.push_exception()
789 def _set_callback_label(self, uglyNumber):
790 prettyNumber = make_pretty(uglyNumber)
791 if len(prettyNumber) == 0:
792 prettyNumber = "No Callback Number"
793 self._callbackSelectButton.set_label(prettyNumber)
795 def _update_alarm_settings(self, recurrence):
797 isEnabled = self._notifyCheckbox.get_active()
798 if isEnabled != self._alarmHandler.isEnabled or recurrence != self._alarmHandler.recurrence:
799 self._alarmHandler.apply_settings(isEnabled, recurrence)
801 self.save_everything()
802 self._notifyCheckbox.set_active(self._alarmHandler.isEnabled)
803 self._minutesEntryButton.set_label("%d Minutes" % self._alarmHandler.recurrence)
805 def _on_callbackentry_clicked(self, *args):
807 actualSelection = make_pretty(self._callbackNumber)
810 (number, "%s (%s)" % (number, description))
811 for (number, description) in self._callbackList
813 defaultSelection = userOptions.get(actualSelection, actualSelection)
815 userSelection = hildonize.touch_selector_entry(
818 list(userOptions.itervalues()),
821 reversedUserOptions = dict(
822 itertools.izip(userOptions.itervalues(), userOptions.iterkeys())
824 selectedNumber = reversedUserOptions.get(userSelection, userSelection)
826 number = make_ugly(selectedNumber)
827 self._set_callback_number(number)
828 except RuntimeError, e:
829 _moduleLogger.exception("%s" % str(e))
831 self._errorDisplay.push_exception()
833 def _on_notify_toggled(self, *args):
835 if self._applyAlarmTimeoutId is not None:
836 gobject.source_remove(self._applyAlarmTimeoutId)
837 self._applyAlarmTimeoutId = None
838 self._applyAlarmTimeoutId = gobject.timeout_add(500, self._on_apply_timeout)
840 self._errorDisplay.push_exception()
842 def _on_minutes_clicked(self, *args):
843 recurrenceChoices = [
859 actualSelection = self._alarmHandler.recurrence
861 closestSelectionIndex = 0
862 for i, possible in enumerate(recurrenceChoices):
863 if possible[0] <= actualSelection:
864 closestSelectionIndex = i
865 recurrenceIndex = hildonize.touch_selector(
868 (("%s" % m[1]) for m in recurrenceChoices),
869 closestSelectionIndex,
871 recurrence = recurrenceChoices[recurrenceIndex][0]
873 self._update_alarm_settings(recurrence)
874 except RuntimeError, e:
875 _moduleLogger.exception("%s" % str(e))
877 self._errorDisplay.push_exception()
879 def _on_apply_timeout(self, *args):
881 self._applyAlarmTimeoutId = None
883 self._update_alarm_settings(self._alarmHandler.recurrence)
885 self._errorDisplay.push_exception()
888 def _on_missed_toggled(self, *args):
890 self._notifyOnMissed = self._missedCheckbox.get_active()
891 self.save_everything()
893 self._errorDisplay.push_exception()
895 def _on_voicemail_toggled(self, *args):
897 self._notifyOnVoicemail = self._voicemailCheckbox.get_active()
898 self.save_everything()
900 self._errorDisplay.push_exception()
902 def _on_sms_toggled(self, *args):
904 self._notifyOnSms = self._smsCheckbox.get_active()
905 self.save_everything()
907 self._errorDisplay.push_exception()
910 class CallHistoryView(object):
918 HISTORY_ITEM_TYPES = ["All", "Received", "Missed", "Placed"]
920 def __init__(self, widgetTree, backend, errorDisplay):
921 self._errorDisplay = errorDisplay
922 self._backend = backend
924 self._isPopulated = False
925 self._historymodel = gtk.ListStore(
926 gobject.TYPE_STRING, # number
927 gobject.TYPE_STRING, # date
928 gobject.TYPE_STRING, # action
929 gobject.TYPE_STRING, # from
930 gobject.TYPE_STRING, # from id
932 self._historymodelfiltered = self._historymodel.filter_new()
933 self._historymodelfiltered.set_visible_func(self._is_history_visible)
934 self._historyview = widgetTree.get_widget("historyview")
935 self._historyviewselection = None
936 self._onRecentviewRowActivatedId = 0
938 textrenderer = gtk.CellRendererText()
939 textrenderer.set_property("yalign", 0)
940 self._dateColumn = gtk.TreeViewColumn("Date")
941 self._dateColumn.pack_start(textrenderer, expand=True)
942 self._dateColumn.add_attribute(textrenderer, "text", self.DATE_IDX)
944 textrenderer = gtk.CellRendererText()
945 textrenderer.set_property("yalign", 0)
946 self._actionColumn = gtk.TreeViewColumn("Action")
947 self._actionColumn.pack_start(textrenderer, expand=True)
948 self._actionColumn.add_attribute(textrenderer, "text", self.ACTION_IDX)
950 textrenderer = gtk.CellRendererText()
951 textrenderer.set_property("yalign", 0)
952 textrenderer.set_property("ellipsize", pango.ELLIPSIZE_END)
953 textrenderer.set_property("width-chars", len("1 (555) 555-1234"))
954 self._numberColumn = gtk.TreeViewColumn("Number")
955 self._numberColumn.pack_start(textrenderer, expand=True)
956 self._numberColumn.add_attribute(textrenderer, "text", self.NUMBER_IDX)
958 textrenderer = gtk.CellRendererText()
959 textrenderer.set_property("yalign", 0)
960 hildonize.set_cell_thumb_selectable(textrenderer)
961 self._nameColumn = gtk.TreeViewColumn("From")
962 self._nameColumn.pack_start(textrenderer, expand=True)
963 self._nameColumn.add_attribute(textrenderer, "text", self.FROM_IDX)
964 self._nameColumn.set_sizing(gtk.TREE_VIEW_COLUMN_FIXED)
966 self._window = gtk_toolbox.find_parent_window(self._historyview)
968 self._historyFilterSelector = widgetTree.get_widget("historyFilterSelector")
969 self._historyFilterSelector.connect("clicked", self._on_history_filter_clicked)
970 self._selectedFilter = "All"
972 self._updateSink = gtk_toolbox.threaded_stage(
974 self._idly_populate_historyview,
975 gtk_toolbox.null_sink(),
980 assert self._backend.is_authed(), "Attempting to enable backend while not logged in"
981 self._historyFilterSelector.set_label(self._selectedFilter)
983 self._historyview.set_model(self._historymodelfiltered)
984 self._historyview.set_fixed_height_mode(False)
986 self._historyview.append_column(self._dateColumn)
987 self._historyview.append_column(self._actionColumn)
988 self._historyview.append_column(self._numberColumn)
989 self._historyview.append_column(self._nameColumn)
990 self._historyviewselection = self._historyview.get_selection()
991 self._historyviewselection.set_mode(gtk.SELECTION_SINGLE)
993 self._onRecentviewRowActivatedId = self._historyview.connect("row-activated", self._on_historyview_row_activated)
996 self._historyview.disconnect(self._onRecentviewRowActivatedId)
1000 self._historyview.remove_column(self._dateColumn)
1001 self._historyview.remove_column(self._actionColumn)
1002 self._historyview.remove_column(self._nameColumn)
1003 self._historyview.remove_column(self._numberColumn)
1004 self._historyview.set_model(None)
1006 def add_contact(self, *args, **kwds):
1008 @note Actual dial function is patched in later
1010 raise NotImplementedError("Horrible unknown error has occurred")
1012 def update(self, force = False):
1013 if not force and self._isPopulated:
1015 self._updateSink.send(())
1019 self._isPopulated = False
1020 self._historymodel.clear()
1024 return "Recent Calls"
1026 def load_settings(self, config, sectionName):
1028 self._selectedFilter = config.get(sectionName, "filter")
1029 if self._selectedFilter not in self.HISTORY_ITEM_TYPES:
1030 self._messageType = self.HISTORY_ITEM_TYPES[0]
1031 except ConfigParser.NoOptionError:
1034 def save_settings(self, config, sectionName):
1036 @note Thread Agnostic
1038 config.set(sectionName, "filter", self._selectedFilter)
1040 def _is_history_visible(self, model, iter):
1042 action = model.get_value(iter, self.ACTION_IDX)
1044 return False # this seems weird but oh well
1046 if self._selectedFilter in [action, "All"]:
1050 except Exception, e:
1051 self._errorDisplay.push_exception()
1053 def _idly_populate_historyview(self):
1054 with gtk_toolbox.gtk_lock():
1055 banner = hildonize.show_busy_banner_start(self._window, "Loading Call History")
1057 self._historymodel.clear()
1058 self._isPopulated = True
1061 historyItems = self._backend.get_recent()
1062 except Exception, e:
1063 self._errorDisplay.push_exception_with_lock()
1064 self._isPopulated = False
1068 gv_backend.decorate_recent(data)
1069 for data in gv_backend.sort_messages(historyItems)
1072 for contactId, personName, phoneNumber, date, action in historyItems:
1074 personName = "Unknown"
1075 date = abbrev_relative_date(date)
1076 prettyNumber = phoneNumber[2:] if phoneNumber.startswith("+1") else phoneNumber
1077 prettyNumber = make_pretty(prettyNumber)
1078 item = (prettyNumber, date, action.capitalize(), personName, contactId)
1079 with gtk_toolbox.gtk_lock():
1080 self._historymodel.append(item)
1081 except Exception, e:
1082 self._errorDisplay.push_exception_with_lock()
1084 with gtk_toolbox.gtk_lock():
1085 hildonize.show_busy_banner_end(banner)
1089 def _on_history_filter_clicked(self, *args, **kwds):
1091 selectedComboIndex = self.HISTORY_ITEM_TYPES.index(self._selectedFilter)
1094 newSelectedComboIndex = hildonize.touch_selector(
1097 self.HISTORY_ITEM_TYPES,
1100 except RuntimeError:
1103 option = self.HISTORY_ITEM_TYPES[newSelectedComboIndex]
1104 self._selectedFilter = option
1105 self._historyFilterSelector.set_label(self._selectedFilter)
1106 self._historymodelfiltered.refilter()
1107 except Exception, e:
1108 self._errorDisplay.push_exception()
1110 def _on_historyview_row_activated(self, treeview, path, view_column):
1112 childPath = self._historymodelfiltered.convert_path_to_child_path(path)
1113 itr = self._historymodel.get_iter(childPath)
1117 number = self._historymodel.get_value(itr, self.NUMBER_IDX)
1118 number = make_ugly(number)
1119 description = self._historymodel.get_value(itr, self.FROM_IDX)
1120 contactId = self._historymodel.get_value(itr, self.FROM_ID_IDX)
1122 contactPhoneNumbers = list(self._backend.get_contact_details(contactId))
1124 (number == make_ugly(contactNumber) or number[1:] == make_ugly(contactNumber))
1125 for (numberDescription, contactNumber) in contactPhoneNumbers
1128 defaultIndex = defaultMatches.index(True)
1130 contactPhoneNumbers.append(("Other", number))
1131 defaultIndex = len(contactPhoneNumbers)-1
1133 "Could not find contact %r's number %s among %r" % (
1134 contactId, number, contactPhoneNumbers
1138 contactPhoneNumbers = [("Phone", number)]
1142 contactPhoneNumbers,
1143 messages = (description, ),
1144 parent = self._window,
1145 defaultIndex = defaultIndex,
1147 self._historyviewselection.unselect_all()
1148 except Exception, e:
1149 self._errorDisplay.push_exception()
1152 class MessagesView(object):
1160 MESSAGE_DATA_IDX = 6
1162 NO_MESSAGES = "None"
1163 VOICEMAIL_MESSAGES = "Voicemail"
1164 TEXT_MESSAGES = "Texts"
1165 ALL_TYPES = "All Messages"
1166 MESSAGE_TYPES = [NO_MESSAGES, VOICEMAIL_MESSAGES, TEXT_MESSAGES, ALL_TYPES]
1168 UNREAD_STATUS = "Unread"
1169 UNARCHIVED_STATUS = "Inbox"
1171 MESSAGE_STATUSES = [UNREAD_STATUS, UNARCHIVED_STATUS, ALL_STATUS]
1173 def __init__(self, widgetTree, backend, errorDisplay):
1174 self._errorDisplay = errorDisplay
1175 self._backend = backend
1177 self._isPopulated = False
1178 self._messagemodel = gtk.ListStore(
1179 gobject.TYPE_STRING, # number
1180 gobject.TYPE_STRING, # date
1181 gobject.TYPE_STRING, # header
1182 gobject.TYPE_STRING, # message
1184 gobject.TYPE_STRING, # from id
1185 object, # message data
1187 self._messagemodelfiltered = self._messagemodel.filter_new()
1188 self._messagemodelfiltered.set_visible_func(self._is_message_visible)
1189 self._messageview = widgetTree.get_widget("messages_view")
1190 self._messageviewselection = None
1191 self._onMessageviewRowActivatedId = 0
1193 self._messageRenderer = gtk.CellRendererText()
1194 self._messageRenderer.set_property("wrap-mode", pango.WRAP_WORD)
1195 self._messageRenderer.set_property("wrap-width", 500)
1196 self._messageColumn = gtk.TreeViewColumn("Messages")
1197 self._messageColumn.pack_start(self._messageRenderer, expand=True)
1198 self._messageColumn.add_attribute(self._messageRenderer, "markup", self.MESSAGE_IDX)
1199 self._messageColumn.set_sizing(gtk.TREE_VIEW_COLUMN_FIXED)
1201 self._window = gtk_toolbox.find_parent_window(self._messageview)
1203 self._messageTypeButton = widgetTree.get_widget("messageTypeButton")
1204 self._onMessageTypeClickedId = 0
1205 self._messageType = self.ALL_TYPES
1206 self._messageStatusButton = widgetTree.get_widget("messageStatusButton")
1207 self._onMessageStatusClickedId = 0
1208 self._messageStatus = self.ALL_STATUS
1210 self._updateSink = gtk_toolbox.threaded_stage(
1212 self._idly_populate_messageview,
1213 gtk_toolbox.null_sink(),
1218 assert self._backend.is_authed(), "Attempting to enable backend while not logged in"
1219 self._messageview.set_model(self._messagemodelfiltered)
1220 self._messageview.set_headers_visible(False)
1221 self._messageview.set_fixed_height_mode(False)
1223 self._messageview.append_column(self._messageColumn)
1224 self._messageviewselection = self._messageview.get_selection()
1225 self._messageviewselection.set_mode(gtk.SELECTION_SINGLE)
1227 self._messageTypeButton.set_label(self._messageType)
1228 self._messageStatusButton.set_label(self._messageStatus)
1230 self._onMessageviewRowActivatedId = self._messageview.connect(
1231 "row-activated", self._on_messageview_row_activated
1233 self._onMessageTypeClickedId = self._messageTypeButton.connect(
1234 "clicked", self._on_message_type_clicked
1236 self._onMessageStatusClickedId = self._messageStatusButton.connect(
1237 "clicked", self._on_message_status_clicked
1241 self._messageview.disconnect(self._onMessageviewRowActivatedId)
1242 self._messageTypeButton.disconnect(self._onMessageTypeClickedId)
1243 self._messageStatusButton.disconnect(self._onMessageStatusClickedId)
1247 self._messageview.remove_column(self._messageColumn)
1248 self._messageview.set_model(None)
1250 def add_contact(self, *args, **kwds):
1252 @note Actual dial function is patched in later
1254 raise NotImplementedError("Horrible unknown error has occurred")
1256 def update(self, force = False):
1257 if not force and self._isPopulated:
1259 self._updateSink.send(())
1263 self._isPopulated = False
1264 self._messagemodel.clear()
1270 def load_settings(self, config, sectionName):
1272 self._messageType = config.get(sectionName, "type")
1273 if self._messageType not in self.MESSAGE_TYPES:
1274 self._messageType = self.ALL_TYPES
1275 self._messageStatus = config.get(sectionName, "status")
1276 if self._messageStatus not in self.MESSAGE_STATUSES:
1277 self._messageStatus = self.ALL_STATUS
1278 except ConfigParser.NoOptionError:
1281 def save_settings(self, config, sectionName):
1283 @note Thread Agnostic
1285 config.set(sectionName, "status", self._messageStatus)
1286 config.set(sectionName, "type", self._messageType)
1288 def _is_message_visible(self, model, iter):
1290 message = model.get_value(iter, self.MESSAGE_DATA_IDX)
1292 return False # this seems weird but oh well
1293 return self._filter_messages(message, self._messageType, self._messageStatus)
1294 except Exception, e:
1295 self._errorDisplay.push_exception()
1298 def _filter_messages(cls, message, type, status):
1299 if type == cls.ALL_TYPES:
1302 messageType = message["type"]
1303 isType = messageType == type
1305 if status == cls.ALL_STATUS:
1308 isUnarchived = not message["isArchived"]
1309 isUnread = not message["isRead"]
1310 if status == cls.UNREAD_STATUS:
1311 isStatus = isUnarchived and isUnread
1312 elif status == cls.UNARCHIVED_STATUS:
1313 isStatus = isUnarchived
1315 assert "Status %s is bad for %r" % (status, message)
1317 return isType and isStatus
1319 _MIN_MESSAGES_SHOWN = 4
1321 def _idly_populate_messageview(self):
1322 with gtk_toolbox.gtk_lock():
1323 banner = hildonize.show_busy_banner_start(self._window, "Loading Messages")
1325 self._messagemodel.clear()
1326 self._isPopulated = True
1328 if self._messageType == self.NO_MESSAGES:
1332 messageItems = self._backend.get_messages()
1333 except Exception, e:
1334 self._errorDisplay.push_exception_with_lock()
1335 self._isPopulated = False
1339 (gv_backend.decorate_message(message), message)
1340 for message in gv_backend.sort_messages(messageItems)
1343 for (contactId, header, number, relativeDate, messages), messageData in messageItems:
1344 prettyNumber = number[2:] if number.startswith("+1") else number
1345 prettyNumber = make_pretty(prettyNumber)
1347 firstMessage = "<b>%s - %s</b> <i>(%s)</i>" % (header, prettyNumber, relativeDate)
1348 expandedMessages = [firstMessage]
1349 expandedMessages.extend(messages)
1350 if (self._MIN_MESSAGES_SHOWN + 1) < len(messages):
1351 firstMessage = "<b>%s - %s</b> <i>(%s)</i>" % (header, prettyNumber, relativeDate)
1352 secondMessage = "<i>%d Messages Hidden...</i>" % (len(messages) - self._MIN_MESSAGES_SHOWN, )
1353 collapsedMessages = [firstMessage, secondMessage]
1354 collapsedMessages.extend(messages[-(self._MIN_MESSAGES_SHOWN+0):])
1356 collapsedMessages = expandedMessages
1357 #collapsedMessages = _collapse_message(collapsedMessages, 60, self._MIN_MESSAGES_SHOWN)
1359 number = make_ugly(number)
1361 row = number, relativeDate, header, "\n".join(collapsedMessages), expandedMessages, contactId, messageData
1362 with gtk_toolbox.gtk_lock():
1363 self._messagemodel.append(row)
1364 except Exception, e:
1365 self._errorDisplay.push_exception_with_lock()
1367 with gtk_toolbox.gtk_lock():
1368 hildonize.show_busy_banner_end(banner)
1369 self._messagemodelfiltered.refilter()
1373 def _on_messageview_row_activated(self, treeview, path, view_column):
1375 childPath = self._messagemodelfiltered.convert_path_to_child_path(path)
1376 itr = self._messagemodel.get_iter(childPath)
1380 number = make_ugly(self._messagemodel.get_value(itr, self.NUMBER_IDX))
1381 description = self._messagemodel.get_value(itr, self.MESSAGES_IDX)
1383 contactId = self._messagemodel.get_value(itr, self.FROM_ID_IDX)
1385 contactPhoneNumbers = list(self._backend.get_contact_details(contactId))
1387 (number == make_ugly(contactNumber) or number[1:] == make_ugly(contactNumber))
1388 for (numberDescription, contactNumber) in contactPhoneNumbers
1391 defaultIndex = defaultMatches.index(True)
1393 contactPhoneNumbers.append(("Other", number))
1394 defaultIndex = len(contactPhoneNumbers)-1
1396 "Could not find contact %r's number %s among %r" % (
1397 contactId, number, contactPhoneNumbers
1401 contactPhoneNumbers = [("Phone", number)]
1405 contactPhoneNumbers,
1406 messages = description,
1407 parent = self._window,
1408 defaultIndex = defaultIndex,
1410 self._messageviewselection.unselect_all()
1411 except Exception, e:
1412 self._errorDisplay.push_exception()
1414 def _on_message_type_clicked(self, *args, **kwds):
1416 selectedIndex = self.MESSAGE_TYPES.index(self._messageType)
1419 newSelectedIndex = hildonize.touch_selector(
1425 except RuntimeError:
1428 if selectedIndex != newSelectedIndex:
1429 self._messageType = self.MESSAGE_TYPES[newSelectedIndex]
1430 self._messageTypeButton.set_label(self._messageType)
1431 self._messagemodelfiltered.refilter()
1432 except Exception, e:
1433 self._errorDisplay.push_exception()
1435 def _on_message_status_clicked(self, *args, **kwds):
1437 selectedIndex = self.MESSAGE_STATUSES.index(self._messageStatus)
1440 newSelectedIndex = hildonize.touch_selector(
1443 self.MESSAGE_STATUSES,
1446 except RuntimeError:
1449 if selectedIndex != newSelectedIndex:
1450 self._messageStatus = self.MESSAGE_STATUSES[newSelectedIndex]
1451 self._messageStatusButton.set_label(self._messageStatus)
1452 self._messagemodelfiltered.refilter()
1453 except Exception, e:
1454 self._errorDisplay.push_exception()
1457 class ContactsView(object):
1459 CONTACT_TYPE_IDX = 0
1460 CONTACT_NAME_IDX = 1
1463 def __init__(self, widgetTree, backend, errorDisplay):
1464 self._errorDisplay = errorDisplay
1465 self._backend = backend
1467 self._addressBook = None
1468 self._selectedComboIndex = 0
1469 self._addressBookFactories = [null_backend.NullAddressBook()]
1471 self._booksList = []
1472 self._bookSelectionButton = widgetTree.get_widget("addressbookSelectButton")
1474 self._isPopulated = False
1475 self._contactsmodel = gtk.ListStore(
1476 gobject.TYPE_STRING, # Contact Type
1477 gobject.TYPE_STRING, # Contact Name
1478 gobject.TYPE_STRING, # Contact ID
1480 self._contactsviewselection = None
1481 self._contactsview = widgetTree.get_widget("contactsview")
1483 self._contactColumn = gtk.TreeViewColumn("Contact")
1484 displayContactSource = False
1485 if displayContactSource:
1486 textrenderer = gtk.CellRendererText()
1487 self._contactColumn.pack_start(textrenderer, expand=False)
1488 self._contactColumn.add_attribute(textrenderer, 'text', self.CONTACT_TYPE_IDX)
1489 textrenderer = gtk.CellRendererText()
1490 hildonize.set_cell_thumb_selectable(textrenderer)
1491 self._contactColumn.pack_start(textrenderer, expand=True)
1492 self._contactColumn.add_attribute(textrenderer, 'text', self.CONTACT_NAME_IDX)
1493 self._contactColumn.set_sizing(gtk.TREE_VIEW_COLUMN_FIXED)
1494 self._contactColumn.set_sort_column_id(1)
1495 self._contactColumn.set_visible(True)
1497 self._onContactsviewRowActivatedId = 0
1498 self._onAddressbookButtonChangedId = 0
1499 self._window = gtk_toolbox.find_parent_window(self._contactsview)
1501 self._updateSink = gtk_toolbox.threaded_stage(
1503 self._idly_populate_contactsview,
1504 gtk_toolbox.null_sink(),
1509 assert self._backend.is_authed(), "Attempting to enable backend while not logged in"
1511 self._contactsview.set_model(self._contactsmodel)
1512 self._contactsview.set_fixed_height_mode(False)
1513 self._contactsview.append_column(self._contactColumn)
1514 self._contactsviewselection = self._contactsview.get_selection()
1515 self._contactsviewselection.set_mode(gtk.SELECTION_SINGLE)
1517 del self._booksList[:]
1518 for (factoryId, bookId), (factoryName, bookName) in self.get_addressbooks():
1519 if factoryName and bookName:
1520 entryName = "%s: %s" % (factoryName, bookName)
1522 entryName = factoryName
1524 entryName = bookName
1526 entryName = "Bad name (%d)" % factoryId
1527 row = (str(factoryId), bookId, entryName)
1528 self._booksList.append(row)
1530 self._onContactsviewRowActivatedId = self._contactsview.connect("row-activated", self._on_contactsview_row_activated)
1531 self._onAddressbookButtonChangedId = self._bookSelectionButton.connect("clicked", self._on_addressbook_button_changed)
1533 if len(self._booksList) <= self._selectedComboIndex:
1534 self._selectedComboIndex = 0
1535 self._bookSelectionButton.set_label(self._booksList[self._selectedComboIndex][2])
1537 selectedFactoryId = self._booksList[self._selectedComboIndex][0]
1538 selectedBookId = self._booksList[self._selectedComboIndex][1]
1539 self.open_addressbook(selectedFactoryId, selectedBookId)
1542 self._contactsview.disconnect(self._onContactsviewRowActivatedId)
1543 self._bookSelectionButton.disconnect(self._onAddressbookButtonChangedId)
1547 self._bookSelectionButton.set_label("")
1548 self._contactsview.set_model(None)
1549 self._contactsview.remove_column(self._contactColumn)
1551 def add_contact(self, *args, **kwds):
1553 @note Actual dial function is patched in later
1555 raise NotImplementedError("Horrible unknown error has occurred")
1557 def get_addressbooks(self):
1559 @returns Iterable of ((Factory Id, Book Id), (Factory Name, Book Name))
1561 for i, factory in enumerate(self._addressBookFactories):
1562 for bookFactory, bookId, bookName in factory.get_addressbooks():
1563 yield (str(i), bookId), (factory.factory_name(), bookName)
1565 def open_addressbook(self, bookFactoryId, bookId):
1566 bookFactoryIndex = int(bookFactoryId)
1567 addressBook = self._addressBookFactories[bookFactoryIndex].open_addressbook(bookId)
1568 self._addressBook = addressBook
1570 def update(self, force = False):
1571 if not force and self._isPopulated:
1573 self._updateSink.send(())
1577 self._isPopulated = False
1578 self._contactsmodel.clear()
1579 for factory in self._addressBookFactories:
1580 factory.clear_caches()
1581 self._addressBook.clear_caches()
1583 def append(self, book):
1584 self._addressBookFactories.append(book)
1586 def extend(self, books):
1587 self._addressBookFactories.extend(books)
1593 def load_settings(self, config, sectionName):
1595 self._selectedComboIndex = config.getint(sectionName, "selectedAddressbook")
1596 except ConfigParser.NoOptionError:
1597 self._selectedComboIndex = 0
1599 def save_settings(self, config, sectionName):
1600 config.set(sectionName, "selectedAddressbook", str(self._selectedComboIndex))
1602 def _idly_populate_contactsview(self):
1603 with gtk_toolbox.gtk_lock():
1604 banner = hildonize.show_busy_banner_start(self._window, "Loading Contacts")
1607 while addressBook is not self._addressBook:
1608 addressBook = self._addressBook
1609 with gtk_toolbox.gtk_lock():
1610 self._contactsview.set_model(None)
1614 contacts = addressBook.get_contacts()
1615 except Exception, e:
1617 self._isPopulated = False
1618 self._errorDisplay.push_exception_with_lock()
1619 for contactId, contactName in contacts:
1620 contactType = addressBook.contact_source_short_name(contactId)
1621 row = contactType, contactName, contactId
1622 self._contactsmodel.append(row)
1624 with gtk_toolbox.gtk_lock():
1625 self._contactsview.set_model(self._contactsmodel)
1627 self._isPopulated = True
1628 except Exception, e:
1629 self._errorDisplay.push_exception_with_lock()
1631 with gtk_toolbox.gtk_lock():
1632 hildonize.show_busy_banner_end(banner)
1635 def _on_addressbook_button_changed(self, *args, **kwds):
1638 newSelectedComboIndex = hildonize.touch_selector(
1641 (("%s" % m[2]) for m in self._booksList),
1642 self._selectedComboIndex,
1644 except RuntimeError:
1647 selectedFactoryId = self._booksList[newSelectedComboIndex][0]
1648 selectedBookId = self._booksList[newSelectedComboIndex][1]
1650 oldAddressbook = self._addressBook
1651 self.open_addressbook(selectedFactoryId, selectedBookId)
1652 forceUpdate = True if oldAddressbook is not self._addressBook else False
1653 self.update(force=forceUpdate)
1655 self._selectedComboIndex = newSelectedComboIndex
1656 self._bookSelectionButton.set_label(self._booksList[self._selectedComboIndex][2])
1657 except Exception, e:
1658 self._errorDisplay.push_exception()
1660 def _on_contactsview_row_activated(self, treeview, path, view_column):
1662 itr = self._contactsmodel.get_iter(path)
1666 contactId = self._contactsmodel.get_value(itr, self.CONTACT_ID_IDX)
1667 contactName = self._contactsmodel.get_value(itr, self.CONTACT_NAME_IDX)
1669 contactDetails = self._addressBook.get_contact_details(contactId)
1670 except Exception, e:
1672 self._errorDisplay.push_exception()
1673 contactPhoneNumbers = [phoneNumber for phoneNumber in contactDetails]
1675 if len(contactPhoneNumbers) == 0:
1679 contactPhoneNumbers,
1680 messages = (contactName, ),
1681 parent = self._window,
1683 self._contactsviewselection.unselect_all()
1684 except Exception, e:
1685 self._errorDisplay.push_exception()