# -*- coding: utf-8 -*- # Copyright(C) 2010 Romain Bignon # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, version 3 of the License. # # This program 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 General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software # Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. import time import logging from PyQt4.QtGui import QWidget, QListWidgetItem, QImage, QIcon, QPixmap, \ QFrame, QMessageBox, QTabWidget, QVBoxLayout, \ QFormLayout, QLabel, QPushButton from PyQt4.QtCore import SIGNAL, Qt from weboob.tools.application.qt import QtDo, HTMLDelegate from weboob.capabilities.contact import ICapContact, Contact from weboob.capabilities.chat import ICapChat from weboob.capabilities.messages import ICapMessages, ICapMessagesPost, Message from weboob.capabilities.base import NotLoaded from .ui.contacts_ui import Ui_Contacts from .ui.contact_thread_ui import Ui_ContactThread from .ui.thread_message_ui import Ui_ThreadMessage from .ui.profile_ui import Ui_Profile class ThreadMessage(QFrame): """ This class represents a message in the thread tab. """ def __init__(self, message, parent=None): QFrame.__init__(self, parent) self.ui = Ui_ThreadMessage() self.ui.setupUi(self) self.set_message(message) def set_message(self, message): self.message = message self.ui.nameLabel.setText(message.sender) header = time.strftime('%Y-%m-%d %H:%M:%S', message.date.timetuple()) if message.flags & message.IS_NOT_ACCUSED: header += u' — Unread' elif message.flags & message.IS_ACCUSED: header += u' — Read' self.ui.headerLabel.setText(header) if message.flags & message.IS_HTML: content = message.content else: content = message.content.replace('&', '&').replace('<', '<').replace('>', '>').replace('\n', '
') self.ui.contentLabel.setText(content) def __eq__(self, m): return self.message == m.message class ContactThread(QWidget): """ The thread of the selected contact. """ def __init__(self, weboob, contact, support_reply, parent=None): QWidget.__init__(self, parent) self.ui = Ui_ContactThread() self.ui.setupUi(self) self.weboob = weboob self.contact = contact self.thread = None self.messages = [] self.process_msg = None self.connect(self.ui.refreshButton, SIGNAL('clicked()'), self.refreshMessages) if support_reply: self.connect(self.ui.sendButton, SIGNAL('clicked()'), self.postReply) else: self.ui.frame.hide() self.refreshMessages() def refreshMessages(self, fillobj=False): if self.process_msg: return self.process_msg = QtDo(self.weboob, self.gotThread, self.gotError) if fillobj and self.thread: self.process_msg.do('fillobj', self.thread, ['root'], backends=self.contact.backend) else: self.process_msg.do('get_thread', self.contact.id, backends=self.contact.backend) def gotError(self, backend, error, backtrace): self.ui.textEdit.setEnabled(False) self.ui.sendButton.setEnabled(False) def gotThread(self, backend, thread): if not thread: #v = self.ui.scrollArea.verticalScrollBar() #print v.minimum(), v.value(), v.maximum(), v.sliderPosition() #self.ui.scrollArea.verticalScrollBar().setValue(self.ui.scrollArea.verticalScrollBar().maximum()) self.process_msg = None return self.ui.textEdit.setEnabled(True) self.ui.sendButton.setEnabled(True) self.thread = thread if thread.root is NotLoaded: self._insert_load_button(0) else: for message in thread.iter_all_messages(): self._insert_message(message) def _insert_message(self, message): widget = ThreadMessage(message) if widget in self.messages: old_widget = self.messages[self.messages.index(widget)] if old_widget.message.flags != widget.message.flags: old_widget.set_message(widget.message) return for i, m in enumerate(self.messages): if widget.message.date > m.message.date: self.ui.scrollAreaContent.layout().insertWidget(i, widget) self.messages.insert(i, widget) if message.parent is NotLoaded: self._insert_load_button(i) return self.ui.scrollAreaContent.layout().addWidget(widget) self.messages.append(widget) if message.parent is NotLoaded: self._insert_load_button(-1) def _insert_load_button(self, pos): button = QPushButton(self.tr('More messages...')) self.connect(button, SIGNAL('clicked()'), lambda: self._load_button_pressed(button)) if pos >= 0: self.ui.scrollAreaContent.layout().insertWidget(pos, button) else: self.ui.scrollAreaContent.layout().addWidget(button) def _load_button_pressed(self, button): self.ui.scrollAreaContent.layout().removeWidget(button) button.hide() button.deleteLater() self.refreshMessages(fillobj=True) def postReply(self): text = unicode(self.ui.textEdit.toPlainText()) self.ui.textEdit.setEnabled(False) self.ui.sendButton.setEnabled(False) m = Message(thread=self.thread, id=0, title=u'', sender=None, receiver=None, content=text, parent=self.messages[0].message if len(self.messages) > 0 else None) self.process_reply = QtDo(self.weboob, self._postReply_cb, self._postReply_eb) self.process_reply.do('post_message', m, backends=self.contact.backend) def _postReply_cb(self, backend, ignored): if not backend: return self.ui.textEdit.clear() self.ui.textEdit.setEnabled(True) self.ui.sendButton.setEnabled(True) self.refreshMessages() self.process_reply = None def _postReply_eb(self, backend, error, backtrace): content = unicode(self.tr('Unable to send message:\n%s\n')) % error if logging.root.level == logging.DEBUG: content += '\n%s\n' % backtrace QMessageBox.critical(self, self.tr('Error while posting reply'), content, QMessageBox.Ok) self.process_reply = None class ContactProfile(QWidget): def __init__(self, weboob, contact, parent=None): QWidget.__init__(self, parent) self.ui = Ui_Profile() self.ui.setupUi(self) self.weboob = weboob self.contact = contact self.loaded_profile = False missing_fields = self.gotProfile(self.weboob.get_backend(contact.backend), contact) if len(missing_fields) > 0: self.process_contact = QtDo(self.weboob, self.gotProfile, self.gotError) self.process_contact.do('fillobj', self.contact, missing_fields, backends=self.contact.backend) def gotError(self, backend, error, backtrace): #self.process_contact.default_eb(backend, error, backtrace) self.ui.frame_photo.hide() self.ui.descriptionEdit.setText('

