From 0473e3e4bebf0087863395d77abaad352a62c99f Mon Sep 17 00:00:00 2001 From: Julien Veyssier Date: Sat, 16 Mar 2013 02:49:14 +0100 Subject: [PATCH] new application qcookboob, in progress --- scripts/qcookboob | 27 + weboob/applications/qcookboob/__init__.py | 3 + weboob/applications/qcookboob/main_window.py | 199 ++++++++ weboob/applications/qcookboob/minirecipe.py | 66 +++ weboob/applications/qcookboob/qcookboob.py | 44 ++ weboob/applications/qcookboob/recipe.py | 81 +++ weboob/applications/qcookboob/ui/Makefile | 13 + weboob/applications/qcookboob/ui/__init__.py | 0 .../applications/qcookboob/ui/main_window.ui | 257 ++++++++++ .../applications/qcookboob/ui/minirecipe.ui | 135 +++++ weboob/applications/qcookboob/ui/recipe.ui | 478 ++++++++++++++++++ 11 files changed, 1303 insertions(+) create mode 100755 scripts/qcookboob create mode 100644 weboob/applications/qcookboob/__init__.py create mode 100644 weboob/applications/qcookboob/main_window.py create mode 100644 weboob/applications/qcookboob/minirecipe.py create mode 100644 weboob/applications/qcookboob/qcookboob.py create mode 100644 weboob/applications/qcookboob/recipe.py create mode 100644 weboob/applications/qcookboob/ui/Makefile create mode 100644 weboob/applications/qcookboob/ui/__init__.py create mode 100644 weboob/applications/qcookboob/ui/main_window.ui create mode 100644 weboob/applications/qcookboob/ui/minirecipe.ui create mode 100644 weboob/applications/qcookboob/ui/recipe.ui diff --git a/scripts/qcookboob b/scripts/qcookboob new file mode 100755 index 0000000000..617f9d09f0 --- /dev/null +++ b/scripts/qcookboob @@ -0,0 +1,27 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- +# vim: ft=python et softtabstop=4 cinoptions=4 shiftwidth=4 ts=4 ai + +# Copyright(C) 2013 Julien Veyssier +# +# 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 . + + +from weboob.applications.qcookboob import QCookboob + + +if __name__ == '__main__': + QCookboob.run() diff --git a/weboob/applications/qcookboob/__init__.py b/weboob/applications/qcookboob/__init__.py new file mode 100644 index 0000000000..18938a1b83 --- /dev/null +++ b/weboob/applications/qcookboob/__init__.py @@ -0,0 +1,3 @@ +from .qcookboob import QCookboob + +__all__ = ['QCookboob'] diff --git a/weboob/applications/qcookboob/main_window.py b/weboob/applications/qcookboob/main_window.py new file mode 100644 index 0000000000..7b8832b8b6 --- /dev/null +++ b/weboob/applications/qcookboob/main_window.py @@ -0,0 +1,199 @@ +# -*- coding: utf-8 -*- + +# Copyright(C) 2013 Julien Veyssier +# +# 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 os +import codecs + +from PyQt4.QtCore import SIGNAL, Qt, QStringList +from PyQt4.QtGui import QApplication, QCompleter + +from weboob.capabilities.recipe import ICapRecipe +from weboob.tools.application.qt import QtMainWindow, QtDo +from weboob.tools.application.qt.backendcfg import BackendCfg + +from weboob.applications.qcookboob.ui.main_window_ui import Ui_MainWindow + +from .minirecipe import MiniRecipe +from .recipe import Recipe + + +class MainWindow(QtMainWindow): + def __init__(self, config, weboob, parent=None): + QtMainWindow.__init__(self, parent) + self.ui = Ui_MainWindow() + self.ui.setupUi(self) + + self.config = config + self.weboob = weboob + self.minis = [] + self.current_info_widget = None + + # search history is a list of patterns which have been searched + self.search_history = self.loadSearchHistory() + self.updateCompletion() + + # action history is composed by the last action and the action list + # An action is a function, a list of arguments and a description string + self.action_history = {'last_action': None, 'action_list': []} + self.connect(self.ui.backButton, SIGNAL("clicked()"), self.doBack) + self.ui.backButton.hide() + + self.connect(self.ui.searchEdit, SIGNAL("returnPressed()"), self.search) + + self.connect(self.ui.actionBackends, SIGNAL("triggered()"), self.backendsConfig) + self.connect(self.ui.actionQuit, SIGNAL("triggered()"), self.close) + + self.loadBackendsList() + + if self.ui.backendEdit.count() == 0: + self.backendsConfig() + + def backendsConfig(self): + bckndcfg = BackendCfg(self.weboob, (ICapRecipe, ), self) + if bckndcfg.run(): + self.loadBackendsList() + + def loadBackendsList(self): + self.ui.backendEdit.clear() + for i, backend in enumerate(self.weboob.iter_backends()): + if i == 0: + self.ui.backendEdit.addItem('All backends', '') + self.ui.backendEdit.addItem(backend.name, backend.name) + if backend.name == self.config.get('settings', 'backend'): + self.ui.backendEdit.setCurrentIndex(i+1) + + if self.ui.backendEdit.count() == 0: + self.ui.searchEdit.setEnabled(False) + else: + self.ui.searchEdit.setEnabled(True) + + def loadSearchHistory(self): + ''' Return search string history list loaded from history file + ''' + result = [] + history_path = os.path.join(self.weboob.workdir, 'qcookboob_history') + if os.path.exists(history_path): + f = codecs.open(history_path, 'r', 'utf-8') + conf_hist = f.read() + f.close() + if conf_hist is not None and conf_hist.strip() != '': + result = conf_hist.strip().split('\n') + return result + + def saveSearchHistory(self): + ''' Save search history in history file + ''' + if len(self.search_history) > 0: + history_path = os.path.join(self.weboob.workdir, 'qcookboob_history') + f = codecs.open(history_path, 'w', 'utf-8') + f.write('\n'.join(self.search_history)) + f.close() + + def updateCompletion(self): + qc = QCompleter(QStringList(self.search_history), self) + qc.setCaseSensitivity(Qt.CaseInsensitive) + self.ui.searchEdit.setCompleter(qc) + + def doAction(self, description, fun, args): + ''' Call fun with args as arguments + and save it in the action history + ''' + self.ui.currentActionLabel.setText(description) + if self.action_history['last_action'] is not None: + self.action_history['action_list'].append(self.action_history['last_action']) + self.ui.backButton.setToolTip(self.action_history['last_action']['description']) + self.ui.backButton.show() + self.action_history['last_action'] = {'function': fun, 'args': args, 'description': description} + return fun(*args) + + def doBack(self): + ''' Go back in action history + Basically call previous function and update history + ''' + if len(self.action_history['action_list']) > 0: + todo = self.action_history['action_list'].pop() + self.ui.currentActionLabel.setText(todo['description']) + self.action_history['last_action'] = todo + if len(self.action_history['action_list']) == 0: + self.ui.backButton.hide() + else: + self.ui.backButton.setToolTip(self.action_history['action_list'][-1]['description']) + return todo['function'](*todo['args']) + + def search(self): + pattern = unicode(self.ui.searchEdit.text()) + # arbitrary max number of completion word + if len(self.search_history) > 50: + self.search_history.pop(0) + if pattern not in self.search_history: + self.search_history.append(pattern) + self.updateCompletion() + + self.searchRecipe() + + def searchRecipe(self): + pattern = unicode(self.ui.searchEdit.text()) + if not pattern: + return + self.doAction(u'Search recipe "%s"' % pattern, self.searchRecipeAction, [pattern]) + + def searchRecipeAction(self, pattern): + self.ui.stackedWidget.setCurrentWidget(self.ui.list_page) + for mini in self.minis: + self.ui.list_content.layout().removeWidget(mini) + mini.hide() + mini.deleteLater() + + self.minis = [] + self.ui.searchEdit.setEnabled(False) + QApplication.setOverrideCursor(Qt.WaitCursor) + + backend_name = str(self.ui.backendEdit.itemData(self.ui.backendEdit.currentIndex()).toString()) + + self.process = QtDo(self.weboob, self.addRecipe) + self.process.do('iter_recipes', pattern, backends=backend_name, caps=ICapRecipe) + + def addRecipe(self, backend, recipe): + if not backend: + self.ui.searchEdit.setEnabled(True) + QApplication.restoreOverrideCursor() + self.process = None + return + minirecipe = MiniRecipe(self.weboob, backend, recipe, self) + self.ui.list_content.layout().addWidget(minirecipe) + self.minis.append(minirecipe) + + def displayRecipe(self, recipe, backend): + self.ui.stackedWidget.setCurrentWidget(self.ui.info_page) + if self.current_info_widget is not None: + self.ui.info_content.layout().removeWidget(self.current_info_widget) + self.current_info_widget.hide() + self.current_info_widget.deleteLater() + wrecipe = Recipe(recipe, backend, self) + self.ui.info_content.layout().addWidget(wrecipe) + self.current_info_widget = wrecipe + QApplication.restoreOverrideCursor() + + def closeEvent(self, ev): + self.config.set('settings', 'backend', str(self.ui.backendEdit.itemData( + self.ui.backendEdit.currentIndex()).toString())) + self.saveSearchHistory() + + self.config.save() + ev.accept() diff --git a/weboob/applications/qcookboob/minirecipe.py b/weboob/applications/qcookboob/minirecipe.py new file mode 100644 index 0000000000..e53ed3c9a6 --- /dev/null +++ b/weboob/applications/qcookboob/minirecipe.py @@ -0,0 +1,66 @@ +# -*- coding: utf-8 -*- + +# Copyright(C) 2013 Julien Veyssier +# +# 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 urllib + +from PyQt4.QtGui import QFrame, QImage, QPixmap, QApplication +from PyQt4.QtCore import Qt + +from weboob.applications.qcookboob.ui.minirecipe_ui import Ui_MiniRecipe +from weboob.capabilities.base import empty + + +class MiniRecipe(QFrame): + def __init__(self, weboob, backend, recipe, parent=None): + QFrame.__init__(self, parent) + self.parent = parent + self.ui = Ui_MiniRecipe() + self.ui.setupUi(self) + + self.weboob = weboob + self.backend = backend + self.recipe = recipe + self.ui.titleLabel.setText(recipe.title) + self.ui.shortDescLabel.setText(recipe.short_description) + self.ui.backendLabel.setText(backend.name) + + self.gotThumbnail() + + def gotThumbnail(self): + if not empty(self.recipe.thumbnail_url): + data = urllib.urlopen(self.recipe.thumbnail_url).read() + img = QImage.fromData(data) + self.ui.imageLabel.setPixmap(QPixmap.fromImage(img)) + + def enterEvent(self, event): + self.setFrameShadow(self.Sunken) + QFrame.enterEvent(self, event) + + def leaveEvent(self, event): + self.setFrameShadow(self.Raised) + QFrame.leaveEvent(self, event) + + def mousePressEvent(self, event): + QFrame.mousePressEvent(self, event) + + QApplication.setOverrideCursor(Qt.WaitCursor) + recipe = self.backend.get_recipe(self.recipe.id) + if recipe: + self.parent.doAction('Details of recipe "%s"' % + recipe.title, self.parent.displayMovie, [recipe, self.backend]) diff --git a/weboob/applications/qcookboob/qcookboob.py b/weboob/applications/qcookboob/qcookboob.py new file mode 100644 index 0000000000..5cab168fbb --- /dev/null +++ b/weboob/applications/qcookboob/qcookboob.py @@ -0,0 +1,44 @@ +# -*- coding: utf-8 -*- + +# Copyright(C) 2013 Julien Veyssier +# +# 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 . + + +from weboob.capabilities.recipe import ICapRecipe +from weboob.tools.application.qt import QtApplication + +from .main_window import MainWindow + + +class QCookboob(QtApplication): + APPNAME = 'qcookboob' + VERSION = '0.f' + COPYRIGHT = 'Copyright(C) 2013 Julien Veyssier' + DESCRIPTION = "Qt application allowing to search recipes." + SHORT_DESCRIPTION = "search recipes" + CAPS = ICapRecipe + CONFIG = {'settings': {'backend': '', + } + } + + def main(self, argv): + self.load_backends([ICapRecipe]) + self.load_config() + + self.main_window = MainWindow(self.config, self.weboob) + self.main_window.show() + return self.weboob.loop() diff --git a/weboob/applications/qcookboob/recipe.py b/weboob/applications/qcookboob/recipe.py new file mode 100644 index 0000000000..4254e06164 --- /dev/null +++ b/weboob/applications/qcookboob/recipe.py @@ -0,0 +1,81 @@ +# -*- coding: utf-8 -*- + +# Copyright(C) 2013 Julien Veyssier +# +# 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 urllib + +from PyQt4.QtCore import Qt, SIGNAL +from PyQt4.QtGui import QFrame, QImage, QPixmap + +from weboob.applications.qcookboob.ui.recipe_ui import Ui_Recipe +from weboob.capabilities.base import empty + + +class Recipe(QFrame): + def __init__(self, recipe, backend, parent=None): + QFrame.__init__(self, parent) + self.parent = parent + self.ui = Ui_Recipe() + self.ui.setupUi(self) + langs = LANGUAGE_CONV.keys() + langs.sort() + for lang in langs: + self.ui.langCombo.addItem(lang) + + self.recipe = recipe + self.backend = backend + self.ui.titleLabel.setText(recipe.original_title) + self.ui.durationLabel.setText(unicode(recipe.duration)) + self.gotThumbnail() + + self.ui.idEdit.setText(u'%s@%s' % (recipe.id, backend.name)) + if not empty(recipe.other_titles): + self.ui.otherTitlesPlain.setPlainText('\n'.join(recipe.other_titles)) + else: + self.ui.otherTitlesPlain.parent().hide() + if not empty(recipe.release_date): + self.ui.releaseDateLabel.setText(recipe.release_date.strftime('%Y-%m-%d')) + else: + self.ui.releaseDateLabel.parent().hide() + if not empty(recipe.duration): + self.ui.durationLabel.setText('%s min' % recipe.duration) + else: + self.ui.durationLabel.parent().hide() + if not empty(recipe.pitch): + self.ui.pitchPlain.setPlainText('%s' % recipe.pitch) + else: + self.ui.pitchPlain.parent().hide() + if not empty(recipe.country): + self.ui.countryLabel.setText('%s' % recipe.country) + else: + self.ui.countryLabel.parent().hide() + if not empty(recipe.note): + self.ui.noteLabel.setText('%s' % recipe.note) + else: + self.ui.noteLabel.parent().hide() + for role in ROLE_LIST: + self.ui.castingCombo.addItem('%ss' % role) + + self.ui.verticalLayout.setAlignment(Qt.AlignTop) + self.ui.verticalLayout_2.setAlignment(Qt.AlignTop) + + def gotThumbnail(self): + if not empty(self.recipe.thumbnail_url): + data = urllib.urlopen(self.recipe.thumbnail_url).read() + img = QImage.fromData(data) + self.ui.imageLabel.setPixmap(QPixmap.fromImage(img)) diff --git a/weboob/applications/qcookboob/ui/Makefile b/weboob/applications/qcookboob/ui/Makefile new file mode 100644 index 0000000000..f0db5154cf --- /dev/null +++ b/weboob/applications/qcookboob/ui/Makefile @@ -0,0 +1,13 @@ +UI_FILES = $(wildcard *.ui) +UI_PY_FILES = $(UI_FILES:%.ui=%_ui.py) +PYUIC = pyuic4 + +all: $(UI_PY_FILES) + +%_ui.py: %.ui + $(PYUIC) -o $@ $^ + +clean: + rm -f *.pyc + rm -f $(UI_PY_FILES) + diff --git a/weboob/applications/qcookboob/ui/__init__.py b/weboob/applications/qcookboob/ui/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/weboob/applications/qcookboob/ui/main_window.ui b/weboob/applications/qcookboob/ui/main_window.ui new file mode 100644 index 0000000000..dcf213df07 --- /dev/null +++ b/weboob/applications/qcookboob/ui/main_window.ui @@ -0,0 +1,257 @@ + + + MainWindow + + + + 0 + 0 + 748 + 463 + + + + QCineoob + + + + + 4 + + + + + QFrame::StyledPanel + + + QFrame::Raised + + + + 0 + + + 0 + + + + + Search: + + + + + + + + + + + movie + + + + + person + + + + + torrent + + + + + subtitle + + + + + + + + + 8 + + + + language + + + + + + + + + + + + + + + + QFrame::NoFrame + + + QFrame::Raised + + + + 0 + + + 0 + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + 60 + 20 + + + + <<back + + + + + + + + 75 + true + + + + + + + Qt::AlignCenter + + + true + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + + + + + + + true + + + + + 0 + 0 + 708 + 292 + + + + + + + + + + + + + + true + + + + + 0 + 0 + 63 + 18 + + + + + + + + + + + + + + + + 0 + 0 + 748 + 23 + + + + + + + toolBar + + + TopToolBarArea + + + false + + + + + + + Backends + + + + + Quit + + + Ctrl+Q + + + + + + diff --git a/weboob/applications/qcookboob/ui/minirecipe.ui b/weboob/applications/qcookboob/ui/minirecipe.ui new file mode 100644 index 0000000000..b952ceae0f --- /dev/null +++ b/weboob/applications/qcookboob/ui/minirecipe.ui @@ -0,0 +1,135 @@ + + + MiniRecipe + + + + 0 + 0 + 464 + 136 + + + + Form + + + QFrame::StyledPanel + + + QFrame::Raised + + + + 9 + + + 5 + + + 5 + + + + + + 0 + 0 + + + + + + + + + + + QFormLayout::ExpandingFieldsGrow + + + + + + 75 + true + + + + Title + + + + + + + + 0 + 0 + + + + + 50 + true + false + + + + TextLabel + + + true + + + + + + + + 75 + true + + + + Short description: + + + + + + + TextLabel + + + true + + + + + + + + 75 + true + + + + Where + + + + + + + TextLabel + + + + + + + + + + diff --git a/weboob/applications/qcookboob/ui/recipe.ui b/weboob/applications/qcookboob/ui/recipe.ui new file mode 100644 index 0000000000..2891fd9689 --- /dev/null +++ b/weboob/applications/qcookboob/ui/recipe.ui @@ -0,0 +1,478 @@ + + + Recipe + + + + 0 + 0 + 645 + 600 + + + + + 0 + 0 + + + + + 2000 + 600 + + + + Frame + + + QFrame::StyledPanel + + + QFrame::Raised + + + + + + + 16777215 + 3000 + + + + QFrame::NoFrame + + + QFrame::Raised + + + + + + + + + + + + + QFrame::NoFrame + + + QFrame::Raised + + + + 0 + + + 0 + + + + + view casting + + + + + + + + all + + + + + + + + + + + QFrame::NoFrame + + + QFrame::Raised + + + + 0 + + + 0 + + + + + search torrents + + + + + + + + + + QFrame::NoFrame + + + QFrame::Raised + + + + 0 + + + 0 + + + + + search subtitles + + + + + + + + + + + + + + + + + 16777215 + 600 + + + + QFrame::NoFrame + + + QFrame::Raised + + + + + + + 16777215 + 40 + + + + QFrame::StyledPanel + + + QFrame::Raised + + + + 3 + + + 3 + + + + + Id: + + + + + + + Qt::AlignCenter + + + true + + + + + + + + + + + 16777215 + 35 + + + + QFrame::StyledPanel + + + QFrame::Raised + + + + + + Title: + + + + + + + + + + + + + + + + + + 0 + 0 + + + + + 16777215 + 200 + + + + QFrame::StyledPanel + + + QFrame::Raised + + + + + + + 100 + 16777215 + + + + Other titles: + + + + + + + Qt::TextSelectableByKeyboard|Qt::TextSelectableByMouse + + + + + + + + + + + 16777215 + 35 + + + + QFrame::StyledPanel + + + QFrame::Raised + + + + + + Duration: + + + + + + + + + + + + + + + + + + 16777215 + 35 + + + + QFrame::StyledPanel + + + QFrame::Raised + + + + + + Release date: + + + + + + + + + + + + + + + + + + 16777215 + 200 + + + + QFrame::StyledPanel + + + QFrame::Raised + + + + + + Pitch: + + + + + + + Qt::TextSelectableByKeyboard|Qt::TextSelectableByMouse + + + + + + + + + + + 16777215 + 35 + + + + QFrame::StyledPanel + + + QFrame::Raised + + + + + + Country: + + + + + + + + + + + + + + + + + + 16777215 + 35 + + + + QFrame::StyledPanel + + + QFrame::Raised + + + + + + Note: + + + + + + + + + + + + + + + + + + 16777215 + 200 + + + + QFrame::StyledPanel + + + QFrame::Raised + + + + + + All release dates: + + + + + + + Qt::TextSelectableByKeyboard|Qt::TextSelectableByMouse + + + + + + + + + + + + + + -- GitLab