From 70e2aebf95b14bb763d68af56c101ac45318a4be Mon Sep 17 00:00:00 2001
From: Luci Stanescu <luci@ag-projects.com>
Date: Tue, 18 May 2010 12:41:14 +0000
Subject: [PATCH] Added a model for accounts and improved the identity combobox

---
 blink/accounts.py   | 210 ++++++++++++++++++++++++++++++++++++++++++++
 blink/mainwindow.py |  60 ++-----------
 resources/blink.ui  |   7 +-
 3 files changed, 225 insertions(+), 52 deletions(-)
 create mode 100644 blink/accounts.py

diff --git a/blink/accounts.py b/blink/accounts.py
new file mode 100644
index 0000000..36fd6b2
--- /dev/null
+++ b/blink/accounts.py
@@ -0,0 +1,210 @@
+# Copyright (C) 2010 AG Projects. See LICENSE for details.
+#
+
+__all__ = ['AccountModel', 'ActiveAccountModel', 'AccountSelector']
+
+from PyQt4.QtCore import Qt, QAbstractListModel, QModelIndex
+from PyQt4.QtGui  import QComboBox, QIcon, QPalette, QPixmap, QSortFilterProxyModel, QStyledItemDelegate
+
+from application.notification import IObserver, NotificationCenter
+from application.python.util import Null
+from zope.interface import implements
+
+from sipsimple.account import Account, AccountManager, BonjourAccount
+
+from blink.resources import Resources
+from blink.util import run_in_gui_thread
+
+
+class AccountInfo(object):
+    def __init__(self, name, account, icon=None):
+        self.name = name
+        self.account = account
+        self.icon = icon
+        self.registration_state = None
+
+    def __eq__(self, other):
+        if isinstance(other, basestring):
+            return self.name == other
+        elif isinstance(other, (Account, BonjourAccount)):
+            return self.account == other
+        return False
+
+    def __ne__(self, other):
+        return not self.__eq__(other)
+
+
+class AccountModel(QAbstractListModel):
+    implements(IObserver)
+
+    def __init__(self, parent=None):
+        super(AccountModel, self).__init__(parent)
+        self.accounts = []
+
+        notification_center = NotificationCenter()
+        notification_center.add_observer(self, name='SIPAccountDidActivate')
+        notification_center.add_observer(self, name='SIPAccountDidDeactivate')
+        notification_center.add_observer(self, name='SIPAccountWillRegister')
+        notification_center.add_observer(self, name='SIPAccountRegistrationDidSucceed')
+        notification_center.add_observer(self, name='SIPAccountRegistrationDidFail')
+        notification_center.add_observer(self, name='SIPAccountRegistrationDidEnd')
+        notification_center.add_observer(self, name='BonjourAccountWillRegister')
+        notification_center.add_observer(self, name='BonjourAccountRegistrationDidSucceed')
+        notification_center.add_observer(self, name='BonjourAccountRegistrationDidFail')
+        notification_center.add_observer(self, name='BonjourAccountRegistrationDidEnd')
+        notification_center.add_observer(self, sender=AccountManager())
+
+    def rowCount(self, parent=QModelIndex()):
+        return len(self.accounts)
+
+    def data(self, index, role=Qt.DisplayRole):
+        if not index.isValid():
+            return None
+        account_info = self.accounts[index.row()]
+        if role == Qt.DisplayRole:
+            return account_info.name
+        elif role == Qt.DecorationRole:
+            return account_info.icon
+        elif role == Qt.UserRole:
+            return account_info
+        return None
+
+    @run_in_gui_thread
+    def handle_notification(self, notification):
+        handler = getattr(self, '_NH_%s' % notification.name, Null)
+        handler(notification)
+
+    def _NH_SIPAccountManagerDidAddAccount(self, notification):
+        account = notification.data.account
+        name = u'Bonjour' if account is BonjourAccount() else unicode(account.id)
+        icon = None
+        if account is BonjourAccount():
+            pixmap = QPixmap()
+            if pixmap.load(Resources.get('icons/bonjour.png')):
+                pixmap = pixmap.scaled(16, 16, Qt.KeepAspectRatio, Qt.SmoothTransformation)
+                icon = QIcon(pixmap)
+        self.beginInsertRows(QModelIndex(), len(self.accounts), len(self.accounts))
+        self.accounts.append(AccountInfo(name, account, icon))
+        self.endInsertRows()
+
+    def _NH_SIPAccountManagerDidRemoveAccount(self, notification):
+        position = self.accounts.index(notification.data.account)
+        self.beginRemoveRows(QModelIndex(), position, position)
+        del self.accounts[position]
+        self.endRemoveRows()
+
+    def _NH_SIPAccountDidActivate(self, notification):
+        position = self.accounts.index(notification.sender)
+        self.dataChanged.emit(self.index(position), self.index(position))
+
+    def _NH_SIPAccountDidDeactivate(self, notification):
+        position = self.accounts.index(notification.sender)
+        self.dataChanged.emit(self.index(position), self.index(position))
+
+    def _NH_SIPAccountWillRegister(self, notification):
+        position = self.accounts.index(notification.sender)
+        self.accounts[position].registration_state = 'started'
+        self.dataChanged.emit(self.index(position), self.index(position))
+
+    def _NH_SIPAccountRegistrationDidSucceed(self, notification):
+        position = self.accounts.index(notification.sender)
+        self.accounts[position].registration_state = 'succeeded'
+        self.dataChanged.emit(self.index(position), self.index(position))
+
+    def _NH_SIPAccountRegistrationDidFail(self, notification):
+        position = self.accounts.index(notification.sender)
+        self.accounts[position].registration_state = 'failed'
+        self.dataChanged.emit(self.index(position), self.index(position))
+
+    def _NH_SIPAccountRegistrationDidEnd(self, notification):
+        position = self.accounts.index(notification.sender)
+        self.accounts[position].registration_state = 'ended'
+        self.dataChanged.emit(self.index(position), self.index(position))
+
+    _NH_BonjourAccountWillRegister = _NH_SIPAccountWillRegister
+    _NH_BonjourAccountRegistrationDidSucceed = _NH_SIPAccountRegistrationDidSucceed
+    _NH_BonjourAccountRegistrationDidFail = _NH_SIPAccountRegistrationDidFail
+    _NH_BonjourAccountRegistrationDidEnd = _NH_SIPAccountRegistrationDidEnd
+
+
+class ActiveAccountModel(QSortFilterProxyModel):
+    def __init__(self, model, parent=None):
+        super(ActiveAccountModel, self).__init__(parent)
+        self.setSourceModel(model)
+        self.setDynamicSortFilter(True)
+
+    def filterAcceptsRow(self, source_row, source_parent):
+        source_model = self.sourceModel()
+        source_index = source_model.index(source_row, 0, source_parent)
+        account_info = source_model.data(source_index, Qt.UserRole)
+        return account_info.account.enabled
+
+
+class AccountDelegate(QStyledItemDelegate):
+    def paint(self, painter, option, index):
+        account_info = index.data(Qt.UserRole).toPyObject()
+        if account_info.registration_state == 'succeeded':
+            option.palette.setColor(QPalette.Text, Qt.black)
+        else:
+            option.palette.setColor(QPalette.Text, Qt.gray)
+        super(AccountDelegate, self).paint(painter, option, index)
+
+
+class AccountSelector(QComboBox):
+    implements(IObserver)
+
+    def __init__(self, parent=None):
+        super(AccountSelector, self).__init__(parent)
+        self.currentIndexChanged[int].connect(self.selection_changed)
+        self.model().dataChanged.connect(self.data_changed)
+        self.view().setItemDelegate(AccountDelegate(self.view()))
+
+        notification_center = NotificationCenter()
+        notification_center.add_observer(self, name="SIPAccountManagerDidChangeDefaultAccount")
+        notification_center.add_observer(self, name="SIPAccountManagerDidStart")
+
+    def setModel(self, model):
+        self.model().dataChanged.disconnect(self.data_changed)
+        model.dataChanged.connect(self.data_changed)
+        super(AccountSelector, self).setModel(model)
+
+    def data_changed(self, topLeft, bottomRight):
+        index = self.currentIndex()
+        if topLeft.row() <= index <= bottomRight.row():
+            account_info = self.itemData(index).toPyObject()
+            palette = self.palette()
+            if account_info.registration_state == 'succeeded':
+                palette.setColor(QPalette.Text, Qt.black)
+            else:
+                palette.setColor(QPalette.Text, Qt.gray)
+            self.setPalette(palette)
+
+    def selection_changed(self, index):
+        account_info = self.itemData(index).toPyObject()
+        palette = self.palette()
+        if account_info.registration_state == 'succeeded':
+            palette.setColor(QPalette.Text, Qt.black)
+        else:
+            palette.setColor(QPalette.Text, Qt.gray)
+        self.setPalette(palette)
+
+    @run_in_gui_thread
+    def handle_notification(self, notification):
+        handler = getattr(self, '_NH_%s' % notification.name, Null)
+        handler(notification)
+
+    def _NH_SIPAccountManagerDidStart(self, notification):
+        account = AccountManager().default_account
+        if account is not None:
+            model = self.model()
+            source_model = model.sourceModel()
+            account_index = source_model.accounts.index(account)
+            self.setCurrentIndex(model.mapFromSource(source_model.index(account_index)).row())
+
+    def _NH_SIPAccountManagerDidChangeDefaultAccount(self, notification):
+        account = notification.data.account
+        if account is not None:
+            model = self.model()
+            source_model = model.sourceModel()
+            account_index = source_model.accounts.index(account)
+            self.setCurrentIndex(model.mapFromSource(source_model.index(account_index)).row())
diff --git a/blink/mainwindow.py b/blink/mainwindow.py
index 00b37d2..cf65ab0 100644
--- a/blink/mainwindow.py
+++ b/blink/mainwindow.py
@@ -6,25 +6,19 @@ from __future__ import with_statement
 __all__ = ['MainWindow']
 
 from PyQt4 import uic