Unable to show profile

%s

' % error) def gotProfile(self, backend, contact): if not backend: return [] missing_fields = set(['photos']) first = True for photo in contact.photos.itervalues(): photo = contact.photos.values()[0] if photo.data: data = photo.data try: missing_fields.remove('photos') except KeyError: pass elif photo.thumbnail_data: data = photo.thumbnail_data else: continue if first: img = QImage.fromData(data) self.ui.photoLabel.setPixmap(QPixmap.fromImage(img)) first = False else: # TODO display thumbnails pass self.ui.nicknameLabel.setText('

%s

' % contact.name) self.ui.statusLabel.setText('%s' % contact.status_msg) if contact.summary is NotLoaded: self.ui.descriptionEdit.setText('

Description

Receiving...

') missing_fields.add('summary') else: self.ui.descriptionEdit.setText('

Description

%s

' % contact.summary.replace('\n', '
')) if not contact.profile: missing_fields.add('profile') elif not self.loaded_profile: self.loaded_profile = True for head in contact.profile: if head.flags & head.HEAD: widget = self.ui.headFrame else: widget = self.ui.profileTab self.process_node(head, widget) return missing_fields def process_node(self, node, widget): # Set the value widget value = None if node.flags & node.SECTION: value = QWidget() value.setLayout(QFormLayout()) for sub in node.value: self.process_node(sub, value) elif isinstance(node.value, list): value = QLabel('
'.join([unicode(s) for s in node.value])) value.setWordWrap(True) elif isinstance(node.value, tuple): value = QLabel(', '.join([unicode(s) for s in node.value])) value.setWordWrap(True) elif isinstance(node.value, (basestring,int,long,float)): value = QLabel(unicode(node.value)) else: logging.warning('Not supported value: %r' % node.value) return # Insert the value widget into the parent widget, depending # of its type. if isinstance(widget, QTabWidget): widget.addTab(value, node.label) elif isinstance(widget.layout(), QFormLayout): label = QLabel(u'%s: ' % node.label) widget.layout().addRow(label, value) elif isinstance(widget.layout(), QVBoxLayout): widget.layout().addWidget(value) else: logging.warning('Not supported widget: %r' % widget) class IGroup(object): def __init__(self, weboob, id, name): self.id = id self.name = name self.weboob = weboob def iter_contacts(self, cb): raise NotImplementedError() class MetaGroup(IGroup): def iter_contacts(self, cb): if self.id == 'online': status = Contact.STATUS_ONLINE|Contact.STATUS_AWAY elif self.id == 'offline': status = Contact.STATUS_OFFLINE else: status = Contact.STATUS_ALL self.process = QtDo(self.weboob, lambda b, d: self.cb(cb, b, d)) self.process.do('iter_contacts', status, caps=ICapContact) def cb(self, cb, backend, contact): if contact: cb(contact) elif not backend: self.process = None cb(None) class ContactsWidget(QWidget): def __init__(self, weboob, parent=None): QWidget.__init__(self, parent) self.ui = Ui_Contacts() self.ui.setupUi(self) self.weboob = weboob self.contact = None self.ui.contactList.setItemDelegate(HTMLDelegate()) self.url_process = None self.photo_processes = {} self.ui.groupBox.addItem('All', MetaGroup(self.weboob, 'all', self.tr('All'))) self.ui.groupBox.addItem('Onlines', MetaGroup(self.weboob, 'online', self.tr('Online'))) self.ui.groupBox.addItem('Offlines', MetaGroup(self.weboob, 'offline', self.tr('Offline'))) self.ui.groupBox.setCurrentIndex(1) self.connect(self.ui.groupBox, SIGNAL('currentIndexChanged(int)'), self.groupChanged) self.connect(self.ui.contactList, SIGNAL('itemClicked(QListWidgetItem*)'), self.contactChanged) self.connect(self.ui.refreshButton, SIGNAL('clicked()'), self.refreshContactList) self.connect(self.ui.urlButton, SIGNAL('clicked()'), self.urlClicked) def load(self): self.refreshContactList() self.ui.backendsList.clear() for backend in self.weboob.iter_backends(): self.ui.backendsList.addItem(backend.name) def groupChanged(self, i): self.refreshContactList() def refreshContactList(self): self.ui.contactList.clear() i = self.ui.groupBox.currentIndex() group = self.ui.groupBox.itemData(i).toPyObject() group.iter_contacts(self.addContact) def setPhoto(self, contact, item): if not contact: return self.photo_processes.pop(contact.id, None) img = None for photo in contact.photos.itervalues(): if photo.thumbnail_data: img = QImage.fromData(photo.thumbnail_data) break if img: item.setIcon(QIcon(QPixmap.fromImage(img))) def addContact(self, contact): if not contact: return status = '' if contact.status == Contact.STATUS_ONLINE: status = u'Online' status_color = 0x00aa00 elif contact.status == Contact.STATUS_OFFLINE: status = u'Offline' status_color = 0xff0000 elif contact.status == Contact.STATUS_AWAY: status = u'Away' status_color = 0xffad16 else: status = u'Unknown' status_color = 0xaaaaaa if contact.status_msg: status += u' — %s' % contact.status_msg item = QListWidgetItem() item.setText('

