mirror of
https://github.com/kovidgoyal/calibre.git
synced 2025-07-09 03:04:10 -04:00
Initial import of get books store chooser dialog
This commit is contained in:
commit
8ed4ca3469
@ -1163,7 +1163,7 @@ class StoreBNStore(StoreBase):
|
|||||||
class StoreBeamEBooksDEStore(StoreBase):
|
class StoreBeamEBooksDEStore(StoreBase):
|
||||||
name = 'Beam EBooks DE'
|
name = 'Beam EBooks DE'
|
||||||
author = 'Charles Haley'
|
author = 'Charles Haley'
|
||||||
description = u'Der eBook Shop.'
|
description = u'Bei uns finden Sie: Tausende deutschsprachige eBooks; Alle eBooks ohne hartes DRM; PDF, ePub und Mobipocket Format; Sofortige Verfügbarkeit - 24 Stunden am Tag; Günstige Preise; eBooks für viele Lesegeräte, PC,Mac und Smartphones; Viele Gratis eBooks'
|
||||||
actual_plugin = 'calibre.gui2.store.beam_ebooks_de_plugin:BeamEBooksDEStore'
|
actual_plugin = 'calibre.gui2.store.beam_ebooks_de_plugin:BeamEBooksDEStore'
|
||||||
|
|
||||||
drm_free_only = True
|
drm_free_only = True
|
||||||
@ -1200,7 +1200,7 @@ class StoreEbookscomStore(StoreBase):
|
|||||||
class StoreEPubBuyDEStore(StoreBase):
|
class StoreEPubBuyDEStore(StoreBase):
|
||||||
name = 'EPUBBuy DE'
|
name = 'EPUBBuy DE'
|
||||||
author = 'Charles Haley'
|
author = 'Charles Haley'
|
||||||
description = u'Deutsch epub-Spezialisten.'
|
description = u'Bei EPUBBuy.com finden Sie ausschliesslich eBooks im weitverbreiteten EPUB-Format und ohne DRM. So haben Sie die freie Wahl, wo Sie Ihr eBook lesen: Tablet, eBook-Reader, Smartphone oder einfach auf Ihrem PC. So macht eBook-Lesen Spaß!'
|
||||||
actual_plugin = 'calibre.gui2.store.epubbuy_de_plugin:EPubBuyDEStore'
|
actual_plugin = 'calibre.gui2.store.epubbuy_de_plugin:EPubBuyDEStore'
|
||||||
|
|
||||||
drm_free_only = True
|
drm_free_only = True
|
||||||
@ -1272,6 +1272,16 @@ class StoreKoboStore(StoreBase):
|
|||||||
headquarters = 'CA'
|
headquarters = 'CA'
|
||||||
formats = ['EPUB']
|
formats = ['EPUB']
|
||||||
|
|
||||||
|
class StoreLegimiStore(StoreBase):
|
||||||
|
name = 'Legimi'
|
||||||
|
author = u'Tomasz Długosz'
|
||||||
|
description = u'Tanie oraz darmowe ebooki, egazety i blogi w formacie EPUB, wprost na Twój e-czytnik, iPhone, iPad, Android i komputer'
|
||||||
|
actual_plugin = 'calibre.gui2.store.legimi_plugin:LegimiStore'
|
||||||
|
|
||||||
|
drm_free_only = False
|
||||||
|
headquarters = 'PL'
|
||||||
|
formats = ['EPUB']
|
||||||
|
|
||||||
class StoreManyBooksStore(StoreBase):
|
class StoreManyBooksStore(StoreBase):
|
||||||
name = 'ManyBooks'
|
name = 'ManyBooks'
|
||||||
description = u'Public domain and creative commons works from many sources.'
|
description = u'Public domain and creative commons works from many sources.'
|
||||||
@ -1306,7 +1316,7 @@ class StoreOpenLibraryStore(StoreBase):
|
|||||||
actual_plugin = 'calibre.gui2.store.open_library_plugin:OpenLibraryStore'
|
actual_plugin = 'calibre.gui2.store.open_library_plugin:OpenLibraryStore'
|
||||||
|
|
||||||
drm_free_only = True
|
drm_free_only = True
|
||||||
headquarters = ['US']
|
headquarters = 'US'
|
||||||
formats = ['DAISY', 'DJVU', 'EPUB', 'MOBI', 'PDF', 'TXT']
|
formats = ['DAISY', 'DJVU', 'EPUB', 'MOBI', 'PDF', 'TXT']
|
||||||
|
|
||||||
class StoreOReillyStore(StoreBase):
|
class StoreOReillyStore(StoreBase):
|
||||||
@ -1371,7 +1381,7 @@ class StoreWoblinkStore(StoreBase):
|
|||||||
actual_plugin = 'calibre.gui2.store.woblink_plugin:WoblinkStore'
|
actual_plugin = 'calibre.gui2.store.woblink_plugin:WoblinkStore'
|
||||||
|
|
||||||
drm_free_only = False
|
drm_free_only = False
|
||||||
location = 'PL'
|
headquarters = 'PL'
|
||||||
formats = ['EPUB']
|
formats = ['EPUB']
|
||||||
|
|
||||||
plugins += [
|
plugins += [
|
||||||
@ -1393,6 +1403,7 @@ plugins += [
|
|||||||
StoreGoogleBooksStore,
|
StoreGoogleBooksStore,
|
||||||
StoreGutenbergStore,
|
StoreGutenbergStore,
|
||||||
StoreKoboStore,
|
StoreKoboStore,
|
||||||
|
StoreLegimiStore,
|
||||||
StoreManyBooksStore,
|
StoreManyBooksStore,
|
||||||
StoreMobileReadStore,
|
StoreMobileReadStore,
|
||||||
StoreNextoStore,
|
StoreNextoStore,
|
||||||
|
@ -34,6 +34,8 @@ class StoreAction(InterfaceAction):
|
|||||||
self.store_list_menu = self.store_menu.addMenu(_('Stores'))
|
self.store_list_menu = self.store_menu.addMenu(_('Stores'))
|
||||||
for n, p in sorted(self.gui.istores.items(), key=lambda x: x[0].lower()):
|
for n, p in sorted(self.gui.istores.items(), key=lambda x: x[0].lower()):
|
||||||
self.store_list_menu.addAction(n, partial(self.open_store, p))
|
self.store_list_menu.addAction(n, partial(self.open_store, p))
|
||||||
|
self.store_menu.addSeparator()
|
||||||
|
self.store_menu.addAction(_('Choose stores'), self.choose)
|
||||||
self.qaction.setMenu(self.store_menu)
|
self.qaction.setMenu(self.store_menu)
|
||||||
|
|
||||||
def do_search(self):
|
def do_search(self):
|
||||||
@ -107,6 +109,13 @@ class StoreAction(InterfaceAction):
|
|||||||
query = 'author:"%s" title:"%s"' % (self._get_author(row), self._get_title(row))
|
query = 'author:"%s" title:"%s"' % (self._get_author(row), self._get_title(row))
|
||||||
self.search(query)
|
self.search(query)
|
||||||
|
|
||||||
|
def choose(self):
|
||||||
|
from calibre.gui2.store.config.chooser.chooser_dialog import StoreChooserDialog
|
||||||
|
d = StoreChooserDialog(self.gui)
|
||||||
|
d.exec_()
|
||||||
|
self.gui.load_store_plugins()
|
||||||
|
self.load_menu()
|
||||||
|
|
||||||
def open_store(self, store_plugin):
|
def open_store(self, store_plugin):
|
||||||
self.show_disclaimer()
|
self.show_disclaimer()
|
||||||
store_plugin.open(self.gui)
|
store_plugin.open(self.gui)
|
||||||
|
0
src/calibre/gui2/store/config/chooser/__init__.py
Normal file
0
src/calibre/gui2/store/config/chooser/__init__.py
Normal file
131
src/calibre/gui2/store/config/chooser/adv_search_builder.py
Normal file
131
src/calibre/gui2/store/config/chooser/adv_search_builder.py
Normal file
@ -0,0 +1,131 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
|
from __future__ import (unicode_literals, division, absolute_import, print_function)
|
||||||
|
|
||||||
|
__license__ = 'GPL 3'
|
||||||
|
__copyright__ = '2011, John Schember <john@nachtimwald.com>'
|
||||||
|
__docformat__ = 'restructuredtext en'
|
||||||
|
|
||||||
|
import re
|
||||||
|
|
||||||
|
from PyQt4.Qt import (QDialog, QDialogButtonBox)
|
||||||
|
|
||||||
|
from calibre.gui2.store.config.chooser.adv_search_builder_ui import Ui_Dialog
|
||||||
|
from calibre.library.caches import CONTAINS_MATCH, EQUALS_MATCH
|
||||||
|
|
||||||
|
class AdvSearchBuilderDialog(QDialog, Ui_Dialog):
|
||||||
|
|
||||||
|
def __init__(self, parent):
|
||||||
|
QDialog.__init__(self, parent)
|
||||||
|
self.setupUi(self)
|
||||||
|
|
||||||
|
self.buttonBox.accepted.connect(self.advanced_search_button_pushed)
|
||||||
|
self.tab_2_button_box.accepted.connect(self.accept)
|
||||||
|
self.tab_2_button_box.rejected.connect(self.reject)
|
||||||
|
self.clear_button.clicked.connect(self.clear_button_pushed)
|
||||||
|
self.adv_search_used = False
|
||||||
|
self.mc = ''
|
||||||
|
|
||||||
|
self.tabWidget.setCurrentIndex(0)
|
||||||
|
self.tabWidget.currentChanged[int].connect(self.tab_changed)
|
||||||
|
self.tab_changed(0)
|
||||||
|
|
||||||
|
def tab_changed(self, idx):
|
||||||
|
if idx == 1:
|
||||||
|
self.tab_2_button_box.button(QDialogButtonBox.Ok).setDefault(True)
|
||||||
|
else:
|
||||||
|
self.buttonBox.button(QDialogButtonBox.Ok).setDefault(True)
|
||||||
|
|
||||||
|
def advanced_search_button_pushed(self):
|
||||||
|
self.adv_search_used = True
|
||||||
|
self.accept()
|
||||||
|
|
||||||
|
def clear_button_pushed(self):
|
||||||
|
self.name_box.setText('')
|
||||||
|
self.description_box.setText('')
|
||||||
|
self.headquarters_box.setText('')
|
||||||
|
self.format_box.setText('')
|
||||||
|
self.enabled_combo.setIndex(0)
|
||||||
|
self.drm_combo.setIndex(0)
|
||||||
|
|
||||||
|
def tokens(self, raw):
|
||||||
|
phrases = re.findall(r'\s*".*?"\s*', raw)
|
||||||
|
for f in phrases:
|
||||||
|
raw = raw.replace(f, ' ')
|
||||||
|
phrases = [t.strip('" ') for t in phrases]
|
||||||
|
return ['"' + self.mc + t + '"' for t in phrases + [r.strip() for r in raw.split()]]
|
||||||
|
|
||||||
|
def search_string(self):
|
||||||
|
if self.adv_search_used:
|
||||||
|
return self.adv_search_string()
|
||||||
|
else:
|
||||||
|
return self.box_search_string()
|
||||||
|
|
||||||
|
def adv_search_string(self):
|
||||||
|
mk = self.matchkind.currentIndex()
|
||||||
|
if mk == CONTAINS_MATCH:
|
||||||
|
self.mc = ''
|
||||||
|
elif mk == EQUALS_MATCH:
|
||||||
|
self.mc = '='
|
||||||
|
else:
|
||||||
|
self.mc = '~'
|
||||||
|
all, any, phrase, none = map(lambda x: unicode(x.text()),
|
||||||
|
(self.all, self.any, self.phrase, self.none))
|
||||||
|
all, any, none = map(self.tokens, (all, any, none))
|
||||||
|
phrase = phrase.strip()
|
||||||
|
all = ' and '.join(all)
|
||||||
|
any = ' or '.join(any)
|
||||||
|
none = ' and not '.join(none)
|
||||||
|
ans = ''
|
||||||
|
if phrase:
|
||||||
|
ans += '"%s"'%phrase
|
||||||
|
if all:
|
||||||
|
ans += (' and ' if ans else '') + all
|
||||||
|
if none:
|
||||||
|
ans += (' and not ' if ans else 'not ') + none
|
||||||
|
if any:
|
||||||
|
ans += (' or ' if ans else '') + any
|
||||||
|
return ans
|
||||||
|
|
||||||
|
def token(self):
|
||||||
|
txt = unicode(self.text.text()).strip()
|
||||||
|
if txt:
|
||||||
|
if self.negate.isChecked():
|
||||||
|
txt = '!'+txt
|
||||||
|
tok = self.FIELDS[unicode(self.field.currentText())]+txt
|
||||||
|
if re.search(r'\s', tok):
|
||||||
|
tok = '"%s"'%tok
|
||||||
|
return tok
|
||||||
|
|
||||||
|
def box_search_string(self):
|
||||||
|
mk = self.matchkind.currentIndex()
|
||||||
|
if mk == CONTAINS_MATCH:
|
||||||
|
self.mc = ''
|
||||||
|
elif mk == EQUALS_MATCH:
|
||||||
|
self.mc = '='
|
||||||
|
else:
|
||||||
|
self.mc = '~'
|
||||||
|
|
||||||
|
ans = []
|
||||||
|
self.box_last_values = {}
|
||||||
|
name = unicode(self.name_box.text()).strip()
|
||||||
|
if name:
|
||||||
|
ans.append('name:"' + self.mc + name + '"')
|
||||||
|
description = unicode(self.description_box.text()).strip()
|
||||||
|
if description:
|
||||||
|
ans.append('description:"' + self.mc + description + '"')
|
||||||
|
headquarters = unicode(self.headquarters_box.text()).strip()
|
||||||
|
if headquarters:
|
||||||
|
ans.append('headquarters:"' + self.mc + headquarters + '"')
|
||||||
|
format = unicode(self.format_box.text()).strip()
|
||||||
|
if format:
|
||||||
|
ans.append('format:"' + self.mc + format + '"')
|
||||||
|
enabled = unicode(self.enabled_combo.currentText()).strip()
|
||||||
|
if enabled:
|
||||||
|
ans.append('enabled:' + enabled)
|
||||||
|
drm = unicode(self.drm_combo.currentText()).strip()
|
||||||
|
if drm:
|
||||||
|
ans.append('drm:' + drm)
|
||||||
|
if ans:
|
||||||
|
return ' and '.join(ans)
|
||||||
|
return ''
|
416
src/calibre/gui2/store/config/chooser/adv_search_builder.ui
Normal file
416
src/calibre/gui2/store/config/chooser/adv_search_builder.ui
Normal file
@ -0,0 +1,416 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<ui version="4.0">
|
||||||
|
<class>Dialog</class>
|
||||||
|
<widget class="QDialog" name="Dialog">
|
||||||
|
<property name="geometry">
|
||||||
|
<rect>
|
||||||
|
<x>0</x>
|
||||||
|
<y>0</y>
|
||||||
|
<width>752</width>
|
||||||
|
<height>472</height>
|
||||||
|
</rect>
|
||||||
|
</property>
|
||||||
|
<property name="windowTitle">
|
||||||
|
<string>Advanced Search</string>
|
||||||
|
</property>
|
||||||
|
<property name="windowIcon">
|
||||||
|
<iconset>
|
||||||
|
<normaloff>:/images/search.png</normaloff>:/images/search.png</iconset>
|
||||||
|
</property>
|
||||||
|
<layout class="QGridLayout" name="gridLayout_2">
|
||||||
|
<item row="0" column="0">
|
||||||
|
<widget class="QLabel" name="label_5">
|
||||||
|
<property name="text">
|
||||||
|
<string>&What kind of match to use:</string>
|
||||||
|
</property>
|
||||||
|
<property name="buddy">
|
||||||
|
<cstring>matchkind</cstring>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item row="0" column="1">
|
||||||
|
<widget class="QComboBox" name="matchkind">
|
||||||
|
<item>
|
||||||
|
<property name="text">
|
||||||
|
<string>Contains: the word or phrase matches anywhere in the metadata field</string>
|
||||||
|
</property>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<property name="text">
|
||||||
|
<string>Equals: the word or phrase must match the entire metadata field</string>
|
||||||
|
</property>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<property name="text">
|
||||||
|
<string>Regular expression: the expression must match anywhere in the metadata field</string>
|
||||||
|
</property>
|
||||||
|
</item>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item row="2" column="0" colspan="2">
|
||||||
|
<widget class="QTabWidget" name="tabWidget">
|
||||||
|
<property name="currentIndex">
|
||||||
|
<number>0</number>
|
||||||
|
</property>
|
||||||
|
<widget class="QWidget" name="tab">
|
||||||
|
<attribute name="title">
|
||||||
|
<string>A&dvanced Search</string>
|
||||||
|
</attribute>
|
||||||
|
<layout class="QGridLayout" name="gridLayout_3">
|
||||||
|
<item row="0" column="0">
|
||||||
|
<widget class="QGroupBox" name="groupBox">
|
||||||
|
<property name="title">
|
||||||
|
<string>Find entries that have...</string>
|
||||||
|
</property>
|
||||||
|
<layout class="QVBoxLayout" name="verticalLayout">
|
||||||
|
<item>
|
||||||
|
<layout class="QHBoxLayout" name="horizontalLayout">
|
||||||
|
<item>
|
||||||
|
<widget class="QLabel" name="label">
|
||||||
|
<property name="text">
|
||||||
|
<string>&All these words:</string>
|
||||||
|
</property>
|
||||||
|
<property name="buddy">
|
||||||
|
<cstring>all</cstring>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<widget class="QLineEdit" name="all"/>
|
||||||
|
</item>
|
||||||
|
</layout>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<layout class="QHBoxLayout" name="horizontalLayout_2">
|
||||||
|
<item>
|
||||||
|
<widget class="QLabel" name="label_2">
|
||||||
|
<property name="text">
|
||||||
|
<string>This exact &phrase:</string>
|
||||||
|
</property>
|
||||||
|
<property name="buddy">
|
||||||
|
<cstring>all</cstring>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<widget class="QLineEdit" name="phrase"/>
|
||||||
|
</item>
|
||||||
|
</layout>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<layout class="QHBoxLayout" name="horizontalLayout_3">
|
||||||
|
<item>
|
||||||
|
<widget class="QLabel" name="label_3">
|
||||||
|
<property name="text">
|
||||||
|
<string>&One or more of these words:</string>
|
||||||
|
</property>
|
||||||
|
<property name="buddy">
|
||||||
|
<cstring>all</cstring>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<widget class="QLineEdit" name="any"/>
|
||||||
|
</item>
|
||||||
|
</layout>
|
||||||
|
</item>
|
||||||
|
</layout>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item row="1" column="0">
|
||||||
|
<widget class="QGroupBox" name="groupBox_2">
|
||||||
|
<property name="title">
|
||||||
|
<string>But dont show entries that have...</string>
|
||||||
|
</property>
|
||||||
|
<layout class="QVBoxLayout" name="verticalLayout_2">
|
||||||
|
<item>
|
||||||
|
<layout class="QHBoxLayout" name="horizontalLayout_4">
|
||||||
|
<item>
|
||||||
|
<widget class="QLabel" name="label_4">
|
||||||
|
<property name="text">
|
||||||
|
<string>Any of these &unwanted words:</string>
|
||||||
|
</property>
|
||||||
|
<property name="buddy">
|
||||||
|
<cstring>all</cstring>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<widget class="QLineEdit" name="none"/>
|
||||||
|
</item>
|
||||||
|
</layout>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<widget class="QLabel" name="label_6">
|
||||||
|
<property name="maximumSize">
|
||||||
|
<size>
|
||||||
|
<width>16777215</width>
|
||||||
|
<height>30</height>
|
||||||
|
</size>
|
||||||
|
</property>
|
||||||
|
<property name="text">
|
||||||
|
<string>See the <a href="http://calibre-ebook.com/user_manual/gui.html#the-search-interface">User Manual</a> for more help</string>
|
||||||
|
</property>
|
||||||
|
<property name="openExternalLinks">
|
||||||
|
<bool>true</bool>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
</layout>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item row="2" column="0">
|
||||||
|
<spacer name="verticalSpacer_2">
|
||||||
|
<property name="orientation">
|
||||||
|
<enum>Qt::Vertical</enum>
|
||||||
|
</property>
|
||||||
|
<property name="sizeHint" stdset="0">
|
||||||
|
<size>
|
||||||
|
<width>20</width>
|
||||||
|
<height>40</height>
|
||||||
|
</size>
|
||||||
|
</property>
|
||||||
|
</spacer>
|
||||||
|
</item>
|
||||||
|
<item row="3" column="0">
|
||||||
|
<widget class="QDialogButtonBox" name="buttonBox">
|
||||||
|
<property name="orientation">
|
||||||
|
<enum>Qt::Horizontal</enum>
|
||||||
|
</property>
|
||||||
|
<property name="standardButtons">
|
||||||
|
<set>QDialogButtonBox::Cancel|QDialogButtonBox::Ok</set>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
</layout>
|
||||||
|
</widget>
|
||||||
|
<widget class="QWidget" name="tab_2">
|
||||||
|
<attribute name="title">
|
||||||
|
<string>Nam&e/Description ...</string>
|
||||||
|
</attribute>
|
||||||
|
<layout class="QGridLayout" name="gridLayout">
|
||||||
|
<item row="1" column="0">
|
||||||
|
<widget class="QLabel" name="label_7">
|
||||||
|
<property name="text">
|
||||||
|
<string>&Name:</string>
|
||||||
|
</property>
|
||||||
|
<property name="buddy">
|
||||||
|
<cstring>name_box</cstring>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item row="1" column="1">
|
||||||
|
<widget class="EnLineEdit" name="name_box">
|
||||||
|
<property name="toolTip">
|
||||||
|
<string>Enter the title.</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item row="2" column="0">
|
||||||
|
<widget class="QLabel" name="label_8">
|
||||||
|
<property name="text">
|
||||||
|
<string>&Description:</string>
|
||||||
|
</property>
|
||||||
|
<property name="buddy">
|
||||||
|
<cstring>description_box</cstring>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item row="3" column="0">
|
||||||
|
<widget class="QLabel" name="price_label">
|
||||||
|
<property name="text">
|
||||||
|
<string>&Headquarters:</string>
|
||||||
|
</property>
|
||||||
|
<property name="buddy">
|
||||||
|
<cstring>headquarters_box</cstring>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item row="8" column="0" colspan="2">
|
||||||
|
<layout class="QHBoxLayout" name="horizontalLayout_6">
|
||||||
|
<item>
|
||||||
|
<widget class="QPushButton" name="clear_button">
|
||||||
|
<property name="text">
|
||||||
|
<string>&Clear</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<widget class="QDialogButtonBox" name="tab_2_button_box">
|
||||||
|
<property name="standardButtons">
|
||||||
|
<set>QDialogButtonBox::Cancel|QDialogButtonBox::Ok</set>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
</layout>
|
||||||
|
</item>
|
||||||
|
<item row="7" column="1">
|
||||||
|
<spacer name="verticalSpacer">
|
||||||
|
<property name="orientation">
|
||||||
|
<enum>Qt::Vertical</enum>
|
||||||
|
</property>
|
||||||
|
<property name="sizeHint" stdset="0">
|
||||||
|
<size>
|
||||||
|
<width>20</width>
|
||||||
|
<height>40</height>
|
||||||
|
</size>
|
||||||
|
</property>
|
||||||
|
</spacer>
|
||||||
|
</item>
|
||||||
|
<item row="0" column="0" colspan="2">
|
||||||
|
<widget class="QLabel" name="label_11">
|
||||||
|
<property name="text">
|
||||||
|
<string>Search only in specific fields:</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item row="2" column="1">
|
||||||
|
<widget class="EnLineEdit" name="description_box"/>
|
||||||
|
</item>
|
||||||
|
<item row="4" column="1">
|
||||||
|
<widget class="QLineEdit" name="format_box"/>
|
||||||
|
</item>
|
||||||
|
<item row="4" column="0">
|
||||||
|
<widget class="QLabel" name="label_10">
|
||||||
|
<property name="text">
|
||||||
|
<string>&Format:</string>
|
||||||
|
</property>
|
||||||
|
<property name="buddy">
|
||||||
|
<cstring>format_box</cstring>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item row="3" column="1">
|
||||||
|
<widget class="EnLineEdit" name="headquarters_box"/>
|
||||||
|
</item>
|
||||||
|
<item row="5" column="0">
|
||||||
|
<widget class="QLabel" name="label_9">
|
||||||
|
<property name="text">
|
||||||
|
<string>Enabled:</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item row="6" column="0">
|
||||||
|
<widget class="QLabel" name="label_12">
|
||||||
|
<property name="text">
|
||||||
|
<string>DRM:</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item row="5" column="1">
|
||||||
|
<widget class="QComboBox" name="enabled_combo">
|
||||||
|
<item>
|
||||||
|
<property name="text">
|
||||||
|
<string/>
|
||||||
|
</property>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<property name="text">
|
||||||
|
<string>true</string>
|
||||||
|
</property>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<property name="text">
|
||||||
|
<string>false</string>
|
||||||
|
</property>
|
||||||
|
</item>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item row="6" column="1">
|
||||||
|
<widget class="QComboBox" name="drm_combo">
|
||||||
|
<item>
|
||||||
|
<property name="text">
|
||||||
|
<string/>
|
||||||
|
</property>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<property name="text">
|
||||||
|
<string>true</string>
|
||||||
|
</property>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<property name="text">
|
||||||
|
<string>false</string>
|
||||||
|
</property>
|
||||||
|
</item>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
</layout>
|
||||||
|
</widget>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item row="1" column="1">
|
||||||
|
<spacer name="verticalSpacer_3">
|
||||||
|
<property name="orientation">
|
||||||
|
<enum>Qt::Vertical</enum>
|
||||||
|
</property>
|
||||||
|
<property name="sizeHint" stdset="0">
|
||||||
|
<size>
|
||||||
|
<width>20</width>
|
||||||
|
<height>40</height>
|
||||||
|
</size>
|
||||||
|
</property>
|
||||||
|
</spacer>
|
||||||
|
</item>
|
||||||
|
</layout>
|
||||||
|
</widget>
|
||||||
|
<customwidgets>
|
||||||
|
<customwidget>
|
||||||
|
<class>EnLineEdit</class>
|
||||||
|
<extends>QLineEdit</extends>
|
||||||
|
<header>widgets.h</header>
|
||||||
|
</customwidget>
|
||||||
|
</customwidgets>
|
||||||
|
<tabstops>
|
||||||
|
<tabstop>all</tabstop>
|
||||||
|
<tabstop>phrase</tabstop>
|
||||||
|
<tabstop>any</tabstop>
|
||||||
|
<tabstop>none</tabstop>
|
||||||
|
<tabstop>buttonBox</tabstop>
|
||||||
|
<tabstop>name_box</tabstop>
|
||||||
|
<tabstop>description_box</tabstop>
|
||||||
|
<tabstop>headquarters_box</tabstop>
|
||||||
|
<tabstop>format_box</tabstop>
|
||||||
|
<tabstop>clear_button</tabstop>
|
||||||
|
<tabstop>tab_2_button_box</tabstop>
|
||||||
|
<tabstop>tabWidget</tabstop>
|
||||||
|
<tabstop>matchkind</tabstop>
|
||||||
|
</tabstops>
|
||||||
|
<resources>
|
||||||
|
<include location="../../../../resources/images.qrc"/>
|
||||||
|
</resources>
|
||||||
|
<connections>
|
||||||
|
<connection>
|
||||||
|
<sender>buttonBox</sender>
|
||||||
|
<signal>accepted()</signal>
|
||||||
|
<receiver>Dialog</receiver>
|
||||||
|
<slot>accept()</slot>
|
||||||
|
<hints>
|
||||||
|
<hint type="sourcelabel">
|
||||||
|
<x>248</x>
|
||||||
|
<y>254</y>
|
||||||
|
</hint>
|
||||||
|
<hint type="destinationlabel">
|
||||||
|
<x>157</x>
|
||||||
|
<y>274</y>
|
||||||
|
</hint>
|
||||||
|
</hints>
|
||||||
|
</connection>
|
||||||
|
<connection>
|
||||||
|
<sender>buttonBox</sender>
|
||||||
|
<signal>rejected()</signal>
|
||||||
|
<receiver>Dialog</receiver>
|
||||||
|
<slot>reject()</slot>
|
||||||
|
<hints>
|
||||||
|
<hint type="sourcelabel">
|
||||||
|
<x>316</x>
|
||||||
|
<y>260</y>
|
||||||
|
</hint>
|
||||||
|
<hint type="destinationlabel">
|
||||||
|
<x>286</x>
|
||||||
|
<y>274</y>
|
||||||
|
</hint>
|
||||||
|
</hints>
|
||||||
|
</connection>
|
||||||
|
</connections>
|
||||||
|
</ui>
|
28
src/calibre/gui2/store/config/chooser/chooser_dialog.py
Normal file
28
src/calibre/gui2/store/config/chooser/chooser_dialog.py
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
|
from __future__ import (unicode_literals, division, absolute_import, print_function)
|
||||||
|
|
||||||
|
__license__ = 'GPL 3'
|
||||||
|
__copyright__ = '2011, John Schember <john@nachtimwald.com>'
|
||||||
|
__docformat__ = 'restructuredtext en'
|
||||||
|
|
||||||
|
from PyQt4.Qt import (QDialog, QDialogButtonBox, QVBoxLayout)
|
||||||
|
|
||||||
|
from calibre.gui2.store.config.chooser.chooser_widget import StoreChooserWidget
|
||||||
|
|
||||||
|
class StoreChooserDialog(QDialog):
|
||||||
|
|
||||||
|
def __init__(self, parent):
|
||||||
|
QDialog.__init__(self, parent)
|
||||||
|
|
||||||
|
self.setWindowTitle(_('Choose stores'))
|
||||||
|
|
||||||
|
button_box = QDialogButtonBox(QDialogButtonBox.Close)
|
||||||
|
button_box.accepted.connect(self.accept)
|
||||||
|
button_box.rejected.connect(self.reject)
|
||||||
|
v = QVBoxLayout(self)
|
||||||
|
self.config_widget = StoreChooserWidget()
|
||||||
|
v.addWidget(self.config_widget)
|
||||||
|
v.addWidget(button_box)
|
||||||
|
|
||||||
|
self.resize(800, 600)
|
35
src/calibre/gui2/store/config/chooser/chooser_widget.py
Normal file
35
src/calibre/gui2/store/config/chooser/chooser_widget.py
Normal file
@ -0,0 +1,35 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
|
from __future__ import (unicode_literals, division, absolute_import, print_function)
|
||||||
|
|
||||||
|
__license__ = 'GPL 3'
|
||||||
|
__copyright__ = '2011, John Schember <john@nachtimwald.com>'
|
||||||
|
__docformat__ = 'restructuredtext en'
|
||||||
|
|
||||||
|
from PyQt4.Qt import (QWidget, QIcon, QDialog)
|
||||||
|
|
||||||
|
from calibre.gui2.store.config.chooser.adv_search_builder import AdvSearchBuilderDialog
|
||||||
|
from calibre.gui2.store.config.chooser.chooser_widget_ui import Ui_Form
|
||||||
|
|
||||||
|
class StoreChooserWidget(QWidget, Ui_Form):
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
QWidget.__init__(self)
|
||||||
|
self.setupUi(self)
|
||||||
|
|
||||||
|
self.adv_search_builder.setIcon(QIcon(I('search.png')))
|
||||||
|
|
||||||
|
self.search.clicked.connect(self.do_search)
|
||||||
|
self.adv_search_builder.clicked.connect(self.build_adv_search)
|
||||||
|
self.results_view.activated.connect(self.toggle_plugin)
|
||||||
|
|
||||||
|
def do_search(self):
|
||||||
|
self.results_view.model().search(unicode(self.query.text()))
|
||||||
|
|
||||||
|
def toggle_plugin(self, index):
|
||||||
|
self.results_view.model().toggle_plugin(index)
|
||||||
|
|
||||||
|
def build_adv_search(self):
|
||||||
|
adv = AdvSearchBuilderDialog(self)
|
||||||
|
if adv.exec_() == QDialog.Accepted:
|
||||||
|
self.query.setText(adv.search_string())
|
87
src/calibre/gui2/store/config/chooser/chooser_widget.ui
Normal file
87
src/calibre/gui2/store/config/chooser/chooser_widget.ui
Normal file
@ -0,0 +1,87 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<ui version="4.0">
|
||||||
|
<class>Form</class>
|
||||||
|
<widget class="QWidget" name="Form">
|
||||||
|
<property name="geometry">
|
||||||
|
<rect>
|
||||||
|
<x>0</x>
|
||||||
|
<y>0</y>
|
||||||
|
<width>610</width>
|
||||||
|
<height>553</height>
|
||||||
|
</rect>
|
||||||
|
</property>
|
||||||
|
<property name="windowTitle">
|
||||||
|
<string>Form</string>
|
||||||
|
</property>
|
||||||
|
<layout class="QVBoxLayout" name="verticalLayout">
|
||||||
|
<item>
|
||||||
|
<layout class="QHBoxLayout" name="horizontalLayout">
|
||||||
|
<item>
|
||||||
|
<widget class="QLabel" name="label">
|
||||||
|
<property name="text">
|
||||||
|
<string>Query:</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<widget class="QToolButton" name="adv_search_builder">
|
||||||
|
<property name="text">
|
||||||
|
<string>...</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<widget class="QLineEdit" name="query"/>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<widget class="QPushButton" name="search">
|
||||||
|
<property name="text">
|
||||||
|
<string>Search</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
</layout>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<widget class="ResultsView" name="results_view">
|
||||||
|
<property name="alternatingRowColors">
|
||||||
|
<bool>true</bool>
|
||||||
|
</property>
|
||||||
|
<property name="selectionMode">
|
||||||
|
<enum>QAbstractItemView::SingleSelection</enum>
|
||||||
|
</property>
|
||||||
|
<property name="selectionBehavior">
|
||||||
|
<enum>QAbstractItemView::SelectRows</enum>
|
||||||
|
</property>
|
||||||
|
<property name="rootIsDecorated">
|
||||||
|
<bool>false</bool>
|
||||||
|
</property>
|
||||||
|
<property name="uniformRowHeights">
|
||||||
|
<bool>true</bool>
|
||||||
|
</property>
|
||||||
|
<property name="itemsExpandable">
|
||||||
|
<bool>false</bool>
|
||||||
|
</property>
|
||||||
|
<property name="sortingEnabled">
|
||||||
|
<bool>true</bool>
|
||||||
|
</property>
|
||||||
|
<property name="expandsOnDoubleClick">
|
||||||
|
<bool>false</bool>
|
||||||
|
</property>
|
||||||
|
<attribute name="headerStretchLastSection">
|
||||||
|
<bool>false</bool>
|
||||||
|
</attribute>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
</layout>
|
||||||
|
</widget>
|
||||||
|
<customwidgets>
|
||||||
|
<customwidget>
|
||||||
|
<class>ResultsView</class>
|
||||||
|
<extends>QTreeView</extends>
|
||||||
|
<header>results_view.h</header>
|
||||||
|
</customwidget>
|
||||||
|
</customwidgets>
|
||||||
|
<resources/>
|
||||||
|
<connections/>
|
||||||
|
</ui>
|
244
src/calibre/gui2/store/config/chooser/models.py
Normal file
244
src/calibre/gui2/store/config/chooser/models.py
Normal file
@ -0,0 +1,244 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
|
from __future__ import (unicode_literals, division, absolute_import, print_function)
|
||||||
|
|
||||||
|
__license__ = 'GPL 3'
|
||||||
|
__copyright__ = '2011, John Schember <john@nachtimwald.com>'
|
||||||
|
__docformat__ = 'restructuredtext en'
|
||||||
|
|
||||||
|
from PyQt4.Qt import (Qt, QAbstractItemModel, QIcon, QVariant, QModelIndex)
|
||||||
|
|
||||||
|
from calibre.gui2 import NONE
|
||||||
|
from calibre.customize.ui import is_disabled, disable_plugin, enable_plugin
|
||||||
|
from calibre.library.caches import _match, CONTAINS_MATCH, EQUALS_MATCH, \
|
||||||
|
REGEXP_MATCH
|
||||||
|
from calibre.utils.icu import sort_key
|
||||||
|
from calibre.utils.search_query_parser import SearchQueryParser
|
||||||
|
|
||||||
|
|
||||||
|
class Matches(QAbstractItemModel):
|
||||||
|
|
||||||
|
HEADERS = [_('Enabled'), _('Name'), _('No DRM'), _('Headquarters'), _('Formats')]
|
||||||
|
HTML_COLS = [1]
|
||||||
|
|
||||||
|
def __init__(self, plugins):
|
||||||
|
QAbstractItemModel.__init__(self)
|
||||||
|
|
||||||
|
self.NO_DRM_ICON = QIcon(I('ok.png'))
|
||||||
|
|
||||||
|
self.all_matches = plugins
|
||||||
|
self.matches = plugins
|
||||||
|
self.filter = ''
|
||||||
|
self.search_filter = SearchFilter(self.all_matches)
|
||||||
|
|
||||||
|
self.sort_col = 1
|
||||||
|
self.sort_order = Qt.AscendingOrder
|
||||||
|
|
||||||
|
def get_plugin(self, index):
|
||||||
|
row = index.row()
|
||||||
|
if row < len(self.matches):
|
||||||
|
return self.matches[row]
|
||||||
|
else:
|
||||||
|
return None
|
||||||
|
|
||||||
|
def search(self, filter):
|
||||||
|
self.filter = filter.strip()
|
||||||
|
if not self.filter:
|
||||||
|
self.matches = self.all_matches
|
||||||
|
else:
|
||||||
|
try:
|
||||||
|
self.matches = list(self.search_filter.parse(self.filter))
|
||||||
|
except:
|
||||||
|
self.matches = self.all_matches
|
||||||
|
self.layoutChanged.emit()
|
||||||
|
self.sort(self.sort_col, self.sort_order)
|
||||||
|
|
||||||
|
def toggle_plugin(self, index):
|
||||||
|
new_index = self.createIndex(index.row(), 0)
|
||||||
|
data = QVariant(is_disabled(self.get_plugin(index)))
|
||||||
|
self.setData(new_index, data, Qt.CheckStateRole)
|
||||||
|
|
||||||
|
def index(self, row, column, parent=QModelIndex()):
|
||||||
|
return self.createIndex(row, column)
|
||||||
|
|
||||||
|
def parent(self, index):
|
||||||
|
if not index.isValid() or index.internalId() == 0:
|
||||||
|
return QModelIndex()
|
||||||
|
return self.createIndex(0, 0)
|
||||||
|
|
||||||
|
def rowCount(self, *args):
|
||||||
|
return len(self.matches)
|
||||||
|
|
||||||
|
def columnCount(self, *args):
|
||||||
|
return len(self.HEADERS)
|
||||||
|
|
||||||
|
def headerData(self, section, orientation, role):
|
||||||
|
if role != Qt.DisplayRole:
|
||||||
|
return NONE
|
||||||
|
text = ''
|
||||||
|
if orientation == Qt.Horizontal:
|
||||||
|
if section < len(self.HEADERS):
|
||||||
|
text = self.HEADERS[section]
|
||||||
|
return QVariant(text)
|
||||||
|
else:
|
||||||
|
return QVariant(section+1)
|
||||||
|
|
||||||
|
def data(self, index, role):
|
||||||
|
row, col = index.row(), index.column()
|
||||||
|
result = self.matches[row]
|
||||||
|
if role in (Qt.DisplayRole, Qt.EditRole):
|
||||||
|
if col == 1:
|
||||||
|
return QVariant('<b>%s</b><br><i>%s</i>' % (result.name, result.description))
|
||||||
|
elif col == 3:
|
||||||
|
return QVariant(result.headquarters)
|
||||||
|
elif col == 4:
|
||||||
|
return QVariant(', '.join(result.formats).upper())
|
||||||
|
elif role == Qt.DecorationRole:
|
||||||
|
if col == 2:
|
||||||
|
if result.drm_free_only:
|
||||||
|
return QVariant(self.NO_DRM_ICON)
|
||||||
|
elif role == Qt.CheckStateRole:
|
||||||
|
if col == 0:
|
||||||
|
if is_disabled(result):
|
||||||
|
return Qt.Unchecked
|
||||||
|
return Qt.Checked
|
||||||
|
elif role == Qt.ToolTipRole:
|
||||||
|
return QVariant('<p>%s</p>' % result.description)
|
||||||
|
return NONE
|
||||||
|
|
||||||
|
def setData(self, index, data, role):
|
||||||
|
if not index.isValid():
|
||||||
|
return False
|
||||||
|
row, col = index.row(), index.column()
|
||||||
|
if col == 0:
|
||||||
|
if data.toBool():
|
||||||
|
enable_plugin(self.get_plugin(index))
|
||||||
|
else:
|
||||||
|
disable_plugin(self.get_plugin(index))
|
||||||
|
self.dataChanged.emit(self.index(index.row(), 0), self.index(index.row(), self.columnCount() - 1))
|
||||||
|
return True
|
||||||
|
|
||||||
|
def flags(self, index):
|
||||||
|
if index.column() == 0:
|
||||||
|
return QAbstractItemModel.flags(self, index) | Qt.ItemIsUserCheckable
|
||||||
|
return QAbstractItemModel.flags(self, index)
|
||||||
|
|
||||||
|
def data_as_text(self, match, col):
|
||||||
|
text = ''
|
||||||
|
if col == 0:
|
||||||
|
text = 'b' if is_disabled(match) else 'a'
|
||||||
|
elif col == 1:
|
||||||
|
text = match.name
|
||||||
|
elif col == 2:
|
||||||
|
text = 'b' if getattr(match, 'drm', True) else 'a'
|
||||||
|
elif col == 3:
|
||||||
|
text = getattr(match, 'headquarters', '')
|
||||||
|
return text
|
||||||
|
|
||||||
|
def sort(self, col, order, reset=True):
|
||||||
|
self.sort_col = col
|
||||||
|
self.sort_order = order
|
||||||
|
if not self.matches:
|
||||||
|
return
|
||||||
|
descending = order == Qt.DescendingOrder
|
||||||
|
self.matches.sort(None,
|
||||||
|
lambda x: sort_key(unicode(self.data_as_text(x, col))),
|
||||||
|
descending)
|
||||||
|
if reset:
|
||||||
|
self.reset()
|
||||||
|
|
||||||
|
|
||||||
|
class SearchFilter(SearchQueryParser):
|
||||||
|
|
||||||
|
USABLE_LOCATIONS = [
|
||||||
|
'all',
|
||||||
|
'description',
|
||||||
|
'drm',
|
||||||
|
'enabled',
|
||||||
|
'format',
|
||||||
|
'formats',
|
||||||
|
'headquarters',
|
||||||
|
'name',
|
||||||
|
]
|
||||||
|
|
||||||
|
def __init__(self, all_plugins=[]):
|
||||||
|
SearchQueryParser.__init__(self, locations=self.USABLE_LOCATIONS)
|
||||||
|
self.srs = set(all_plugins)
|
||||||
|
|
||||||
|
def universal_set(self):
|
||||||
|
return self.srs
|
||||||
|
|
||||||
|
def get_matches(self, location, query):
|
||||||
|
location = location.lower().strip()
|
||||||
|
if location == 'formats':
|
||||||
|
location = 'format'
|
||||||
|
|
||||||
|
matchkind = CONTAINS_MATCH
|
||||||
|
if len(query) > 1:
|
||||||
|
if query.startswith('\\'):
|
||||||
|
query = query[1:]
|
||||||
|
elif query.startswith('='):
|
||||||
|
matchkind = EQUALS_MATCH
|
||||||
|
query = query[1:]
|
||||||
|
elif query.startswith('~'):
|
||||||
|
matchkind = REGEXP_MATCH
|
||||||
|
query = query[1:]
|
||||||
|
if matchkind != REGEXP_MATCH: ### leave case in regexps because it can be significant e.g. \S \W \D
|
||||||
|
query = query.lower()
|
||||||
|
|
||||||
|
if location not in self.USABLE_LOCATIONS:
|
||||||
|
return set([])
|
||||||
|
matches = set([])
|
||||||
|
all_locs = set(self.USABLE_LOCATIONS) - set(['all'])
|
||||||
|
locations = all_locs if location == 'all' else [location]
|
||||||
|
q = {
|
||||||
|
'description': lambda x: x.description.lower(),
|
||||||
|
'drm': lambda x: not x.drm_free_only,
|
||||||
|
'enabled': lambda x: not is_disabled(x),
|
||||||
|
'format': lambda x: ','.join(x.formats).lower(),
|
||||||
|
'headquarters': lambda x: x.headquarters.lower(),
|
||||||
|
'name': lambda x : x.name.lower(),
|
||||||
|
}
|
||||||
|
q['formats'] = q['format']
|
||||||
|
for sr in self.srs:
|
||||||
|
for locvalue in locations:
|
||||||
|
accessor = q[locvalue]
|
||||||
|
if query == 'true':
|
||||||
|
if locvalue in ('drm', 'enabled'):
|
||||||
|
if accessor(sr) == True:
|
||||||
|
matches.add(sr)
|
||||||
|
elif accessor(sr) is not None:
|
||||||
|
matches.add(sr)
|
||||||
|
continue
|
||||||
|
if query == 'false':
|
||||||
|
if locvalue in ('drm', 'enabled'):
|
||||||
|
if accessor(sr) == False:
|
||||||
|
matches.add(sr)
|
||||||
|
elif accessor(sr) is None:
|
||||||
|
matches.add(sr)
|
||||||
|
continue
|
||||||
|
# this is bool, so can't match below
|
||||||
|
if locvalue in ('drm', 'enabled'):
|
||||||
|
continue
|
||||||
|
try:
|
||||||
|
### Can't separate authors because comma is used for name sep and author sep
|
||||||
|
### Exact match might not get what you want. For that reason, turn author
|
||||||
|
### exactmatch searches into contains searches.
|
||||||
|
if locvalue == 'name' and matchkind == EQUALS_MATCH:
|
||||||
|
m = CONTAINS_MATCH
|
||||||
|
else:
|
||||||
|
m = matchkind
|
||||||
|
|
||||||
|
if locvalue == 'format':
|
||||||
|
vals = accessor(sr).split(',')
|
||||||
|
else:
|
||||||
|
vals = [accessor(sr)]
|
||||||
|
if _match(query, vals, m):
|
||||||
|
matches.add(sr)
|
||||||
|
break
|
||||||
|
except ValueError: # Unicode errors
|
||||||
|
import traceback
|
||||||
|
traceback.print_exc()
|
||||||
|
return matches
|
||||||
|
|
||||||
|
|
34
src/calibre/gui2/store/config/chooser/results_view.py
Normal file
34
src/calibre/gui2/store/config/chooser/results_view.py
Normal file
@ -0,0 +1,34 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
|
from __future__ import (unicode_literals, division, absolute_import, print_function)
|
||||||
|
|
||||||
|
__license__ = 'GPL 3'
|
||||||
|
__copyright__ = '2011, John Schember <john@nachtimwald.com>'
|
||||||
|
__docformat__ = 'restructuredtext en'
|
||||||
|
|
||||||
|
from PyQt4.Qt import (Qt, QTreeView, QSize)
|
||||||
|
|
||||||
|
from calibre.customize.ui import store_plugins
|
||||||
|
from calibre.gui2.metadata.single_download import RichTextDelegate
|
||||||
|
from calibre.gui2.store.config.chooser.models import Matches
|
||||||
|
|
||||||
|
class ResultsView(QTreeView):
|
||||||
|
|
||||||
|
def __init__(self, *args):
|
||||||
|
QTreeView.__init__(self,*args)
|
||||||
|
|
||||||
|
self._model = Matches([p for p in store_plugins()])
|
||||||
|
self.setModel(self._model)
|
||||||
|
|
||||||
|
self.setIconSize(QSize(24, 24))
|
||||||
|
|
||||||
|
self.rt_delegate = RichTextDelegate(self)
|
||||||
|
|
||||||
|
for i in self._model.HTML_COLS:
|
||||||
|
self.setItemDelegateForColumn(i, self.rt_delegate)
|
||||||
|
|
||||||
|
for i in xrange(self._model.columnCount()):
|
||||||
|
self.resizeColumnToContents(i)
|
||||||
|
|
||||||
|
self.model().sort(1, Qt.AscendingOrder)
|
||||||
|
self.header().setSortIndicator(self.model().sort_col, self.model().sort_order)
|
0
src/calibre/gui2/store/config/search/__init__.py
Normal file
0
src/calibre/gui2/store/config/search/__init__.py
Normal file
@ -9,7 +9,7 @@ __docformat__ = 'restructuredtext en'
|
|||||||
from PyQt4.Qt import QWidget
|
from PyQt4.Qt import QWidget
|
||||||
|
|
||||||
from calibre.gui2 import JSONConfig
|
from calibre.gui2 import JSONConfig
|
||||||
from calibre.gui2.store.config.search_widget_ui import Ui_Form
|
from calibre.gui2.store.config.search.search_widget_ui import Ui_Form
|
||||||
|
|
||||||
class StoreConfigWidget(QWidget, Ui_Form):
|
class StoreConfigWidget(QWidget, Ui_Form):
|
||||||
|
|
@ -93,13 +93,13 @@
|
|||||||
<item>
|
<item>
|
||||||
<widget class="QGroupBox" name="groupBox_3">
|
<widget class="QGroupBox" name="groupBox_3">
|
||||||
<property name="title">
|
<property name="title">
|
||||||
<string>Performance</string>
|
<string>Threads</string>
|
||||||
</property>
|
</property>
|
||||||
<layout class="QFormLayout" name="formLayout_3">
|
<layout class="QFormLayout" name="formLayout_3">
|
||||||
<item row="0" column="0">
|
<item row="0" column="0">
|
||||||
<widget class="QLabel" name="label_4">
|
<widget class="QLabel" name="label_4">
|
||||||
<property name="text">
|
<property name="text">
|
||||||
<string>Number of simultaneous searches</string>
|
<string>Number of search threads to use</string>
|
||||||
</property>
|
</property>
|
||||||
</widget>
|
</widget>
|
||||||
</item>
|
</item>
|
||||||
@ -113,7 +113,7 @@
|
|||||||
<item row="1" column="0">
|
<item row="1" column="0">
|
||||||
<widget class="QLabel" name="label_5">
|
<widget class="QLabel" name="label_5">
|
||||||
<property name="text">
|
<property name="text">
|
||||||
<string>Number of simultaneous cache updates</string>
|
<string>Number of cache update threads to use</string>
|
||||||
</property>
|
</property>
|
||||||
</widget>
|
</widget>
|
||||||
</item>
|
</item>
|
||||||
@ -127,7 +127,7 @@
|
|||||||
<item row="2" column="0">
|
<item row="2" column="0">
|
||||||
<widget class="QLabel" name="label_6">
|
<widget class="QLabel" name="label_6">
|
||||||
<property name="text">
|
<property name="text">
|
||||||
<string>Number of simultaneous cover downloads</string>
|
<string>Number of conver download threads to use</string>
|
||||||
</property>
|
</property>
|
||||||
</widget>
|
</widget>
|
||||||
</item>
|
</item>
|
||||||
@ -141,7 +141,7 @@
|
|||||||
<item row="3" column="0">
|
<item row="3" column="0">
|
||||||
<widget class="QLabel" name="label_7">
|
<widget class="QLabel" name="label_7">
|
||||||
<property name="text">
|
<property name="text">
|
||||||
<string>Number of simultaneous details downloads</string>
|
<string>Number of details threads to use</string>
|
||||||
</property>
|
</property>
|
||||||
</widget>
|
</widget>
|
||||||
</item>
|
</item>
|
@ -11,7 +11,7 @@ Config widget access functions for configuring the store action.
|
|||||||
'''
|
'''
|
||||||
|
|
||||||
def config_widget():
|
def config_widget():
|
||||||
from calibre.gui2.store.config.search_widget import StoreConfigWidget
|
from calibre.gui2.store.config.search.search_widget import StoreConfigWidget
|
||||||
return StoreConfigWidget()
|
return StoreConfigWidget()
|
||||||
|
|
||||||
def save_settings(config_widget):
|
def save_settings(config_widget):
|
||||||
|
@ -3,3 +3,6 @@ or asked not to be included in the store integration.
|
|||||||
|
|
||||||
* Borders (http://www.borders.com/)
|
* Borders (http://www.borders.com/)
|
||||||
* WH Smith (http://www.whsmith.co.uk/)
|
* WH Smith (http://www.whsmith.co.uk/)
|
||||||
|
Refused to permit signing up for the affiliate program
|
||||||
|
* Libraria Rizzoli (http://libreriarizzoli.corriere.it/).
|
||||||
|
No reply with two attempts over 2 weeks
|
75
src/calibre/gui2/store/legimi_plugin.py
Normal file
75
src/calibre/gui2/store/legimi_plugin.py
Normal file
@ -0,0 +1,75 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
|
from __future__ import (unicode_literals, division, absolute_import, print_function)
|
||||||
|
|
||||||
|
__license__ = 'GPL 3'
|
||||||
|
__copyright__ = '2011, Tomasz Długosz <tomek3d@gmail.com>'
|
||||||
|
__docformat__ = 'restructuredtext en'
|
||||||
|
|
||||||
|
import re
|
||||||
|
import urllib
|
||||||
|
from contextlib import closing
|
||||||
|
|
||||||
|
from lxml import html
|
||||||
|
|
||||||
|
from PyQt4.Qt import QUrl
|
||||||
|
|
||||||
|
from calibre import browser, url_slash_cleaner
|
||||||
|
from calibre.gui2 import open_url
|
||||||
|
from calibre.gui2.store import StorePlugin
|
||||||
|
from calibre.gui2.store.basic_config import BasicStoreConfig
|
||||||
|
from calibre.gui2.store.search_result import SearchResult
|
||||||
|
from calibre.gui2.store.web_store_dialog import WebStoreDialog
|
||||||
|
|
||||||
|
class LegimiStore(BasicStoreConfig, StorePlugin):
|
||||||
|
|
||||||
|
def open(self, parent=None, detail_item=None, external=False):
|
||||||
|
|
||||||
|
url = 'http://www.legimi.com/pl/ebooks/?price=any'
|
||||||
|
detail_url = None
|
||||||
|
|
||||||
|
if detail_item:
|
||||||
|
detail_url = detail_item
|
||||||
|
|
||||||
|
if external or self.config.get('open_external', False):
|
||||||
|
open_url(QUrl(url_slash_cleaner(detail_url if detail_url else url)))
|
||||||
|
else:
|
||||||
|
d = WebStoreDialog(self.gui, url, parent, detail_url)
|
||||||
|
d.setWindowTitle(self.name)
|
||||||
|
d.set_tags(self.config.get('tags', ''))
|
||||||
|
d.exec_()
|
||||||
|
|
||||||
|
def search(self, query, max_results=10, timeout=60):
|
||||||
|
url = 'http://www.legimi.com/pl/ebooks/?price=any&lang=pl&search=' + urllib.quote_plus(query.encode('utf-8')) + '&sort=relevance'
|
||||||
|
|
||||||
|
br = browser()
|
||||||
|
|
||||||
|
counter = max_results
|
||||||
|
with closing(br.open(url, timeout=timeout)) as f:
|
||||||
|
doc = html.fromstring(f.read())
|
||||||
|
for data in doc.xpath('//div[@class="list"]/ul/li'):
|
||||||
|
if counter <= 0:
|
||||||
|
break
|
||||||
|
|
||||||
|
id = ''.join(data.xpath('.//div[@class="item_cover_container"]/a[1]/@href'))
|
||||||
|
if not id:
|
||||||
|
continue
|
||||||
|
|
||||||
|
cover_url = ''.join(data.xpath('.//div[@class="item_cover_container"]/a/img/@src'))
|
||||||
|
title = ''.join(data.xpath('.//div[@class="item_entries"]/h2/a/text()'))
|
||||||
|
author = ''.join(data.xpath('.//div[@class="item_entries"]/span[1]/a/text()'))
|
||||||
|
price = ''.join(data.xpath('.//div[@class="item_entries"]/span[3]/text()'))
|
||||||
|
price = re.sub(r'[^0-9,]*','',price) + ' zł'
|
||||||
|
|
||||||
|
counter -= 1
|
||||||
|
|
||||||
|
s = SearchResult()
|
||||||
|
s.cover_url = 'http://www.legimi.com/' + cover_url
|
||||||
|
s.title = title.strip()
|
||||||
|
s.author = author.strip()
|
||||||
|
s.price = price
|
||||||
|
s.detail_item = 'http://www.legimi.com/' + id.strip()
|
||||||
|
s.drm = SearchResult.DRM_LOCKED
|
||||||
|
s.formats = 'EPUB'
|
||||||
|
|
||||||
|
yield s
|
@ -47,6 +47,7 @@ class BooksModel(QAbstractItemModel):
|
|||||||
self.books = list(self.search_filter.parse(self.filter))
|
self.books = list(self.search_filter.parse(self.filter))
|
||||||
except:
|
except:
|
||||||
self.books = self.all_books
|
self.books = self.all_books
|
||||||
|
self.layoutChanged.emit()
|
||||||
self.sort(self.sort_col, self.sort_order)
|
self.sort(self.sort_col, self.sort_order)
|
||||||
self.total_changed.emit(self.rowCount())
|
self.total_changed.emit(self.rowCount())
|
||||||
|
|
||||||
|
@ -116,7 +116,7 @@ class AdvSearchBuilderDialog(QDialog, Ui_Dialog):
|
|||||||
if price:
|
if price:
|
||||||
ans.append('price:"' + self.mc + price + '"')
|
ans.append('price:"' + self.mc + price + '"')
|
||||||
format = unicode(self.format_box.text()).strip()
|
format = unicode(self.format_box.text()).strip()
|
||||||
if author:
|
if format:
|
||||||
ans.append('format:"' + self.mc + format + '"')
|
ans.append('format:"' + self.mc + format + '"')
|
||||||
if ans:
|
if ans:
|
||||||
return ' and '.join(ans)
|
return ' and '.join(ans)
|
||||||
|
@ -14,7 +14,7 @@ from PyQt4.Qt import (Qt, QDialog, QDialogButtonBox, QTimer, QCheckBox,
|
|||||||
|
|
||||||
from calibre.gui2 import JSONConfig, info_dialog
|
from calibre.gui2 import JSONConfig, info_dialog
|
||||||
from calibre.gui2.progress_indicator import ProgressIndicator
|
from calibre.gui2.progress_indicator import ProgressIndicator
|
||||||
from calibre.gui2.store.config.search_widget import StoreConfigWidget
|
from calibre.gui2.store.config.search.search_widget import StoreConfigWidget
|
||||||
from calibre.gui2.store.search.adv_search_builder import AdvSearchBuilderDialog
|
from calibre.gui2.store.search.adv_search_builder import AdvSearchBuilderDialog
|
||||||
from calibre.gui2.store.search.download_thread import SearchThreadPool, \
|
from calibre.gui2.store.search.download_thread import SearchThreadPool, \
|
||||||
CacheUpdateThreadPool
|
CacheUpdateThreadPool
|
||||||
@ -238,7 +238,7 @@ class SearchDialog(QDialog, Ui_Dialog):
|
|||||||
v = QVBoxLayout(d)
|
v = QVBoxLayout(d)
|
||||||
button_box.accepted.connect(d.accept)
|
button_box.accepted.connect(d.accept)
|
||||||
button_box.rejected.connect(d.reject)
|
button_box.rejected.connect(d.reject)
|
||||||
d.setWindowTitle(_('Customize Get Books'))
|
d.setWindowTitle(_('Customize get books search'))
|
||||||
config_widget = StoreConfigWidget(self.config)
|
config_widget = StoreConfigWidget(self.config)
|
||||||
v.addWidget(config_widget)
|
v.addWidget(config_widget)
|
||||||
v.addWidget(button_box)
|
v.addWidget(button_box)
|
||||||
|
@ -82,8 +82,8 @@
|
|||||||
<rect>
|
<rect>
|
||||||
<x>0</x>
|
<x>0</x>
|
||||||
<y>0</y>
|
<y>0</y>
|
||||||
<width>102</width>
|
<width>125</width>
|
||||||
<height>129</height>
|
<height>127</height>
|
||||||
</rect>
|
</rect>
|
||||||
</property>
|
</property>
|
||||||
</widget>
|
</widget>
|
||||||
@ -159,6 +159,9 @@
|
|||||||
<property name="expandsOnDoubleClick">
|
<property name="expandsOnDoubleClick">
|
||||||
<bool>false</bool>
|
<bool>false</bool>
|
||||||
</property>
|
</property>
|
||||||
|
<attribute name="headerStretchLastSection">
|
||||||
|
<bool>false</bool>
|
||||||
|
</attribute>
|
||||||
</widget>
|
</widget>
|
||||||
</item>
|
</item>
|
||||||
<item>
|
<item>
|
||||||
|
Loading…
x
Reference in New Issue
Block a user