-from PyQt4.QtCore import Qt, QVariant
-from PyQt4.QtGui  import QBrush, QColor, QIcon, QPainter, QPen, QPixmap
+from PyQt4.QtCore import Qt
+from PyQt4.QtGui  import QBrush, QColor, QPainter, QPen, QPixmap
 
-from application.notification import IObserver, NotificationCenter
-from application.python.util import Null
-from zope.interface import implements
-
-from sipsimple.account import AccountManager, BonjourAccount
+from sipsimple.account import AccountManager
 
+from blink.accounts import AccountModel, ActiveAccountModel
 from blink.contacts import Contact, ContactGroup, ContactEditorDialog, ContactModel, ContactSearchModel
 from blink.resources import Resources
-from blink.util import run_in_gui_thread
 
 
 ui_class, base_class = uic.loadUiType(Resources.get('blink.ui'))
 
 class MainWindow(base_class, ui_class):
-    implements(IObserver)
-
     def __init__(self, parent=None):
         super(MainWindow, self).__init__(parent)
 
@@ -37,6 +31,10 @@ class MainWindow(base_class, ui_class):
         self.set_user_icon(Resources.get("icons/default-avatar.png")) # ":/resources/icons/default-avatar.png"
         self.enable_call_buttons(False)
 
+        self.account_model = AccountModel(self)
+        self.enabled_account_model = ActiveAccountModel(self.account_model, self)
+        self.identity.setModel(self.enabled_account_model)
+
         self.contact_model = ContactModel(self)
         self.contact_search_model = ContactSearchModel(self.contact_model, self)
         self.contact_list.setModel(self.contact_model)
