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
38 from backends import gv_backend
39 from backends import null_backend
42 _moduleLogger = logging.getLogger("gv_views")
45 def make_ugly(prettynumber):
47 function to take a phone number and strip out all non-numeric
50 >>> make_ugly("+012-(345)-678-90")
53 return normalize_number(prettynumber)
56 def normalize_number(prettynumber):
58 function to take a phone number and strip out all non-numeric
61 >>> normalize_number("+012-(345)-678-90")
63 >>> normalize_number("1-(345)-678-9000")
65 >>> normalize_number("+1-(345)-678-9000")
68 uglynumber = re.sub('[^0-9+]', '', prettynumber)
69 if uglynumber.startswith("+"):
71 elif uglynumber.startswith("1") and len(uglynumber) == 11:
72 uglynumber = "+"+uglynumber
73 elif len(uglynumber) == 10:
74 uglynumber = "+1"+uglynumber
78 #validateRe = re.compile("^\+?[0-9]{10,}$")
79 #assert validateRe.match(uglynumber) is not None
84 def _make_pretty_with_areacodde(phonenumber):
85 prettynumber = "(%s)" % (phonenumber[0:3], )
86 if 3 < len(phonenumber):
87 prettynumber += " %s" % (phonenumber[3:6], )
88 if 6 < len(phonenumber):
89 prettynumber += "-%s" % (phonenumber[6:], )
93 def _make_pretty_local(phonenumber):
94 prettynumber = "%s" % (phonenumber[0:3], )
95 if 3 < len(phonenumber):
96 prettynumber += "-%s" % (phonenumber[3:], )
100 def _make_pretty_international(phonenumber):
101 prettynumber = phonenumber
102 if phonenumber.startswith("0"):
103 prettynumber = "+%s " % (phonenumber[0:3], )
104 if 3 < len(phonenumber):
105 prettynumber += _make_pretty_with_areacodde(phonenumber[3:])
106 if phonenumber.startswith("1"):
108 prettynumber += _make_pretty_with_areacodde(phonenumber[1:])
112 def make_pretty(phonenumber):
114 Function to take a phone number and return the pretty version
116 if phonenumber begins with 0:
118 if phonenumber begins with 1: ( for gizmo callback numbers )
120 if phonenumber is 13 digits:
122 if phonenumber is 10 digits:
124 >>> make_pretty("12")
126 >>> make_pretty("1234567")
128 >>> make_pretty("2345678901")
130 >>> make_pretty("12345678901")
132 >>> make_pretty("01234567890")
134 >>> make_pretty("+01234567890")
136 >>> make_pretty("+12")
138 >>> make_pretty("+123")
140 >>> make_pretty("+1234")
143 if phonenumber is None or phonenumber is "":
146 phonenumber = normalize_number(phonenumber)
148 if phonenumber[0] == "+":
149 prettynumber = _make_pretty_international(phonenumber[1:])
150 if not prettynumber.startswith("+"):
151 prettynumber = "+"+prettynumber
152 elif 8 < len(phonenumber) and phonenumber[0] in ("0", "1"):
153 prettynumber = _make_pretty_international(phonenumber)
154 elif 7 < len(phonenumber):
155 prettynumber = _make_pretty_with_areacodde(phonenumber)
156 elif 3 < len(phonenumber):
157 prettynumber = _make_pretty_local(phonenumber)
159 prettynumber = phonenumber
160 return prettynumber.strip()
163 def abbrev_relative_date(date):
165 >>> abbrev_relative_date("42 hours ago")
167 >>> abbrev_relative_date("2 days ago")
169 >>> abbrev_relative_date("4 weeks ago")
172 parts = date.split(" ")
173 return "%s %s" % (parts[0], parts[1][0])
176 def _collapse_message(messageLines, maxCharsPerLine, maxLines):
179 numLines = len(messageLines)
180 for line in messageLines[0:min(maxLines, numLines)]:
181 linesPerLine = max(1, int(len(line) / maxCharsPerLine))
182 allowedLines = maxLines - lines
183 acceptedLines = min(allowedLines, linesPerLine)
184 acceptedChars = acceptedLines * maxCharsPerLine
186 if acceptedChars < (len(line) + 3):
189 acceptedChars = len(line) # eh, might as well complete the line
191 abbrevMessage = "%s%s" % (line[0:acceptedChars], suffix)
194 lines += acceptedLines
195 if maxLines <= lines:
199 def collapse_message(message, maxCharsPerLine, maxLines):
201 >>> collapse_message("Hello", 60, 2)
203 >>> collapse_message("Hello world how are you doing today? 01234567890123456789012345678901234567890123456789012345678901234567890123456789", 60, 2)
204 'Hello world how are you doing today? 01234567890123456789012...'
205 >>> collapse_message('''Hello world how are you doing today?
206 ... 01234567890123456789
207 ... 01234567890123456789
208 ... 01234567890123456789
209 ... 01234567890123456789''', 60, 2)
210 'Hello world how are you doing today?\n01234567890123456789'
211 >>> collapse_message('''
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
217 ... Hello world how are you doing today? 01234567890123456789012345678901234567890123456789012345678901234567890123456789''', 60, 2)
218 '\nHello world how are you doing today? 01234567890123456789012...'
220 messageLines = message.split("\n")
221 return "\n".join(_collapse_message(messageLines, maxCharsPerLine, maxLines))
224 class SmsEntryDialog(object):
226 @todo Add multi-SMS messages like GoogleVoice
229 ACTION_CANCEL = "cancel"
231 ACTION_SEND_SMS = "sms"
235 def __init__(self, widgetTree):
236 self._clipboard = gtk.clipboard_get()
237 self._widgetTree = widgetTree
238 self._dialog = self._widgetTree.get_widget("smsDialog")
240 self._smsButton = self._widgetTree.get_widget("sendSmsButton")
241 self._smsButton.connect("clicked", self._on_send)
242 self._dialButton = self._widgetTree.get_widget("dialButton")
243 self._dialButton.connect("clicked", self._on_dial)
244 self._cancelButton = self._widgetTree.get_widget("cancelSmsButton")
245 self._cancelButton.connect("clicked", self._on_cancel)
247 self._letterCountLabel = self._widgetTree.get_widget("smsLetterCount")
249 self._messagemodel = gtk.ListStore(gobject.TYPE_STRING)
250 self._messagesView = self._widgetTree.get_widget("smsMessages")
252 self._conversationView = self._messagesView.get_parent()
253 self._conversationViewPort = self._conversationView.get_parent()
254 self._scrollWindow = self._conversationViewPort.get_parent()
256 self._phoneButton = self._widgetTree.get_widget("phoneTypeSelection")
257 self._smsEntry = self._widgetTree.get_widget("smsEntry")
258 self._smsEntrySize = None
260 self._action = self.ACTION_CANCEL
262 self._numberIndex = -1
263 self._contactDetails = []
265 def run(self, contactDetails, messages = (), parent = None, defaultIndex = -1):
266 entryConnectId = self._smsEntry.get_buffer().connect("changed", self._on_entry_changed)
267 phoneConnectId = self._phoneButton.connect("clicked", self._on_phone)
268 keyConnectId = self._keyPressEventId = self._dialog.connect("key-press-event", self._on_key_press)
270 # Setup the phone selection button
271 del self._contactDetails[:]
272 for phoneType, phoneNumber in contactDetails:
273 display = " - ".join((make_pretty(phoneNumber), phoneType))
274 row = (phoneNumber, display)
275 self._contactDetails.append(row)
276 if 0 < len(self._contactDetails):
277 self._numberIndex = defaultIndex if defaultIndex != -1 else 0
278 self._phoneButton.set_label(self._contactDetails[self._numberIndex][1])
280 self._numberIndex = -1
281 self._phoneButton.set_label("Error: No Number Available")
283 # Add the column to the messages tree view
284 self._messagemodel.clear()
285 self._messagesView.set_model(self._messagemodel)
286 self._messagesView.set_fixed_height_mode(False)
288 textrenderer = gtk.CellRendererText()
289 textrenderer.set_property("wrap-mode", pango.WRAP_WORD)
290 textrenderer.set_property("wrap-width", 450)
291 messageColumn = gtk.TreeViewColumn("")
292 messageColumn.pack_start(textrenderer, expand=True)
293 messageColumn.add_attribute(textrenderer, "markup", 0)
294 messageColumn.set_sizing(gtk.TREE_VIEW_COLUMN_FIXED)
295 self._messagesView.append_column(messageColumn)
296 self._messagesView.set_headers_visible(False)
299 for message in messages:
301 self._messagemodel.append(row)
302 self._messagesView.show()
303 self._scrollWindow.show()
304 messagesSelection = self._messagesView.get_selection()
305 messagesSelection.select_path((len(messages)-1, ))
307 self._messagesView.hide()
308 self._scrollWindow.hide()
310 self._smsEntry.get_buffer().set_text("")
311 self._update_letter_count()
313 if parent is not None:
314 self._dialog.set_transient_for(parent)
315 parentSize = parent.get_size()
316 self._dialog.resize(parentSize[0], max(parentSize[1]-10, 100))
320 self._dialog.show_all()
321 self._smsEntry.grab_focus()
322 adjustment = self._scrollWindow.get_vadjustment()
323 dx = self._conversationView.get_allocation().height - self._conversationViewPort.get_allocation().height
325 adjustment.value = dx
327 if 1 < len(self._contactDetails):
328 if defaultIndex == -1:
329 self._request_number()
330 self._phoneButton.set_sensitive(True)
332 self._phoneButton.set_sensitive(False)
334 userResponse = self._dialog.run()
336 self._dialog.hide_all()
338 # Process the users response
339 if userResponse == gtk.RESPONSE_OK and 0 <= self._numberIndex:
340 phoneNumber = self._contactDetails[self._numberIndex][0]
341 phoneNumber = make_ugly(phoneNumber)
345 self._action = self.ACTION_CANCEL
346 if self._action == self.ACTION_SEND_SMS:
347 entryBuffer = self._smsEntry.get_buffer()
348 enteredMessage = entryBuffer.get_text(entryBuffer.get_start_iter(), entryBuffer.get_end_iter())
349 enteredMessage = enteredMessage[0:self.MAX_CHAR].strip()
350 if not enteredMessage:
352 self._action = self.ACTION_CANCEL
356 self._messagesView.remove_column(messageColumn)
357 self._messagesView.set_model(None)
359 return self._action, phoneNumber, enteredMessage
361 self._smsEntry.get_buffer().disconnect(entryConnectId)
362 self._phoneButton.disconnect(phoneConnectId)
363 self._keyPressEventId = self._dialog.disconnect(keyConnectId)
365 def _update_letter_count(self, *args):
366 if self._smsEntrySize is None:
367 self._smsEntrySize = self._smsEntry.size_request()
369 self._smsEntry.set_size_request(*self._smsEntrySize)
370 entryLength = self._smsEntry.get_buffer().get_char_count()
372 charsLeft = self.MAX_CHAR - entryLength
373 self._letterCountLabel.set_text(str(charsLeft))
374 if charsLeft < 0 or charsLeft == self.MAX_CHAR:
375 self._smsButton.set_sensitive(False)
377 self._smsButton.set_sensitive(True)
380 self._dialButton.set_sensitive(True)
382 self._dialButton.set_sensitive(False)
384 def _request_number(self):
386 assert 0 <= self._numberIndex, "%r" % self._numberIndex
388 self._numberIndex = hildonize.touch_selector(
391 (description for (number, description) in self._contactDetails),
394 self._phoneButton.set_label(self._contactDetails[self._numberIndex][1])
396 _moduleLogger.exception("%s" % str(e))
398 def _on_phone(self, *args):
399 self._request_number()
401 def _on_entry_changed(self, *args):
402 self._update_letter_count()
404 def _on_send(self, *args):
405 self._dialog.response(gtk.RESPONSE_OK)
406 self._action = self.ACTION_SEND_SMS
408 def _on_dial(self, *args):
409 self._dialog.response(gtk.RESPONSE_OK)
410 self._action = self.ACTION_DIAL
412 def _on_cancel(self, *args):
413 self._dialog.response(gtk.RESPONSE_CANCEL)
414 self._action = self.ACTION_CANCEL
416 def _on_key_press(self, widget, event):
418 if event.keyval == ord("c") and event.get_state() & gtk.gdk.CONTROL_MASK:
421 for messagePart in self._messagemodel
423 self._clipboard.set_text(str(message))
425 _moduleLogger.exception(str(e))
428 class Dialpad(object):
430 def __init__(self, widgetTree, errorDisplay):
431 self._clipboard = gtk.clipboard_get()
432 self._errorDisplay = errorDisplay
433 self._smsDialog = SmsEntryDialog(widgetTree)
435 self._numberdisplay = widgetTree.get_widget("numberdisplay")
436 self._smsButton = widgetTree.get_widget("sms")
437 self._dialButton = widgetTree.get_widget("dial")
438 self._backButton = widgetTree.get_widget("back")
439 self._zeroOrPlusButton = widgetTree.get_widget("digit0")
440 self._phonenumber = ""
441 self._prettynumber = ""
444 "on_digit_clicked": self._on_digit_clicked,
446 widgetTree.signal_autoconnect(callbackMapping)
447 self._dialButton.connect("clicked", self._on_dial_clicked)
448 self._smsButton.connect("clicked", self._on_sms_clicked)
450 self._originalLabel = self._backButton.get_label()
451 self._backTapHandler = gtk_toolbox.TapOrHold(self._backButton)
452 self._backTapHandler.on_tap = self._on_backspace
453 self._backTapHandler.on_hold = self._on_clearall
454 self._backTapHandler.on_holding = self._set_clear_button
455 self._backTapHandler.on_cancel = self._reset_back_button
456 self._zeroOrPlusTapHandler = gtk_toolbox.TapOrHold(self._zeroOrPlusButton)
457 self._zeroOrPlusTapHandler.on_tap = self._on_zero
458 self._zeroOrPlusTapHandler.on_hold = self._on_plus
460 self._window = gtk_toolbox.find_parent_window(self._numberdisplay)
461 self._keyPressEventId = 0
464 self._dialButton.grab_focus()
465 self._backTapHandler.enable()
466 self._zeroOrPlusTapHandler.enable()
467 self._keyPressEventId = self._window.connect("key-press-event", self._on_key_press)
470 self._window.disconnect(self._keyPressEventId)
471 self._keyPressEventId = 0
472 self._reset_back_button()
473 self._backTapHandler.disable()
474 self._zeroOrPlusTapHandler.disable()
476 def number_selected(self, action, number, message):
478 @note Actual dial function is patched in later
480 raise NotImplementedError("Horrible unknown error has occurred")
482 def get_number(self):
483 return self._phonenumber
485 def set_number(self, number):
487 Set the number to dial
490 self._phonenumber = make_ugly(number)
491 self._prettynumber = make_pretty(self._phonenumber)
492 self._numberdisplay.set_label("<span size='30000' weight='bold'>%s</span>" % (self._prettynumber))
494 self._errorDisplay.push_exception()
503 def load_settings(self, config, section):
506 def save_settings(self, config, section):
508 @note Thread Agnostic
512 def _on_key_press(self, widget, event):
514 if event.keyval == ord("v") and event.get_state() & gtk.gdk.CONTROL_MASK:
515 contents = self._clipboard.wait_for_text()
516 if contents is not None:
517 self.set_number(contents)
519 self._errorDisplay.push_exception()
521 def _on_sms_clicked(self, widget):
523 phoneNumber = self.get_number()
524 action, phoneNumber, message = self._smsDialog.run([("Dialer", phoneNumber)], (), self._window)
526 if action == SmsEntryDialog.ACTION_CANCEL:
528 self.number_selected(action, phoneNumber, message)
530 self._errorDisplay.push_exception()
532 def _on_dial_clicked(self, widget):
534 action = SmsEntryDialog.ACTION_DIAL
535 phoneNumber = self.get_number()
537 self.number_selected(action, phoneNumber, message)
539 self._errorDisplay.push_exception()
541 def _on_digit_clicked(self, widget):
543 self.set_number(self._phonenumber + widget.get_name()[-1])
545 self._errorDisplay.push_exception()
547 def _on_zero(self, *args):
549 self.set_number(self._phonenumber + "0")
551 self._errorDisplay.push_exception()
553 def _on_plus(self, *args):
555 self.set_number(self._phonenumber + "+")
557 self._errorDisplay.push_exception()
559 def _on_backspace(self, taps):
561 self.set_number(self._phonenumber[:-taps])
562 self._reset_back_button()
564 self._errorDisplay.push_exception()
566 def _on_clearall(self, taps):
569 self._reset_back_button()
571 self._errorDisplay.push_exception()
574 def _set_clear_button(self):
576 self._backButton.set_label("gtk-clear")
578 self._errorDisplay.push_exception()
580 def _reset_back_button(self):
582 self._backButton.set_label(self._originalLabel)
584 self._errorDisplay.push_exception()
587 class AccountInfo(object):
589 def __init__(self, widgetTree, backend, alarmHandler, errorDisplay):
590 self._errorDisplay = errorDisplay
591 self._backend = backend
592 self._isPopulated = False
593 self._alarmHandler = alarmHandler
594 self._notifyOnMissed = False
595 self._notifyOnVoicemail = False
596 self._notifyOnSms = False
598 self._callbackList = []
599 self._accountViewNumberDisplay = widgetTree.get_widget("gcnumber_display")
600 self._callbackSelectButton = widgetTree.get_widget("callbackSelectButton")
601 self._onCallbackSelectChangedId = 0
603 self._notifyCheckbox = widgetTree.get_widget("notifyCheckbox")
604 self._minutesEntryButton = widgetTree.get_widget("minutesEntryButton")
605 self._missedCheckbox = widgetTree.get_widget("missedCheckbox")
606 self._voicemailCheckbox = widgetTree.get_widget("voicemailCheckbox")
607 self._smsCheckbox = widgetTree.get_widget("smsCheckbox")
608 self._onNotifyToggled = 0
609 self._onMinutesChanged = 0
610 self._onMissedToggled = 0
611 self._onVoicemailToggled = 0
612 self._onSmsToggled = 0
613 self._applyAlarmTimeoutId = None
615 self._window = gtk_toolbox.find_parent_window(self._minutesEntryButton)
616 self._callbackNumber = ""
619 assert self._backend.is_authed(), "Attempting to enable backend while not logged in"
621 self._accountViewNumberDisplay.set_use_markup(True)
622 self.set_account_number("")
624 del self._callbackList[:]
625 self._onCallbackSelectChangedId = self._callbackSelectButton.connect("clicked", self._on_callbackentry_clicked)
626 self._set_callback_label("")
628 if self._alarmHandler is not None:
629 self._notifyCheckbox.set_active(self._alarmHandler.isEnabled)
630 self._minutesEntryButton.set_label("%d minutes" % self._alarmHandler.recurrence)
631 self._missedCheckbox.set_active(self._notifyOnMissed)
632 self._voicemailCheckbox.set_active(self._notifyOnVoicemail)
633 self._smsCheckbox.set_active(self._notifyOnSms)
635 self._onNotifyToggled = self._notifyCheckbox.connect("toggled", self._on_notify_toggled)
636 self._onMinutesChanged = self._minutesEntryButton.connect("clicked", self._on_minutes_clicked)
637 self._onMissedToggled = self._missedCheckbox.connect("toggled", self._on_missed_toggled)
638 self._onVoicemailToggled = self._voicemailCheckbox.connect("toggled", self._on_voicemail_toggled)
639 self._onSmsToggled = self._smsCheckbox.connect("toggled", self._on_sms_toggled)
641 self._notifyCheckbox.set_sensitive(False)
642 self._minutesEntryButton.set_sensitive(False)
643 self._missedCheckbox.set_sensitive(False)
644 self._voicemailCheckbox.set_sensitive(False)
645 self._smsCheckbox.set_sensitive(False)
647 self.update(force=True)
650 self._callbackSelectButton.disconnect(self._onCallbackSelectChangedId)
651 self._onCallbackSelectChangedId = 0
652 self._set_callback_label("")
654 if self._alarmHandler is not None:
655 self._notifyCheckbox.disconnect(self._onNotifyToggled)
656 self._minutesEntryButton.disconnect(self._onMinutesChanged)
657 self._missedCheckbox.disconnect(self._onNotifyToggled)
658 self._voicemailCheckbox.disconnect(self._onNotifyToggled)
659 self._smsCheckbox.disconnect(self._onNotifyToggled)
660 self._onNotifyToggled = 0
661 self._onMinutesChanged = 0
662 self._onMissedToggled = 0
663 self._onVoicemailToggled = 0
664 self._onSmsToggled = 0
666 self._notifyCheckbox.set_sensitive(True)
667 self._minutesEntryButton.set_sensitive(True)
668 self._missedCheckbox.set_sensitive(True)
669 self._voicemailCheckbox.set_sensitive(True)
670 self._smsCheckbox.set_sensitive(True)
673 del self._callbackList[:]
675 def set_account_number(self, number):
677 Displays current account number
679 self._accountViewNumberDisplay.set_label("<span size='23000' weight='bold'>%s</span>" % (number))
681 def update(self, force = False):
682 if not force and self._isPopulated:
684 self._populate_callback_combo()
685 self.set_account_number(self._backend.get_account_number())
689 self._set_callback_label("")
690 self.set_account_number("")
691 self._isPopulated = False
693 def save_everything(self):
694 raise NotImplementedError
698 return "Account Info"
700 def load_settings(self, config, section):
701 self._callbackNumber = make_ugly(config.get(section, "callback"))
702 self._notifyOnMissed = config.getboolean(section, "notifyOnMissed")
703 self._notifyOnVoicemail = config.getboolean(section, "notifyOnVoicemail")
704 self._notifyOnSms = config.getboolean(section, "notifyOnSms")
706 def save_settings(self, config, section):
708 @note Thread Agnostic
710 config.set(section, "callback", self._callbackNumber)
711 config.set(section, "notifyOnMissed", repr(self._notifyOnMissed))
712 config.set(section, "notifyOnVoicemail", repr(self._notifyOnVoicemail))
713 config.set(section, "notifyOnSms", repr(self._notifyOnSms))
715 def _populate_callback_combo(self):
716 self._isPopulated = True
717 del self._callbackList[:]
719 callbackNumbers = self._backend.get_callback_numbers()
721 self._errorDisplay.push_exception()
722 self._isPopulated = False
725 if len(callbackNumbers) == 0:
726 callbackNumbers = {"": "No callback numbers available"}
728 for number, description in callbackNumbers.iteritems():
729 self._callbackList.append((make_pretty(number), description))
731 self._set_callback_number(self._callbackNumber)
733 def _set_callback_number(self, number):
735 if not self._backend.is_valid_syntax(number) and 0 < len(number):
736 self._errorDisplay.push_message("%s is not a valid callback number" % number)
737 elif number == self._backend.get_callback_number() and 0 < len(number):
738 _moduleLogger.warning(
739 "Callback number already is %s" % (
740 self._backend.get_callback_number(),
743 self._set_callback_label(number)
745 if number.startswith("1747"): number = "+" + number
746 self._backend.set_callback_number(number)
747 assert make_ugly(number) == make_ugly(self._backend.get_callback_number()), "Callback number should be %s but instead is %s" % (
748 make_pretty(number), make_pretty(self._backend.get_callback_number())
750 self._callbackNumber = make_ugly(number)
751 self._set_callback_label(number)
753 "Callback number set to %s" % (
754 self._backend.get_callback_number(),
758 self._errorDisplay.push_exception()
760 def _set_callback_label(self, uglyNumber):
761 prettyNumber = make_pretty(uglyNumber)
762 if len(prettyNumber) == 0:
763 prettyNumber = "No Callback Number"
764 self._callbackSelectButton.set_label(prettyNumber)
766 def _update_alarm_settings(self, recurrence):
768 isEnabled = self._notifyCheckbox.get_active()
769 if isEnabled != self._alarmHandler.isEnabled or recurrence != self._alarmHandler.recurrence:
770 self._alarmHandler.apply_settings(isEnabled, recurrence)
772 self.save_everything()
773 self._notifyCheckbox.set_active(self._alarmHandler.isEnabled)
774 self._minutesEntryButton.set_label("%d Minutes" % self._alarmHandler.recurrence)
776 def _on_callbackentry_clicked(self, *args):
778 actualSelection = make_pretty(self._callbackNumber)
781 (number, "%s (%s)" % (number, description))
782 for (number, description) in self._callbackList
784 defaultSelection = userOptions.get(actualSelection, actualSelection)
786 userSelection = hildonize.touch_selector_entry(
789 list(userOptions.itervalues()),
792 reversedUserOptions = dict(
793 itertools.izip(userOptions.itervalues(), userOptions.iterkeys())
795 selectedNumber = reversedUserOptions.get(userSelection, userSelection)
797 number = make_ugly(selectedNumber)
798 self._set_callback_number(number)
799 except RuntimeError, e:
800 _moduleLogger.exception("%s" % str(e))
802 self._errorDisplay.push_exception()
804 def _on_notify_toggled(self, *args):
806 if self._applyAlarmTimeoutId is not None:
807 gobject.source_remove(self._applyAlarmTimeoutId)
808 self._applyAlarmTimeoutId = None
809 self._applyAlarmTimeoutId = gobject.timeout_add(500, self._on_apply_timeout)
811 self._errorDisplay.push_exception()
813 def _on_minutes_clicked(self, *args):
814 recurrenceChoices = [
830 actualSelection = self._alarmHandler.recurrence
832 closestSelectionIndex = 0
833 for i, possible in enumerate(recurrenceChoices):
834 if possible[0] <= actualSelection:
835 closestSelectionIndex = i
836 recurrenceIndex = hildonize.touch_selector(
839 (("%s" % m[1]) for m in recurrenceChoices),
840 closestSelectionIndex,
842 recurrence = recurrenceChoices[recurrenceIndex][0]
844 self._update_alarm_settings(recurrence)
845 except RuntimeError, e:
846 _moduleLogger.exception("%s" % str(e))
848 self._errorDisplay.push_exception()
850 def _on_apply_timeout(self, *args):
852 self._applyAlarmTimeoutId = None
854 self._update_alarm_settings(self._alarmHandler.recurrence)
856 self._errorDisplay.push_exception()
859 def _on_missed_toggled(self, *args):
861 self._notifyOnMissed = self._missedCheckbox.get_active()
862 self.save_everything()
864 self._errorDisplay.push_exception()
866 def _on_voicemail_toggled(self, *args):
868 self._notifyOnVoicemail = self._voicemailCheckbox.get_active()
869 self.save_everything()
871 self._errorDisplay.push_exception()
873 def _on_sms_toggled(self, *args):
875 self._notifyOnSms = self._smsCheckbox.get_active()
876 self.save_everything()
878 self._errorDisplay.push_exception()
881 class CallHistoryView(object):
889 HISTORY_ITEM_TYPES = ["All", "Received", "Missed", "Placed"]
891 def __init__(self, widgetTree, backend, errorDisplay):
892 self._errorDisplay = errorDisplay
893 self._backend = backend
895 self._isPopulated = False
896 self._historymodel = gtk.ListStore(
897 gobject.TYPE_STRING, # number
898 gobject.TYPE_STRING, # date
899 gobject.TYPE_STRING, # action
900 gobject.TYPE_STRING, # from
901 gobject.TYPE_STRING, # from id
903 self._historymodelfiltered = self._historymodel.filter_new()
904 self._historymodelfiltered.set_visible_func(self._is_history_visible)
905 self._historyview = widgetTree.get_widget("historyview")
906 self._historyviewselection = None
907 self._onRecentviewRowActivatedId = 0
909 textrenderer = gtk.CellRendererText()
910 textrenderer.set_property("yalign", 0)
911 self._dateColumn = gtk.TreeViewColumn("Date")
912 self._dateColumn.pack_start(textrenderer, expand=True)
913 self._dateColumn.add_attribute(textrenderer, "text", self.DATE_IDX)
915 textrenderer = gtk.CellRendererText()
916 textrenderer.set_property("yalign", 0)
917 self._actionColumn = gtk.TreeViewColumn("Action")
918 self._actionColumn.pack_start(textrenderer, expand=True)
919 self._actionColumn.add_attribute(textrenderer, "text", self.ACTION_IDX)
921 textrenderer = gtk.CellRendererText()
922 textrenderer.set_property("yalign", 0)
923 textrenderer.set_property("ellipsize", pango.ELLIPSIZE_END)
924 textrenderer.set_property("width-chars", len("1 (555) 555-1234"))
925 self._numberColumn = gtk.TreeViewColumn("Number")
926 self._numberColumn.pack_start(textrenderer, expand=True)
927 self._numberColumn.add_attribute(textrenderer, "text", self.NUMBER_IDX)
929 textrenderer = gtk.CellRendererText()
930 textrenderer.set_property("yalign", 0)
931 hildonize.set_cell_thumb_selectable(textrenderer)
932 self._nameColumn = gtk.TreeViewColumn("From")
933 self._nameColumn.pack_start(textrenderer, expand=True)
934 self._nameColumn.add_attribute(textrenderer, "text", self.FROM_IDX)
935 self._nameColumn.set_sizing(gtk.TREE_VIEW_COLUMN_FIXED)
937 self._window = gtk_toolbox.find_parent_window(self._historyview)
938 self._phoneTypeSelector = SmsEntryDialog(widgetTree)
940 self._historyFilterSelector = widgetTree.get_widget("historyFilterSelector")
941 self._historyFilterSelector.connect("clicked", self._on_history_filter_clicked)
942 self._selectedFilter = "All"
944 self._updateSink = gtk_toolbox.threaded_stage(
946 self._idly_populate_historyview,
947 gtk_toolbox.null_sink(),
952 assert self._backend.is_authed(), "Attempting to enable backend while not logged in"
953 self._historyFilterSelector.set_label(self._selectedFilter)
955 self._historyview.set_model(self._historymodelfiltered)
956 self._historyview.set_fixed_height_mode(False)
958 self._historyview.append_column(self._dateColumn)
959 self._historyview.append_column(self._actionColumn)
960 self._historyview.append_column(self._numberColumn)
961 self._historyview.append_column(self._nameColumn)
962 self._historyviewselection = self._historyview.get_selection()
963 self._historyviewselection.set_mode(gtk.SELECTION_SINGLE)
965 self._onRecentviewRowActivatedId = self._historyview.connect("row-activated", self._on_historyview_row_activated)
968 self._historyview.disconnect(self._onRecentviewRowActivatedId)
972 self._historyview.remove_column(self._dateColumn)
973 self._historyview.remove_column(self._actionColumn)
974 self._historyview.remove_column(self._nameColumn)
975 self._historyview.remove_column(self._numberColumn)
976 self._historyview.set_model(None)
978 def number_selected(self, action, number, message):
980 @note Actual dial function is patched in later
982 raise NotImplementedError("Horrible unknown error has occurred")
984 def update(self, force = False):
985 if not force and self._isPopulated:
987 self._updateSink.send(())
991 self._isPopulated = False
992 self._historymodel.clear()
996 return "Recent Calls"
998 def load_settings(self, config, sectionName):
1000 self._selectedFilter = config.get(sectionName, "filter")
1001 if self._selectedFilter not in self.HISTORY_ITEM_TYPES:
1002 self._messageType = self.HISTORY_ITEM_TYPES[0]
1003 except ConfigParser.NoOptionError:
1006 def save_settings(self, config, sectionName):
1008 @note Thread Agnostic
1010 config.set(sectionName, "filter", self._selectedFilter)
1012 def _is_history_visible(self, model, iter):
1014 action = model.get_value(iter, self.ACTION_IDX)
1016 return False # this seems weird but oh well
1018 if self._selectedFilter in [action, "All"]:
1022 except Exception, e:
1023 self._errorDisplay.push_exception()
1025 def _idly_populate_historyview(self):
1026 with gtk_toolbox.gtk_lock():
1027 banner = hildonize.show_busy_banner_start(self._window, "Loading Call History")
1029 self._historymodel.clear()
1030 self._isPopulated = True
1033 historyItems = self._backend.get_recent()
1034 except Exception, e:
1035 self._errorDisplay.push_exception_with_lock()
1036 self._isPopulated = False
1040 gv_backend.decorate_recent(data)
1041 for data in gv_backend.sort_messages(historyItems)
1044 for contactId, personName, phoneNumber, date, action in historyItems:
1046 personName = "Unknown"
1047 date = abbrev_relative_date(date)
1048 prettyNumber = phoneNumber[2:] if phoneNumber.startswith("+1") else phoneNumber
1049 prettyNumber = make_pretty(prettyNumber)
1050 item = (prettyNumber, date, action.capitalize(), personName, contactId)
1051 with gtk_toolbox.gtk_lock():
1052 self._historymodel.append(item)
1053 except Exception, e:
1054 self._errorDisplay.push_exception_with_lock()
1056 with gtk_toolbox.gtk_lock():
1057 hildonize.show_busy_banner_end(banner)
1061 def _on_history_filter_clicked(self, *args, **kwds):
1063 selectedComboIndex = self.HISTORY_ITEM_TYPES.index(self._selectedFilter)
1066 newSelectedComboIndex = hildonize.touch_selector(
1069 self.HISTORY_ITEM_TYPES,
1072 except RuntimeError:
1075 option = self.HISTORY_ITEM_TYPES[newSelectedComboIndex]
1076 self._selectedFilter = option
1077 self._historyFilterSelector.set_label(self._selectedFilter)
1078 self._historymodelfiltered.refilter()
1079 except Exception, e:
1080 self._errorDisplay.push_exception()
1082 def _on_historyview_row_activated(self, treeview, path, view_column):
1084 childPath = self._historymodelfiltered.convert_path_to_child_path(path)
1085 itr = self._historymodel.get_iter(childPath)
1089 number = self._historymodel.get_value(itr, self.NUMBER_IDX)
1090 number = make_ugly(number)
1091 description = self._historymodel.get_value(itr, self.FROM_IDX)
1092 contactId = self._historymodel.get_value(itr, self.FROM_ID_IDX)
1094 contactPhoneNumbers = list(self._backend.get_contact_details(contactId))
1096 (number == make_ugly(contactNumber) or number[1:] == make_ugly(contactNumber))
1097 for (numberDescription, contactNumber) in contactPhoneNumbers
1100 defaultIndex = defaultMatches.index(True)
1102 contactPhoneNumbers.append(("Other", number))
1103 defaultIndex = len(contactPhoneNumbers)-1
1105 "Could not find contact %r's number %s among %r" % (
1106 contactId, number, contactPhoneNumbers
1110 contactPhoneNumbers = [("Phone", number)]
1113 action, phoneNumber, message = self._phoneTypeSelector.run(
1114 contactPhoneNumbers,
1115 messages = (description, ),
1116 parent = self._window,
1117 defaultIndex = defaultIndex,
1119 if action == SmsEntryDialog.ACTION_CANCEL:
1121 assert phoneNumber, "A lack of phone number exists"
1123 self.number_selected(action, phoneNumber, message)
1124 self._historyviewselection.unselect_all()
1125 except Exception, e:
1126 self._errorDisplay.push_exception()
1129 class MessagesView(object):
1137 MESSAGE_DATA_IDX = 6
1139 NO_MESSAGES = "None"
1140 VOICEMAIL_MESSAGES = "Voicemail"
1141 TEXT_MESSAGES = "Texts"
1142 ALL_TYPES = "All Messages"
1143 MESSAGE_TYPES = [NO_MESSAGES, VOICEMAIL_MESSAGES, TEXT_MESSAGES, ALL_TYPES]
1145 UNREAD_STATUS = "Unread"
1146 UNARCHIVED_STATUS = "Inbox"
1148 MESSAGE_STATUSES = [UNREAD_STATUS, UNARCHIVED_STATUS, ALL_STATUS]
1150 def __init__(self, widgetTree, backend, errorDisplay):
1151 self._errorDisplay = errorDisplay
1152 self._backend = backend
1154 self._isPopulated = False
1155 self._messagemodel = gtk.ListStore(
1156 gobject.TYPE_STRING, # number
1157 gobject.TYPE_STRING, # date
1158 gobject.TYPE_STRING, # header
1159 gobject.TYPE_STRING, # message
1161 gobject.TYPE_STRING, # from id
1162 object, # message data
1164 self._messagemodelfiltered = self._messagemodel.filter_new()
1165 self._messagemodelfiltered.set_visible_func(self._is_message_visible)
1166 self._messageview = widgetTree.get_widget("messages_view")
1167 self._messageviewselection = None
1168 self._onMessageviewRowActivatedId = 0
1170 self._messageRenderer = gtk.CellRendererText()
1171 self._messageRenderer.set_property("wrap-mode", pango.WRAP_WORD)
1172 self._messageRenderer.set_property("wrap-width", 500)
1173 self._messageColumn = gtk.TreeViewColumn("Messages")
1174 self._messageColumn.pack_start(self._messageRenderer, expand=True)
1175 self._messageColumn.add_attribute(self._messageRenderer, "markup", self.MESSAGE_IDX)
1176 self._messageColumn.set_sizing(gtk.TREE_VIEW_COLUMN_FIXED)
1178 self._window = gtk_toolbox.find_parent_window(self._messageview)
1179 self._phoneTypeSelector = SmsEntryDialog(widgetTree)
1181 self._messageTypeButton = widgetTree.get_widget("messageTypeButton")
1182 self._onMessageTypeClickedId = 0
1183 self._messageType = self.ALL_TYPES
1184 self._messageStatusButton = widgetTree.get_widget("messageStatusButton")
1185 self._onMessageStatusClickedId = 0
1186 self._messageStatus = self.ALL_STATUS
1188 self._updateSink = gtk_toolbox.threaded_stage(
1190 self._idly_populate_messageview,
1191 gtk_toolbox.null_sink(),
1196 assert self._backend.is_authed(), "Attempting to enable backend while not logged in"
1197 self._messageview.set_model(self._messagemodelfiltered)
1198 self._messageview.set_headers_visible(False)
1199 self._messageview.set_fixed_height_mode(False)
1201 self._messageview.append_column(self._messageColumn)
1202 self._messageviewselection = self._messageview.get_selection()
1203 self._messageviewselection.set_mode(gtk.SELECTION_SINGLE)
1205 self._messageTypeButton.set_label(self._messageType)
1206 self._messageStatusButton.set_label(self._messageStatus)
1208 self._onMessageviewRowActivatedId = self._messageview.connect(
1209 "row-activated", self._on_messageview_row_activated
1211 self._onMessageTypeClickedId = self._messageTypeButton.connect(
1212 "clicked", self._on_message_type_clicked
1214 self._onMessageStatusClickedId = self._messageStatusButton.connect(
1215 "clicked", self._on_message_status_clicked
1219 self._messageview.disconnect(self._onMessageviewRowActivatedId)
1220 self._messageTypeButton.disconnect(self._onMessageTypeClickedId)
1221 self._messageStatusButton.disconnect(self._onMessageStatusClickedId)
1225 self._messageview.remove_column(self._messageColumn)
1226 self._messageview.set_model(None)
1228 def number_selected(self, action, number, message):
1230 @note Actual dial function is patched in later
1232 raise NotImplementedError("Horrible unknown error has occurred")
1234 def update(self, force = False):
1235 if not force and self._isPopulated:
1237 self._updateSink.send(())
1241 self._isPopulated = False
1242 self._messagemodel.clear()
1248 def load_settings(self, config, sectionName):
1250 self._messageType = config.get(sectionName, "type")
1251 if self._messageType not in self.MESSAGE_TYPES:
1252 self._messageType = self.ALL_TYPES
1253 self._messageStatus = config.get(sectionName, "status")
1254 if self._messageStatus not in self.MESSAGE_STATUSES:
1255 self._messageStatus = self.ALL_STATUS
1256 except ConfigParser.NoOptionError:
1259 def save_settings(self, config, sectionName):
1261 @note Thread Agnostic
1263 config.set(sectionName, "status", self._messageStatus)
1264 config.set(sectionName, "type", self._messageType)
1266 def _is_message_visible(self, model, iter):
1268 message = model.get_value(iter, self.MESSAGE_DATA_IDX)
1270 return False # this seems weird but oh well
1271 return self._filter_messages(message, self._messageType, self._messageStatus)
1272 except Exception, e:
1273 self._errorDisplay.push_exception()
1276 def _filter_messages(cls, message, type, status):
1277 if type == cls.ALL_TYPES:
1280 messageType = message["type"]
1281 isType = messageType == type
1283 if status == cls.ALL_STATUS:
1286 isUnarchived = not message["isArchived"]
1287 isUnread = not message["isRead"]
1288 if status == cls.UNREAD_STATUS:
1289 isStatus = isUnarchived and isUnread
1290 elif status == cls.UNARCHIVED_STATUS:
1291 isStatus = isUnarchived
1293 assert "Status %s is bad for %r" % (status, message)
1295 return isType and isStatus
1297 _MIN_MESSAGES_SHOWN = 4
1299 def _idly_populate_messageview(self):
1300 with gtk_toolbox.gtk_lock():
1301 banner = hildonize.show_busy_banner_start(self._window, "Loading Messages")
1303 self._messagemodel.clear()
1304 self._isPopulated = True
1306 if self._messageType == self.NO_MESSAGES:
1310 messageItems = self._backend.get_messages()
1311 except Exception, e:
1312 self._errorDisplay.push_exception_with_lock()
1313 self._isPopulated = False
1317 (gv_backend.decorate_message(message), message)
1318 for message in gv_backend.sort_messages(messageItems)
1321 for (contactId, header, number, relativeDate, messages), messageData in messageItems:
1322 prettyNumber = number[2:] if number.startswith("+1") else number
1323 prettyNumber = make_pretty(prettyNumber)
1325 firstMessage = "<b>%s - %s</b> <i>(%s)</i>" % (header, prettyNumber, relativeDate)
1326 expandedMessages = [firstMessage]
1327 expandedMessages.extend(messages)
1328 if (self._MIN_MESSAGES_SHOWN + 1) < len(messages):
1329 firstMessage = "<b>%s - %s</b> <i>(%s)</i>" % (header, prettyNumber, relativeDate)
1330 secondMessage = "<i>%d Messages Hidden...</i>" % (len(messages) - self._MIN_MESSAGES_SHOWN, )
1331 collapsedMessages = [firstMessage, secondMessage]
1332 collapsedMessages.extend(messages[-(self._MIN_MESSAGES_SHOWN+0):])
1334 collapsedMessages = expandedMessages
1335 #collapsedMessages = _collapse_message(collapsedMessages, 60, self._MIN_MESSAGES_SHOWN)
1337 number = make_ugly(number)
1339 row = number, relativeDate, header, "\n".join(collapsedMessages), expandedMessages, contactId, messageData
1340 with gtk_toolbox.gtk_lock():
1341 self._messagemodel.append(row)
1342 except Exception, e:
1343 self._errorDisplay.push_exception_with_lock()
1345 with gtk_toolbox.gtk_lock():
1346 hildonize.show_busy_banner_end(banner)
1347 self._messagemodelfiltered.refilter()
1351 def _on_messageview_row_activated(self, treeview, path, view_column):
1353 childPath = self._messagemodelfiltered.convert_path_to_child_path(path)
1354 itr = self._messagemodel.get_iter(childPath)
1358 number = make_ugly(self._messagemodel.get_value(itr, self.NUMBER_IDX))
1359 description = self._messagemodel.get_value(itr, self.MESSAGES_IDX)
1361 contactId = self._messagemodel.get_value(itr, self.FROM_ID_IDX)
1363 contactPhoneNumbers = list(self._backend.get_contact_details(contactId))
1365 (number == make_ugly(contactNumber) or number[1:] == make_ugly(contactNumber))
1366 for (numberDescription, contactNumber) in contactPhoneNumbers
1369 defaultIndex = defaultMatches.index(True)
1371 contactPhoneNumbers.append(("Other", number))
1372 defaultIndex = len(contactPhoneNumbers)-1
1374 "Could not find contact %r's number %s among %r" % (
1375 contactId, number, contactPhoneNumbers
1379 contactPhoneNumbers = [("Phone", number)]
1382 action, phoneNumber, message = self._phoneTypeSelector.run(
1383 contactPhoneNumbers,
1384 messages = description,
1385 parent = self._window,
1386 defaultIndex = defaultIndex,
1388 if action == SmsEntryDialog.ACTION_CANCEL:
1390 assert phoneNumber, "A lock of phone number exists"
1392 self.number_selected(action, phoneNumber, message)
1393 self._messageviewselection.unselect_all()
1394 except Exception, e:
1395 self._errorDisplay.push_exception()
1397 def _on_message_type_clicked(self, *args, **kwds):
1399 selectedIndex = self.MESSAGE_TYPES.index(self._messageType)
1402 newSelectedIndex = hildonize.touch_selector(
1408 except RuntimeError:
1411 if selectedIndex != newSelectedIndex:
1412 self._messageType = self.MESSAGE_TYPES[newSelectedIndex]
1413 self._messageTypeButton.set_label(self._messageType)
1414 self._messagemodelfiltered.refilter()
1415 except Exception, e:
1416 self._errorDisplay.push_exception()
1418 def _on_message_status_clicked(self, *args, **kwds):
1420 selectedIndex = self.MESSAGE_STATUSES.index(self._messageStatus)
1423 newSelectedIndex = hildonize.touch_selector(
1426 self.MESSAGE_STATUSES,
1429 except RuntimeError:
1432 if selectedIndex != newSelectedIndex:
1433 self._messageStatus = self.MESSAGE_STATUSES[newSelectedIndex]
1434 self._messageStatusButton.set_label(self._messageStatus)
1435 self._messagemodelfiltered.refilter()
1436 except Exception, e:
1437 self._errorDisplay.push_exception()
1440 class ContactsView(object):
1442 CONTACT_TYPE_IDX = 0
1443 CONTACT_NAME_IDX = 1
1446 def __init__(self, widgetTree, backend, errorDisplay):
1447 self._errorDisplay = errorDisplay
1448 self._backend = backend
1450 self._addressBook = None
1451 self._selectedComboIndex = 0
1452 self._addressBookFactories = [null_backend.NullAddressBook()]
1454 self._booksList = []
1455 self._bookSelectionButton = widgetTree.get_widget("addressbookSelectButton")
1457 self._isPopulated = False
1458 self._contactsmodel = gtk.ListStore(
1459 gobject.TYPE_STRING, # Contact Type
1460 gobject.TYPE_STRING, # Contact Name
1461 gobject.TYPE_STRING, # Contact ID
1463 self._contactsviewselection = None
1464 self._contactsview = widgetTree.get_widget("contactsview")
1466 self._contactColumn = gtk.TreeViewColumn("Contact")
1467 displayContactSource = False
1468 if displayContactSource:
1469 textrenderer = gtk.CellRendererText()
1470 self._contactColumn.pack_start(textrenderer, expand=False)
1471 self._contactColumn.add_attribute(textrenderer, 'text', self.CONTACT_TYPE_IDX)
1472 textrenderer = gtk.CellRendererText()
1473 hildonize.set_cell_thumb_selectable(textrenderer)
1474 self._contactColumn.pack_start(textrenderer, expand=True)
1475 self._contactColumn.add_attribute(textrenderer, 'text', self.CONTACT_NAME_IDX)
1476 self._contactColumn.set_sizing(gtk.TREE_VIEW_COLUMN_FIXED)
1477 self._contactColumn.set_sort_column_id(1)
1478 self._contactColumn.set_visible(True)
1480 self._onContactsviewRowActivatedId = 0
1481 self._onAddressbookButtonChangedId = 0
1482 self._window = gtk_toolbox.find_parent_window(self._contactsview)
1483 self._phoneTypeSelector = SmsEntryDialog(widgetTree)
1485 self._updateSink = gtk_toolbox.threaded_stage(
1487 self._idly_populate_contactsview,
1488 gtk_toolbox.null_sink(),
1493 assert self._backend.is_authed(), "Attempting to enable backend while not logged in"
1495 self._contactsview.set_model(self._contactsmodel)
1496 self._contactsview.set_fixed_height_mode(False)
1497 self._contactsview.append_column(self._contactColumn)
1498 self._contactsviewselection = self._contactsview.get_selection()
1499 self._contactsviewselection.set_mode(gtk.SELECTION_SINGLE)
1501 del self._booksList[:]
1502 for (factoryId, bookId), (factoryName, bookName) in self.get_addressbooks():
1503 if factoryName and bookName:
1504 entryName = "%s: %s" % (factoryName, bookName)
1506 entryName = factoryName
1508 entryName = bookName
1510 entryName = "Bad name (%d)" % factoryId
1511 row = (str(factoryId), bookId, entryName)
1512 self._booksList.append(row)
1514 self._onContactsviewRowActivatedId = self._contactsview.connect("row-activated", self._on_contactsview_row_activated)
1515 self._onAddressbookButtonChangedId = self._bookSelectionButton.connect("clicked", self._on_addressbook_button_changed)
1517 if len(self._booksList) <= self._selectedComboIndex:
1518 self._selectedComboIndex = 0
1519 self._bookSelectionButton.set_label(self._booksList[self._selectedComboIndex][2])
1521 selectedFactoryId = self._booksList[self._selectedComboIndex][0]
1522 selectedBookId = self._booksList[self._selectedComboIndex][1]
1523 self.open_addressbook(selectedFactoryId, selectedBookId)
1526 self._contactsview.disconnect(self._onContactsviewRowActivatedId)
1527 self._bookSelectionButton.disconnect(self._onAddressbookButtonChangedId)
1531 self._bookSelectionButton.set_label("")
1532 self._contactsview.set_model(None)
1533 self._contactsview.remove_column(self._contactColumn)
1535 def number_selected(self, action, number, message):
1537 @note Actual dial function is patched in later
1539 raise NotImplementedError("Horrible unknown error has occurred")
1541 def get_addressbooks(self):
1543 @returns Iterable of ((Factory Id, Book Id), (Factory Name, Book Name))
1545 for i, factory in enumerate(self._addressBookFactories):
1546 for bookFactory, bookId, bookName in factory.get_addressbooks():
1547 yield (str(i), bookId), (factory.factory_name(), bookName)
1549 def open_addressbook(self, bookFactoryId, bookId):
1550 bookFactoryIndex = int(bookFactoryId)
1551 addressBook = self._addressBookFactories[bookFactoryIndex].open_addressbook(bookId)
1552 self._addressBook = addressBook
1554 def update(self, force = False):
1555 if not force and self._isPopulated:
1557 self._updateSink.send(())
1561 self._isPopulated = False
1562 self._contactsmodel.clear()
1563 for factory in self._addressBookFactories:
1564 factory.clear_caches()
1565 self._addressBook.clear_caches()
1567 def append(self, book):
1568 self._addressBookFactories.append(book)
1570 def extend(self, books):
1571 self._addressBookFactories.extend(books)
1577 def load_settings(self, config, sectionName):
1579 self._selectedComboIndex = config.getint(sectionName, "selectedAddressbook")
1580 except ConfigParser.NoOptionError:
1581 self._selectedComboIndex = 0
1583 def save_settings(self, config, sectionName):
1584 config.set(sectionName, "selectedAddressbook", str(self._selectedComboIndex))
1586 def _idly_populate_contactsview(self):
1587 with gtk_toolbox.gtk_lock():
1588 banner = hildonize.show_busy_banner_start(self._window, "Loading Contacts")
1591 while addressBook is not self._addressBook:
1592 addressBook = self._addressBook
1593 with gtk_toolbox.gtk_lock():
1594 self._contactsview.set_model(None)
1598 contacts = addressBook.get_contacts()
1599 except Exception, e:
1601 self._isPopulated = False
1602 self._errorDisplay.push_exception_with_lock()
1603 for contactId, contactName in contacts:
1604 contactType = addressBook.contact_source_short_name(contactId)
1605 row = contactType, contactName, contactId
1606 self._contactsmodel.append(row)
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, self.CONTACT_ID_IDX)
1651 contactName = self._contactsmodel.get_value(itr, self.CONTACT_NAME_IDX)
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()