KG updates

This commit is contained in:
GRiker 2011-01-24 06:01:53 -07:00
commit a10327e0bc
11 changed files with 281 additions and 46 deletions

View File

@ -0,0 +1,36 @@
from calibre.web.feeds.news import BasicNewsRecipe
class AdvancedUserRecipe1295088390(BasicNewsRecipe):
title = u'Everett Herald'
language = 'en'
__author__ = '77ja65'
oldest_article = 4
max_articles_per_feed = 50
no_stylesheets = True
masthead_url = 'http://heraldnet.com/images/hnet/jQueryComponents/jQueryNavigation/heraldnet_logo.png'
extra_css = '.headline {font-size: x-large;} \n .fact { padding-top: 10pt }'
feeds = [(u'Local News',
u'http://heraldnet.com/section/RSS02&mime=xml'),
(u'Sports', u'http://heraldnet.com/section/RSS04&mime=xml'),
(u'Entertainment',
u'http://heraldnet.com/section/RSS07&mime=xml'),
(u'Life', u'http://heraldnet.com/section/RSS03&mime=xml'),
(u'Breaking News',
u'http://heraldnet.com/section/RSS34&mime=xml'),
(u'Seahawks', u'http://heraldnet.com/section/RSS22&mime=xml'),
(u'HeraldNet', u'http://heraldnet.com/section/RSS01&mime=xml'),
(u'Inside Everett',
u'http://heraldnet.com/section/RSS26&mime=xml')
]
def print_version(self, url):
return url + "&template=PrinterFriendly"
extra_css = '''
h1{font-family:Arial,Helvetica,sans-serif; font-
weight:bold;font-size:large;}
h2{font-family:Arial,Helvetica,sans-serif; font-
weight:normal;font-size:small;}
'''

View File

@ -137,17 +137,17 @@ class HeuristicProcessor(object):
] ]
ITALICIZE_STYLE_PATS = [ ITALICIZE_STYLE_PATS = [
r'(?msu)(?<=\s)_(?P<words>\S[^_]{0,40}?\S)?_(?=\s)', r'(?msu)(?<=\s)_(?P<words>\S[^_]{0,40}?\S)?_(?=[\s\.,\!\?])',
r'(?msu)(?<=\s)/(?P<words>\S[^/]{0,40}?\S)?/(?=\s)', r'(?msu)(?<=\s)/(?P<words>\S[^/]{0,40}?\S)?/(?=[\s\.,\!\?])',
r'(?msu)(?<=\s)~~(?P<words>\S[^~]{0,40}?\S)?~~(?=\s)', r'(?msu)(?<=\s)~~(?P<words>\S[^~]{0,40}?\S)?~~(?=[\s\.,\!\?])',
r'(?msu)(?<=\s)\*(?P<words>\S[^\*]{0,40}?\S)?\*(?=\s)', r'(?msu)(?<=\s)\*(?P<words>\S[^\*]{0,40}?\S)?\*(?=[\s\.,\!\?])',
r'(?msu)(?<=\s)~(?P<words>\S[^~]{0,40}?\S)?~(?=\s)', r'(?msu)(?<=\s)~(?P<words>\S[^~]{0,40}?\S)?~(?=[\s\.,\!\?])',
r'(?msu)(?<=\s)_/(?P<words>\S[^/_]{0,40}?\S)?/_(?=\s)', r'(?msu)(?<=\s)_/(?P<words>\S[^/_]{0,40}?\S)?/_(?=[\s\.,\!\?])',
r'(?msu)(?<=\s)_\*(?P<words>\S[^\*_]{0,40}?\S)?\*_(?=\s)', r'(?msu)(?<=\s)_\*(?P<words>\S[^\*_]{0,40}?\S)?\*_(?=[\s\.,\!\?])',
r'(?msu)(?<=\s)\*/(?P<words>\S[^/\*]{0,40}?\S)?/\*(?=\s)', r'(?msu)(?<=\s)\*/(?P<words>\S[^/\*]{0,40}?\S)?/\*(?=[\s\.,\!\?])',
r'(?msu)(?<=\s)_\*/(?P<words>\S[^\*_]{0,40}?\S)?/\*_(?=\s)', r'(?msu)(?<=\s)_\*/(?P<words>\S[^\*_]{0,40}?\S)?/\*_(?=[\s\.,\!\?])',
r'(?msu)(?<=\s)/:(?P<words>\S[^:/]{0,40}?\S)?:/(?=\s)', r'(?msu)(?<=\s)/:(?P<words>\S[^:/]{0,40}?\S)?:/(?=[\s\.,\!\?])',
r'(?msu)(?<=\s)\|:(?P<words>\S[^:\|]{0,40}?\S)?:\|(?=\s)', r'(?msu)(?<=\s)\|:(?P<words>\S[^:\|]{0,40}?\S)?:\|(?=[\s\.,\!\?])',
] ]
for word in ITALICIZE_WORDS: for word in ITALICIZE_WORDS:

View File

@ -166,6 +166,8 @@ class RegexEdit(QWidget, Ui_Edit):
def builder(self): def builder(self):
bld = RegexBuilder(self.db, self.book_id, self.edit.text(), self) bld = RegexBuilder(self.db, self.book_id, self.edit.text(), self)
if bld.cancelled:
return
if bld.exec_() == bld.Accepted: if bld.exec_() == bld.Accepted:
self.edit.setText(bld.regex.text()) self.edit.setText(bld.regex.text())

View File

@ -13,7 +13,7 @@ from calibre.customize.ui import available_input_formats, available_output_forma
device_plugins device_plugins
from calibre.devices.interface import DevicePlugin from calibre.devices.interface import DevicePlugin
from calibre.devices.errors import UserFeedback, OpenFeedback from calibre.devices.errors import UserFeedback, OpenFeedback
from calibre.gui2.dialogs.choose_format import ChooseFormatDialog from calibre.gui2.dialogs.choose_format_device import ChooseFormatDeviceDialog
from calibre.utils.ipc.job import BaseJob from calibre.utils.ipc.job import BaseJob
from calibre.devices.scanner import DeviceScanner from calibre.devices.scanner import DeviceScanner
from calibre.gui2 import config, error_dialog, Dispatcher, dynamic, \ from calibre.gui2 import config, error_dialog, Dispatcher, dynamic, \
@ -826,8 +826,24 @@ class DeviceMixin(object): # {{{
fmt = None fmt = None
if specific: if specific:
d = ChooseFormatDialog(self, _('Choose format to send to device'), formats = []
self.device_manager.device.settings().format_map) aval_out_formats = available_output_formats()
format_count = {}
for row in rows:
fmts = self.library_view.model().db.formats(row.row())
if fmts:
for f in fmts.split(','):
f = f.lower()
if format_count.has_key(f):
format_count[f] += 1
else:
format_count[f] = 1
for f in self.device_manager.device.settings().format_map:
if f in format_count.keys():
formats.append((f, _('%i of %i Books' % (format_count[f], len(rows))), True if f in aval_out_formats else False))
elif f in aval_out_formats:
formats.append((f, _('0 of %i Books' % len(rows)), True))
d = ChooseFormatDeviceDialog(self, _('Choose format to send to device'), formats)
if d.exec_() != QDialog.Accepted: if d.exec_() != QDialog.Accepted:
return return
if d.format(): if d.format():

View File