%s

%s
%s' % (contact.name, status_color, status, contact.backend)) item.setData(Qt.UserRole, contact) process = QtDo(self.weboob, lambda b, c: self.setPhoto(c, item)) process.do('fillobj', contact, ['photos'], backends=contact.backend) self.photo_processes[contact.id] = process for i in xrange(self.ui.contactList.count()): if self.ui.contactList.item(i).data(Qt.UserRole).toPyObject().status > contact.status: self.ui.contactList.insertItem(i, item) return self.ui.contactList.addItem(item) def contactChanged(self, current): if not current: return contact = current.data(Qt.UserRole).toPyObject() self.setContact(contact) def setContact(self, contact): if not contact or contact == self.contact: return self.ui.tabWidget.clear() self.contact = contact backend = self.weboob.get_backend(self.contact.backend) self.ui.tabWidget.addTab(ContactProfile(self.weboob, self.contact), self.tr('Profile')) if backend.has_caps(ICapMessages): self.ui.tabWidget.addTab(ContactThread(self.weboob, self.contact, backend.has_caps(ICapMessagesPost)), self.tr('Messages')) if backend.has_caps(ICapChat): self.ui.tabWidget.addTab(QWidget(), self.tr('Chat')) self.ui.tabWidget.addTab(QWidget(), self.tr('Calendar')) self.ui.tabWidget.addTab(QWidget(), self.tr('Notes')) def urlClicked(self): url = unicode(self.ui.urlEdit.text()) if not url: return backend_name = unicode(self.ui.backendsList.currentText()) self.ui.urlButton.setEnabled(False) self.url_process = QtDo(self.weboob, self.urlClicked_cb, self.urlClicked_eb) self.url_process.do('get_contact', url, backends=backend_name) def urlClicked_cb(self, backend, contact): if not backend: self.url_process = None self.ui.urlButton.setEnabled(True) return self.ui.urlEdit.clear() self.setContact(contact) def urlClicked_eb(self, backend, error, backtrace): content = unicode(self.tr('Unable to send message:\n%s\n')) % error if logging.root.level == logging.DEBUG: content += '\n%s\n' % backtrace QMessageBox.critical(self, self.tr('Error while posting reply'), content, QMessageBox.Ok)