# -*- coding: utf-8 -*- # Copyright(C) 2010-2011 Romain Bignon # # This file is part of weboob. # # weboob is free software: you can redistribute it and/or modify # it under the terms of the GNU Affero General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # weboob 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 Affero General Public License for more details. # # You should have received a copy of the GNU Affero General Public License # along with weboob. If not, see . import sys import logging import re from threading import Event from copy import copy from PyQt4.QtCore import QTimer, SIGNAL, QObject, QString, QSize, QVariant, QMutex from PyQt4.QtGui import QMainWindow, QApplication, QStyledItemDelegate, \ QStyleOptionViewItemV4, QTextDocument, QStyle, \ QAbstractTextDocumentLayout, QPalette, QMessageBox, \ QSpinBox, QLineEdit, QComboBox, QCheckBox, QInputDialog from weboob.core.ouiboube import Weboob from weboob.core.scheduler import IScheduler from weboob.tools.browser import BrowserUnavailable, BrowserIncorrectPassword, BrowserForbidden from weboob.tools.value import ValueInt, ValueBool, ValueBackendPassword from weboob.tools.misc import to_unicode from weboob.capabilities import UserError from ..base import BaseApplication __all__ = ['QtApplication', 'QtMainWindow', 'QtDo', 'HTMLDelegate'] class QtScheduler(IScheduler): def __init__(self, app): self.app = app self.count = 0 self.timers = {} def schedule(self, interval, function, *args): timer = QTimer() timer.setInterval(interval * 1000) timer.setSingleShot(True) count = self.count self.count += 1 timer.start() self.app.connect(timer, SIGNAL("timeout()"), lambda: self.timeout(count, None, function, *args)) self.timers[count] = timer def repeat(self, interval, function, *args): timer = QTimer() timer.setSingleShot(False) count = self.count self.count += 1 timer.start(0) self.app.connect(timer, SIGNAL("timeout()"), lambda: self.timeout(count, interval, function, *args)) self.timers[count] = timer def timeout(self, _id, interval, function, *args): function(*args) if interval is None: self.timers.pop(_id) else: self.timers[_id].setInterval(interval * 1000) def want_stop(self): self.app.quit() def run(self): self.app.exec_() class QCallbacksManager(QObject): class Request(object): def __init__(self): self.event = Event() self.answer = None def __call__(self): raise NotImplementedError() class LoginRequest(Request): def __init__(self, backend_name, value): QCallbacksManager.Request.__init__(self) self.backend_name = backend_name self.value = value def __call__(self): password, ok = QInputDialog.getText(None, '%s request' % self.value.label, 'Please enter %s for %s' % (self.value.label, self.backend_name), QLineEdit.Password) return password def __init__(self, weboob, parent=None): QObject.__init__(self, parent) self.weboob = weboob self.weboob.callbacks['login'] = self.callback(self.LoginRequest) self.mutex = QMutex() self.requests = [] self.connect(self, SIGNAL('new_request'), self.do_request) def callback(self, klass): def cb(*args, **kwargs): return self.add_request(klass(*args, **kwargs)) return cb def do_request(self): self.mutex.lock() request = self.requests.pop() request.answer = request() request.event.set() self.mutex.unlock() def add_request(self, request): self.mutex.lock() self.requests.append(request) self.mutex.unlock() self.emit(SIGNAL('new_request')) request.event.wait() return request.answer class QtApplication(QApplication, BaseApplication): def __init__(self): QApplication.__init__(self, sys.argv) self.setApplicationName(self.APPNAME) BaseApplication.__init__(self) self.cbmanager = QCallbacksManager(self.weboob, self) def create_weboob(self): return Weboob(scheduler=QtScheduler(self)) class QtMainWindow(QMainWindow): def __init__(self, parent=None): QMainWindow.__init__(self, parent) class QtDo(QObject): def __init__(self, weboob, cb, eb=None): QObject.__init__(self) if not eb: eb = self.default_eb self.weboob = weboob self.process = None self.cb = cb self.eb = eb self.connect(self, SIGNAL('cb'), self.local_cb) self.connect(self, SIGNAL('eb'), self.local_eb) def do(self, *args, **kwargs): self.process = self.weboob.do(*args, **kwargs) self.process.callback_thread(self.thread_cb, self.thread_eb) def default_eb(self, backend, error, backtrace): msg = unicode(error) if isinstance(error, BrowserIncorrectPassword): if not msg: msg = 'Invalid login/password.' elif isinstance(error, BrowserUnavailable): if not msg: msg = 'Website is unavailable.' elif isinstance(error, BrowserForbidden): if not msg: msg = 'This action is forbidden.' elif isinstance(error, NotImplementedError): msg = u'This feature is not supported by this backend.\n\n' \ u'To help the maintainer of this backend implement this feature, please contact: %s <%s>' % (backend.MAINTAINER, backend.EMAIL) elif isinstance(error, UserError): if not msg: msg = type(error).__name__ elif logging.root.level == logging.DEBUG: msg += u'
' ul_opened = False for line in backtrace.split('\n'): m = re.match(' File (.*)', line) if m: if not ul_opened: msg += u'' print >>sys.stderr, error print >>sys.stderr, backtrace QMessageBox.critical(None, unicode(self.tr('Error with backend %s')) % backend.name, msg, QMessageBox.Ok) def local_cb(self, backend, data): self.cb(backend, data) if not backend: self.disconnect(self, SIGNAL('cb'), self.local_cb) self.disconnect(self, SIGNAL('eb'), self.local_eb) self.process = None def local_eb(self, backend, error, backtrace): self.eb(backend, error, backtrace) self.disconnect(self, SIGNAL('cb'), self.local_cb) self.disconnect(self, SIGNAL('eb'), self.local_eb) self.process = None def thread_cb(self, backend, data): self.emit(SIGNAL('cb'), backend, data) def thread_eb(self, backend, error, backtrace): self.emit(SIGNAL('eb'), backend, error, backtrace) class HTMLDelegate(QStyledItemDelegate): def paint(self, painter, option, index): optionV4 = QStyleOptionViewItemV4(option) self.initStyleOption(optionV4, index) style = optionV4.widget.style() if optionV4.widget else QApplication.style() doc = QTextDocument() doc.setHtml(optionV4.text) # painting item without text optionV4.text = QString() style.drawControl(QStyle.CE_ItemViewItem, optionV4, painter) ctx = QAbstractTextDocumentLayout.PaintContext() # Hilight text if item is selected if optionV4.state & QStyle.State_Selected: ctx.palette.setColor(QPalette.Text, optionV4.palette.color(QPalette.Active, QPalette.HighlightedText)) textRect = style.subElementRect(QStyle.SE_ItemViewItemText, optionV4) painter.save() painter.translate(textRect.topLeft()) painter.setClipRect(textRect.translated(-textRect.topLeft())) doc.documentLayout().draw(painter, ctx) painter.restore() def sizeHint(self, option, index): optionV4 = QStyleOptionViewItemV4(option) self.initStyleOption(optionV4, index) doc = QTextDocument() doc.setHtml(optionV4.text) doc.setTextWidth(optionV4.rect.width()) return QSize(doc.idealWidth(), max(doc.size().height(), optionV4.decorationSize.height())) class _QtValueStr(QLineEdit): def __init__(self, value): QLineEdit.__init__(self) self._value = value if value.default: self.setText(unicode(value.default)) if value.masked: self.setEchoMode(self.Password) def set_value(self, value): self._value = value self.setText(self._value.get()) def get_value(self): self._value.set(unicode(self.text())) return self._value class _QtValueBackendPassword(_QtValueStr): def get_value(self): self._value._domain = None return _QtValueStr.get_value(self) class _QtValueBool(QCheckBox): def __init__(self, value): QCheckBox.__init__(self) self._value = value if value.default: self.setChecked(True) def set_value(self, value): self._value = value self.setChecked(self._value.get()) def get_value(self): self._value.set(self.isChecked()) return self._value class _QtValueInt(QSpinBox): def __init__(self, value): QSpinBox.__init__(self) self._value = value if value.default: self.setValue(int(value.default)) def set_value(self, value): self._value = value self.setValue(self._value.get()) def get_value(self): self._value.set(self.getValue()) return self._value class _QtValueChoices(QComboBox): def __init__(self, value): QComboBox.__init__(self) self._value = value for k, l in value.choices.iteritems(): self.addItem(l, QVariant(k)) if value.default == k: self.setCurrentIndex(self.count()-1) def set_value(self, value): self._value = value for i in xrange(self.count()): if unicode(self.itemData(i).toString()) == self._value.get(): self.setCurrentIndex(i) return def get_value(self): self._value.set(unicode(self.itemData(self.currentIndex()).toString())) return self._value def QtValue(value): if isinstance(value, ValueBool): klass = _QtValueBool elif isinstance(value, ValueInt): klass = _QtValueInt elif isinstance(value, ValueBackendPassword): klass = _QtValueBackendPassword elif value.choices is not None: klass = _QtValueChoices else: klass = _QtValueStr return klass(copy(value))