@ -0,0 +1,53 @@
__license__ = 'GPL v3'
__copyright__ = '2011, John Schember <john@nachtimwald.com>'
from PyQt4.Qt import QDialog, QTreeWidgetItem, QIcon, SIGNAL
from calibre.gui2 import file_icon_provider
from calibre.gui2.dialogs.choose_format_device_ui import Ui_ChooseFormatDeviceDialog
class ChooseFormatDeviceDialog(QDialog, Ui_ChooseFormatDeviceDialog):
def __init__(self, window, msg, formats):
'''
formats is a list of tuples: [(format, exists, convertible)].
format: Lower case format identifier. E.G. mobi
exists: String representing the number of books that
exist in the format.
convertible: True if the format is a convertible format.
formats should be ordered in the device's preferred format ordering.
'''
QDialog.__init__(self, window)
Ui_ChooseFormatDeviceDialog.__init__(self)
self.setupUi(self)
self.connect(self.formats, SIGNAL('activated(QModelIndex)'),
self.activated_slot)
self.msg.setText(msg)
for i, (format, exists, convertible) in enumerate(formats):
t_item = QTreeWidgetItem()
t_item.setIcon(0, file_icon_provider().icon_from_ext(format.lower()))
t_item.setText(0, format.upper())
t_item.setText(1, exists)
if convertible:
t_item.setIcon(2, QIcon(I('ok.png')))
self.formats.addTopLevelItem(t_item)
if i == 0:
self.formats.setCurrentItem(t_item)
t_item.setSelected(True)
self.formats.resizeColumnToContents(2)
self.formats.resizeColumnToContents(1)
self.formats.resizeColumnToContents(0)
self.formats.header().resizeSection(0, self.formats.header().sectionSize(0) * 2)
self._format = None
def activated_slot(self, *args):
self.accept()
def format(self):
return self._format
def accept(self):
self._format = unicode(self.formats.currentItem().text(0))
return QDialog.accept(self)

View File

@ -0,0 +1,111 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>ChooseFormatDeviceDialog</class>
<widget class="QDialog" name="ChooseFormatDeviceDialog">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>507</width>
<height>377</height>
</rect>
</property>
<property name="windowTitle">
<string>Choose Format</string>
</property>
<property name="windowIcon">
<iconset resource="../../../../resources/images.qrc">
<normaloff>:/images/mimetypes/unknown.png</normaloff>:/images/mimetypes/unknown.png</iconset>
</property>
<layout class="QVBoxLayout">
<item>
<widget class="QLabel" name="msg">
<property name="text">
<string/>
</property>
</widget>
</item>
<item>
<widget class="QTreeWidget" name="formats">
<property name="alternatingRowColors">
<bool>true</bool>
</property>
<property name="iconSize">
<size>
<width>64</width>
<height>64</height>
</size>
</property>
<property name="allColumnsShowFocus">
<bool>true</bool>
</property>
<column>
<property name="text">
<string>Format</string>
</property>
</column>
<column>
<property name="text">
<string>Existing</string>
</property>
<property name="textAlignment">
<set>AlignLeft|AlignVCenter</set>
</property>
</column>
<column>
<property name="text">
<string>Convertible</string>
</property>
</column>
</widget>
</item>
<item>
<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>
<resources>
<include location="../../../../resources/images.qrc"/>
</resources>
<connections>
<connection>
<sender>buttonBox</sender>
<signal>accepted()</signal>
<receiver>ChooseFormatDeviceDialog</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>ChooseFormatDeviceDialog</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>

View File

