Fix #1044619 (When connected to itunes and set to device won't sort by author) and add a link ot the catalog help in the catalog dialog

This commit is contained in:
Kovid Goyal 2012-09-06 16:23:33 +05:30
commit 36939fbfe0
9 changed files with 3811 additions and 2865 deletions

View File

@ -13,7 +13,8 @@ from calibre.constants import isosx, iswindows
from calibre.devices.errors import OpenFeedback, UserFeedback
from calibre.devices.usbms.deviceconfig import DeviceConfig
from calibre.devices.interface import DevicePlugin
from calibre.ebooks.metadata import authors_to_string, MetaInformation, title_sort
from calibre.ebooks.metadata import (author_to_author_sort, authors_to_string,
MetaInformation, title_sort)
from calibre.ebooks.metadata.book.base import Metadata
from calibre.utils.config import config_dir, dynamic, prefs
from calibre.utils.date import now, parse_date
@ -3478,6 +3479,7 @@ class Book(Metadata):
'''
def __init__(self,title,author):
Metadata.__init__(self, title, authors=author.split(' & '))
self.author_sort = author_to_author_sort(author)
@property
def title_sorter(self):

View File

@ -67,7 +67,7 @@ class GenerateCatalogAction(InterfaceAction):
# jobs.results is a list - the first entry is the intended title for the dialog
# Subsequent strings are error messages
dialog_title = job.result.pop(0)
if re.match('warning:', job.result[0].lower()):
if re.search('warning', job.result[0].lower()):
msg = _("Catalog generation complete, with warnings.")
warning_dialog(self.gui, dialog_title, msg, det_msg='\n'.join(job.result), show=True)
else:

View File

@ -6,17 +6,19 @@ __license__ = 'GPL v3'
__copyright__ = '2009, Kovid Goyal <kovid@kovidgoyal.net>'
__docformat__ = 'restructuredtext en'
from copy import copy
import re, sys
from functools import partial
from calibre.ebooks.conversion.config import load_defaults
from calibre.gui2 import gprefs, question_dialog
from calibre.gui2 import gprefs, open_url, question_dialog
from calibre.utils.icu import sort_key
from catalog_epub_mobi_ui import Ui_Form
from PyQt4.Qt import (Qt, QAbstractItemView, QCheckBox, QComboBox,
QDoubleSpinBox, QIcon, QLineEdit, QObject, QRadioButton, QSize, QSizePolicy,
QTableWidget, QTableWidgetItem, QToolButton, QVBoxLayout, QWidget,
QTableWidget, QTableWidgetItem, QTextEdit, QToolButton, QUrl,
QVBoxLayout, QWidget,
SIGNAL)
class PluginWidget(QWidget,Ui_Form):
@ -44,6 +46,7 @@ class PluginWidget(QWidget,Ui_Form):
LineEditControls = []
RadioButtonControls = []
TableWidgetControls = []
TextEditControls = []
for item in self.__dict__:
if type(self.__dict__[item]) is QCheckBox:
@ -58,6 +61,8 @@ class PluginWidget(QWidget,Ui_Form):
RadioButtonControls.append(str(self.__dict__[item].objectName()))
elif type(self.__dict__[item]) is QTableWidget:
TableWidgetControls.append(str(self.__dict__[item].objectName()))
elif type(self.__dict__[item]) is QTextEdit:
TextEditControls.append(str(self.__dict__[item].objectName()))
option_fields = zip(CheckBoxControls,
[True for i in CheckBoxControls],
@ -72,20 +77,23 @@ class PluginWidget(QWidget,Ui_Form):
# LineEditControls
option_fields += zip(['exclude_genre'],['\[.+\]|\+'],['line_edit'])
# TextEditControls
#option_fields += zip(['exclude_genre_results'],['excluded genres will appear here'],['text_edit'])
# SpinBoxControls
option_fields += zip(['thumb_width'],[1.00],['spin_box'])
# Exclusion rules
option_fields += zip(['exclusion_rules_tw','exclusion_rules_tw'],
option_fields += zip(['exclusion_rules_tw'],
[{'ordinal':0,
'enabled':True,
'name':'Catalogs',
'field':'Tags',
'pattern':'Catalog'},],
['table_widget','table_widget'])
['table_widget'])
# Prefix rules
option_fields += zip(['prefix_rules_tw','prefix_rules_tw','prefix_rules_tw'],
option_fields += zip(['prefix_rules_tw','prefix_rules_tw'],
[{'ordinal':0,
'enabled':True,
'name':'Read book',
@ -98,7 +106,7 @@ class PluginWidget(QWidget,Ui_Form):
'field':'Tags',
'pattern':'Wishlist',
'prefix':u'\u00d7'},],
['table_widget','table_widget','table_widget'])
['table_widget','table_widget'])
self.OPTION_FIELDS = option_fields
@ -110,13 +118,13 @@ class PluginWidget(QWidget,Ui_Form):
'''
rule_set = []
for stored_rule in opt_value:
rule = copy(stored_rule)
rule = stored_rule.copy()
# Skip disabled and incomplete rules
if not rule['enabled']:
continue
elif not rule['field'] or not rule['pattern']:
continue
elif 'prefix' in rule and not rule['prefix']:
elif 'prefix' in rule and rule['prefix'] is None:
continue
else:
if rule['field'] != 'Tags':
@ -130,12 +138,58 @@ class PluginWidget(QWidget,Ui_Form):
pr = (rule['name'],rule['field'],rule['pattern'],rule['prefix'])
else:
pr = (rule['name'],rule['field'],rule['pattern'])
rule_set.append(pr)
opt_value = tuple(rule_set)
# Strip off the trailing '_tw'
opts_dict[c_name[:-3]] = opt_value
def fetchEligibleCustomFields(self):
def exclude_genre_changed(self, regex):
""" Dynamically compute excluded genres.
Run exclude_genre regex against db.all_tags() to show excluded tags.
PROVISIONAL CODE, NEEDS TESTING
Args:
regex (QLineEdit.text()): regex to compile, compute
Output:
self.exclude_genre_results (QLabel): updated to show tags to be excluded as genres
"""
results = _('No genres will be excluded')
if not regex:
self.exclude_genre_results.clear()
self.exclude_genre_results.setText(results)
return
try:
pattern = re.compile((str(regex)))
except:
results = _("regex error: %s") % sys.exc_info()[1]
else:
excluded_tags = []
for tag in self.all_tags:
hit = pattern.search(tag)
if hit:
excluded_tags.append(hit.string)
if excluded_tags:
if set(excluded_tags) == set(self.all_tags):
results = _("All genres will be excluded")
else:
results = ', '.join(sorted(excluded_tags))
finally:
if self.DEBUG:
print(results)
self.exclude_genre_results.clear()
self.exclude_genre_results.setText(results)
def exclude_genre_reset(self):
for default in self.OPTION_FIELDS:
if default[0] == 'exclude_genre':
self.exclude_genre.setText(default[1])
break
def fetch_eligible_custom_fields(self):
self.all_custom_fields = self.db.custom_field_keys()
custom_fields = {}
custom_fields['Tags'] = {'field':'tag', 'datatype':u'text'}
@ -146,28 +200,42 @@ class PluginWidget(QWidget,Ui_Form):
'datatype':field_md['datatype']}
self.eligible_custom_fields = custom_fields
def generate_descriptions_changed(self, enabled):
'''
Toggle Description-related controls
'''
self.header_note_source_field.setEnabled(enabled)
self.thumb_width.setEnabled(enabled)
self.merge_source_field.setEnabled(enabled)
self.merge_before.setEnabled(enabled)
self.merge_after.setEnabled(enabled)
self.include_hr.setEnabled(enabled)
def initialize(self, name, db):
'''
CheckBoxControls (c_type: check_box):
['generate_titles','generate_series','generate_genres',
'generate_recently_added','generate_descriptions','include_hr']
'generate_recently_added','generate_descriptions','include_hr']
ComboBoxControls (c_type: combo_box):
['exclude_source_field','header_note_source_field',
'merge_source_field']
'merge_source_field']
LineEditControls (c_type: line_edit):
['exclude_genre']
RadioButtonControls (c_type: radio_button):
['merge_before','merge_after']
['merge_before','merge_after','generate_new_cover', 'use_existing_cover']
SpinBoxControls (c_type: spin_box):
['thumb_width']
TableWidgetControls (c_type: table_widget):
['exclusion_rules_tw','prefix_rules_tw']
TextEditControls (c_type: text_edit):
['exclude_genre_results']
'''
self.name = name
self.db = db
self.fetchEligibleCustomFields()
self.all_tags = db.all_tags()
self.fetch_eligible_custom_fields()
self.populate_combo_boxes()
# Update dialog fields from stored options
@ -200,9 +268,16 @@ class PluginWidget(QWidget,Ui_Form):
if opt_value not in prefix_rules:
prefix_rules.append(opt_value)
# Add icon to the reset button
# Add icon to the reset button, hook textChanged signal
self.reset_exclude_genres_tb.setIcon(QIcon(I('trash.png')))
self.reset_exclude_genres_tb.clicked.connect(self.reset_exclude_genres)
self.reset_exclude_genres_tb.clicked.connect(self.exclude_genre_reset)
# Hook textChanged event for exclude_genre QLineEdit
self.exclude_genre.textChanged.connect(self.exclude_genre_changed)
# Hook Descriptions checkbox for related options, init
self.generate_descriptions.clicked.connect(self.generate_descriptions_changed)
self.generate_descriptions_changed(self.generate_descriptions.isChecked())
# Init self.merge_source_field_name
self.merge_source_field_name = ''
@ -226,6 +301,9 @@ class PluginWidget(QWidget,Ui_Form):
self.prefix_rules_table = PrefixRules(self.prefix_rules_gb,
"prefix_rules_tw",prefix_rules, self.eligible_custom_fields,self.db)
# Initialize excluded genres preview
self.exclude_genre_changed(unicode(getattr(self, 'exclude_genre').text()).strip())
def options(self):
# Save/return the current options
# exclude_genre stores literally
@ -275,16 +353,21 @@ class PluginWidget(QWidget,Ui_Form):
elif self.merge_after.isChecked():
checked = 'after'
include_hr = self.include_hr.isChecked()
opts_dict['merge_comments'] = "%s:%s:%s" % \
opts_dict['merge_comments_rule'] = "%s:%s:%s" % \
(self.merge_source_field_name, checked, include_hr)
opts_dict['header_note_source_field'] = self.header_note_source_field_name
# Fix up exclude_genre regex if blank. Assume blank = no exclusions
if opts_dict['exclude_genre'] == '':
opts_dict['exclude_genre'] = 'a^'
# Append the output profile
try:
opts_dict['output_profile'] = [load_defaults('page_setup')['output_profile']]
except:
opts_dict['output_profile'] = ['default']
if self.DEBUG:
print "opts_dict"
for opt in sorted(opts_dict.keys(), key=sort_key):
@ -377,11 +460,11 @@ class PluginWidget(QWidget,Ui_Form):
self.merge_after.setEnabled(False)
self.include_hr.setEnabled(False)
def reset_exclude_genres(self):
for default in self.OPTION_FIELDS:
if default[0] == 'exclude_genre':
self.exclude_genre.setText(default[1])
break
def show_help(self):
'''
Display help file
'''
open_url(QUrl('http://manual.calibre-ebook.com/catalogs.html'))
class CheckableTableWidgetItem(QTableWidgetItem):
'''

View File

@ -6,7 +6,7 @@
<rect>
<x>0</x>
<y>0</y>
<width>650</width>
<width>658</width>
<height>603</height>
</rect>
</property>
@ -41,151 +41,74 @@
<string>Included sections</string>
</property>
<layout class="QGridLayout" name="gridLayout_2">
<item row="0" column="1">
<widget class="QCheckBox" name="generate_genres">
<property name="text">
<string>Books by &amp;Genre</string>
</property>
</widget>
</item>
<item row="4" column="1">
<widget class="QCheckBox" name="generate_recently_added">
<property name="text">
<string>Recently &amp;Added</string>
</property>
</widget>
</item>
<item row="5" column="1">
<widget class="QCheckBox" name="generate_descriptions">
<property name="text">
<string>&amp;Descriptions</string>
</property>
</widget>
</item>
<item row="5" column="0">
<widget class="QCheckBox" name="generate_series">
<property name="text">
<string>Books by &amp;Series</string>
</property>
</widget>
</item>
<item row="4" column="0">
<widget class="QCheckBox" name="generate_titles">
<property name="text">
<string>Books by &amp;Title</string>
</property>
</widget>
</item>
<item row="0" column="0">
<widget class="QCheckBox" name="generate_authors">
<property name="enabled">
<bool>true</bool>
</property>
<property name="text">
<string>Books by Author</string>
<string>&amp;Authors</string>
</property>
<property name="checked">
<bool>false</bool>
</property>
</widget>
</item>
<item row="0" column="2">
<widget class="QCheckBox" name="generate_titles">
<property name="text">
<string>&amp;Titles</string>
</property>
</widget>
</item>
<item row="0" column="3">
<widget class="QCheckBox" name="generate_series">
<property name="text">
<string>&amp;Series</string>
</property>
</widget>
</item>
<item row="4" column="0">
<widget class="QCheckBox" name="generate_genres">
<property name="text">
<string>&amp;Genres</string>
</property>
</widget>
</item>
<item row="4" column="2">
<widget class="QCheckBox" name="generate_recently_added">
<property name="text">
<string>&amp;Recently Added</string>
</property>
</widget>
</item>
<item row="4" column="3">
<widget class="QCheckBox" name="generate_descriptions">
<property name="text">
<string>&amp;Descriptions</string>
</property>
</widget>
</item>
</layout>
</widget>
</item>
<item>
<widget class="QGroupBox" name="excludedGenres">
<widget class="QGroupBox" name="prefix_rules_gb">
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Maximum">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="minimumSize">
<size>
<width>0</width>
<height>0</height>
</size>
</property>
<property name="toolTip">
<string>A regular expression describing genres to be excluded from the generated catalog. Genres are derived from the tags applied to your books.
The default pattern \[.+\]|\+ excludes tags of the form [tag], e.g., [Test book], and '+', the default tag for a read book.</string>
<string>The first matching prefix rule applies a prefix to book listings in the generated catalog.</string>
</property>
<property name="title">
<string>Excluded genres</string>
<string>Prefixes</string>
</property>
<layout class="QFormLayout" name="formLayout_3">
<property name="fieldGrowthPolicy">
<enum>QFormLayout::FieldsStayAtSizeHint</enum>
</property>
<item row="0" column="0" colspan="2">
<layout class="QHBoxLayout" name="horizontalLayout">
<property name="spacing">
<number>-1</number>
</property>
<property name="leftMargin">
<number>0</number>
</property>
<item>
<widget class="QLabel" name="label">
<property name="minimumSize">
<size>
<width>175</width>
<height>0</height>
</size>
</property>
<property name="maximumSize">
<size>
<width>200</width>
<height>16777215</height>
</size>
</property>
<property name="text">
<string>Tags to &amp;exclude</string>
</property>
<property name="textFormat">
<enum>Qt::AutoText</enum>
</property>
<property name="alignment">
<set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
</property>
<property name="wordWrap">
<bool>true</bool>
</property>
<property name="buddy">
<cstring>exclude_genre</cstring>
</property>
</widget>
</item>
<item>
<widget class="QLineEdit" name="exclude_genre">
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="minimumSize">
<size>
<width>0</width>
<height>0</height>
</size>
</property>
<property name="toolTip">
<string extracomment="Default: \[[\w]*\]"/>
</property>
</widget>
</item>
<item>
<widget class="QToolButton" name="reset_exclude_genres_tb">
<property name="toolTip">
<string>Reset to default</string>
</property>
<property name="text">
<string>...</string>
</property>
</widget>
</item>
</layout>
<layout class="QHBoxLayout" name="horizontalLayout_8">
<item>
<layout class="QHBoxLayout" name="horizontalLayout_6"/>
</item>
</layout>
</widget>
@ -218,22 +141,148 @@ The default pattern \[.+\]|\+ excludes tags of the form [tag], e.g., [Test book]
</widget>
</item>
<item>
<widget class="QGroupBox" name="prefix_rules_gb">
<widget class="QGroupBox" name="excludedGenres">
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Maximum">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="minimumSize">
<size>
<width>0</width>
<height>0</height>
</size>
</property>
<property name="toolTip">
<string>The first matching prefix rule applies a prefix to book listings in the generated catalog.</string>
<string>A regular expression describing genres to be excluded from the generated catalog. Genres are derived from the tags applied to your books.
The default pattern \[.+\]|\+ excludes tags of the form [tag], e.g., [Test book], and '+', the default tag for a read book.</string>
</property>
<property name="title">
<string>Prefixes</string>
<string>Excluded genres</string>
</property>
<layout class="QHBoxLayout" name="horizontalLayout_8">
<item>
<layout class="QHBoxLayout" name="horizontalLayout_6"/>
<layout class="QGridLayout" name="gridLayout">
<item row="0" column="0">
<widget class="QLabel" name="label">
<property name="minimumSize">
<size>
<width>175</width>
<height>0</height>
</size>
</property>
<property name="maximumSize">
<size>
<width>200</width>
<height>16777215</height>
</size>
</property>
<property name="text">
<string>Tags to &amp;exclude (regex):</string>
</property>
<property name="textFormat">
<enum>Qt::AutoText</enum>
</property>
<property name="alignment">
<set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
</property>
<property name="wordWrap">
<bool>true</bool>
</property>
<property name="buddy">
<cstring>exclude_genre</cstring>
</property>
</widget>
</item>
<item row="0" column="1">
<widget class="QLineEdit" name="exclude_genre">
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="minimumSize">
<size>
<width>0</width>
<height>0</height>
</size>
</property>
<property name="toolTip">
<string extracomment="Default: \[[\w]*\]"/>
</property>
</widget>
</item>
<item row="0" column="2">
<widget class="QToolButton" name="reset_exclude_genres_tb">
<property name="toolTip">
<string>Reset to default</string>
</property>
<property name="text">
<string>...</string>
</property>
</widget>
</item>
<item row="1" column="0">
<widget class="QLabel" name="label_2">
<property name="minimumSize">
<size>
<width>175</width>
<height>0</height>
</size>
</property>
<property name="maximumSize">
<size>
<width>200</width>
<height>16777215</height>
</size>
</property>
<property name="text">
<string>Results of regex:</string>
</property>
<property name="textFormat">
<enum>Qt::AutoText</enum>
</property>
<property name="alignment">
<set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
</property>
<property name="wordWrap">
<bool>true</bool>
</property>
<property name="buddy">
<cstring>exclude_genre</cstring>
</property>
</widget>
</item>
<item row="1" column="1">
<widget class="QLabel" name="exclude_genre_results">
<property name="sizePolicy">
<sizepolicy hsizetype="Minimum" vsizetype="Expanding">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="minimumSize">
<size>
<width>0</width>
<height>0</height>
</size>
</property>
<property name="toolTip">
<string>Tags that will be excluded as genres</string>
</property>
<property name="frameShape">
<enum>QFrame::StyledPanel</enum>
</property>
<property name="text">
<string/>
</property>
<property name="textFormat">
<enum>Qt::PlainText</enum>
</property>
<property name="wordWrap">
<bool>true</bool>
</property>
</widget>
</item>
</layout>
</widget>
@ -255,142 +304,9 @@ The default pattern \[.+\]|\+ excludes tags of the form [tag], e.g., [Test book]
<property name="title">
<string>Other options</string>
</property>
<layout class="QFormLayout" name="formLayout_4">
<property name="fieldGrowthPolicy">
<enum>QFormLayout::FieldsStayAtSizeHint</enum>
</property>
<item row="0" column="0" colspan="2">
<layout class="QHBoxLayout" name="horizontalLayout_5">
<item>
<widget class="QLabel" name="label_10">
<property name="minimumSize">
<size>
<width>175</width>
<height>0</height>
</size>
</property>
<property name="maximumSize">
<size>
<width>200</width>
<height>16777215</height>
</size>
</property>
<property name="text">
<string>&amp;Thumb width</string>
</property>
<property name="alignment">
<set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
</property>
<property name="buddy">
<cstring>merge_source_field</cstring>
</property>
</widget>
</item>
<item>
<widget class="QDoubleSpinBox" name="thumb_width">
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="maximumSize">
<size>
<width>137</width>
<height>16777215</height>
</size>
</property>
<property name="toolTip">
<string>Size hint for cover thumbnails included in Descriptions section.</string>
</property>
<property name="suffix">
<string> inch</string>
</property>
<property name="decimals">
<number>2</number>
</property>
<property name="minimum">
<double>1.000000000000000</double>
</property>
<property name="maximum">
<double>2.000000000000000</double>
</property>
<property name="singleStep">
<double>0.100000000000000</double>
</property>
</widget>
</item>
<item>
<widget class="Line" name="line_3">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="label_3">
<property name="text">
<string>&amp;Extra note</string>
</property>
<property name="buddy">
<cstring>header_note_source_field</cstring>
</property>
</widget>
</item>
<item>
<widget class="QComboBox" name="header_note_source_field">
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="minimumSize">
<size>
<width>0</width>
<height>0</height>
</size>
</property>
<property name="maximumSize">
<size>
<width>16777215</width>
<height>16777215</height>
</size>
</property>
<property name="toolTip">
<string>Custom column source for text to include in Description section.</string>
</property>
</widget>
</item>
</layout>
</item>
<item row="1" column="0" colspan="2">
<layout class="QHBoxLayout" name="horizontalLayout_7">
<item>
<widget class="QLabel" name="label_9">
<property name="minimumSize">
<size>
<width>175</width>
<height>0</height>
</size>
</property>
<property name="maximumSize">
<size>
<width>200</width>
<height>16777215</height>
</size>
</property>
<property name="text">
<string>&amp;Merge with Comments</string>
</property>
<property name="alignment">
<set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
</property>
<property name="buddy">
<cstring>merge_source_field</cstring>
</property>
</widget>
</item>
<layout class="QGridLayout" name="gridLayout_3">
<item row="2" column="1">
<layout class="QHBoxLayout" name="merge_with_comments_hl">
<item>
<widget class="QComboBox" name="merge_source_field">
<property name="minimumSize">
@ -419,6 +335,9 @@ The default pattern \[.+\]|\+ excludes tags of the form [tag], e.g., [Test book]
<property name="text">
<string>&amp;Before</string>
</property>
<attribute name="buttonGroup">
<string>merge_options_bg</string>
</attribute>
</widget>
</item>
<item>
@ -429,6 +348,9 @@ The default pattern \[.+\]|\+ excludes tags of the form [tag], e.g., [Test book]
<property name="text">
<string>&amp;After</string>
</property>
<attribute name="buttonGroup">
<string>merge_options_bg</string>
</attribute>
</widget>
</item>
<item>
@ -444,7 +366,196 @@ The default pattern \[.+\]|\+ excludes tags of the form [tag], e.g., [Test book]
<string>Separate Comments metadata and additional content with a horizontal rule.</string>
</property>
<property name="text">
<string>&amp;Separator</string>
<string>Include &amp;Separator</string>
</property>
</widget>
</item>
</layout>
</item>
<item row="2" column="0">
<widget class="QLabel" name="label_9">
<property name="minimumSize">
<size>
<width>175</width>
<height>0</height>
</size>
</property>
<property name="maximumSize">
<size>
<width>200</width>
<height>16777215</height>
</size>
</property>
<property name="text">
<string>&amp;Merge with Comments:</string>
</property>
<property name="alignment">
<set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
</property>
<property name="buddy">
<cstring>merge_source_field</cstring>
</property>
</widget>
</item>
<item row="0" column="0">
<widget class="QLabel" name="label_4">
<property name="minimumSize">
<size>
<width>175</width>
<height>20</height>
</size>
</property>
<property name="text">
<string>Catalog cover:</string>
</property>
<property name="alignment">
<set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
</property>
</widget>
</item>
<item row="0" column="1">
<layout class="QHBoxLayout" name="replace_cover_hl">
<item>
<widget class="QRadioButton" name="generate_new_cover">
<property name="text">
<string>Generate new cover</string>
</property>
<property name="checked">
<bool>true</bool>
</property>
<attribute name="buttonGroup">
<string>cover_options_bg</string>
</attribute>
</widget>
</item>
<item>
<widget class="QRadioButton" name="use_existing_cover">
<property name="text">
<string>Use existing cover</string>
</property>
<attribute name="buttonGroup">
<string>cover_options_bg</string>
</attribute>
</widget>
</item>
<item>
<widget class="QLabel" name="spacer_label">
<property name="text">
<string/>
</property>
</widget>
</item>
</layout>
</item>
<item row="1" column="0">
<widget class="QLabel" name="label_3">
<property name="text">
<string>E&amp;xtra Description note:</string>
</property>
<property name="alignment">
<set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
</property>
<property name="buddy">
<cstring>header_note_source_field</cstring>
</property>
</widget>
</item>
<item row="1" column="1">
<layout class="QHBoxLayout" name="horizontalLayout">
<item>
<widget class="QComboBox" name="header_note_source_field">
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="minimumSize">
<size>
<width>0</width>
<height>0</height>
</size>
</property>
<property name="maximumSize">
<size>
<width>16777215</width>
<height>16777215</height>
</size>
</property>
<property name="toolTip">
<string>Custom column source for text to include in Description section.</string>
</property>
</widget>
</item>
<item>
<widget class="Line" name="line_4">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="label_10">
<property name="sizePolicy">
<sizepolicy hsizetype="Minimum" vsizetype="Preferred">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="minimumSize">
<size>
<width>0</width>
<height>0</height>
</size>
</property>
<property name="maximumSize">
<size>
<width>16777215</width>
<height>16777215</height>
</size>
</property>
<property name="text">
<string>&amp;Thumb width:</string>
</property>
<property name="alignment">
<set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
</property>
<property name="buddy">
<cstring>merge_source_field</cstring>
</property>
</widget>
</item>
<item>
<widget class="QDoubleSpinBox" name="thumb_width">
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="maximumSize">
<size>
<width>16777215</width>
<height>16777215</height>
</size>
</property>
<property name="toolTip">
<string>Size hint for cover thumbnails included in Descriptions section.</string>
</property>
<property name="suffix">
<string> inch</string>
</property>
<property name="decimals">
<number>2</number>
</property>
<property name="minimum">
<double>1.000000000000000</double>
</property>
<property name="maximum">
<double>2.000000000000000</double>
</property>
<property name="singleStep">
<double>0.100000000000000</double>
</property>
</widget>
</item>
@ -457,4 +568,8 @@ The default pattern \[.+\]|\+ excludes tags of the form [tag], e.g., [Test book]
</widget>
<resources/>
<connections/>
<buttongroups>
<buttongroup name="cover_options_bg"/>
<buttongroup name="merge_options_bg"/>
</buttongroups>
</ui>

View File

@ -10,7 +10,7 @@ import os, sys, importlib
from calibre.customize.ui import config
from calibre.gui2.dialogs.catalog_ui import Ui_Dialog
from calibre.gui2 import dynamic, ResizableDialog
from calibre.gui2 import dynamic, ResizableDialog, info_dialog
from calibre.customize.ui import catalog_plugins
class Catalog(ResizableDialog, Ui_Dialog):
@ -22,7 +22,6 @@ class Catalog(ResizableDialog, Ui_Dialog):
from PyQt4.uic import compileUi
ResizableDialog.__init__(self, parent)
self.dbspec, self.ids = dbspec, ids
# Display the number of books we've been passed
@ -115,6 +114,7 @@ class Catalog(ResizableDialog, Ui_Dialog):
self.format.currentIndexChanged.connect(self.show_plugin_tab)
self.buttonBox.button(self.buttonBox.Apply).clicked.connect(self.apply)
self.buttonBox.button(self.buttonBox.Help).clicked.connect(self.help)
self.show_plugin_tab(None)
geom = dynamic.get('catalog_window_geom', None)
@ -129,6 +129,10 @@ class Catalog(ResizableDialog, Ui_Dialog):
if cf in pw.formats:
self.tabs.addTab(pw, pw.TITLE)
break
if hasattr(self.tabs.widget(1),'show_help'):
self.buttonBox.button(self.buttonBox.Help).setVisible(True)
else:
self.buttonBox.button(self.buttonBox.Help).setVisible(False)
def format_changed(self, idx):
cf = unicode(self.format.currentText())
@ -165,6 +169,29 @@ class Catalog(ResizableDialog, Ui_Dialog):
self.save_catalog_settings()
return ResizableDialog.accept(self)
def help(self):
'''
To add help functionality for a specific format:
In gui2.catalog.catalog_<format>.py, add the following:
from calibre.gui2 import open_url
from PyQt4.Qt import QUrl
In the PluginWidget() class, add this method:
def show_help(self):
url = 'file:///' + P('catalog/help_<format>.html')
open_url(QUrl(url))
Create the help file at resources/catalog/help_<format>.html
'''
if self.tabs.count() > 1 and hasattr(self.tabs.widget(1),'show_help'):
try:
self.tabs.widget(1).show_help()
except:
info_dialog(self, _('No help available'),
_('No help available for this output format.'),
show_copy_button=False,
show=True)
def reject(self):
dynamic.set('catalog_window_geom', bytearray(self.saveGeometry()))
ResizableDialog.reject(self)

View File

@ -14,7 +14,7 @@
<string>Generate catalog</string>
</property>
<property name="windowIcon">
<iconset resource="../../../../resources/images.qrc">
<iconset>
<normaloff>:/images/lt.png</normaloff>:/images/lt.png</iconset>
</property>
<layout class="QGridLayout" name="gridLayout">
@ -37,7 +37,7 @@
<enum>Qt::Horizontal</enum>
</property>
<property name="standardButtons">
<set>QDialogButtonBox::Apply|QDialogButtonBox::Cancel|QDialogButtonBox::Ok</set>
<set>QDialogButtonBox::Apply|QDialogButtonBox::Cancel|QDialogButtonBox::Help|QDialogButtonBox::Ok</set>
</property>
</widget>
</item>
@ -54,8 +54,8 @@
<rect>
<x>0</x>
<y>0</y>
<width>666</width>
<height>599</height>
<width>650</width>
<height>575</height>
</rect>
</property>
<layout class="QVBoxLayout" name="verticalLayout">

View File

@ -17,4 +17,6 @@ FIELDS = ['all', 'title', 'title_sort', 'author_sort', 'authors', 'comments',
TEMPLATE_ALLOWED_FIELDS = [ 'author_sort', 'authors', 'id', 'isbn', 'pubdate', 'title_sort',
'publisher', 'series_index', 'series', 'tags', 'timestamp', 'title', 'uuid' ]
class AuthorSortMismatchException(Exception): pass
class EmptyCatalogException(Exception): pass

View File

@ -7,13 +7,14 @@ __license__ = 'GPL v3'
__copyright__ = '2012, Kovid Goyal <kovid@kovidgoyal.net>'
__docformat__ = 'restructuredtext en'
import os
import os, shutil, sys, time
from collections import namedtuple
from calibre import strftime
from calibre.customize import CatalogPlugin
from calibre.customize.conversion import OptionRecommendation, DummyReporter
from calibre.ebooks import calibre_cover
from calibre.library.catalogs import AuthorSortMismatchException, EmptyCatalogException
from calibre.ptempfile import PersistentTemporaryFile
Option = namedtuple('Option', 'option, default, dest, action, help')
@ -120,9 +121,9 @@ class EPUB_MOBI(CatalogPlugin):
help=_("Custom field containing note text to insert in Description header.\n"
"Default: '%default'\n"
"Applies to: AZW3, ePub, MOBI output formats")),
Option('--merge-comments',
Option('--merge-comments-rule',
default='::',
dest='merge_comments',
dest='merge_comments_rule',
action = None,
help=_("#<custom field>:[before|after]:[True|False] specifying:\n"
" <custom field> Custom field containing notes to merge with Comments\n"
@ -146,6 +147,13 @@ class EPUB_MOBI(CatalogPlugin):
"When multiple rules are defined, the first matching rule will be used.\n"
"Default:\n" + '"' + '%default' + '"' + "\n"
"Applies to AZW3, ePub, MOBI output formats")),
Option('--use-existing-cover',
default=False,
dest='use_existing_cover',
action = 'store_true',
help=_("Replace existing cover when generating the catalog.\n"
"Default: '%default'\n"
"Applies to: AZW3, ePub, MOBI output formats")),
Option('--thumb-width',
default='1.0',
dest='thumb_width',
@ -182,8 +190,8 @@ class EPUB_MOBI(CatalogPlugin):
else:
op = "kindle"
opts.descriptionClip = 380 if op.endswith('dx') or 'kindle' not in op else 100
opts.authorClip = 100 if op.endswith('dx') or 'kindle' not in op else 60
opts.description_clip = 380 if op.endswith('dx') or 'kindle' not in op else 100
opts.author_clip = 100 if op.endswith('dx') or 'kindle' not in op else 60
opts.output_profile = op
opts.basename = "Catalog"
@ -198,11 +206,12 @@ class EPUB_MOBI(CatalogPlugin):
(self.name,self.fmt,'for %s ' % opts.output_profile if opts.output_profile else '',
'CLI' if opts.cli_environment else 'GUI'))
# If exclude_genre is blank, assume user wants all genre tags included
# If exclude_genre is blank, assume user wants all tags as genres
if opts.exclude_genre.strip() == '':
opts.exclude_genre = '\[^.\]'
build_log.append(" converting empty exclude_genre to '\[^.\]'")
#opts.exclude_genre = '\[^.\]'
#build_log.append(" converting empty exclude_genre to '\[^.\]'")
opts.exclude_genre = 'a^'
build_log.append(" converting empty exclude_genre to 'a^'")
if opts.connected_device['is_device_connected'] and \
opts.connected_device['kind'] == 'device':
if opts.connected_device['serial']:
@ -304,13 +313,13 @@ class EPUB_MOBI(CatalogPlugin):
keys.sort()
build_log.append(" opts:")
for key in keys:
if key in ['catalog_title','authorClip','connected_kindle','descriptionClip',
if key in ['catalog_title','author_clip','connected_kindle','description_clip',
'exclude_book_marker','exclude_genre','exclude_tags',
'exclusion_rules',
'header_note_source_field','merge_comments',
'exclusion_rules', 'fmt',
'header_note_source_field','merge_comments_rule',
'output_profile','prefix_rules','read_book_marker',
'search_text','sort_by','sort_descriptions_by_author','sync',
'thumb_width','wishlist_tag']:
'thumb_width','use_existing_cover','wishlist_tag']:
build_log.append(" %s: %s" % (key, repr(opts_dict[key])))
if opts.verbose:
@ -323,26 +332,30 @@ class EPUB_MOBI(CatalogPlugin):
if opts.verbose:
log.info(" Begin catalog source generation")
catalog.createDirectoryStructure()
catalog.copyResources()
catalog.calculateThumbnailSize()
catalog_source_built = catalog.buildSources()
if opts.verbose:
if catalog_source_built:
try:
catalog_source_built = catalog.build_sources()
if opts.verbose:
log.info(" Completed catalog source generation\n")
else:
log.error(" *** Terminated catalog generation, check log for details ***")
except (AuthorSortMismatchException, EmptyCatalogException), e:
log.error(" *** Terminated catalog generation: %s ***" % e)
except:
log.error(" unhandled exception in catalog generator")
raise
if catalog_source_built:
else:
recommendations = []
recommendations.append(('remove_fake_margins', False,
OptionRecommendation.HIGH))
recommendations.append(('comments', '', OptionRecommendation.HIGH))
# >>> Use to debug generated catalog code before conversion <<<
if False:
setattr(opts,'debug_pipeline',os.path.expanduser("~/Desktop/Catalog debug"))
"""
>>> Use to debug generated catalog code before pipeline conversion <<<
"""
GENERATE_DEBUG_EPUB = False
if GENERATE_DEBUG_EPUB:
catalog_debug_path = os.path.join(os.path.expanduser('~'),'Desktop','Catalog debug')
setattr(opts,'debug_pipeline',os.path.expanduser(catalog_debug_path))
dp = getattr(opts, 'debug_pipeline', None)
if dp is not None:
@ -357,9 +370,9 @@ class EPUB_MOBI(CatalogPlugin):
recommendations.append(('book_producer',opts.output_profile,
OptionRecommendation.HIGH))
# If cover exists, use it
# Use existing cover or generate new cover
cpath = None
generate_new_cover = False
existing_cover = False
try:
search_text = 'title:"%s" author:%s' % (
opts.catalog_title.replace('"', '\\"'), 'calibre')
@ -367,19 +380,18 @@ class EPUB_MOBI(CatalogPlugin):
if matches:
cpath = db.cover(matches[0], index_is_id=True, as_path=True)
if cpath and os.path.exists(cpath):
recommendations.append(('cover', cpath,
OptionRecommendation.HIGH))
log.info("using existing cover")
else:
log.info("no existing cover, generating new cover")
generate_new_cover = True
else:
log.info("no existing cover, generating new cover")
generate_new_cover = True
existing_cover = True
except:
pass
if generate_new_cover:
if self.opts.use_existing_cover and not existing_cover:
log.warning("no existing catalog cover found")
if self.opts.use_existing_cover and existing_cover:
recommendations.append(('cover', cpath, OptionRecommendation.HIGH))
log.info("using existing catalog cover")
else:
log.info("replacing catalog cover")
new_cover_path = PersistentTemporaryFile(suffix='.jpg')
new_cover = calibre_cover(opts.catalog_title.replace('"', '\\"'), 'calibre')
new_cover_path.write(new_cover)
@ -388,7 +400,7 @@ class EPUB_MOBI(CatalogPlugin):
# Run ebook-convert
from calibre.ebooks.conversion.plumber import Plumber
plumber = Plumber(os.path.join(catalog.catalogPath,
plumber = Plumber(os.path.join(catalog.catalog_path,
opts.basename + '.opf'), path_to_output, log, report_progress=notification,
abort_after_input_dump=False)
plumber.merge_ui_recommendations(recommendations)
@ -399,6 +411,13 @@ class EPUB_MOBI(CatalogPlugin):
except:
pass
if GENERATE_DEBUG_EPUB:
from calibre.ebooks.tweak import zip_rebuilder
input_path = os.path.join(catalog_debug_path,'input')
shutil.copy(P('catalog/mimetype'),input_path)
shutil.copytree(P('catalog/META-INF'),os.path.join(input_path,'META-INF'))
zip_rebuilder(input_path, os.path.join(catalog_debug_path,'input.epub'))
# returns to gui2.actions.catalog:catalog_generated()
return catalog.error

File diff suppressed because it is too large Load Diff