@@ -73,12 +71,6 @@ class MainWindow(base_class, ui_class):
 
         #self.connect(self.contact_list, QtCore.SIGNAL("doubleClicked(const QModelIndex &)"), self.double_click_action)
 
-        notification_center = NotificationCenter()
-        notification_center.add_observer(self, name="SIPAccountManagerDidChangeDefaultAccount")
-        notification_center.add_observer(self, name="SIPAccountManagerDidStart")
-        notification_center.add_observer(self, name="SIPAccountDidActivate")
-        notification_center.add_observer(self, name="SIPAccountDidDeactivate")
-
     def add_contact(self, clicked):
         model = self.contact_model
         selected_items = ((index.row(), model.data(index)) for index in self.contact_list.selectionModel().selectedIndexes())
@@ -116,7 +108,7 @@ class MainWindow(base_class, ui_class):
 
     def set_identity(self, index):
         account_manager = AccountManager()
-        account_manager.default_account = self.identity.itemData(index).toPyObject()
+        account_manager.default_account = self.identity.itemData(index).toPyObject().account
 
     def search_box_text_changed(self, text):
         if text:
@@ -156,40 +148,6 @@ class MainWindow(base_class, ui_class):
         self.main_view.setCurrentWidget(widget)
         self.switch_view_button.setText(widget.sibling_name)
 
