X-Git-Url: http://git.maemo.org/git/?p=gc-dialer;a=blobdiff_plain;f=src%2Fgv_views.py;h=f57995ce2848c65ef85d179773a4f1e9f3bccd05;hp=d7ef86324ce5540c04f9ca7cbfda2d08805611c9;hb=9708c3b670d58c0370a45e28dcbd0abc9d8276a8;hpb=06b81b02fc1aaea7ddb277e2d16053fb313d3e3c diff --git a/src/gv_views.py b/src/gv_views.py index d7ef863..f57995c 100644 --- a/src/gv_views.py +++ b/src/gv_views.py @@ -1,1186 +1,464 @@ #!/usr/bin/env python -""" -DialCentral - Front end for Google's GoogleVoice service. -Copyright (C) 2008 Mark Bergman bergman AT merctech DOT com - -This library is free software; you can redistribute it and/or -modify it under the terms of the GNU Lesser General Public -License as published by the Free Software Foundation; either -version 2.1 of the License, or (at your option) any later version. - -This library is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU -Lesser General Public License for more details. - -You should have received a copy of the GNU Lesser General Public -License along with this library; if not, write to the Free Software -Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - -@todo Collapse voicemails -""" - from __future__ import with_statement +from __future__ import division -import re -import ConfigParser +import datetime +import string import itertools import logging -import gobject -import pango -import gtk - -import gtk_toolbox -import hildonize -from backends import gv_backend -from backends import null_backend - - -_moduleLogger = logging.getLogger("gv_views") - - -def make_ugly(prettynumber): - """ - function to take a phone number and strip out all non-numeric - characters - - >>> make_ugly("+012-(345)-678-90") - '+01234567890' - """ - return normalize_number(prettynumber) - - -def normalize_number(prettynumber): - """ - function to take a phone number and strip out all non-numeric - characters - - >>> normalize_number("+012-(345)-678-90") - '+01234567890' - >>> normalize_number("1-(345)-678-9000") - '+13456789000' - >>> normalize_number("+1-(345)-678-9000") - '+13456789000' - """ - uglynumber = re.sub('[^0-9+]', '', prettynumber) - if uglynumber.startswith("+"): - pass - elif uglynumber.startswith("1") and len(uglynumber) == 11: - uglynumber = "+"+uglynumber - elif len(uglynumber) == 10: - uglynumber = "+1"+uglynumber - else: - pass - - #validateRe = re.compile("^\+?[0-9]{10,}$") - #assert validateRe.match(uglynumber) is not None - - return uglynumber - - -def _make_pretty_with_areacodde(phonenumber): - prettynumber = "(%s)" % (phonenumber[0:3], ) - if 3 < len(phonenumber): - prettynumber += " %s" % (phonenumber[3:6], ) - if 6 < len(phonenumber): - prettynumber += "-%s" % (phonenumber[6:], ) - return prettynumber - - -def _make_pretty_local(phonenumber): - prettynumber = "%s" % (phonenumber[0:3], ) - if 3 < len(phonenumber): - prettynumber += "-%s" % (phonenumber[3:], ) - return prettynumber - - -def _make_pretty_international(phonenumber): - prettynumber = phonenumber - if phonenumber.startswith("0"): - prettynumber = "+%s " % (phonenumber[0:3], ) - if 3 < len(phonenumber): - prettynumber += _make_pretty_with_areacodde(phonenumber[3:]) - if phonenumber.startswith("1"): - prettynumber = "1 " - prettynumber += _make_pretty_with_areacodde(phonenumber[1:]) - return prettynumber - - -def make_pretty(phonenumber): - """ - Function to take a phone number and return the pretty version - pretty numbers: - if phonenumber begins with 0: - ...-(...)-...-.... - if phonenumber begins with 1: ( for gizmo callback numbers ) - 1 (...)-...-.... - if phonenumber is 13 digits: - (...)-...-.... - if phonenumber is 10 digits: - ...-.... - >>> make_pretty("12") - '12' - >>> make_pretty("1234567") - '123-4567' - >>> make_pretty("2345678901") - '+1 (234) 567-8901' - >>> make_pretty("12345678901") - '+1 (234) 567-8901' - >>> make_pretty("01234567890") - '+012 (345) 678-90' - >>> make_pretty("+01234567890") - '+012 (345) 678-90' - >>> make_pretty("+12") - '+1 (2)' - >>> make_pretty("+123") - '+1 (23)' - >>> make_pretty("+1234") - '+1 (234)' - """ - if phonenumber is None or phonenumber is "": - return "" - - phonenumber = normalize_number(phonenumber) - - if phonenumber[0] == "+": - prettynumber = _make_pretty_international(phonenumber[1:]) - if not prettynumber.startswith("+"): - prettynumber = "+"+prettynumber - elif 8 < len(phonenumber) and phonenumber[0] in ("0", "1"): - prettynumber = _make_pretty_international(phonenumber) - elif 7 < len(phonenumber): - prettynumber = _make_pretty_with_areacodde(phonenumber) - elif 3 < len(phonenumber): - prettynumber = _make_pretty_local(phonenumber) - else: - prettynumber = phonenumber - return prettynumber.strip() - - -def abbrev_relative_date(date): - """ - >>> abbrev_relative_date("42 hours ago") - '42 h' - >>> abbrev_relative_date("2 days ago") - '2 d' - >>> abbrev_relative_date("4 weeks ago") - '4 w' - """ - parts = date.split(" ") - return "%s %s" % (parts[0], parts[1][0]) - - -def _collapse_message(messageLines, maxCharsPerLine, maxLines): - lines = 0 - - numLines = len(messageLines) - for line in messageLines[0:min(maxLines, numLines)]: - linesPerLine = max(1, int(len(line) / maxCharsPerLine)) - allowedLines = maxLines - lines - acceptedLines = min(allowedLines, linesPerLine) - acceptedChars = acceptedLines * maxCharsPerLine - - if acceptedChars < (len(line) + 3): - suffix = "..." - else: - acceptedChars = len(line) # eh, might as well complete the line - suffix = "" - abbrevMessage = "%s%s" % (line[0:acceptedChars], suffix) - yield abbrevMessage - - lines += acceptedLines - if maxLines <= lines: - break - - -def collapse_message(message, maxCharsPerLine, maxLines): - r""" - >>> collapse_message("Hello", 60, 2) - 'Hello' - >>> collapse_message("Hello world how are you doing today? 01234567890123456789012345678901234567890123456789012345678901234567890123456789", 60, 2) - 'Hello world how are you doing today? 01234567890123456789012...' - >>> collapse_message('''Hello world how are you doing today? - ... 01234567890123456789 - ... 01234567890123456789 - ... 01234567890123456789 - ... 01234567890123456789''', 60, 2) - 'Hello world how are you doing today?\n01234567890123456789' - >>> collapse_message(''' - ... Hello world how are you doing today? 01234567890123456789012345678901234567890123456789012345678901234567890123456789 - ... Hello world how are you doing today? 01234567890123456789012345678901234567890123456789012345678901234567890123456789 - ... Hello world how are you doing today? 01234567890123456789012345678901234567890123456789012345678901234567890123456789 - ... Hello world how are you doing today? 01234567890123456789012345678901234567890123456789012345678901234567890123456789 - ... Hello world how are you doing today? 01234567890123456789012345678901234567890123456789012345678901234567890123456789 - ... Hello world how are you doing today? 01234567890123456789012345678901234567890123456789012345678901234567890123456789''', 60, 2) - '\nHello world how are you doing today? 01234567890123456789012...' - """ - messageLines = message.split("\n") - return "\n".join(_collapse_message(messageLines, maxCharsPerLine, maxLines)) - - -class SmsEntryWindow(object): - - MAX_CHAR = 160 - - def __init__(self, widgetTree): - self._clipboard = gtk.clipboard_get() - self._widgetTree = widgetTree - self._window = self._widgetTree.get_widget("smsWindow") - self._window.connect("delete-event", self._on_delete) - self._window.connect("key-press-event", self._on_key_press) - - self._smsButton = self._widgetTree.get_widget("sendSmsButton") - self._smsButton.connect("clicked", self._on_send) - self._dialButton = self._widgetTree.get_widget("dialButton") - self._dialButton.connect("clicked", self._on_dial) - - self._letterCountLabel = self._widgetTree.get_widget("smsLetterCount") - - self._messagemodel = gtk.ListStore(gobject.TYPE_STRING) - self._messagesView = self._widgetTree.get_widget("smsMessages") - - textrenderer = gtk.CellRendererText() - textrenderer.set_property("wrap-mode", pango.WRAP_WORD) - textrenderer.set_property("wrap-width", 450) - messageColumn = gtk.TreeViewColumn("") - messageColumn.pack_start(textrenderer, expand=True) - messageColumn.add_attribute(textrenderer, "markup", 0) - messageColumn.set_sizing(gtk.TREE_VIEW_COLUMN_FIXED) - self._messagesView.append_column(messageColumn) - self._messagesView.set_headers_visible(False) - self._messagesView.set_model(self._messagemodel) - self._messagesView.set_fixed_height_mode(False) - - self._conversationView = self._messagesView.get_parent() - self._conversationViewPort = self._conversationView.get_parent() - self._scrollWindow = self._conversationViewPort.get_parent() - - self._targetList = self._widgetTree.get_widget("smsTargetList") - self._phoneButton = self._widgetTree.get_widget("phoneTypeSelection") - self._phoneButton.connect("clicked", self._on_phone) - self._smsEntry = self._widgetTree.get_widget("smsEntry") - self._smsEntry.get_buffer().connect("changed", self._on_entry_changed) - self._smsEntrySize = None - - self._contacts = [] - - def add_contact(self, contactDetails, messages = (), parent = None, defaultIndex = -1): - contactNumbers = list(self._to_contact_numbers(contactDetails)) - assert contactNumbers - contactIndex = defaultIndex if defaultIndex != -1 else 0 - contact = contactNumbers, contactIndex, messages - self._contacts.append(contact) - - selector = gtk.Button(contactNumbers[0][1]) - removeContact = gtk.Button(stock="gtk-delete") - row = gtk.HBox() - row.pack_start(selector, True, True) - row.pack_start(removeContact, False, False) - row.show_all() - self._targetList.pack_start(row) - selector.connect("clicked", self._on_choose_phone_n, row) - removeContact.connect("clicked", self._on_remove_phone_n, row) - self._update_button_state() - self._update_context() - - if parent is not None: - parentSize = parent.get_size() - self._window.resize(parentSize[0], max(parentSize[1]-10, 100)) - self._window.show() - self._window.present() - - self._smsEntry.grab_focus() - dx = self._conversationView.get_allocation().height - self._conversationViewPort.get_allocation().height - dx = max(dx, 0) - adjustment = self._scrollWindow.get_vadjustment() - adjustment.value = dx +from PyQt4 import QtGui +from PyQt4 import QtCore - def clear(self): - del self._contacts[:] +from util import qtpie +from util import qui_utils +from util import misc as misc_utils - for contactNumberSelector in list(self._targetList.get_children()): - self._targetList.remove(contactNumberSelector) - self._smsEntry.get_buffer().set_text("") - self._update_letter_count() - self._update_context() +import backends.null_backend as null_backend +import backends.file_backend as file_backend - def _remove_contact(self, contactIndex): - del self._contacts[contactIndex] - contactNumberSelector = list(self._targetList.get_children())[contactIndex] - self._targetList.remove(contactNumberSelector) - self._update_button_state() - self._update_context() - - def _update_letter_count(self): - if self._smsEntrySize is None: - self._smsEntrySize = self._smsEntry.size_request() - else: - self._smsEntry.set_size_request(*self._smsEntrySize) - entryLength = self._smsEntry.get_buffer().get_char_count() - - numTexts, numCharInText = divmod(entryLength, self.MAX_CHAR) - if numTexts: - self._letterCountLabel.set_text("%s.%s" % (numTexts, numCharInText)) - else: - self._letterCountLabel.set_text("%s" % (numCharInText, )) - - self._update_button_state() - - def _update_context(self): - self._messagemodel.clear() - if len(self._contacts) == 0: - self._messagesView.hide() - self._targetList.hide() - self._phoneButton.hide() - self._phoneButton.set_label("Error: You shouldn't see this") - elif len(self._contacts) == 1: - contactNumbers, index, messages = self._contacts[0] - if messages: - self._messagesView.show() - for message in messages: - row = (message, ) - self._messagemodel.append(row) - messagesSelection = self._messagesView.get_selection() - messagesSelection.select_path((len(messages)-1, )) - else: - self._messagesView.hide() - self._targetList.hide() - self._phoneButton.show() - self._phoneButton.set_label(contactNumbers[index][1]) - if 1 < len(contactNumbers): - self._phoneButton.set_sensitive(True) - else: - self._phoneButton.set_sensitive(False) - else: - self._messagesView.hide() - self._targetList.show() - self._phoneButton.hide() - self._phoneButton.set_label("Error: You shouldn't see this") - - def _update_button_state(self): - if len(self._contacts) == 0: - self._dialButton.set_sensitive(False) - self._smsButton.set_sensitive(False) - elif len(self._contacts) == 1: - entryLength = self._smsEntry.get_buffer().get_char_count() - if entryLength == 0: - self._dialButton.set_sensitive(True) - self._smsButton.set_sensitive(False) - else: - self._dialButton.set_sensitive(False) - self._smsButton.set_sensitive(True) - else: - self._dialButton.set_sensitive(False) - self._smsButton.set_sensitive(True) - - def _to_contact_numbers(self, contactDetails): - for phoneType, phoneNumber in contactDetails: - display = " - ".join((make_pretty(phoneNumber), phoneType)) - yield (phoneNumber, display) - - def _hide(self): - self.clear() - self._window.hide() - - def _request_number(self, contactIndex): - contactNumbers, index, messages = self._contacts[contactIndex] - assert 0 <= index, "%r" % index - - index = hildonize.touch_selector( - self._window, - "Phone Numbers", - (description for (number, description) in contactNumbers), - index, - ) - self._contacts[contactIndex] = contactNumbers, index, messages - - def _on_phone(self, *args): - try: - assert len(self._contacts) == 1 - self._request_number(0) - - contactNumbers, numberIndex, messages = self._contacts[0] - self._phoneButton.set_label(contactNumbers[numberIndex][1]) - row = list(self._targetList.get_children())[0] - phoneButton = list(row.get_children())[0] - phoneButton.set_label(contactNumbers[numberIndex][1]) - except Exception, e: - _moduleLogger.exception("%s" % str(e)) - - def _on_choose_phone_n(self, button, row): - try: - assert 1 < len(self._contacts) - targetList = list(self._targetList.get_children()) - index = targetList.index(row) - self._request_number(index) - - contactNumbers, numberIndex, messages = self._contacts[0] - phoneButton = list(row.get_children())[0] - phoneButton.set_label(contactNumbers[numberIndex][1]) - except Exception, e: - _moduleLogger.exception("%s" % str(e)) - - def _on_remove_phone_n(self, button, row): - try: - assert 1 < len(self._contacts) - targetList = list(self._targetList.get_children()) - index = targetList.index(row) - - del self._contacts[index] - self._targetList.remove(row) - self._update_context() - self._update_button_state() - except Exception, e: - _moduleLogger.exception("%s" % str(e)) - - def _on_entry_changed(self, *args): - self._update_letter_count() - - def _on_send(self, *args): - assert 0 < len(self._contacts), "%r" % self._contacts - phoneNumbers = [ - make_ugly(contact[0][contact[1]][0]) - for contact in self._contacts - ] - - entryBuffer = self._smsEntry.get_buffer() - enteredMessage = entryBuffer.get_text(entryBuffer.get_start_iter(), entryBuffer.get_end_iter()) - enteredMessage = enteredMessage.strip() - assert enteredMessage - # @todo - self._hide() - - def _on_dial(self, *args): - assert len(self._contacts) == 1, "%r" % self._contacts - contact = self._contacts[0] - contactNumber = contact[0][contact[1]][0] - phoneNumber = make_ugly(contactNumber) - # @todo - self._hide() - - def _on_delete(self, *args): - self._window.emit_stop_by_name("delete-event") - self._hide() - return True - - def _on_key_press(self, widget, event): - try: - if event.keyval == ord("c") and event.get_state() & gtk.gdk.CONTROL_MASK: - message = "\n".join( - messagePart[0] - for messagePart in self._messagemodel - ) - self._clipboard.set_text(str(message)) - except Exception, e: - _moduleLogger.exception(str(e)) +_moduleLogger = logging.getLogger(__name__) class Dialpad(object): - def __init__(self, widgetTree, errorDisplay): - self._clipboard = gtk.clipboard_get() - self._errorDisplay = errorDisplay - - self._numberdisplay = widgetTree.get_widget("numberdisplay") - self._smsButton = widgetTree.get_widget("sms") - self._dialButton = widgetTree.get_widget("dial") - self._backButton = widgetTree.get_widget("back") - self._zeroOrPlusButton = widgetTree.get_widget("digit0") - self._phonenumber = "" - self._prettynumber = "" - - callbackMapping = { - "on_digit_clicked": self._on_digit_clicked, - } - widgetTree.signal_autoconnect(callbackMapping) - self._dialButton.connect("clicked", self._on_dial_clicked) - self._smsButton.connect("clicked", self._on_sms_clicked) - - self._originalLabel = self._backButton.get_label() - self._backTapHandler = gtk_toolbox.TapOrHold(self._backButton) - self._backTapHandler.on_tap = self._on_backspace - self._backTapHandler.on_hold = self._on_clearall - self._backTapHandler.on_holding = self._set_clear_button - self._backTapHandler.on_cancel = self._reset_back_button - self._zeroOrPlusTapHandler = gtk_toolbox.TapOrHold(self._zeroOrPlusButton) - self._zeroOrPlusTapHandler.on_tap = self._on_zero - self._zeroOrPlusTapHandler.on_hold = self._on_plus - - self._window = gtk_toolbox.find_parent_window(self._numberdisplay) - self._keyPressEventId = 0 + def __init__(self, app, session, errorLog): + self._app = app + self._session = session + self._errorLog = errorLog + + self._plus = QtGui.QPushButton("+") + self._plus.clicked.connect(lambda: self._on_keypress("+")) + self._entry = QtGui.QLineEdit() + + backAction = QtGui.QAction(None) + backAction.setText("Back") + backAction.triggered.connect(self._on_backspace) + backPieItem = qtpie.QActionPieItem(backAction) + clearAction = QtGui.QAction(None) + clearAction.setText("Clear") + clearAction.triggered.connect(self._on_clear_text) + clearPieItem = qtpie.QActionPieItem(clearAction) + backSlices = [ + qtpie.PieFiling.NULL_CENTER, + clearPieItem, + qtpie.PieFiling.NULL_CENTER, + qtpie.PieFiling.NULL_CENTER, + ] + self._back = qtpie.QPieButton(backPieItem) + self._back.set_center(backPieItem) + for slice in backSlices: + self._back.insertItem(slice) + + self._entryLayout = QtGui.QHBoxLayout() + self._entryLayout.addWidget(self._plus, 1, QtCore.Qt.AlignCenter) + self._entryLayout.addWidget(self._entry, 1000) + self._entryLayout.addWidget(self._back, 1, QtCore.Qt.AlignCenter) + + smsIcon = self._app.get_icon("messages.png") + self._smsButton = QtGui.QPushButton(smsIcon, "SMS") + self._smsButton.clicked.connect(self._on_sms_clicked) + self._smsButton.setSizePolicy(QtGui.QSizePolicy( + QtGui.QSizePolicy.MinimumExpanding, + QtGui.QSizePolicy.MinimumExpanding, + QtGui.QSizePolicy.PushButton, + )) + callIcon = self._app.get_icon("dialpad.png") + self._callButton = QtGui.QPushButton(callIcon, "Call") + self._callButton.clicked.connect(self._on_call_clicked) + self._callButton.setSizePolicy(QtGui.QSizePolicy( + QtGui.QSizePolicy.MinimumExpanding, + QtGui.QSizePolicy.MinimumExpanding, + QtGui.QSizePolicy.PushButton, + )) + + self._padLayout = QtGui.QGridLayout() + rows = [0, 0, 0, 1, 1, 1, 2, 2, 2] + columns = [0, 1, 2] * 3 + keys = [ + ("1", ""), + ("2", "ABC"), + ("3", "DEF"), + ("4", "GHI"), + ("5", "JKL"), + ("6", "MNO"), + ("7", "PQRS"), + ("8", "TUV"), + ("9", "WXYZ"), + ] + for (num, letters), (row, column) in zip(keys, zip(rows, columns)): + self._padLayout.addWidget(self._generate_key_button(num, letters), row, column) + self._zerothButton = QtGui.QPushButton("0") + self._zerothButton.clicked.connect(lambda: self._on_keypress("0")) + self._zerothButton.setSizePolicy(QtGui.QSizePolicy( + QtGui.QSizePolicy.MinimumExpanding, + QtGui.QSizePolicy.MinimumExpanding, + QtGui.QSizePolicy.PushButton, + )) + self._padLayout.addWidget(self._smsButton, 3, 0) + self._padLayout.addWidget(self._zerothButton) + self._padLayout.addWidget(self._callButton, 3, 2) + + self._layout = QtGui.QVBoxLayout() + self._layout.addLayout(self._entryLayout, 0) + self._layout.addLayout(self._padLayout, 1000000) + self._widget = QtGui.QWidget() + self._widget.setLayout(self._layout) + + @property + def toplevel(self): + return self._widget def enable(self): - self._dialButton.grab_focus() - self._backTapHandler.enable() - self._zeroOrPlusTapHandler.enable() - self._keyPressEventId = self._window.connect("key-press-event", self._on_key_press) + self._smsButton.setEnabled(True) + self._callButton.setEnabled(True) def disable(self): - self._window.disconnect(self._keyPressEventId) - self._keyPressEventId = 0 - self._reset_back_button() - self._backTapHandler.disable() - self._zeroOrPlusTapHandler.disable() - - def add_contact(self, *args, **kwds): - """ - @note Actual dial function is patched in later - """ - raise NotImplementedError("Horrible unknown error has occurred") - - def get_number(self): - return self._phonenumber - - def set_number(self, number): - """ - Set the number to dial - """ - try: - self._phonenumber = make_ugly(number) - self._prettynumber = make_pretty(self._phonenumber) - self._numberdisplay.set_label("%s" % (self._prettynumber)) - except TypeError, e: - self._errorDisplay.push_exception() + self._smsButton.setEnabled(False) + self._callButton.setEnabled(False) - def clear(self): - self.set_number("") + def get_settings(self): + return {} - @staticmethod - def name(): - return "Dialpad" - - def load_settings(self, config, section): - pass - - def save_settings(self, config, section): - """ - @note Thread Agnostic - """ + def set_settings(self, settings): pass - def _on_key_press(self, widget, event): - try: - if event.keyval == ord("v") and event.get_state() & gtk.gdk.CONTROL_MASK: - contents = self._clipboard.wait_for_text() - if contents is not None: - self.set_number(contents) - except Exception, e: - self._errorDisplay.push_exception() - - def _on_sms_clicked(self, widget): - try: - phoneNumber = self.get_number() - self.add_contact( - [("Dialer", phoneNumber)], (), self._window - ) - except Exception, e: - self._errorDisplay.push_exception() - - def _on_dial_clicked(self, widget): - try: - #self.number_selected(action, phoneNumbers, message) TODO - pass - except Exception, e: - self._errorDisplay.push_exception() - - def _on_digit_clicked(self, widget): - try: - self.set_number(self._phonenumber + widget.get_name()[-1]) - except Exception, e: - self._errorDisplay.push_exception() - - def _on_zero(self, *args): - try: - self.set_number(self._phonenumber + "0") - except Exception, e: - self._errorDisplay.push_exception() - - def _on_plus(self, *args): - try: - self.set_number(self._phonenumber + "+") - except Exception, e: - self._errorDisplay.push_exception() - - def _on_backspace(self, taps): - try: - self.set_number(self._phonenumber[:-taps]) - self._reset_back_button() - except Exception, e: - self._errorDisplay.push_exception() - - def _on_clearall(self, taps): - try: - self.clear() - self._reset_back_button() - except Exception, e: - self._errorDisplay.push_exception() - return False - - def _set_clear_button(self): - try: - self._backButton.set_label("gtk-clear") - except Exception, e: - self._errorDisplay.push_exception() - - def _reset_back_button(self): - try: - self._backButton.set_label(self._originalLabel) - except Exception, e: - self._errorDisplay.push_exception() - - -class AccountInfo(object): - - def __init__(self, widgetTree, backend, alarmHandler, errorDisplay): - self._errorDisplay = errorDisplay - self._backend = backend - self._isPopulated = False - self._alarmHandler = alarmHandler - self._notifyOnMissed = False - self._notifyOnVoicemail = False - self._notifyOnSms = False - - self._callbackList = [] - self._accountViewNumberDisplay = widgetTree.get_widget("gcnumber_display") - self._callbackSelectButton = widgetTree.get_widget("callbackSelectButton") - self._onCallbackSelectChangedId = 0 - - self._notifyCheckbox = widgetTree.get_widget("notifyCheckbox") - self._minutesEntryButton = widgetTree.get_widget("minutesEntryButton") - self._missedCheckbox = widgetTree.get_widget("missedCheckbox") - self._voicemailCheckbox = widgetTree.get_widget("voicemailCheckbox") - self._smsCheckbox = widgetTree.get_widget("smsCheckbox") - self._onNotifyToggled = 0 - self._onMinutesChanged = 0 - self._onMissedToggled = 0 - self._onVoicemailToggled = 0 - self._onSmsToggled = 0 - self._applyAlarmTimeoutId = None - - self._window = gtk_toolbox.find_parent_window(self._minutesEntryButton) - self._callbackNumber = "" - - def enable(self): - assert self._backend.is_authed(), "Attempting to enable backend while not logged in" - - self._accountViewNumberDisplay.set_use_markup(True) - self.set_account_number("") - - del self._callbackList[:] - self._onCallbackSelectChangedId = self._callbackSelectButton.connect("clicked", self._on_callbackentry_clicked) - self._set_callback_label("") - - if self._alarmHandler is not None: - self._notifyCheckbox.set_active(self._alarmHandler.isEnabled) - self._minutesEntryButton.set_label("%d minutes" % self._alarmHandler.recurrence) - self._missedCheckbox.set_active(self._notifyOnMissed) - self._voicemailCheckbox.set_active(self._notifyOnVoicemail) - self._smsCheckbox.set_active(self._notifyOnSms) - - self._onNotifyToggled = self._notifyCheckbox.connect("toggled", self._on_notify_toggled) - self._onMinutesChanged = self._minutesEntryButton.connect("clicked", self._on_minutes_clicked) - self._onMissedToggled = self._missedCheckbox.connect("toggled", self._on_missed_toggled) - self._onVoicemailToggled = self._voicemailCheckbox.connect("toggled", self._on_voicemail_toggled) - self._onSmsToggled = self._smsCheckbox.connect("toggled", self._on_sms_toggled) - else: - self._notifyCheckbox.set_sensitive(False) - self._minutesEntryButton.set_sensitive(False) - self._missedCheckbox.set_sensitive(False) - self._voicemailCheckbox.set_sensitive(False) - self._smsCheckbox.set_sensitive(False) - - self.update(force=True) - - def disable(self): - self._callbackSelectButton.disconnect(self._onCallbackSelectChangedId) - self._onCallbackSelectChangedId = 0 - self._set_callback_label("") - - if self._alarmHandler is not None: - self._notifyCheckbox.disconnect(self._onNotifyToggled) - self._minutesEntryButton.disconnect(self._onMinutesChanged) - self._missedCheckbox.disconnect(self._onNotifyToggled) - self._voicemailCheckbox.disconnect(self._onNotifyToggled) - self._smsCheckbox.disconnect(self._onNotifyToggled) - self._onNotifyToggled = 0 - self._onMinutesChanged = 0 - self._onMissedToggled = 0 - self._onVoicemailToggled = 0 - self._onSmsToggled = 0 - else: - self._notifyCheckbox.set_sensitive(True) - self._minutesEntryButton.set_sensitive(True) - self._missedCheckbox.set_sensitive(True) - self._voicemailCheckbox.set_sensitive(True) - self._smsCheckbox.set_sensitive(True) - - self.clear() - del self._callbackList[:] - - def set_account_number(self, number): - """ - Displays current account number - """ - self._accountViewNumberDisplay.set_label("%s" % (number)) - - def update(self, force = False): - if not force and self._isPopulated: - return False - self._populate_callback_combo() - self.set_account_number(self._backend.get_account_number()) - return True - def clear(self): - self._set_callback_label("") - self.set_account_number("") - self._isPopulated = False - - def save_everything(self): - raise NotImplementedError - - @staticmethod - def name(): - return "Account Info" - - def load_settings(self, config, section): - self._callbackNumber = make_ugly(config.get(section, "callback")) - self._notifyOnMissed = config.getboolean(section, "notifyOnMissed") - self._notifyOnVoicemail = config.getboolean(section, "notifyOnVoicemail") - self._notifyOnSms = config.getboolean(section, "notifyOnSms") - - def save_settings(self, config, section): - """ - @note Thread Agnostic - """ - config.set(section, "callback", self._callbackNumber) - config.set(section, "notifyOnMissed", repr(self._notifyOnMissed)) - config.set(section, "notifyOnVoicemail", repr(self._notifyOnVoicemail)) - config.set(section, "notifyOnSms", repr(self._notifyOnSms)) - - def _populate_callback_combo(self): - self._isPopulated = True - del self._callbackList[:] - try: - callbackNumbers = self._backend.get_callback_numbers() - except Exception, e: - self._errorDisplay.push_exception() - self._isPopulated = False - return - - if len(callbackNumbers) == 0: - callbackNumbers = {"": "No callback numbers available"} - - for number, description in callbackNumbers.iteritems(): - self._callbackList.append((make_pretty(number), description)) - - self._set_callback_number(self._callbackNumber) - - def _set_callback_number(self, number): - try: - if not self._backend.is_valid_syntax(number) and 0 < len(number): - self._errorDisplay.push_message("%s is not a valid callback number" % number) - elif number == self._backend.get_callback_number() and 0 < len(number): - _moduleLogger.warning( - "Callback number already is %s" % ( - self._backend.get_callback_number(), - ), - ) - self._set_callback_label(number) - else: - if number.startswith("1747"): number = "+" + number - self._backend.set_callback_number(number) - assert make_ugly(number) == make_ugly(self._backend.get_callback_number()), "Callback number should be %s but instead is %s" % ( - make_pretty(number), make_pretty(self._backend.get_callback_number()) - ) - self._callbackNumber = make_ugly(number) - self._set_callback_label(number) - _moduleLogger.info( - "Callback number set to %s" % ( - self._backend.get_callback_number(), - ), - ) - except Exception, e: - self._errorDisplay.push_exception() - - def _set_callback_label(self, uglyNumber): - prettyNumber = make_pretty(uglyNumber) - if len(prettyNumber) == 0: - prettyNumber = "No Callback Number" - self._callbackSelectButton.set_label(prettyNumber) - - def _update_alarm_settings(self, recurrence): - try: - isEnabled = self._notifyCheckbox.get_active() - if isEnabled != self._alarmHandler.isEnabled or recurrence != self._alarmHandler.recurrence: - self._alarmHandler.apply_settings(isEnabled, recurrence) - finally: - self.save_everything() - self._notifyCheckbox.set_active(self._alarmHandler.isEnabled) - self._minutesEntryButton.set_label("%d Minutes" % self._alarmHandler.recurrence) - - def _on_callbackentry_clicked(self, *args): - try: - actualSelection = make_pretty(self._callbackNumber) + pass - userOptions = dict( - (number, "%s (%s)" % (number, description)) - for (number, description) in self._callbackList - ) - defaultSelection = userOptions.get(actualSelection, actualSelection) + def refresh(self, force = True): + pass - userSelection = hildonize.touch_selector_entry( - self._window, - "Callback Number", - list(userOptions.itervalues()), - defaultSelection, + def _generate_key_button(self, center, letters): + button = QtGui.QPushButton("%s\n%s" % (center, letters)) + button.setSizePolicy(QtGui.QSizePolicy( + QtGui.QSizePolicy.MinimumExpanding, + QtGui.QSizePolicy.MinimumExpanding, + QtGui.QSizePolicy.PushButton, + )) + button.clicked.connect(lambda: self._on_keypress(center)) + return button + + @misc_utils.log_exception(_moduleLogger) + def _on_keypress(self, key): + with qui_utils.notify_error(self._errorLog): + self._entry.insert(key) + + @misc_utils.log_exception(_moduleLogger) + def _on_backspace(self, toggled = False): + with qui_utils.notify_error(self._errorLog): + self._entry.backspace() + + @misc_utils.log_exception(_moduleLogger) + def _on_clear_text(self, toggled = False): + with qui_utils.notify_error(self._errorLog): + self._entry.clear() + + @QtCore.pyqtSlot() + @QtCore.pyqtSlot(bool) + @misc_utils.log_exception(_moduleLogger) + def _on_sms_clicked(self, checked = False): + with qui_utils.notify_error(self._errorLog): + number = misc_utils.make_ugly(str(self._entry.text())) + self._entry.clear() + + contactId = number + title = misc_utils.make_pretty(number) + description = misc_utils.make_pretty(number) + numbersWithDescriptions = [(number, "")] + self._session.draft.add_contact(contactId, None, title, description, numbersWithDescriptions) + + @QtCore.pyqtSlot() + @QtCore.pyqtSlot(bool) + @misc_utils.log_exception(_moduleLogger) + def _on_call_clicked(self, checked = False): + with qui_utils.notify_error(self._errorLog): + number = misc_utils.make_ugly(str(self._entry.text())) + self._entry.clear() + + contactId = number + title = misc_utils.make_pretty(number) + description = misc_utils.make_pretty(number) + numbersWithDescriptions = [(number, "")] + self._session.draft.clear() + self._session.draft.add_contact(contactId, None, title, description, numbersWithDescriptions) + self._session.draft.call() + + +class TimeCategories(object): + + _NOW_SECTION = 0 + _TODAY_SECTION = 1 + _WEEK_SECTION = 2 + _MONTH_SECTION = 3 + _REST_SECTION = 4 + _MAX_SECTIONS = 5 + + _NO_ELAPSED = datetime.timedelta(hours=1) + _WEEK_ELAPSED = datetime.timedelta(weeks=1) + _MONTH_ELAPSED = datetime.timedelta(days=30) + + def __init__(self, parentItem): + self._timeItems = [ + QtGui.QStandardItem(description) + for (i, description) in zip( + xrange(self._MAX_SECTIONS), + ["Now", "Today", "Week", "Month", "Past"], ) - reversedUserOptions = dict( - itertools.izip(userOptions.itervalues(), userOptions.iterkeys()) - ) - selectedNumber = reversedUserOptions.get(userSelection, userSelection) - - number = make_ugly(selectedNumber) - self._set_callback_number(number) - except RuntimeError, e: - _moduleLogger.exception("%s" % str(e)) - except Exception, e: - self._errorDisplay.push_exception() - - def _on_notify_toggled(self, *args): - try: - if self._applyAlarmTimeoutId is not None: - gobject.source_remove(self._applyAlarmTimeoutId) - self._applyAlarmTimeoutId = None - self._applyAlarmTimeoutId = gobject.timeout_add(500, self._on_apply_timeout) - except Exception, e: - self._errorDisplay.push_exception() - - def _on_minutes_clicked(self, *args): - recurrenceChoices = [ - (1, "1 minute"), - (2, "2 minutes"), - (3, "3 minutes"), - (5, "5 minutes"), - (8, "8 minutes"), - (10, "10 minutes"), - (15, "15 minutes"), - (30, "30 minutes"), - (45, "45 minutes"), - (60, "1 hour"), - (3*60, "3 hours"), - (6*60, "6 hours"), - (12*60, "12 hours"), ] - try: - actualSelection = self._alarmHandler.recurrence - - closestSelectionIndex = 0 - for i, possible in enumerate(recurrenceChoices): - if possible[0] <= actualSelection: - closestSelectionIndex = i - recurrenceIndex = hildonize.touch_selector( - self._window, - "Minutes", - (("%s" % m[1]) for m in recurrenceChoices), - closestSelectionIndex, - ) - recurrence = recurrenceChoices[recurrenceIndex][0] - - self._update_alarm_settings(recurrence) - except RuntimeError, e: - _moduleLogger.exception("%s" % str(e)) - except Exception, e: - self._errorDisplay.push_exception() - - def _on_apply_timeout(self, *args): - try: - self._applyAlarmTimeoutId = None - - self._update_alarm_settings(self._alarmHandler.recurrence) - except Exception, e: - self._errorDisplay.push_exception() - return False - - def _on_missed_toggled(self, *args): - try: - self._notifyOnMissed = self._missedCheckbox.get_active() - self.save_everything() - except Exception, e: - self._errorDisplay.push_exception() + for item in self._timeItems: + item.setEditable(False) + item.setCheckable(False) + row = (item, ) + parentItem.appendRow(row) + + self._today = datetime.datetime(1900, 1, 1) + + self.prepare_for_update(self._today) + + def prepare_for_update(self, newToday): + self._today = newToday + for item in self._timeItems: + item.removeRows(0, item.rowCount()) + try: + hour = self._today.strftime("%X") + day = self._today.strftime("%x") + except ValueError: + _moduleLogger.exception("Can't format times") + hour = "Now" + day = "Today" + self._timeItems[self._NOW_SECTION].setText(hour) + self._timeItems[self._TODAY_SECTION].setText(day) + + def add_row(self, rowDate, row): + elapsedTime = self._today - rowDate + todayTuple = self._today.timetuple() + rowTuple = rowDate.timetuple() + if elapsedTime < self._NO_ELAPSED: + section = self._NOW_SECTION + elif todayTuple[0:3] == rowTuple[0:3]: + section = self._TODAY_SECTION + elif elapsedTime < self._WEEK_ELAPSED: + section = self._WEEK_SECTION + elif elapsedTime < self._MONTH_ELAPSED: + section = self._MONTH_SECTION + else: + section = self._REST_SECTION + self._timeItems[section].appendRow(row) - def _on_voicemail_toggled(self, *args): - try: - self._notifyOnVoicemail = self._voicemailCheckbox.get_active() - self.save_everything() - except Exception, e: - self._errorDisplay.push_exception() + def get_item(self, timeIndex, rowIndex, column): + timeItem = self._timeItems[timeIndex] + item = timeItem.child(rowIndex, column) + return item - def _on_sms_toggled(self, *args): - try: - self._notifyOnSms = self._smsCheckbox.get_active() - self.save_everything() - except Exception, e: - self._errorDisplay.push_exception() +class History(object): -class CallHistoryView(object): + DETAILS_IDX = 0 + FROM_IDX = 1 + MAX_IDX = 2 - NUMBER_IDX = 0 - DATE_IDX = 1 - ACTION_IDX = 2 - FROM_IDX = 3 - FROM_ID_IDX = 4 + HISTORY_RECEIVED = "Received" + HISTORY_MISSED = "Missed" + HISTORY_PLACED = "Placed" + HISTORY_ALL = "All" - HISTORY_ITEM_TYPES = ["All", "Received", "Missed", "Placed"] + HISTORY_ITEM_TYPES = [HISTORY_RECEIVED, HISTORY_MISSED, HISTORY_PLACED, HISTORY_ALL] + HISTORY_COLUMNS = ["Details", "From"] + assert len(HISTORY_COLUMNS) == MAX_IDX - def __init__(self, widgetTree, backend, errorDisplay): - self._errorDisplay = errorDisplay - self._backend = backend + def __init__(self, app, session, errorLog): + self._selectedFilter = self.HISTORY_ITEM_TYPES[-1] + self._app = app + self._session = session + self._session.historyUpdated.connect(self._on_history_updated) + self._errorLog = errorLog - self._isPopulated = False - self._historymodel = gtk.ListStore( - gobject.TYPE_STRING, # number - gobject.TYPE_STRING, # date - gobject.TYPE_STRING, # action - gobject.TYPE_STRING, # from - gobject.TYPE_STRING, # from id + self._typeSelection = QtGui.QComboBox() + self._typeSelection.addItems(self.HISTORY_ITEM_TYPES) + self._typeSelection.setCurrentIndex( + self.HISTORY_ITEM_TYPES.index(self._selectedFilter) ) - self._historymodelfiltered = self._historymodel.filter_new() - self._historymodelfiltered.set_visible_func(self._is_history_visible) - self._historyview = widgetTree.get_widget("historyview") - self._historyviewselection = None - self._onRecentviewRowActivatedId = 0 - - textrenderer = gtk.CellRendererText() - textrenderer.set_property("yalign", 0) - self._dateColumn = gtk.TreeViewColumn("Date") - self._dateColumn.pack_start(textrenderer, expand=True) - self._dateColumn.add_attribute(textrenderer, "text", self.DATE_IDX) - - textrenderer = gtk.CellRendererText() - textrenderer.set_property("yalign", 0) - self._actionColumn = gtk.TreeViewColumn("Action") - self._actionColumn.pack_start(textrenderer, expand=True) - self._actionColumn.add_attribute(textrenderer, "text", self.ACTION_IDX) - - textrenderer = gtk.CellRendererText() - textrenderer.set_property("yalign", 0) - textrenderer.set_property("ellipsize", pango.ELLIPSIZE_END) - textrenderer.set_property("width-chars", len("1 (555) 555-1234")) - self._numberColumn = gtk.TreeViewColumn("Number") - self._numberColumn.pack_start(textrenderer, expand=True) - self._numberColumn.add_attribute(textrenderer, "text", self.NUMBER_IDX) - - textrenderer = gtk.CellRendererText() - textrenderer.set_property("yalign", 0) - hildonize.set_cell_thumb_selectable(textrenderer) - self._nameColumn = gtk.TreeViewColumn("From") - self._nameColumn.pack_start(textrenderer, expand=True) - self._nameColumn.add_attribute(textrenderer, "text", self.FROM_IDX) - self._nameColumn.set_sizing(gtk.TREE_VIEW_COLUMN_FIXED) - - self._window = gtk_toolbox.find_parent_window(self._historyview) - - self._historyFilterSelector = widgetTree.get_widget("historyFilterSelector") - self._historyFilterSelector.connect("clicked", self._on_history_filter_clicked) - self._selectedFilter = "All" - - self._updateSink = gtk_toolbox.threaded_stage( - gtk_toolbox.comap( - self._idly_populate_historyview, - gtk_toolbox.null_sink(), - ) + self._typeSelection.currentIndexChanged[str].connect(self._on_filter_changed) + refreshIcon = qui_utils.get_theme_icon( + ("view-refresh", "general_refresh", "gtk-refresh", ) ) + self._refreshButton = QtGui.QPushButton(refreshIcon, "") + self._refreshButton.clicked.connect(self._on_refresh_clicked) + self._refreshButton.setSizePolicy(QtGui.QSizePolicy( + QtGui.QSizePolicy.Minimum, + QtGui.QSizePolicy.Minimum, + QtGui.QSizePolicy.PushButton, + )) + self._managerLayout = QtGui.QHBoxLayout() + self._managerLayout.addWidget(self._typeSelection, 1000) + self._managerLayout.addWidget(self._refreshButton, 0) + + self._itemStore = QtGui.QStandardItemModel() + self._itemStore.setHorizontalHeaderLabels(self.HISTORY_COLUMNS) + self._categoryManager = TimeCategories(self._itemStore) + + self._itemView = QtGui.QTreeView() + self._itemView.setModel(self._itemStore) + self._itemView.setUniformRowHeights(True) + self._itemView.setRootIsDecorated(False) + self._itemView.setHorizontalScrollBarPolicy(QtCore.Qt.ScrollBarAlwaysOff) + self._itemView.setSelectionBehavior(QtGui.QAbstractItemView.SelectRows) + self._itemView.setSelectionMode(QtGui.QAbstractItemView.SingleSelection) + self._itemView.setHeaderHidden(True) + self._itemView.setItemsExpandable(False) + self._itemView.header().setResizeMode(QtGui.QHeaderView.ResizeToContents) + self._itemView.activated.connect(self._on_row_activated) + + self._layout = QtGui.QVBoxLayout() + self._layout.addLayout(self._managerLayout) + self._layout.addWidget(self._itemView) + self._widget = QtGui.QWidget() + self._widget.setLayout(self._layout) + + self._populate_items() + + @property + def toplevel(self): + return self._widget def enable(self): - assert self._backend.is_authed(), "Attempting to enable backend while not logged in" - self._historyFilterSelector.set_label(self._selectedFilter) - - self._historyview.set_model(self._historymodelfiltered) - self._historyview.set_fixed_height_mode(False) - - self._historyview.append_column(self._dateColumn) - self._historyview.append_column(self._actionColumn) - self._historyview.append_column(self._numberColumn) - self._historyview.append_column(self._nameColumn) - self._historyviewselection = self._historyview.get_selection() - self._historyviewselection.set_mode(gtk.SELECTION_SINGLE) - - self._onRecentviewRowActivatedId = self._historyview.connect("row-activated", self._on_historyview_row_activated) + self._itemView.setEnabled(True) def disable(self): - self._historyview.disconnect(self._onRecentviewRowActivatedId) - - self.clear() - - self._historyview.remove_column(self._dateColumn) - self._historyview.remove_column(self._actionColumn) - self._historyview.remove_column(self._nameColumn) - self._historyview.remove_column(self._numberColumn) - self._historyview.set_model(None) + self._itemView.setEnabled(False) - def add_contact(self, *args, **kwds): - """ - @note Actual dial function is patched in later - """ - raise NotImplementedError("Horrible unknown error has occurred") - - def update(self, force = False): - if not force and self._isPopulated: - return False - self._updateSink.send(()) - return True - - def clear(self): - self._isPopulated = False - self._historymodel.clear() - - @staticmethod - def name(): - return "Recent Calls" - - def load_settings(self, config, sectionName): - try: - self._selectedFilter = config.get(sectionName, "filter") - if self._selectedFilter not in self.HISTORY_ITEM_TYPES: - self._messageType = self.HISTORY_ITEM_TYPES[0] - except ConfigParser.NoOptionError: - pass - - def save_settings(self, config, sectionName): - """ - @note Thread Agnostic - """ - config.set(sectionName, "filter", self._selectedFilter) - - def _is_history_visible(self, model, iter): - try: - action = model.get_value(iter, self.ACTION_IDX) - if action is None: - return False # this seems weird but oh well - - if self._selectedFilter in [action, "All"]: - return True - else: - return False - except Exception, e: - self._errorDisplay.push_exception() + def get_settings(self): + return { + "filter": self._selectedFilter, + } - def _idly_populate_historyview(self): - with gtk_toolbox.gtk_lock(): - banner = hildonize.show_busy_banner_start(self._window, "Loading Call History") - try: - self._historymodel.clear() - self._isPopulated = True - - try: - historyItems = self._backend.get_recent() - except Exception, e: - self._errorDisplay.push_exception_with_lock() - self._isPopulated = False - historyItems = [] - - historyItems = ( - gv_backend.decorate_recent(data) - for data in gv_backend.sort_messages(historyItems) + def set_settings(self, settings): + selectedFilter = settings.get("filter", self.HISTORY_ITEM_TYPES[-1]) + if selectedFilter in self.HISTORY_ITEM_TYPES: + self._selectedFilter = selectedFilter + self._typeSelection.setCurrentIndex( + self.HISTORY_ITEM_TYPES.index(selectedFilter) ) - for contactId, personName, phoneNumber, date, action in historyItems: - if not personName: - personName = "Unknown" - date = abbrev_relative_date(date) - prettyNumber = phoneNumber[2:] if phoneNumber.startswith("+1") else phoneNumber - prettyNumber = make_pretty(prettyNumber) - item = (prettyNumber, date, action.capitalize(), personName, contactId) - with gtk_toolbox.gtk_lock(): - self._historymodel.append(item) - except Exception, e: - self._errorDisplay.push_exception_with_lock() - finally: - with gtk_toolbox.gtk_lock(): - hildonize.show_busy_banner_end(banner) - - return False - - def _on_history_filter_clicked(self, *args, **kwds): - try: - selectedComboIndex = self.HISTORY_ITEM_TYPES.index(self._selectedFilter) - - try: - newSelectedComboIndex = hildonize.touch_selector( - self._window, - "History", - self.HISTORY_ITEM_TYPES, - selectedComboIndex, - ) - except RuntimeError: - return - - option = self.HISTORY_ITEM_TYPES[newSelectedComboIndex] - self._selectedFilter = option - self._historyFilterSelector.set_label(self._selectedFilter) - self._historymodelfiltered.refilter() - except Exception, e: - self._errorDisplay.push_exception() - - def _on_historyview_row_activated(self, treeview, path, view_column): - try: - childPath = self._historymodelfiltered.convert_path_to_child_path(path) - itr = self._historymodel.get_iter(childPath) - if not itr: + def clear(self): + self._itemView.clear() + + def refresh(self, force=True): + self._itemView.setFocus(QtCore.Qt.OtherFocusReason) + + if self._selectedFilter == self.HISTORY_RECEIVED: + self._session.update_history(self._session.HISTORY_RECEIVED, force) + elif self._selectedFilter == self.HISTORY_MISSED: + self._session.update_history(self._session.HISTORY_MISSED, force) + elif self._selectedFilter == self.HISTORY_PLACED: + self._session.update_history(self._session.HISTORY_PLACED, force) + elif self._selectedFilter == self.HISTORY_ALL: + self._session.update_history(self._session.HISTORY_ALL, force) + else: + assert False, "How did we get here?" + + if self._app.notifyOnMissed and self._app.alarmHandler.alarmType == self._app.alarmHandler.ALARM_BACKGROUND: + self._app.ledHandler.off() + + def _populate_items(self): + self._categoryManager.prepare_for_update(self._session.get_when_history_updated()) + + history = self._session.get_history() + history.sort(key=lambda item: item["time"], reverse=True) + for event in history: + if self._selectedFilter not in [self.HISTORY_ITEM_TYPES[-1], event["action"]]: + continue + + relTime = misc_utils.abbrev_relative_date(event["relTime"]) + action = event["action"] + number = event["number"] + prettyNumber = misc_utils.make_pretty(number) + name = event["name"] + if not name or name == number: + name = event["location"] + if not name: + name = "Unknown" + + detailsItem = QtGui.QStandardItem("%s - %s\n%s" % (relTime, action, prettyNumber)) + detailsFont = detailsItem.font() + detailsFont.setPointSize(max(detailsFont.pointSize() - 7, 5)) + detailsItem.setFont(detailsFont) + nameItem = QtGui.QStandardItem(name) + nameFont = nameItem.font() + nameFont.setPointSize(nameFont.pointSize() + 4) + nameItem.setFont(nameFont) + row = detailsItem, nameItem + for item in row: + item.setEditable(False) + item.setCheckable(False) + row[0].setData(event) + self._categoryManager.add_row(event["time"], row) + self._itemView.expandAll() + + @QtCore.pyqtSlot(str) + @misc_utils.log_exception(_moduleLogger) + def _on_filter_changed(self, newItem): + with qui_utils.notify_error(self._errorLog): + self._selectedFilter = str(newItem) + self._populate_items() + + @QtCore.pyqtSlot() + @misc_utils.log_exception(_moduleLogger) + def _on_history_updated(self): + with qui_utils.notify_error(self._errorLog): + self._populate_items() + + @QtCore.pyqtSlot() + @misc_utils.log_exception(_moduleLogger) + def _on_refresh_clicked(self, arg = None): + with qui_utils.notify_error(self._errorLog): + self.refresh(force=True) + + @QtCore.pyqtSlot(QtCore.QModelIndex) + @misc_utils.log_exception(_moduleLogger) + def _on_row_activated(self, index): + with qui_utils.notify_error(self._errorLog): + timeIndex = index.parent() + if not timeIndex.isValid(): return - - number = self._historymodel.get_value(itr, self.NUMBER_IDX) - number = make_ugly(number) - description = self._historymodel.get_value(itr, self.FROM_IDX) - contactId = self._historymodel.get_value(itr, self.FROM_ID_IDX) - if contactId: - contactPhoneNumbers = list(self._backend.get_contact_details(contactId)) - defaultMatches = [ - (number == make_ugly(contactNumber) or number[1:] == make_ugly(contactNumber)) - for (numberDescription, contactNumber) in contactPhoneNumbers - ] - try: - defaultIndex = defaultMatches.index(True) - except ValueError: - contactPhoneNumbers.append(("Other", number)) - defaultIndex = len(contactPhoneNumbers)-1 - _moduleLogger.warn( - "Could not find contact %r's number %s among %r" % ( - contactId, number, contactPhoneNumbers - ) - ) - else: - contactPhoneNumbers = [("Phone", number)] - defaultIndex = -1 - - self.add_contact( - contactPhoneNumbers, - messages = (description, ), - parent = self._window, - defaultIndex = defaultIndex, - ) - self._historyviewselection.unselect_all() - except Exception, e: - self._errorDisplay.push_exception() - - -class MessagesView(object): - - NUMBER_IDX = 0 - DATE_IDX = 1 - HEADER_IDX = 2 - MESSAGE_IDX = 3 - MESSAGES_IDX = 4 - FROM_ID_IDX = 5 - MESSAGE_DATA_IDX = 6 + timeRow = timeIndex.row() + row = index.row() + detailsItem = self._categoryManager.get_item(timeRow, row, self.DETAILS_IDX) + fromItem = self._categoryManager.get_item(timeRow, row, self.FROM_IDX) + contactDetails = detailsItem.data().toPyObject() + + title = unicode(fromItem.text()) + number = str(contactDetails[QtCore.QString("number")]) + contactId = number # ids don't seem too unique so using numbers + + descriptionRows = [] + for t in xrange(self._itemStore.rowCount()): + randomTimeItem = self._itemStore.item(t, 0) + for i in xrange(randomTimeItem.rowCount()): + iItem = randomTimeItem.child(i, 0) + iContactDetails = iItem.data().toPyObject() + iNumber = str(iContactDetails[QtCore.QString("number")]) + if number != iNumber: + continue + relTime = misc_utils.abbrev_relative_date(iContactDetails[QtCore.QString("relTime")]) + action = str(iContactDetails[QtCore.QString("action")]) + number = str(iContactDetails[QtCore.QString("number")]) + prettyNumber = misc_utils.make_pretty(number) + rowItems = relTime, action, prettyNumber + descriptionRows.append("