X-Git-Url: http://git.maemo.org/git/?p=gc-dialer;a=blobdiff_plain;f=src%2Fgv_views.py;h=f57995ce2848c65ef85d179773a4f1e9f3bccd05;hp=8792e3d041907d87612cff92e65d6911a6cfe10d;hb=9708c3b670d58c0370a45e28dcbd0abc9d8276a8;hpb=8dac134a201553e8ca9e310a628bd6a3c37d126f diff --git a/src/gv_views.py b/src/gv_views.py index 8792e3d..f57995c 100644 --- a/src/gv_views.py +++ b/src/gv_views.py @@ -1,1675 +1,950 @@ -#!/usr/bin/python2.5 - -""" -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 -@todo Alternate UI for dialogs (stackables) -""" +#!/usr/bin/env python from __future__ import with_statement +from __future__ import division -import ConfigParser -import logging +import datetime +import string import itertools +import logging -import gobject -import pango -import gtk - -import gtk_toolbox -import hildonize -import gv_backend -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' - """ - import re - uglynumber = re.sub('\D', '', prettynumber) - return uglynumber - - -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") - '(234)-567-8901' - >>> make_pretty("12345678901") - '1 (234)-567-8901' - >>> make_pretty("01234567890") - '+012-(345)-678-90' - """ - if phonenumber is None or phonenumber is "": - return "" - - phonenumber = make_ugly(phonenumber) - - if len(phonenumber) < 3: - return phonenumber - - if phonenumber[0] == "0": - prettynumber = "" - prettynumber += "+%s" % phonenumber[0:3] - if 3 < len(phonenumber): - prettynumber += "-(%s)" % phonenumber[3:6] - if 6 < len(phonenumber): - prettynumber += "-%s" % phonenumber[6:9] - if 9 < len(phonenumber): - prettynumber += "-%s" % phonenumber[9:] - return prettynumber - elif len(phonenumber) <= 7: - prettynumber = "%s-%s" % (phonenumber[0:3], phonenumber[3:]) - elif len(phonenumber) > 8 and phonenumber[0] == "1": - prettynumber = "1 (%s)-%s-%s" % (phonenumber[1:4], phonenumber[4:7], phonenumber[7:]) - elif len(phonenumber) > 7: - prettynumber = "(%s)-%s-%s" % (phonenumber[0:3], phonenumber[3:6], phonenumber[6:]) - return prettynumber - - -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 MergedAddressBook(object): - """ - Merger of all addressbooks - """ - - def __init__(self, addressbookFactories, sorter = None): - self.__addressbookFactories = addressbookFactories - self.__addressbooks = None - self.__sort_contacts = sorter if sorter is not None else self.null_sorter - - def clear_caches(self): - self.__addressbooks = None - for factory in self.__addressbookFactories: - factory.clear_caches() - - def get_addressbooks(self): - """ - @returns Iterable of (Address Book Factory, Book Id, Book Name) - """ - yield self, "", "" - - def open_addressbook(self, bookId): - return self - - def contact_source_short_name(self, contactId): - if self.__addressbooks is None: - return "" - bookIndex, originalId = contactId.split("-", 1) - return self.__addressbooks[int(bookIndex)].contact_source_short_name(originalId) - - @staticmethod - def factory_name(): - return "All Contacts" - - def get_contacts(self): - """ - @returns Iterable of (contact id, contact name) - """ - if self.__addressbooks is None: - self.__addressbooks = list( - factory.open_addressbook(id) - for factory in self.__addressbookFactories - for (f, id, name) in factory.get_addressbooks() - ) - contacts = ( - ("-".join([str(bookIndex), contactId]), contactName) - for (bookIndex, addressbook) in enumerate(self.__addressbooks) - for (contactId, contactName) in addressbook.get_contacts() - ) - sortedContacts = self.__sort_contacts(contacts) - return sortedContacts - - def get_contact_details(self, contactId): - """ - @returns Iterable of (Phone Type, Phone Number) - """ - if self.__addressbooks is None: - return [] - bookIndex, originalId = contactId.split("-", 1) - return self.__addressbooks[int(bookIndex)].get_contact_details(originalId) - - @staticmethod - def null_sorter(contacts): - """ - Good for speed/low memory - """ - return contacts +from PyQt4 import QtGui +from PyQt4 import QtCore - @staticmethod - def basic_firtname_sorter(contacts): - """ - Expects names in "First Last" format - """ - contactsWithKey = [ - (contactName.rsplit(" ", 1)[0], (contactId, contactName)) - for (contactId, contactName) in contacts - ] - contactsWithKey.sort() - return (contactData for (lastName, contactData) in contactsWithKey) +from util import qtpie +from util import qui_utils +from util import misc as misc_utils - @staticmethod - def basic_lastname_sorter(contacts): - """ - Expects names in "First Last" format - """ - contactsWithKey = [ - (contactName.rsplit(" ", 1)[-1], (contactId, contactName)) - for (contactId, contactName) in contacts - ] - contactsWithKey.sort() - return (contactData for (lastName, contactData) in contactsWithKey) +import backends.null_backend as null_backend +import backends.file_backend as file_backend - @staticmethod - def reversed_firtname_sorter(contacts): - """ - Expects names in "Last, First" format - """ - contactsWithKey = [ - (contactName.split(", ", 1)[-1], (contactId, contactName)) - for (contactId, contactName) in contacts - ] - contactsWithKey.sort() - return (contactData for (lastName, contactData) in contactsWithKey) - @staticmethod - def reversed_lastname_sorter(contacts): - """ - Expects names in "Last, First" format - """ - contactsWithKey = [ - (contactName.split(", ", 1)[0], (contactId, contactName)) - for (contactId, contactName) in contacts - ] - contactsWithKey.sort() - return (contactData for (lastName, contactData) in contactsWithKey) +_moduleLogger = logging.getLogger(__name__) - @staticmethod - def guess_firstname(name): - if ", " in name: - return name.split(", ", 1)[-1] - else: - return name.rsplit(" ", 1)[0] - @staticmethod - def guess_lastname(name): - if ", " in name: - return name.split(", ", 1)[0] - else: - return name.rsplit(" ", 1)[-1] +class Dialpad(object): - @classmethod - def advanced_firstname_sorter(cls, contacts): - contactsWithKey = [ - (cls.guess_firstname(contactName), (contactId, contactName)) - for (contactId, contactName) in contacts + 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, ] - contactsWithKey.sort() - return (contactData for (lastName, contactData) in contactsWithKey) - - @classmethod - def advanced_lastname_sorter(cls, contacts): - contactsWithKey = [ - (cls.guess_lastname(contactName), (contactId, contactName)) - for (contactId, contactName) in contacts + 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"), ] - contactsWithKey.sort() - return (contactData for (lastName, contactData) in contactsWithKey) - - -class SmsEntryDialog(object): - """ - @todo Add multi-SMS messages like GoogleVoice - """ - - ACTION_CANCEL = "cancel" - ACTION_DIAL = "dial" - ACTION_SEND_SMS = "sms" - - MAX_CHAR = 160 - - def __init__(self, widgetTree): - self._clipboard = gtk.clipboard_get() - self._widgetTree = widgetTree - self._dialog = self._widgetTree.get_widget("smsDialog") - - 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._cancelButton = self._widgetTree.get_widget("cancelSmsButton") - self._cancelButton.connect("clicked", self._on_cancel) - - self._letterCountLabel = self._widgetTree.get_widget("smsLetterCount") - - self._messagemodel = gtk.ListStore(gobject.TYPE_STRING) - self._messagesView = self._widgetTree.get_widget("smsMessages") - - self._conversationView = self._messagesView.get_parent() - self._conversationViewPort = self._conversationView.get_parent() - self._scrollWindow = self._conversationViewPort.get_parent() - - self._phoneButton = self._widgetTree.get_widget("phoneTypeSelection") - self._smsEntry = self._widgetTree.get_widget("smsEntry") - - self._action = self.ACTION_CANCEL - - self._numberIndex = -1 - self._contactDetails = [] - - def run(self, contactDetails, messages = (), parent = None, defaultIndex = -1): - entryConnectId = self._smsEntry.get_buffer().connect("changed", self._on_entry_changed) - phoneConnectId = self._phoneButton.connect("clicked", self._on_phone) - keyConnectId = self._keyPressEventId = self._dialog.connect("key-press-event", self._on_key_press) - try: - # Setup the phone selection button - del self._contactDetails[:] - for phoneType, phoneNumber in contactDetails: - display = " - ".join((make_pretty(phoneNumber), phoneType)) - row = (phoneNumber, display) - self._contactDetails.append(row) - if 0 < len(self._contactDetails): - self._numberIndex = defaultIndex if defaultIndex != -1 else 0 - self._phoneButton.set_label(self._contactDetails[self._numberIndex][1]) - else: - self._numberIndex = -1 - self._phoneButton.set_label("Error: No Number Available") - - # Add the column to the messages tree view - self._messagemodel.clear() - self._messagesView.set_model(self._messagemodel) - self._messagesView.set_fixed_height_mode(False) - - 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) - - if messages: - for message in messages: - row = (message, ) - self._messagemodel.append(row) - self._messagesView.show() - self._scrollWindow.show() - messagesSelection = self._messagesView.get_selection() - messagesSelection.select_path((len(messages)-1, )) - else: - self._messagesView.hide() - self._scrollWindow.hide() - - self._smsEntry.get_buffer().set_text("") - self._update_letter_count() - - if parent is not None: - self._dialog.set_transient_for(parent) - parentSize = parent.get_size() - self._dialog.resize(parentSize[0], max(parentSize[1]-10, 100)) - - # Run - try: - self._dialog.show_all() - self._smsEntry.grab_focus() - adjustment = self._scrollWindow.get_vadjustment() - dx = self._conversationView.get_allocation().height - self._conversationViewPort.get_allocation().height - dx = max(dx, 0) - adjustment.value = dx - - if 1 < len(self._contactDetails): - if defaultIndex == -1: - self._request_number() - self._phoneButton.set_sensitive(True) - else: - self._phoneButton.set_sensitive(False) - - userResponse = self._dialog.run() - finally: - self._dialog.hide_all() - - # Process the users response - if userResponse == gtk.RESPONSE_OK and 0 <= self._numberIndex: - phoneNumber = self._contactDetails[self._numberIndex][0] - phoneNumber = make_ugly(phoneNumber) - else: - phoneNumber = "" - if not phoneNumber: - self._action = self.ACTION_CANCEL - if self._action == self.ACTION_SEND_SMS: - entryBuffer = self._smsEntry.get_buffer() - enteredMessage = entryBuffer.get_text(entryBuffer.get_start_iter(), entryBuffer.get_end_iter()) - enteredMessage = enteredMessage[0:self.MAX_CHAR].strip() - if not enteredMessage: - phoneNumber = "" - self._action = self.ACTION_CANCEL - else: - enteredMessage = "" - - self._messagesView.remove_column(messageColumn) - self._messagesView.set_model(None) - - return self._action, phoneNumber, enteredMessage - finally: - self._smsEntry.get_buffer().disconnect(entryConnectId) - self._phoneButton.disconnect(phoneConnectId) - self._keyPressEventId = self._dialog.disconnect(keyConnectId) - - def _update_letter_count(self, *args): - entryLength = self._smsEntry.get_buffer().get_char_count() - - charsLeft = self.MAX_CHAR - entryLength - self._letterCountLabel.set_text(str(charsLeft)) - if charsLeft < 0 or charsLeft == self.MAX_CHAR: - self._smsButton.set_sensitive(False) - else: - self._smsButton.set_sensitive(True) - - if entryLength == 0: - self._dialButton.set_sensitive(True) - else: - self._dialButton.set_sensitive(False) - - def _request_number(self): - try: - assert 0 <= self._numberIndex, "%r" % self._numberIndex - - self._numberIndex = hildonize.touch_selector( - self._dialog, - "Phone Numbers", - (description for (number, description) in self._contactDetails), - self._numberIndex, - ) - self._phoneButton.set_label(self._contactDetails[self._numberIndex][1]) - except Exception, e: - _moduleLogger.exception("%s" % str(e)) - - def _on_phone(self, *args): - self._request_number() - - def _on_entry_changed(self, *args): - self._update_letter_count() - - def _on_send(self, *args): - self._dialog.response(gtk.RESPONSE_OK) - self._action = self.ACTION_SEND_SMS - - def _on_dial(self, *args): - self._dialog.response(gtk.RESPONSE_OK) - self._action = self.ACTION_DIAL - - def _on_cancel(self, *args): - self._dialog.response(gtk.RESPONSE_CANCEL) - self._action = self.ACTION_CANCEL - - 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 - ) - # For some reason this kills clipboard stuff - #self._clipboard.set_text(message) - except Exception, e: - _moduleLogger.exception(str(e)) - - -class Dialpad(object): - - def __init__(self, widgetTree, errorDisplay): - self._clipboard = gtk.clipboard_get() - self._errorDisplay = errorDisplay - self._smsDialog = SmsEntryDialog(widgetTree) - - 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._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._window = gtk_toolbox.find_parent_window(self._numberdisplay) - self._keyPressEventId = 0 + 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._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() - - def number_selected(self, action, number, message): - """ - @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("") - - @staticmethod - def name(): - return "Dialpad" - - def load_settings(self, config, section): - pass + def get_settings(self): + return {} - 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() - action, phoneNumber, message = self._smsDialog.run([("Dialer", phoneNumber)], (), self._window) - - if action == SmsEntryDialog.ACTION_CANCEL: - return - self.number_selected(action, phoneNumber, message) - except Exception, e: - self._errorDisplay.push_exception() - - def _on_dial_clicked(self, widget): - try: - action = SmsEntryDialog.ACTION_DIAL - phoneNumber = self.get_number() - message = "" - self.number_selected(action, phoneNumber, message) - 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_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._defaultCallback = "" - - 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) - - 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 - - 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 get_selected_callback_number(self): - currentLabel = self._callbackSelectButton.get_label() - if currentLabel is not None: - return make_ugly(currentLabel) - else: - return "" - - 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._defaultCallback = 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 - """ - callback = self.get_selected_callback_number() - config.set(section, "callback", callback) - 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._defaultCallback) - - 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: - 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._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.get_selected_callback_number()) + 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, - ) - reversedUserOptions = dict( - itertools.izip(userOptions.itervalues(), userOptions.iterkeys()) + 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"], ) - selectedNumber = reversedUserOptions.get(userSelection, userSelection) + ] + 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) - 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 get_item(self, timeIndex, rowIndex, column): + timeItem = self._timeItems[timeIndex] + item = timeItem.child(rowIndex, column) + return item - 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() +class History(object): - def _on_apply_timeout(self, *args): - try: - self._applyAlarmTimeoutId = None + DETAILS_IDX = 0 + FROM_IDX = 1 + MAX_IDX = 2 - self._update_alarm_settings(self._alarmHandler.recurrence) - except Exception, e: - self._errorDisplay.push_exception() - return False + HISTORY_RECEIVED = "Received" + HISTORY_MISSED = "Missed" + HISTORY_PLACED = "Placed" + HISTORY_ALL = "All" - def _on_missed_toggled(self, *args): - try: - self._notifyOnMissed = self._missedCheckbox.get_active() - self.save_everything() - except Exception, e: - self._errorDisplay.push_exception() + HISTORY_ITEM_TYPES = [HISTORY_RECEIVED, HISTORY_MISSED, HISTORY_PLACED, HISTORY_ALL] + HISTORY_COLUMNS = ["Details", "From"] + assert len(HISTORY_COLUMNS) == MAX_IDX - def _on_voicemail_toggled(self, *args): - try: - self._notifyOnVoicemail = self._voicemailCheckbox.get_active() - self.save_everything() - except Exception, e: - self._errorDisplay.push_exception() + 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 - def _on_sms_toggled(self, *args): - try: - self._notifyOnSms = self._smsCheckbox.get_active() - self.save_everything() - except Exception, e: - self._errorDisplay.push_exception() - - -class RecentCallsView(object): - - NUMBER_IDX = 0 - DATE_IDX = 1 - ACTION_IDX = 2 - FROM_IDX = 3 - FROM_ID_IDX = 4 - - def __init__(self, widgetTree, backend, errorDisplay): - self._errorDisplay = errorDisplay - self._backend = backend - - self._isPopulated = False - self._recentmodel = 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._recentview = widgetTree.get_widget("recentview") - self._recentviewselection = 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._recentview) - self._phoneTypeSelector = SmsEntryDialog(widgetTree) - - self._updateSink = gtk_toolbox.threaded_stage( - gtk_toolbox.comap( - self._idly_populate_recentview, - 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._recentview.set_model(self._recentmodel) - self._recentview.set_fixed_height_mode(False) - - self._recentview.append_column(self._dateColumn) - self._recentview.append_column(self._actionColumn) - self._recentview.append_column(self._numberColumn) - self._recentview.append_column(self._nameColumn) - self._recentviewselection = self._recentview.get_selection() - self._recentviewselection.set_mode(gtk.SELECTION_SINGLE) - - self._onRecentviewRowActivatedId = self._recentview.connect("row-activated", self._on_recentview_row_activated) + self._itemView.setEnabled(True) def disable(self): - self._recentview.disconnect(self._onRecentviewRowActivatedId) - - self.clear() - - self._recentview.remove_column(self._dateColumn) - self._recentview.remove_column(self._actionColumn) - self._recentview.remove_column(self._nameColumn) - self._recentview.remove_column(self._numberColumn) - self._recentview.set_model(None) - - def number_selected(self, action, number, message): - """ - @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._recentmodel.clear() - - @staticmethod - def name(): - return "Recent Calls" - - def load_settings(self, config, section): - pass + self._itemView.setEnabled(False) - def save_settings(self, config, section): - """ - @note Thread Agnostic - """ - pass + def get_settings(self): + return { + "filter": self._selectedFilter, + } - def _idly_populate_recentview(self): - with gtk_toolbox.gtk_lock(): - banner = hildonize.show_busy_banner_start(self._window, "Loading Recent History") - try: - self._recentmodel.clear() - self._isPopulated = True - - try: - recentItems = self._backend.get_recent() - except Exception, e: - self._errorDisplay.push_exception_with_lock() - self._isPopulated = False - recentItems = [] - - recentItems = ( - gv_backend.decorate_recent(data) - for data in gv_backend.sort_messages(recentItems) + 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 recentItems: - 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._recentmodel.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_recentview_row_activated(self, treeview, path, view_column): - try: - itr = self._recentmodel.get_iter(path) - if not itr: - return - - number = self._recentmodel.get_value(itr, self.NUMBER_IDX) - number = make_ugly(number) - description = self._recentmodel.get_value(itr, self.FROM_IDX) - contactId = self._recentmodel.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 - - action, phoneNumber, message = self._phoneTypeSelector.run( - contactPhoneNumbers, - messages = (description, ), - parent = self._window, - defaultIndex = defaultIndex, - ) - if action == SmsEntryDialog.ACTION_CANCEL: + 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 - assert phoneNumber, "A lack of phone number exists" - - self.number_selected(action, phoneNumber, message) - self._recentviewselection.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 + 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("