-    @run_in_gui_thread
-    def handle_notification(self, notification):
-        handler = getattr(self, '_NH_%s' % notification.name, Null)
-        handler(notification)
-
-    def _NH_SIPAccountDidActivate(self, notification):
-        account = notification.sender
-        name = u'Bonjour' if account is BonjourAccount() else account.id
-        icon = None
-        if account is BonjourAccount():
-            pixmap = QPixmap()
-            if pixmap.load(Resources.get('icons/bonjour.png')):
-                pixmap = pixmap.scaled(16, 16, Qt.KeepAspectRatio, Qt.SmoothTransformation)
-                icon = QIcon(pixmap)
-        if icon is not None:
-            self.identity.addItem(icon, name, QVariant(account))
-        else:
-            self.identity.addItem(name, QVariant(account))
-
-    def _NH_SIPAccountDidDeactivate(self, notification):
-        account = notification.sender
-        name = u'Bonjour' if account is BonjourAccount() else account.id
-        self.identity.removeItem(self.identity.findText(name))
-
-    def _NH_SIPAccountManagerDidStart(self, notification):
-        account = AccountManager().default_account
-        name = u'Bonjour' if account is BonjourAccount() else account.id
-        self.identity.setCurrentIndex(self.identity.findText(name))
-
-    def _NH_SIPAccountManagerDidChangeDefaultAccount(self, notification):
-        account = notification.data.account
-        name = u'Bonjour' if account is BonjourAccount() else account.id
-        self.identity.setCurrentIndex(self.identity.findText(name))
-
 del ui_class, base_class
 
 
diff --git a/resources/blink.ui b/resources/blink.ui
index eaf3a6b..5c4ebe1 100644
--- a/resources/blink.ui
+++ b/resources/blink.ui
@@ -41,7 +41,7 @@
      <number>1</number>
     </property>
     <item>
-     <widget class="QComboBox" name="identity">
+     <widget class="AccountSelector" name="identity">
       <property name="maximumSize">
        <size>
         <width>16777215</width>
@@ -825,6 +825,11 @@ buttons below.</string>
    <extends>QListView</extends>
    <header>blink.contacts</header>
   </customwidget>
+  <customwidget>
+   <class>AccountSelector</class>
+   <extends>QComboBox</extends>
+   <header>blink.accounts</header>
+  </customwidget>
  </customwidgets>
  <tabstops>
   <tabstop>search_box</tabstop>
-- 
2.21.0