@ -6,8 +6,7 @@ __copyright__ = '2008, Kovid Goyal <kovid at kovidgoyal.net>'
import re, os import re, os
from PyQt4.Qt import Qt, QDialog, QGridLayout, QVBoxLayout, QFont, QLabel, \ from PyQt4.Qt import Qt, QDialog, QGridLayout, QVBoxLayout, QFont, QLabel, \
pyqtSignal, QDialogButtonBox pyqtSignal, QDialogButtonBox, QDate, QLineEdit
from PyQt4 import QtGui
from calibre.gui2.dialogs.metadata_bulk_ui import Ui_MetadataBulkDialog from calibre.gui2.dialogs.metadata_bulk_ui import Ui_MetadataBulkDialog
from calibre.gui2.dialogs.tag_editor import TagEditor from calibre.gui2.dialogs.tag_editor import TagEditor
@ -302,6 +301,7 @@ class MetadataBulkDialog(ResizableDialog, Ui_MetadataBulkDialog):
self.pubdate.setSpecialValueText(_('Undefined')) self.pubdate.setSpecialValueText(_('Undefined'))
self.clear_pubdate_button.clicked.connect(self.clear_pubdate) self.clear_pubdate_button.clicked.connect(self.clear_pubdate)
self.pubdate.dateChanged.connect(self.do_apply_pubdate) self.pubdate.dateChanged.connect(self.do_apply_pubdate)
self.adddate.setDate(QDate.currentDate())
self.adddate.setMinimumDate(UNDEFINED_QDATE) self.adddate.setMinimumDate(UNDEFINED_QDATE)
self.adddate.setSpecialValueText(_('Undefined')) self.adddate.setSpecialValueText(_('Undefined'))
self.clear_adddate_button.clicked.connect(self.clear_adddate) self.clear_adddate_button.clicked.connect(self.clear_adddate)
@ -365,16 +365,16 @@ class MetadataBulkDialog(ResizableDialog, Ui_MetadataBulkDialog):
offset = 10 offset = 10
self.s_r_number_of_books = min(10, len(self.ids)) self.s_r_number_of_books = min(10, len(self.ids))
for i in range(1,self.s_r_number_of_books+1): for i in range(1,self.s_r_number_of_books+1):
w = QtGui.QLabel(self.tabWidgetPage3) w = QLabel(self.tabWidgetPage3)
w.setText(_('Book %d:')%i) w.setText(_('Book %d:')%i)
self.testgrid.addWidget(w, i+offset, 0, 1, 1) self.testgrid.addWidget(w, i+offset, 0, 1, 1)
w = QtGui.QLineEdit(self.tabWidgetPage3) w = QLineEdit(self.tabWidgetPage3)
w.setReadOnly(True) w.setReadOnly(True)
name = 'book_%d_text'%i name = 'book_%d_text'%i
setattr(self, name, w) setattr(self, name, w)
self.book_1_text.setObjectName(name) self.book_1_text.setObjectName(name)
self.testgrid.addWidget(w, i+offset, 1, 1, 1) self.testgrid.addWidget(w, i+offset, 1, 1, 1)
w = QtGui.QLineEdit(self.tabWidgetPage3) w = QLineEdit(self.tabWidgetPage3)
w.setReadOnly(True) w.setReadOnly(True)
name = 'book_%d_result'%i name = 'book_%d_result'%i
setattr(self, name, w) setattr(self, name, w)

View File

