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
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")
53 uglynumber = re.sub('\D', '', prettynumber)
57 def make_pretty(phonenumber):
59 Function to take a phone number and return the pretty version
61 if phonenumber begins with 0:
63 if phonenumber begins with 1: ( for gizmo callback numbers )
65 if phonenumber is 13 digits:
67 if phonenumber is 10 digits:
71 >>> make_pretty("1234567")
73 >>> make_pretty("2345678901")
75 >>> make_pretty("12345678901")
77 >>> make_pretty("01234567890")
80 if phonenumber is None or phonenumber is "":
83 phonenumber = make_ugly(phonenumber)
85 if len(phonenumber) < 3:
88 if phonenumber[0] == "0":
90 prettynumber += "+%s" % phonenumber[0:3]
91 if 3 < len(phonenumber):
92 prettynumber += "-(%s)" % phonenumber[3:6]
93 if 6 < len(phonenumber):
94 prettynumber += "-%s" % phonenumber[6:9]
95 if 9 < len(phonenumber):
96 prettynumber += "-%s" % phonenumber[9:]
98 elif len(phonenumber) <= 7:
99 prettynumber = "%s-%s" % (phonenumber[0:3], phonenumber[3:])
100 elif len(phonenumber) > 8 and phonenumber[0] == "1":
101 prettynumber = "1 (%s)-%s-%s" % (phonenumber[1:4], phonenumber[4:7], phonenumber[7:])
102 elif len(phonenumber) > 7:
103 prettynumber = "(%s)-%s-%s" % (phonenumber[0:3], phonenumber[3:6], phonenumber[6:])
107 def abbrev_relative_date(date):
109 >>> abbrev_relative_date("42 hours ago")
111 >>> abbrev_relative_date("2 days ago")
113 >>> abbrev_relative_date("4 weeks ago")
116 parts = date.split(" ")
117 return "%s %s" % (parts[0], parts[1][0])
120 def _collapse_message(messageLines, maxCharsPerLine, maxLines):
123 numLines = len(messageLines)
124 for line in messageLines[0:min(maxLines, numLines)]:
125 linesPerLine = max(1, int(len(line) / maxCharsPerLine))
126 allowedLines = maxLines - lines
127 acceptedLines = min(allowedLines, linesPerLine)
128 acceptedChars = acceptedLines * maxCharsPerLine
130 if acceptedChars < (len(line) + 3):
133 acceptedChars = len(line) # eh, might as well complete the line
135 abbrevMessage = "%s%s" % (line[0:acceptedChars], suffix)
138 lines += acceptedLines
139 if maxLines <= lines:
143 def collapse_message(message, maxCharsPerLine, maxLines):
145 >>> collapse_message("Hello", 60, 2)
147 >>> collapse_message("Hello world how are you doing today? 01234567890123456789012345678901234567890123456789012345678901234567890123456789", 60, 2)
148 'Hello world how are you doing today? 01234567890123456789012...'
149 >>> collapse_message('''Hello world how are you doing today?
150 ... 01234567890123456789
151 ... 01234567890123456789
152 ... 01234567890123456789
153 ... 01234567890123456789''', 60, 2)
154 'Hello world how are you doing today?\n01234567890123456789'
155 >>> collapse_message('''
156 ... Hello world how are you doing today? 01234567890123456789012345678901234567890123456789012345678901234567890123456789
157 ... Hello world how are you doing today? 01234567890123456789012345678901234567890123456789012345678901234567890123456789
158 ... Hello world how are you doing today? 01234567890123456789012345678901234567890123456789012345678901234567890123456789
159 ... Hello world how are you doing today? 01234567890123456789012345678901234567890123456789012345678901234567890123456789
160 ... Hello world how are you doing today? 01234567890123456789012345678901234567890123456789012345678901234567890123456789
161 ... Hello world how are you doing today? 01234567890123456789012345678901234567890123456789012345678901234567890123456789''', 60, 2)
162 '\nHello world how are you doing today? 01234567890123456789012...'
164 messageLines = message.split("\n")
165 return "\n".join(_collapse_message(messageLines, maxCharsPerLine, maxLines))
168 class SmsEntryDialog(object):
170 @todo Add multi-SMS messages like GoogleVoice
173 ACTION_CANCEL = "cancel"
175 ACTION_SEND_SMS = "sms"
179 def __init__(self, widgetTree):
180 self._clipboard = gtk.clipboard_get()
181 self._widgetTree = widgetTree
182 self._dialog = self._widgetTree.get_widget("smsDialog")
184 self._smsButton = self._widgetTree.get_widget("sendSmsButton")
185 self._smsButton.connect("clicked", self._on_send)
186 self._dialButton = self._widgetTree.get_widget("dialButton")
187 self._dialButton.connect("clicked", self._on_dial)
188 self._cancelButton = self._widgetTree.get_widget("cancelSmsButton")
189 self._cancelButton.connect("clicked", self._on_cancel)
191 self._letterCountLabel = self._widgetTree.get_widget("smsLetterCount")
193 self._messagemodel = gtk.ListStore(gobject.TYPE_STRING)
194 self._messagesView = self._widgetTree.get_widget("smsMessages")
196 self._conversationView = self._messagesView.get_parent()
197 self._conversationViewPort = self._conversationView.get_parent()
198 self._scrollWindow = self._conversationViewPort.get_parent()
200 self._phoneButton = self._widgetTree.get_widget("phoneTypeSelection")
201 self._smsEntry = self._widgetTree.get_widget("smsEntry")
202 self._smsEntrySize = None
204 self._action = self.ACTION_CANCEL
206 self._numberIndex = -1
207 self._contactDetails = []
209 def run(self, contactDetails, messages = (), parent = None, defaultIndex = -1):
210 entryConnectId = self._smsEntry.get_buffer().connect("changed", self._on_entry_changed)
211 phoneConnectId = self._phoneButton.connect("clicked", self._on_phone)
212 keyConnectId = self._keyPressEventId = self._dialog.connect("key-press-event", self._on_key_press)
214 # Setup the phone selection button
215 del self._contactDetails[:]
216 for phoneType, phoneNumber in contactDetails:
217 display = " - ".join((make_pretty(phoneNumber), phoneType))
218 row = (phoneNumber, display)
219 self._contactDetails.append(row)
220 if 0 < len(self._contactDetails):
221 self._numberIndex = defaultIndex if defaultIndex != -1 else 0
222 self._phoneButton.set_label(self._contactDetails[self._numberIndex][1])
224 self._numberIndex = -1
225 self._phoneButton.set_label("Error: No Number Available")
227 # Add the column to the messages tree view
228 self._messagemodel.clear()
229 self._messagesView.set_model(self._messagemodel)
230 self._messagesView.set_fixed_height_mode(False)
232 textrenderer = gtk.CellRendererText()
233 textrenderer.set_property("wrap-mode", pango.WRAP_WORD)
234 textrenderer.set_property("wrap-width", 450)
235 messageColumn = gtk.TreeViewColumn("")
236 messageColumn.pack_start(textrenderer, expand=True)
237 messageColumn.add_attribute(textrenderer, "markup", 0)
238 messageColumn.set_sizing(gtk.TREE_VIEW_COLUMN_FIXED)
239 self._messagesView.append_column(messageColumn)
240 self._messagesView.set_headers_visible(False)
243 for message in messages:
245 self._messagemodel.append(row)
246 self._messagesView.show()
247 self._scrollWindow.show()
248 messagesSelection = self._messagesView.get_selection()
249 messagesSelection.select_path((len(messages)-1, ))
251 self._messagesView.hide()
252 self._scrollWindow.hide()
254 self._smsEntry.get_buffer().set_text("")
255 self._update_letter_count()
257 if parent is not None:
258 self._dialog.set_transient_for(parent)
259 parentSize = parent.get_size()
260 self._dialog.resize(parentSize[0], max(parentSize[1]-10, 100))
264 self._dialog.show_all()
265 self._smsEntry.grab_focus()
266 adjustment = self._scrollWindow.get_vadjustment()
267 dx = self._conversationView.get_allocation().height - self._conversationViewPort.get_allocation().height
269 adjustment.value = dx
271 if 1 < len(self._contactDetails):
272 if defaultIndex == -1:
273 self._request_number()
274 self._phoneButton.set_sensitive(True)
276 self._phoneButton.set_sensitive(False)
278 userResponse = self._dialog.run()
280 self._dialog.hide_all()
282 # Process the users response
283 if userResponse == gtk.RESPONSE_OK and 0 <= self._numberIndex:
284 phoneNumber = self._contactDetails[self._numberIndex][0]
285 phoneNumber = make_ugly(phoneNumber)
289 self._action = self.ACTION_CANCEL
290 if self._action == self.ACTION_SEND_SMS:
291 entryBuffer = self._smsEntry.get_buffer()
292 enteredMessage = entryBuffer.get_text(entryBuffer.get_start_iter(), entryBuffer.get_end_iter())
293 enteredMessage = enteredMessage[0:self.MAX_CHAR].strip()
294 if not enteredMessage:
296 self._action = self.ACTION_CANCEL
300 self._messagesView.remove_column(messageColumn)
301 self._messagesView.set_model(None)
303 return self._action, phoneNumber, enteredMessage
305 self._smsEntry.get_buffer().disconnect(entryConnectId)
306 self._phoneButton.disconnect(phoneConnectId)
307 self._keyPressEventId = self._dialog.disconnect(keyConnectId)
309 def _update_letter_count(self, *args):
310 if self._smsEntrySize is None:
311 self._smsEntrySize = self._smsEntry.size_request()
313 self._smsEntry.set_size_request(*self._smsEntrySize)
314 entryLength = self._smsEntry.get_buffer().get_char_count()
316 charsLeft = self.MAX_CHAR - entryLength
317 self._letterCountLabel.set_text(str(charsLeft))
318 if charsLeft < 0 or charsLeft == self.MAX_CHAR:
319 self._smsButton.set_sensitive(False)
321 self._smsButton.set_sensitive(True)
324 self._dialButton.set_sensitive(True)
326 self._dialButton.set_sensitive(False)
328 def _request_number(self):
330 assert 0 <= self._numberIndex, "%r" % self._numberIndex
332 self._numberIndex = hildonize.touch_selector(
335 (description for (number, description) in self._contactDetails),
338 self._phoneButton.set_label(self._contactDetails[self._numberIndex][1])
340 _moduleLogger.exception("%s" % str(e))
342 def _on_phone(self, *args):
343 self._request_number()
345 def _on_entry_changed(self, *args):
346 self._update_letter_count()
348 def _on_send(self, *args):
349 self._dialog.response(gtk.RESPONSE_OK)
350 self._action = self.ACTION_SEND_SMS
352 def _on_dial(self, *args):
353 self._dialog.response(gtk.RESPONSE_OK)
354 self._action = self.ACTION_DIAL
356 def _on_cancel(self, *args):
357 self._dialog.response(gtk.RESPONSE_CANCEL)
358 self._action = self.ACTION_CANCEL
360 def _on_key_press(self, widget, event):
362 if event.keyval == ord("c") and event.get_state() & gtk.gdk.CONTROL_MASK:
365 for messagePart in self._messagemodel
367 self._clipboard.set_text(str(message))
369 _moduleLogger.exception(str(e))
372 class Dialpad(object):
374 def __init__(self, widgetTree, errorDisplay):
375 self._clipboard = gtk.clipboard_get()
376 self._errorDisplay = errorDisplay
377 self._smsDialog = SmsEntryDialog(widgetTree)
379 self._numberdisplay = widgetTree.get_widget("numberdisplay")
380 self._smsButton = widgetTree.get_widget("sms")
381 self._dialButton = widgetTree.get_widget("dial")
382 self._backButton = widgetTree.get_widget("back")
383 self._phonenumber = ""
384 self._prettynumber = ""
387 "on_digit_clicked": self._on_digit_clicked,
389 widgetTree.signal_autoconnect(callbackMapping)
390 self._dialButton.connect("clicked", self._on_dial_clicked)
391 self._smsButton.connect("clicked", self._on_sms_clicked)
393 self._originalLabel = self._backButton.get_label()
394 self._backTapHandler = gtk_toolbox.TapOrHold(self._backButton)
395 self._backTapHandler.on_tap = self._on_backspace
396 self._backTapHandler.on_hold = self._on_clearall
397 self._backTapHandler.on_holding = self._set_clear_button
398 self._backTapHandler.on_cancel = self._reset_back_button
400 self._window = gtk_toolbox.find_parent_window(self._numberdisplay)
401 self._keyPressEventId = 0
404 self._dialButton.grab_focus()
405 self._backTapHandler.enable()
406 self._keyPressEventId = self._window.connect("key-press-event", self._on_key_press)
409 self._window.disconnect(self._keyPressEventId)
410 self._keyPressEventId = 0
411 self._reset_back_button()
412 self._backTapHandler.disable()
414 def number_selected(self, action, number, message):
416 @note Actual dial function is patched in later
418 raise NotImplementedError("Horrible unknown error has occurred")
420 def get_number(self):
421 return self._phonenumber
423 def set_number(self, number):
425 Set the number to dial
428 self._phonenumber = make_ugly(number)
429 self._prettynumber = make_pretty(self._phonenumber)
430 self._numberdisplay.set_label("<span size='30000' weight='bold'>%s</span>" % (self._prettynumber))
432 self._errorDisplay.push_exception()
441 def load_settings(self, config, section):
444 def save_settings(self, config, section):
446 @note Thread Agnostic
450 def _on_key_press(self, widget, event):
452 if event.keyval == ord("v") and event.get_state() & gtk.gdk.CONTROL_MASK:
453 contents = self._clipboard.wait_for_text()
454 if contents is not None:
455 self.set_number(contents)
457 self._errorDisplay.push_exception()
459 def _on_sms_clicked(self, widget):
461 phoneNumber = self.get_number()
462 action, phoneNumber, message = self._smsDialog.run([("Dialer", phoneNumber)], (), self._window)
464 if action == SmsEntryDialog.ACTION_CANCEL:
466 self.number_selected(action, phoneNumber, message)
468 self._errorDisplay.push_exception()
470 def _on_dial_clicked(self, widget):
472 action = SmsEntryDialog.ACTION_DIAL
473 phoneNumber = self.get_number()
475 self.number_selected(action, phoneNumber, message)
477 self._errorDisplay.push_exception()
479 def _on_digit_clicked(self, widget):
481 self.set_number(self._phonenumber + widget.get_name()[-1])
483 self._errorDisplay.push_exception()
485 def _on_backspace(self, taps):
487 self.set_number(self._phonenumber[:-taps])
488 self._reset_back_button()
490 self._errorDisplay.push_exception()
492 def _on_clearall(self, taps):
495 self._reset_back_button()
497 self._errorDisplay.push_exception()
500 def _set_clear_button(self):
502 self._backButton.set_label("gtk-clear")
504 self._errorDisplay.push_exception()
506 def _reset_back_button(self):
508 self._backButton.set_label(self._originalLabel)
510 self._errorDisplay.push_exception()
513 class AccountInfo(object):
515 def __init__(self, widgetTree, backend, alarmHandler, errorDisplay):
516 self._errorDisplay = errorDisplay
517 self._backend = backend
518 self._isPopulated = False
519 self._alarmHandler = alarmHandler
520 self._notifyOnMissed = False
521 self._notifyOnVoicemail = False
522 self._notifyOnSms = False
524 self._callbackList = []
525 self._accountViewNumberDisplay = widgetTree.get_widget("gcnumber_display")
526 self._callbackSelectButton = widgetTree.get_widget("callbackSelectButton")
527 self._onCallbackSelectChangedId = 0
529 self._notifyCheckbox = widgetTree.get_widget("notifyCheckbox")
530 self._minutesEntryButton = widgetTree.get_widget("minutesEntryButton")
531 self._missedCheckbox = widgetTree.get_widget("missedCheckbox")
532 self._voicemailCheckbox = widgetTree.get_widget("voicemailCheckbox")
533 self._smsCheckbox = widgetTree.get_widget("smsCheckbox")
534 self._onNotifyToggled = 0
535 self._onMinutesChanged = 0
536 self._onMissedToggled = 0
537 self._onVoicemailToggled = 0
538 self._onSmsToggled = 0
539 self._applyAlarmTimeoutId = None
541 self._window = gtk_toolbox.find_parent_window(self._minutesEntryButton)
542 self._callbackNumber = ""
545 assert self._backend.is_authed(), "Attempting to enable backend while not logged in"
547 self._accountViewNumberDisplay.set_use_markup(True)
548 self.set_account_number("")
550 del self._callbackList[:]
551 self._onCallbackSelectChangedId = self._callbackSelectButton.connect("clicked", self._on_callbackentry_clicked)
552 self._set_callback_label("")
554 if self._alarmHandler is not None:
555 self._notifyCheckbox.set_active(self._alarmHandler.isEnabled)
556 self._minutesEntryButton.set_label("%d minutes" % self._alarmHandler.recurrence)
557 self._missedCheckbox.set_active(self._notifyOnMissed)
558 self._voicemailCheckbox.set_active(self._notifyOnVoicemail)
559 self._smsCheckbox.set_active(self._notifyOnSms)
561 self._onNotifyToggled = self._notifyCheckbox.connect("toggled", self._on_notify_toggled)
562 self._onMinutesChanged = self._minutesEntryButton.connect("clicked", self._on_minutes_clicked)
563 self._onMissedToggled = self._missedCheckbox.connect("toggled", self._on_missed_toggled)
564 self._onVoicemailToggled = self._voicemailCheckbox.connect("toggled", self._on_voicemail_toggled)
565 self._onSmsToggled = self._smsCheckbox.connect("toggled", self._on_sms_toggled)
567 self._notifyCheckbox.set_sensitive(False)
568 self._minutesEntryButton.set_sensitive(False)
569 self._missedCheckbox.set_sensitive(False)
570 self._voicemailCheckbox.set_sensitive(False)
571 self._smsCheckbox.set_sensitive(False)
573 self.update(force=True)
576 self._callbackSelectButton.disconnect(self._onCallbackSelectChangedId)
577 self._onCallbackSelectChangedId = 0
578 self._set_callback_label("")
580 if self._alarmHandler is not None:
581 self._notifyCheckbox.disconnect(self._onNotifyToggled)
582 self._minutesEntryButton.disconnect(self._onMinutesChanged)
583 self._missedCheckbox.disconnect(self._onNotifyToggled)
584 self._voicemailCheckbox.disconnect(self._onNotifyToggled)
585 self._smsCheckbox.disconnect(self._onNotifyToggled)
586 self._onNotifyToggled = 0
587 self._onMinutesChanged = 0
588 self._onMissedToggled = 0
589 self._onVoicemailToggled = 0
590 self._onSmsToggled = 0
592 self._notifyCheckbox.set_sensitive(True)
593 self._minutesEntryButton.set_sensitive(True)
594 self._missedCheckbox.set_sensitive(True)
595 self._voicemailCheckbox.set_sensitive(True)
596 self._smsCheckbox.set_sensitive(True)
599 del self._callbackList[:]
601 def set_account_number(self, number):
603 Displays current account number
605 self._accountViewNumberDisplay.set_label("<span size='23000' weight='bold'>%s</span>" % (number))
607 def update(self, force = False):
608 if not force and self._isPopulated:
610 self._populate_callback_combo()
611 self.set_account_number(self._backend.get_account_number())
615 self._set_callback_label("")
616 self.set_account_number("")
617 self._isPopulated = False
619 def save_everything(self):
620 raise NotImplementedError
624 return "Account Info"
626 def load_settings(self, config, section):
627 self._callbackNumber = make_ugly(config.get(section, "callback"))
628 self._notifyOnMissed = config.getboolean(section, "notifyOnMissed")
629 self._notifyOnVoicemail = config.getboolean(section, "notifyOnVoicemail")
630 self._notifyOnSms = config.getboolean(section, "notifyOnSms")
632 def save_settings(self, config, section):
634 @note Thread Agnostic
636 config.set(section, "callback", self._callbackNumber)
637 config.set(section, "notifyOnMissed", repr(self._notifyOnMissed))
638 config.set(section, "notifyOnVoicemail", repr(self._notifyOnVoicemail))
639 config.set(section, "notifyOnSms", repr(self._notifyOnSms))
641 def _populate_callback_combo(self):
642 self._isPopulated = True
643 del self._callbackList[:]
645 callbackNumbers = self._backend.get_callback_numbers()
647 self._errorDisplay.push_exception()
648 self._isPopulated = False
651 if len(callbackNumbers) == 0:
652 callbackNumbers = {"": "No callback numbers available"}
654 for number, description in callbackNumbers.iteritems():
655 self._callbackList.append((make_pretty(number), description))
657 self._set_callback_number(self._callbackNumber)
659 def _set_callback_number(self, number):
661 if not self._backend.is_valid_syntax(number) and 0 < len(number):
662 self._errorDisplay.push_message("%s is not a valid callback number" % number)
663 elif number == self._backend.get_callback_number() and 0 < len(number):
664 _moduleLogger.warning(
665 "Callback number already is %s" % (
666 self._backend.get_callback_number(),
669 self._set_callback_label(number)
671 if number.startswith("1747"): number = "+" + number
672 self._backend.set_callback_number(number)
673 assert make_ugly(number) == make_ugly(self._backend.get_callback_number()), "Callback number should be %s but instead is %s" % (
674 make_pretty(number), make_pretty(self._backend.get_callback_number())
676 self._callbackNumber = make_ugly(number)
677 self._set_callback_label(number)
679 "Callback number set to %s" % (
680 self._backend.get_callback_number(),
684 self._errorDisplay.push_exception()
686 def _set_callback_label(self, uglyNumber):
687 prettyNumber = make_pretty(uglyNumber)
688 if len(prettyNumber) == 0:
689 prettyNumber = "No Callback Number"
690 self._callbackSelectButton.set_label(prettyNumber)
692 def _update_alarm_settings(self, recurrence):
694 isEnabled = self._notifyCheckbox.get_active()
695 if isEnabled != self._alarmHandler.isEnabled or recurrence != self._alarmHandler.recurrence:
696 self._alarmHandler.apply_settings(isEnabled, recurrence)
698 self.save_everything()
699 self._notifyCheckbox.set_active(self._alarmHandler.isEnabled)
700 self._minutesEntryButton.set_label("%d Minutes" % self._alarmHandler.recurrence)
702 def _on_callbackentry_clicked(self, *args):
704 actualSelection = make_pretty(self._callbackNumber)
707 (number, "%s (%s)" % (number, description))
708 for (number, description) in self._callbackList
710 defaultSelection = userOptions.get(actualSelection, actualSelection)
712 userSelection = hildonize.touch_selector_entry(
715 list(userOptions.itervalues()),
718 reversedUserOptions = dict(
719 itertools.izip(userOptions.itervalues(), userOptions.iterkeys())
721 selectedNumber = reversedUserOptions.get(userSelection, userSelection)
723 number = make_ugly(selectedNumber)
724 self._set_callback_number(number)
725 except RuntimeError, e:
726 _moduleLogger.exception("%s" % str(e))
728 self._errorDisplay.push_exception()
730 def _on_notify_toggled(self, *args):
732 if self._applyAlarmTimeoutId is not None:
733 gobject.source_remove(self._applyAlarmTimeoutId)
734 self._applyAlarmTimeoutId = None
735 self._applyAlarmTimeoutId = gobject.timeout_add(500, self._on_apply_timeout)
737 self._errorDisplay.push_exception()
739 def _on_minutes_clicked(self, *args):
740 recurrenceChoices = [
756 actualSelection = self._alarmHandler.recurrence
758 closestSelectionIndex = 0
759 for i, possible in enumerate(recurrenceChoices):
760 if possible[0] <= actualSelection:
761 closestSelectionIndex = i
762 recurrenceIndex = hildonize.touch_selector(
765 (("%s" % m[1]) for m in recurrenceChoices),
766 closestSelectionIndex,
768 recurrence = recurrenceChoices[recurrenceIndex][0]
770 self._update_alarm_settings(recurrence)
771 except RuntimeError, e:
772 _moduleLogger.exception("%s" % str(e))
774 self._errorDisplay.push_exception()
776 def _on_apply_timeout(self, *args):
778 self._applyAlarmTimeoutId = None
780 self._update_alarm_settings(self._alarmHandler.recurrence)
782 self._errorDisplay.push_exception()
785 def _on_missed_toggled(self, *args):
787 self._notifyOnMissed = self._missedCheckbox.get_active()
788 self.save_everything()
790 self._errorDisplay.push_exception()
792 def _on_voicemail_toggled(self, *args):
794 self._notifyOnVoicemail = self._voicemailCheckbox.get_active()
795 self.save_everything()
797 self._errorDisplay.push_exception()
799 def _on_sms_toggled(self, *args):
801 self._notifyOnSms = self._smsCheckbox.get_active()
802 self.save_everything()
804 self._errorDisplay.push_exception()
807 class CallHistoryView(object):
815 HISTORY_ITEM_TYPES = ["All", "Received", "Missed", "Placed"]
817 def __init__(self, widgetTree, backend, errorDisplay):
818 self._errorDisplay = errorDisplay
819 self._backend = backend
821 self._isPopulated = False
822 self._historymodel = gtk.ListStore(
823 gobject.TYPE_STRING, # number
824 gobject.TYPE_STRING, # date
825 gobject.TYPE_STRING, # action
826 gobject.TYPE_STRING, # from
827 gobject.TYPE_STRING, # from id
829 self._historymodelfiltered = self._historymodel.filter_new()
830 self._historymodelfiltered.set_visible_func(self._is_history_visible)
831 self._historyview = widgetTree.get_widget("historyview")
832 self._historyviewselection = None
833 self._onRecentviewRowActivatedId = 0
835 textrenderer = gtk.CellRendererText()
836 textrenderer.set_property("yalign", 0)
837 self._dateColumn = gtk.TreeViewColumn("Date")
838 self._dateColumn.pack_start(textrenderer, expand=True)
839 self._dateColumn.add_attribute(textrenderer, "text", self.DATE_IDX)
841 textrenderer = gtk.CellRendererText()
842 textrenderer.set_property("yalign", 0)
843 self._actionColumn = gtk.TreeViewColumn("Action")
844 self._actionColumn.pack_start(textrenderer, expand=True)
845 self._actionColumn.add_attribute(textrenderer, "text", self.ACTION_IDX)
847 textrenderer = gtk.CellRendererText()
848 textrenderer.set_property("yalign", 0)
849 textrenderer.set_property("ellipsize", pango.ELLIPSIZE_END)
850 textrenderer.set_property("width-chars", len("1 (555) 555-1234"))
851 self._numberColumn = gtk.TreeViewColumn("Number")
852 self._numberColumn.pack_start(textrenderer, expand=True)
853 self._numberColumn.add_attribute(textrenderer, "text", self.NUMBER_IDX)
855 textrenderer = gtk.CellRendererText()
856 textrenderer.set_property("yalign", 0)
857 hildonize.set_cell_thumb_selectable(textrenderer)
858 self._nameColumn = gtk.TreeViewColumn("From")
859 self._nameColumn.pack_start(textrenderer, expand=True)
860 self._nameColumn.add_attribute(textrenderer, "text", self.FROM_IDX)
861 self._nameColumn.set_sizing(gtk.TREE_VIEW_COLUMN_FIXED)
863 self._window = gtk_toolbox.find_parent_window(self._historyview)
864 self._phoneTypeSelector = SmsEntryDialog(widgetTree)
866 self._historyFilterSelector = widgetTree.get_widget("historyFilterSelector")
867 self._historyFilterSelector.connect("clicked", self._on_history_filter_clicked)
868 self._selectedFilter = "All"
870 self._updateSink = gtk_toolbox.threaded_stage(
872 self._idly_populate_historyview,
873 gtk_toolbox.null_sink(),
878 assert self._backend.is_authed(), "Attempting to enable backend while not logged in"
879 self._historyFilterSelector.set_label(self._selectedFilter)
881 self._historyview.set_model(self._historymodelfiltered)
882 self._historyview.set_fixed_height_mode(False)
884 self._historyview.append_column(self._dateColumn)
885 self._historyview.append_column(self._actionColumn)
886 self._historyview.append_column(self._numberColumn)
887 self._historyview.append_column(self._nameColumn)
888 self._historyviewselection = self._historyview.get_selection()
889 self._historyviewselection.set_mode(gtk.SELECTION_SINGLE)
891 self._onRecentviewRowActivatedId = self._historyview.connect("row-activated", self._on_historyview_row_activated)
894 self._historyview.disconnect(self._onRecentviewRowActivatedId)
898 self._historyview.remove_column(self._dateColumn)
899 self._historyview.remove_column(self._actionColumn)
900 self._historyview.remove_column(self._nameColumn)
901 self._historyview.remove_column(self._numberColumn)
902 self._historyview.set_model(None)
904 def number_selected(self, action, number, message):
906 @note Actual dial function is patched in later
908 raise NotImplementedError("Horrible unknown error has occurred")
910 def update(self, force = False):
911 if not force and self._isPopulated:
913 self._updateSink.send(())
917 self._isPopulated = False
918 self._historymodel.clear()
922 return "Recent Calls"
924 def load_settings(self, config, sectionName):
926 self._selectedFilter = config.get(sectionName, "filter")
927 if self._selectedFilter not in self.HISTORY_ITEM_TYPES:
928 self._messageType = self.HISTORY_ITEM_TYPES[0]
929 except ConfigParser.NoOptionError:
932 def save_settings(self, config, sectionName):
934 @note Thread Agnostic
936 config.set(sectionName, "filter", self._selectedFilter)
938 def _is_history_visible(self, model, iter):
940 action = model.get_value(iter, self.ACTION_IDX)
942 return False # this seems weird but oh well
944 if self._selectedFilter in [action, "All"]:
949 self._errorDisplay.push_exception()
951 def _idly_populate_historyview(self):
952 with gtk_toolbox.gtk_lock():
953 banner = hildonize.show_busy_banner_start(self._window, "Loading Call History")
955 self._historymodel.clear()
956 self._isPopulated = True
959 historyItems = self._backend.get_recent()
961 self._errorDisplay.push_exception_with_lock()
962 self._isPopulated = False
966 gv_backend.decorate_recent(data)
967 for data in gv_backend.sort_messages(historyItems)
970 for contactId, personName, phoneNumber, date, action in historyItems:
972 personName = "Unknown"
973 date = abbrev_relative_date(date)
974 prettyNumber = phoneNumber[2:] if phoneNumber.startswith("+1") else phoneNumber
975 prettyNumber = make_pretty(prettyNumber)
976 item = (prettyNumber, date, action.capitalize(), personName, contactId)
977 with gtk_toolbox.gtk_lock():
978 self._historymodel.append(item)
980 self._errorDisplay.push_exception_with_lock()
982 with gtk_toolbox.gtk_lock():
983 hildonize.show_busy_banner_end(banner)
987 def _on_history_filter_clicked(self, *args, **kwds):
989 selectedComboIndex = self.HISTORY_ITEM_TYPES.index(self._selectedFilter)
992 newSelectedComboIndex = hildonize.touch_selector(
995 self.HISTORY_ITEM_TYPES,
1001 option = self.HISTORY_ITEM_TYPES[newSelectedComboIndex]
1002 self._selectedFilter = option
1003 self._historyFilterSelector.set_label(self._selectedFilter)
1004 self._historymodelfiltered.refilter()
1005 except Exception, e:
1006 self._errorDisplay.push_exception()
1008 def _on_historyview_row_activated(self, treeview, path, view_column):
1010 childPath = self._historymodelfiltered.convert_path_to_child_path(path)
1011 itr = self._historymodel.get_iter(childPath)
1015 number = self._historymodel.get_value(itr, self.NUMBER_IDX)
1016 number = make_ugly(number)
1017 description = self._historymodel.get_value(itr, self.FROM_IDX)
1018 contactId = self._historymodel.get_value(itr, self.FROM_ID_IDX)
1020 contactPhoneNumbers = list(self._backend.get_contact_details(contactId))
1022 (number == make_ugly(contactNumber) or number[1:] == make_ugly(contactNumber))
1023 for (numberDescription, contactNumber) in contactPhoneNumbers
1026 defaultIndex = defaultMatches.index(True)
1028 contactPhoneNumbers.append(("Other", number))
1029 defaultIndex = len(contactPhoneNumbers)-1
1031 "Could not find contact %r's number %s among %r" % (
1032 contactId, number, contactPhoneNumbers
1036 contactPhoneNumbers = [("Phone", number)]
1039 action, phoneNumber, message = self._phoneTypeSelector.run(
1040 contactPhoneNumbers,
1041 messages = (description, ),
1042 parent = self._window,
1043 defaultIndex = defaultIndex,
1045 if action == SmsEntryDialog.ACTION_CANCEL:
1047 assert phoneNumber, "A lack of phone number exists"
1049 self.number_selected(action, phoneNumber, message)
1050 self._historyviewselection.unselect_all()
1051 except Exception, e:
1052 self._errorDisplay.push_exception()
1055 class MessagesView(object):
1063 MESSAGE_DATA_IDX = 6
1065 NO_MESSAGES = "None"
1066 VOICEMAIL_MESSAGES = "Voicemail"
1067 TEXT_MESSAGES = "Texts"
1068 ALL_TYPES = "All Messages"
1069 MESSAGE_TYPES = [NO_MESSAGES, VOICEMAIL_MESSAGES, TEXT_MESSAGES, ALL_TYPES]
1071 UNREAD_STATUS = "Unread"
1072 UNARCHIVED_STATUS = "Inbox"
1074 MESSAGE_STATUSES = [UNREAD_STATUS, UNARCHIVED_STATUS, ALL_STATUS]
1076 def __init__(self, widgetTree, backend, errorDisplay):
1077 self._errorDisplay = errorDisplay
1078 self._backend = backend
1080 self._isPopulated = False
1081 self._messagemodel = gtk.ListStore(
1082 gobject.TYPE_STRING, # number
1083 gobject.TYPE_STRING, # date
1084 gobject.TYPE_STRING, # header
1085 gobject.TYPE_STRING, # message
1087 gobject.TYPE_STRING, # from id
1088 object, # message data
1090 self._messagemodelfiltered = self._messagemodel.filter_new()
1091 self._messagemodelfiltered.set_visible_func(self._is_message_visible)
1092 self._messageview = widgetTree.get_widget("messages_view")
1093 self._messageviewselection = None
1094 self._onMessageviewRowActivatedId = 0
1096 self._messageRenderer = gtk.CellRendererText()
1097 self._messageRenderer.set_property("wrap-mode", pango.WRAP_WORD)
1098 self._messageRenderer.set_property("wrap-width", 500)
1099 self._messageColumn = gtk.TreeViewColumn("Messages")
1100 self._messageColumn.pack_start(self._messageRenderer, expand=True)
1101 self._messageColumn.add_attribute(self._messageRenderer, "markup", self.MESSAGE_IDX)
1102 self._messageColumn.set_sizing(gtk.TREE_VIEW_COLUMN_FIXED)
1104 self._window = gtk_toolbox.find_parent_window(self._messageview)
1105 self._phoneTypeSelector = SmsEntryDialog(widgetTree)
1107 self._messageTypeButton = widgetTree.get_widget("messageTypeButton")
1108 self._onMessageTypeClickedId = 0
1109 self._messageType = self.ALL_TYPES
1110 self._messageStatusButton = widgetTree.get_widget("messageStatusButton")
1111 self._onMessageStatusClickedId = 0
1112 self._messageStatus = self.ALL_STATUS
1114 self._updateSink = gtk_toolbox.threaded_stage(
1116 self._idly_populate_messageview,
1117 gtk_toolbox.null_sink(),
1122 assert self._backend.is_authed(), "Attempting to enable backend while not logged in"
1123 self._messageview.set_model(self._messagemodelfiltered)
1124 self._messageview.set_headers_visible(False)
1125 self._messageview.set_fixed_height_mode(False)
1127 self._messageview.append_column(self._messageColumn)
1128 self._messageviewselection = self._messageview.get_selection()
1129 self._messageviewselection.set_mode(gtk.SELECTION_SINGLE)
1131 self._messageTypeButton.set_label(self._messageType)
1132 self._messageStatusButton.set_label(self._messageStatus)
1134 self._onMessageviewRowActivatedId = self._messageview.connect(
1135 "row-activated", self._on_messageview_row_activated
1137 self._onMessageTypeClickedId = self._messageTypeButton.connect(
1138 "clicked", self._on_message_type_clicked
1140 self._onMessageStatusClickedId = self._messageStatusButton.connect(
1141 "clicked", self._on_message_status_clicked
1145 self._messageview.disconnect(self._onMessageviewRowActivatedId)
1146 self._messageTypeButton.disconnect(self._onMessageTypeClickedId)
1147 self._messageStatusButton.disconnect(self._onMessageStatusClickedId)
1151 self._messageview.remove_column(self._messageColumn)
1152 self._messageview.set_model(None)
1154 def number_selected(self, action, number, message):
1156 @note Actual dial function is patched in later
1158 raise NotImplementedError("Horrible unknown error has occurred")
1160 def update(self, force = False):
1161 if not force and self._isPopulated:
1163 self._updateSink.send(())
1167 self._isPopulated = False
1168 self._messagemodel.clear()
1174 def load_settings(self, config, sectionName):
1176 self._messageType = config.get(sectionName, "type")
1177 if self._messageType not in self.MESSAGE_TYPES:
1178 self._messageType = self.ALL_TYPES
1179 self._messageStatus = config.get(sectionName, "status")
1180 if self._messageStatus not in self.MESSAGE_STATUSES:
1181 self._messageStatus = self.ALL_STATUS
1182 except ConfigParser.NoOptionError:
1185 def save_settings(self, config, sectionName):
1187 @note Thread Agnostic
1189 config.set(sectionName, "status", self._messageStatus)
1190 config.set(sectionName, "type", self._messageType)
1192 def _is_message_visible(self, model, iter):
1194 message = model.get_value(iter, self.MESSAGE_DATA_IDX)
1196 return False # this seems weird but oh well
1197 return self._filter_messages(message, self._messageType, self._messageStatus)
1198 except Exception, e:
1199 self._errorDisplay.push_exception()
1202 def _filter_messages(cls, message, type, status):
1203 if type == cls.ALL_TYPES:
1206 messageType = message["type"]
1207 isType = messageType == type
1209 if status == cls.ALL_STATUS:
1212 isUnarchived = not message["isArchived"]
1213 isUnread = not message["isRead"]
1214 if status == cls.UNREAD_STATUS:
1215 isStatus = isUnarchived and isUnread
1216 elif status == cls.UNARCHIVED_STATUS:
1217 isStatus = isUnarchived
1219 assert "Status %s is bad for %r" % (status, message)
1221 return isType and isStatus
1223 _MIN_MESSAGES_SHOWN = 4
1225 def _idly_populate_messageview(self):
1226 with gtk_toolbox.gtk_lock():
1227 banner = hildonize.show_busy_banner_start(self._window, "Loading Messages")
1229 self._messagemodel.clear()
1230 self._isPopulated = True
1232 if self._messageType == self.NO_MESSAGES:
1236 messageItems = self._backend.get_messages()
1237 except Exception, e:
1238 self._errorDisplay.push_exception_with_lock()
1239 self._isPopulated = False
1243 (gv_backend.decorate_message(message), message)
1244 for message in gv_backend.sort_messages(messageItems)
1247 for (contactId, header, number, relativeDate, messages), messageData in messageItems:
1248 prettyNumber = number[2:] if number.startswith("+1") else number
1249 prettyNumber = make_pretty(prettyNumber)
1251 firstMessage = "<b>%s - %s</b> <i>(%s)</i>" % (header, prettyNumber, relativeDate)
1252 expandedMessages = [firstMessage]
1253 expandedMessages.extend(messages)
1254 if (self._MIN_MESSAGES_SHOWN + 1) < len(messages):
1255 firstMessage = "<b>%s - %s</b> <i>(%s)</i>" % (header, prettyNumber, relativeDate)
1256 secondMessage = "<i>%d Messages Hidden...</i>" % (len(messages) - self._MIN_MESSAGES_SHOWN, )
1257 collapsedMessages = [firstMessage, secondMessage]
1258 collapsedMessages.extend(messages[-(self._MIN_MESSAGES_SHOWN+0):])
1260 collapsedMessages = expandedMessages
1261 #collapsedMessages = _collapse_message(collapsedMessages, 60, self._MIN_MESSAGES_SHOWN)
1263 number = make_ugly(number)
1265 row = number, relativeDate, header, "\n".join(collapsedMessages), expandedMessages, contactId, messageData
1266 with gtk_toolbox.gtk_lock():
1267 self._messagemodel.append(row)
1268 except Exception, e:
1269 self._errorDisplay.push_exception_with_lock()
1271 with gtk_toolbox.gtk_lock():
1272 hildonize.show_busy_banner_end(banner)
1273 self._messagemodelfiltered.refilter()
1277 def _on_messageview_row_activated(self, treeview, path, view_column):
1279 childPath = self._messagemodelfiltered.convert_path_to_child_path(path)
1280 itr = self._messagemodel.get_iter(childPath)
1284 number = make_ugly(self._messagemodel.get_value(itr, self.NUMBER_IDX))
1285 description = self._messagemodel.get_value(itr, self.MESSAGES_IDX)
1287 contactId = self._messagemodel.get_value(itr, self.FROM_ID_IDX)
1289 contactPhoneNumbers = list(self._backend.get_contact_details(contactId))
1291 (number == make_ugly(contactNumber) or number[1:] == make_ugly(contactNumber))
1292 for (numberDescription, contactNumber) in contactPhoneNumbers
1295 defaultIndex = defaultMatches.index(True)
1297 contactPhoneNumbers.append(("Other", number))
1298 defaultIndex = len(contactPhoneNumbers)-1
1300 "Could not find contact %r's number %s among %r" % (
1301 contactId, number, contactPhoneNumbers
1305 contactPhoneNumbers = [("Phone", number)]
1308 action, phoneNumber, message = self._phoneTypeSelector.run(
1309 contactPhoneNumbers,
1310 messages = description,
1311 parent = self._window,
1312 defaultIndex = defaultIndex,
1314 if action == SmsEntryDialog.ACTION_CANCEL:
1316 assert phoneNumber, "A lock of phone number exists"
1318 self.number_selected(action, phoneNumber, message)
1319 self._messageviewselection.unselect_all()
1320 except Exception, e:
1321 self._errorDisplay.push_exception()
1323 def _on_message_type_clicked(self, *args, **kwds):
1325 selectedIndex = self.MESSAGE_TYPES.index(self._messageType)
1328 newSelectedIndex = hildonize.touch_selector(
1334 except RuntimeError:
1337 if selectedIndex != newSelectedIndex:
1338 self._messageType = self.MESSAGE_TYPES[newSelectedIndex]
1339 self._messageTypeButton.set_label(self._messageType)
1340 self._messagemodelfiltered.refilter()
1341 except Exception, e:
1342 self._errorDisplay.push_exception()
1344 def _on_message_status_clicked(self, *args, **kwds):
1346 selectedIndex = self.MESSAGE_STATUSES.index(self._messageStatus)
1349 newSelectedIndex = hildonize.touch_selector(
1352 self.MESSAGE_STATUSES,
1355 except RuntimeError:
1358 if selectedIndex != newSelectedIndex:
1359 self._messageStatus = self.MESSAGE_STATUSES[newSelectedIndex]
1360 self._messageStatusButton.set_label(self._messageStatus)
1361 self._messagemodelfiltered.refilter()
1362 except Exception, e:
1363 self._errorDisplay.push_exception()
1366 class ContactsView(object):
1368 CONTACT_TYPE_IDX = 0
1369 CONTACT_NAME_IDX = 1
1372 def __init__(self, widgetTree, backend, errorDisplay):
1373 self._errorDisplay = errorDisplay
1374 self._backend = backend
1376 self._addressBook = None
1377 self._selectedComboIndex = 0
1378 self._addressBookFactories = [null_backend.NullAddressBook()]
1380 self._booksList = []
1381 self._bookSelectionButton = widgetTree.get_widget("addressbookSelectButton")
1383 self._isPopulated = False
1384 self._contactsmodel = gtk.ListStore(
1385 gobject.TYPE_STRING, # Contact Type
1386 gobject.TYPE_STRING, # Contact Name
1387 gobject.TYPE_STRING, # Contact ID
1389 self._contactsviewselection = None
1390 self._contactsview = widgetTree.get_widget("contactsview")
1392 self._contactColumn = gtk.TreeViewColumn("Contact")
1393 displayContactSource = False
1394 if displayContactSource:
1395 textrenderer = gtk.CellRendererText()
1396 self._contactColumn.pack_start(textrenderer, expand=False)
1397 self._contactColumn.add_attribute(textrenderer, 'text', self.CONTACT_TYPE_IDX)
1398 textrenderer = gtk.CellRendererText()
1399 hildonize.set_cell_thumb_selectable(textrenderer)
1400 self._contactColumn.pack_start(textrenderer, expand=True)
1401 self._contactColumn.add_attribute(textrenderer, 'text', self.CONTACT_NAME_IDX)
1402 self._contactColumn.set_sizing(gtk.TREE_VIEW_COLUMN_FIXED)
1403 self._contactColumn.set_sort_column_id(1)
1404 self._contactColumn.set_visible(True)
1406 self._onContactsviewRowActivatedId = 0
1407 self._onAddressbookButtonChangedId = 0
1408 self._window = gtk_toolbox.find_parent_window(self._contactsview)
1409 self._phoneTypeSelector = SmsEntryDialog(widgetTree)
1411 self._updateSink = gtk_toolbox.threaded_stage(
1413 self._idly_populate_contactsview,
1414 gtk_toolbox.null_sink(),
1419 assert self._backend.is_authed(), "Attempting to enable backend while not logged in"
1421 self._contactsview.set_model(self._contactsmodel)
1422 self._contactsview.set_fixed_height_mode(False)
1423 self._contactsview.append_column(self._contactColumn)
1424 self._contactsviewselection = self._contactsview.get_selection()
1425 self._contactsviewselection.set_mode(gtk.SELECTION_SINGLE)
1427 del self._booksList[:]
1428 for (factoryId, bookId), (factoryName, bookName) in self.get_addressbooks():
1429 if factoryName and bookName:
1430 entryName = "%s: %s" % (factoryName, bookName)
1432 entryName = factoryName
1434 entryName = bookName
1436 entryName = "Bad name (%d)" % factoryId
1437 row = (str(factoryId), bookId, entryName)
1438 self._booksList.append(row)
1440 self._onContactsviewRowActivatedId = self._contactsview.connect("row-activated", self._on_contactsview_row_activated)
1441 self._onAddressbookButtonChangedId = self._bookSelectionButton.connect("clicked", self._on_addressbook_button_changed)
1443 if len(self._booksList) <= self._selectedComboIndex:
1444 self._selectedComboIndex = 0
1445 self._bookSelectionButton.set_label(self._booksList[self._selectedComboIndex][2])
1447 selectedFactoryId = self._booksList[self._selectedComboIndex][0]
1448 selectedBookId = self._booksList[self._selectedComboIndex][1]
1449 self.open_addressbook(selectedFactoryId, selectedBookId)
1452 self._contactsview.disconnect(self._onContactsviewRowActivatedId)
1453 self._bookSelectionButton.disconnect(self._onAddressbookButtonChangedId)
1457 self._bookSelectionButton.set_label("")
1458 self._contactsview.set_model(None)
1459 self._contactsview.remove_column(self._contactColumn)
1461 def number_selected(self, action, number, message):
1463 @note Actual dial function is patched in later
1465 raise NotImplementedError("Horrible unknown error has occurred")
1467 def get_addressbooks(self):
1469 @returns Iterable of ((Factory Id, Book Id), (Factory Name, Book Name))
1471 for i, factory in enumerate(self._addressBookFactories):
1472 for bookFactory, bookId, bookName in factory.get_addressbooks():
1473 yield (str(i), bookId), (factory.factory_name(), bookName)
1475 def open_addressbook(self, bookFactoryId, bookId):
1476 bookFactoryIndex = int(bookFactoryId)
1477 addressBook = self._addressBookFactories[bookFactoryIndex].open_addressbook(bookId)
1478 self._addressBook = addressBook
1480 def update(self, force = False):
1481 if not force and self._isPopulated:
1483 self._updateSink.send(())
1487 self._isPopulated = False
1488 self._contactsmodel.clear()
1489 for factory in self._addressBookFactories:
1490 factory.clear_caches()
1491 self._addressBook.clear_caches()
1493 def append(self, book):
1494 self._addressBookFactories.append(book)
1496 def extend(self, books):
1497 self._addressBookFactories.extend(books)
1503 def load_settings(self, config, sectionName):
1505 self._selectedComboIndex = config.getint(sectionName, "selectedAddressbook")
1506 except ConfigParser.NoOptionError:
1507 self._selectedComboIndex = 0
1509 def save_settings(self, config, sectionName):
1510 config.set(sectionName, "selectedAddressbook", str(self._selectedComboIndex))
1512 def _idly_populate_contactsview(self):
1513 with gtk_toolbox.gtk_lock():
1514 banner = hildonize.show_busy_banner_start(self._window, "Loading Contacts")
1517 while addressBook is not self._addressBook:
1518 addressBook = self._addressBook
1519 with gtk_toolbox.gtk_lock():
1520 self._contactsview.set_model(None)
1524 contacts = addressBook.get_contacts()
1525 except Exception, e:
1527 self._isPopulated = False
1528 self._errorDisplay.push_exception_with_lock()
1529 for contactId, contactName in contacts:
1530 contactType = addressBook.contact_source_short_name(contactId)
1531 row = contactType, contactName, contactId
1532 self._contactsmodel.append(row)
1534 with gtk_toolbox.gtk_lock():
1535 self._contactsview.set_model(self._contactsmodel)
1537 self._isPopulated = True
1538 except Exception, e:
1539 self._errorDisplay.push_exception_with_lock()
1541 with gtk_toolbox.gtk_lock():
1542 hildonize.show_busy_banner_end(banner)
1545 def _on_addressbook_button_changed(self, *args, **kwds):
1548 newSelectedComboIndex = hildonize.touch_selector(
1551 (("%s" % m[2]) for m in self._booksList),
1552 self._selectedComboIndex,
1554 except RuntimeError:
1557 selectedFactoryId = self._booksList[newSelectedComboIndex][0]
1558 selectedBookId = self._booksList[newSelectedComboIndex][1]
1560 oldAddressbook = self._addressBook
1561 self.open_addressbook(selectedFactoryId, selectedBookId)
1562 forceUpdate = True if oldAddressbook is not self._addressBook else False
1563 self.update(force=forceUpdate)
1565 self._selectedComboIndex = newSelectedComboIndex
1566 self._bookSelectionButton.set_label(self._booksList[self._selectedComboIndex][2])
1567 except Exception, e:
1568 self._errorDisplay.push_exception()
1570 def _on_contactsview_row_activated(self, treeview, path, view_column):
1572 itr = self._contactsmodel.get_iter(path)
1576 contactId = self._contactsmodel.get_value(itr, self.CONTACT_ID_IDX)
1577 contactName = self._contactsmodel.get_value(itr, self.CONTACT_NAME_IDX)
1579 contactDetails = self._addressBook.get_contact_details(contactId)
1580 except Exception, e:
1582 self._errorDisplay.push_exception()
1583 contactPhoneNumbers = [phoneNumber for phoneNumber in contactDetails]
1585 if len(contactPhoneNumbers) == 0:
1588 action, phoneNumber, message = self._phoneTypeSelector.run(
1589 contactPhoneNumbers,
1590 messages = (contactName, ),
1591 parent = self._window,
1593 if action == SmsEntryDialog.ACTION_CANCEL:
1595 assert phoneNumber, "A lack of phone number exists"
1597 self.number_selected(action, phoneNumber, message)
1598 self._contactsviewselection.unselect_all()
1599 except Exception, e:
1600 self._errorDisplay.push_exception()