@ -515,8 +515,8 @@ class TagsModel(QAbstractItemModel): # {{{
QAbstractItemModel.__init__(self, parent) QAbstractItemModel.__init__(self, parent)
# must do this here because 'QPixmap: Must construct a QApplication # must do this here because 'QPixmap: Must construct a QApplication
# before a QPaintDevice'. The ':' in front avoids polluting either the # before a QPaintDevice'. The ':' at the end avoids polluting either of
# user-defined categories (':' at end) or columns namespaces (no ':'). # the other namespaces (alpha, '#', or '@')
iconmap = {} iconmap = {}
for key in category_icon_map: for key in category_icon_map:
iconmap[key] = QIcon(I(category_icon_map[key])) iconmap[key] = QIcon(I(category_icon_map[key]))
@ -690,7 +690,7 @@ class TagsModel(QAbstractItemModel): # {{{
tb_cats = self.db.field_metadata tb_cats = self.db.field_metadata
for user_cat in sorted(self.db.prefs.get('user_categories', {}).keys(), for user_cat in sorted(self.db.prefs.get('user_categories', {}).keys(),
key=sort_key): key=sort_key):
cat_name = user_cat+':' # add the ':' to avoid name collision cat_name = '@' + user_cat # add the '@' to avoid name collision
tb_cats.add_user_category(label=cat_name, name=user_cat) tb_cats.add_user_category(label=cat_name, name=user_cat)
if len(saved_searches().names()): if len(saved_searches().names()):
tb_cats.add_search_category(label='search', name=_('Searches')) tb_cats.add_search_category(label='search', name=_('Searches'))
@ -997,7 +997,7 @@ class TagsModel(QAbstractItemModel): # {{{
if self.hidden_categories and self.categories[i] in self.hidden_categories: if self.hidden_categories and self.categories[i] in self.hidden_categories:
continue continue
row_index += 1 row_index += 1
if key.endswith(':'): if key.startswith('@'):
# User category, so skip it. The tag will be marked in its real category # User category, so skip it. The tag will be marked in its real category
continue continue
category_item = self.root_item.children[row_index] category_item = self.root_item.children[row_index]
@ -1016,7 +1016,7 @@ class TagsModel(QAbstractItemModel): # {{{
ans.append('%s%s:"=%s"'%(prefix, category, tag.name)) ans.append('%s%s:"=%s"'%(prefix, category, tag.name))
return ans return ans
def find_node(self, key, txt, start_path): def find_item_node(self, key, txt, start_path):
''' '''
Search for an item (a node) in the tags browser list that matches both Search for an item (a node) in the tags browser list that matches both
the key (exact case-insensitive match) and txt (contains case- the key (exact case-insensitive match) and txt (contains case-
@ -1070,6 +1070,22 @@ class TagsModel(QAbstractItemModel): # {{{
break break
return self.path_found return self.path_found
def find_category_node(self, key):
'''
Search for an category node (a top-level node) in the tags browser list
that matches the key (exact case-insensitive match). Returns the path to
the node. Paths are as in find_item_node.
'''
if not key:
return None
for i in xrange(self.rowCount(QModelIndex())):
idx = self.index(i, 0, QModelIndex())
ckey = idx.internalPointer().category_key
if strcmp(ckey, key) == 0:
return self.path_for_index(idx)
return None
def show_item_at_path(self, path, box=False): def show_item_at_path(self, path, box=False):
''' '''
Scroll the browser and open categories to show the item referenced by Scroll the browser and open categories to show the item referenced by
@ -1355,15 +1371,15 @@ class TagBrowserWidget(QWidget): # {{{
self.search_button.setFocus(True) self.search_button.setFocus(True)
self.item_search.lineEdit().blockSignals(False) self.item_search.lineEdit().blockSignals(False)
colon = txt.find(':')
key = None key = None
colon = txt.rfind(':') if len(txt) > 2 else 0
if colon > 0: if colon > 0:
key = self.parent.library_view.model().db.\ key = self.parent.library_view.model().db.\
field_metadata.search_term_to_field_key(txt[:colon]) field_metadata.search_term_to_field_key(txt[:colon])
txt = txt[colon+1:] txt = txt[colon+1:]
self.current_find_position = model.find_node(key, txt, self.current_find_position = \
self.current_find_position) model.find_item_node(key, txt, self.current_find_position)
if self.current_find_position: if self.current_find_position:
model.show_item_at_path(self.current_find_position, box=True) model.show_item_at_path(self.current_find_position, box=True)
elif self.item_search.text(): elif self.item_search.text():

View File

@ -319,7 +319,7 @@ class LibraryDatabase2(LibraryDatabase, SchemaUpgrade, CustomColumns):
self.field_metadata.remove_dynamic_categories() self.field_metadata.remove_dynamic_categories()
tb_cats = self.field_metadata tb_cats = self.field_metadata
for user_cat in sorted(self.prefs.get('user_categories', {}).keys(), key=sort_key): for user_cat in sorted(self.prefs.get('user_categories', {}).keys(), key=sort_key):
cat_name = user_cat+':' # add the ':' to avoid name collision cat_name = '@' + user_cat # add the '@' to avoid name collision
tb_cats.add_user_category(label=cat_name, name=user_cat) tb_cats.add_user_category(label=cat_name, name=user_cat)
if len(saved_searches().names()): if len(saved_searches().names()):
tb_cats.add_search_category(label='search', name=_('Searches')) tb_cats.add_search_category(label='search', name=_('Searches'))
@ -1243,7 +1243,7 @@ class LibraryDatabase2(LibraryDatabase, SchemaUpgrade, CustomColumns):
if category in icon_map: if category in icon_map:
icon = icon_map[label] icon = icon_map[label]
else: else:
icon = icon_map[':custom'] icon = icon_map['custom:']
icon_map[category] = icon icon_map[category] = icon
datatype = cat['datatype'] datatype = cat['datatype']
@ -1339,20 +1339,19 @@ class LibraryDatabase2(LibraryDatabase, SchemaUpgrade, CustomColumns):
if label in taglist and name in taglist[label]: if label in taglist and name in taglist[label]:
items.append(taglist[label][name]) items.append(taglist[label][name])
# else: do nothing, to not include nodes w zero counts # else: do nothing, to not include nodes w zero counts
if len(items): cat_name = '@' + user_cat # add the '@' to avoid name collision
cat_name = user_cat+':' # add the ':' to avoid name collision # Not a problem if we accumulate entries in the icon map
# Not a problem if we accumulate entries in the icon map if icon_map is not None:
if icon_map is not None: icon_map[cat_name] = icon_map['user:']
icon_map[cat_name] = icon_map[':user'] if sort == 'popularity':
if sort == 'popularity': categories[cat_name] = \
categories[cat_name] = \ sorted(items, key=lambda x: x.count, reverse=True)
sorted(items, key=lambda x: x.count, reverse=True) elif sort == 'name':
elif sort == 'name': categories[cat_name] = \
categories[cat_name] = \ sorted(items, key=lambda x: sort_key(x.sort))
sorted(items, key=lambda x: sort_key(x.sort)) else:
else: categories[cat_name] = \
categories[cat_name] = \ sorted(items, key=lambda x:x.avg_rating, reverse=True)
sorted(items, key=lambda x:x.avg_rating, reverse=True)
#### Finally, the saved searches category #### #### Finally, the saved searches category ####
items = [] items = []

View File

@ -16,7 +16,7 @@ class TagsIcons(dict):
''' '''
category_icons = ['authors', 'series', 'formats', 'publisher', 'rating', category_icons = ['authors', 'series', 'formats', 'publisher', 'rating',
'news', 'tags', ':custom', ':user', 'search',] 'news', 'tags', 'custom:', 'user:', 'search',]
def __init__(self, icon_dict): def __init__(self, icon_dict):
for a in self.category_icons: for a in self.category_icons:
if a not in icon_dict: if a not in icon_dict:
@ -31,8 +31,8 @@ category_icon_map = {
'rating' : 'rating.png', 'rating' : 'rating.png',
'news' : 'news.png', 'news' : 'news.png',
'tags' : 'tags.png', 'tags' : 'tags.png',
':custom' : 'column.png', 'custom:' : 'column.png',
':user' : 'drawer.png', 'user:' : 'drawer.png',
'search' : 'search.png' 'search' : 'search.png'
} }

View File

@ -478,6 +478,8 @@ Calibre has several keyboard shortcuts to save you time and mouse movement. Thes
- Focus the search bar - Focus the search bar
* - :kbd:`Shift+Ctrl+F` * - :kbd:`Shift+Ctrl+F`
- Open the advanced search dialog - Open the advanced search dialog
* - :kbd:`Esc`
- Clear the current search
* - :kbd:`N or F3` * - :kbd:`N or F3`
- Find the next book that matches the current search (only works if the highlight checkbox next to the search bar is checked) - Find the next book that matches the current search (only works if the highlight checkbox next to the search bar is checked)
* - :kbd:`Shift+N or Shift+F3` * - :kbd:`Shift+N or Shift+F3`