diff --git a/src/calibre/customize/profiles.py b/src/calibre/customize/profiles.py index 84db12e161..78b61d4345 100644 --- a/src/calibre/customize/profiles.py +++ b/src/calibre/customize/profiles.py @@ -251,10 +251,8 @@ class OutputProfile(Plugin): periodical_date_in_title = True #: Characters used in jackets and catalogs - missing_char = u'x' ratings_char = u'*' empty_ratings_char = u' ' - read_char = u'+' #: Unsupported unicode characters to be replaced during preprocessing unsupported_unicode_chars = [] @@ -292,10 +290,8 @@ class iPadOutput(OutputProfile): } ] - missing_char = u'\u2715\u200a' # stylized 'x' plus hair space ratings_char = u'\u2605' # filled star empty_ratings_char = u'\u2606' # hollow star - read_char = u'\u2713' # check mark touchscreen = True # touchscreen_news_css {{{ @@ -626,10 +622,8 @@ class KindleOutput(OutputProfile): supports_mobi_indexing = True periodical_date_in_title = False - missing_char = u'x\u2009' empty_ratings_char = u'\u2606' ratings_char = u'\u2605' - read_char = u'\u2713' mobi_ems_per_blockquote = 2.0 @@ -651,10 +645,8 @@ class KindleDXOutput(OutputProfile): #comic_screen_size = (741, 1022) supports_mobi_indexing = True periodical_date_in_title = False - missing_char = u'x\u2009' empty_ratings_char = u'\u2606' ratings_char = u'\u2605' - read_char = u'\u2713' mobi_ems_per_blockquote = 2.0 @classmethod diff --git a/src/calibre/gui2/catalog/catalog_epub_mobi.py b/src/calibre/gui2/catalog/catalog_epub_mobi.py index 57a23211e3..485f84a642 100644 --- a/src/calibre/gui2/catalog/catalog_epub_mobi.py +++ b/src/calibre/gui2/catalog/catalog_epub_mobi.py @@ -6,6 +6,7 @@ __license__ = 'GPL v3' __copyright__ = '2009, Kovid Goyal ' __docformat__ = 'restructuredtext en' +from copy import copy from functools import partial from calibre.ebooks.conversion.config import load_defaults @@ -138,7 +139,6 @@ class PluginWidget(QWidget,Ui_Form): self.fetchEligibleCustomFields() self.populate_combo_boxes() - # Update dialog fields from stored options exclusion_rules = [] prefix_rules = [] @@ -161,12 +161,13 @@ class PluginWidget(QWidget,Ui_Form): getattr(self, c_name).setChecked(opt_value) elif c_type in ['spin_box']: getattr(self, c_name).setValue(float(opt_value)) - elif c_type in ['table_widget'] and c_name == 'exclusion_rules_tw': - if opt_value not in exclusion_rules: - exclusion_rules.append(opt_value) - elif c_type in ['table_widget'] and c_name == 'prefix_rules_tw': - if opt_value not in prefix_rules: - prefix_rules.append(opt_value) + if c_type == 'table_widget': + if c_name == 'exclusion_rules_tw': + if opt_value not in exclusion_rules: + exclusion_rules.append(opt_value) + if c_name == 'prefix_rules_tw': + if opt_value not in prefix_rules: + prefix_rules.append(opt_value) # Add icon to the reset button self.reset_exclude_genres_tb.setIcon(QIcon(I('trash.png'))) @@ -187,20 +188,13 @@ class PluginWidget(QWidget,Ui_Form): self.header_note_source_field_name = header_note_source_spec['field'] # Initialize exclusion rules - self.exclusion_rules_table = ExclusionRules(self.exclusion_rules_gb_hl, + self.exclusion_rules_table = ExclusionRules(self.exclusion_rules_gb, "exclusion_rules_tw",exclusion_rules, self.eligible_custom_fields,self.db) # Initialize prefix rules - self.prefix_rules_table = PrefixRules(self.prefix_rules_gb_hl, + self.prefix_rules_table = PrefixRules(self.prefix_rules_gb, "prefix_rules_tw",prefix_rules, self.eligible_custom_fields,self.db) - # Hook changes to thumb_width - self.thumb_width.valueChanged.connect(self.thumb_width_changed) - - # Hook changes to Description section - self.generate_descriptions.stateChanged.connect(self.generate_descriptions_changed) - - def options(self): # Save/return the current options # exclude_genre stores literally @@ -214,6 +208,11 @@ class PluginWidget(QWidget,Ui_Form): for opt in self.OPTION_FIELDS: c_name, c_def, c_type = opt + if c_name == 'exclusion_rules_tw' and exclusion_rules_processed: + continue + if c_name == 'prefix_rules_tw' and prefix_rules_processed: + continue + if c_type in ['check_box', 'radio_button']: opt_value = getattr(self, c_name).isChecked() elif c_type in ['combo_box']: @@ -225,22 +224,21 @@ class PluginWidget(QWidget,Ui_Form): elif c_type in ['table_widget']: if c_name == 'prefix_rules_tw': opt_value = self.prefix_rules_table.get_data() + prefix_rules_processed = True if c_name == 'exclusion_rules_tw': opt_value = self.exclusion_rules_table.get_data() + exclusion_rules_processed = True + # Store UI values to gui.json in config dir gprefs.set(self.name + '_' + c_name, opt_value) # Construct opts object for catalog builder - if c_name == 'exclude_tags': - # store as list - opts_dict[c_name] = opt_value.split(',') - elif c_name == 'prefix_rules_tw': - if prefix_rules_processed: - continue + if c_name == 'prefix_rules_tw': rule_set = [] - for rule in opt_value: + for stored_rule in opt_value: # Test for empty name/field/pattern/prefix, continue # If pattern = any or unspecified, convert to regex + rule = copy(stored_rule) if not rule['enabled']: continue elif not rule['field'] or not rule['pattern'] or not rule['prefix']: @@ -250,24 +248,22 @@ class PluginWidget(QWidget,Ui_Form): # Look up custom column name #print(self.eligible_custom_fields[rule['field']]['field']) rule['field'] = self.eligible_custom_fields[rule['field']]['field'] - if rule['pattern'].startswith('any'): - rule['pattern'] = '.*' - elif rule['pattern'] == 'unspecified': - rule['pattern'] = 'None' + if rule['pattern'].startswith('any'): + rule['pattern'] = '.*' + elif rule['pattern'] == 'unspecified': + rule['pattern'] = 'None' - pr = (rule['name'],rule['field'],rule['pattern'],rule['prefix']) - rule_set.append(pr) + pr = (rule['name'],rule['field'],rule['pattern'],rule['prefix']) + rule_set.append(pr) opt_value = tuple(rule_set) opts_dict['prefix_rules'] = opt_value - prefix_rules_processed = True elif c_name == 'exclusion_rules_tw': - if exclusion_rules_processed: - continue rule_set = [] - for rule in opt_value: + for stored_rule in opt_value: # Test for empty name/field/pattern/prefix, continue # If pattern = any or unspecified, convert to regex + rule = copy(stored_rule) if not rule['enabled']: continue elif not rule['field'] or not rule['pattern']: @@ -277,17 +273,15 @@ class PluginWidget(QWidget,Ui_Form): # Look up custom column name #print(self.eligible_custom_fields[rule['field']]['field']) rule['field'] = self.eligible_custom_fields[rule['field']]['field'] - if rule['pattern'].startswith('any'): - rule['pattern'] = '.*' - elif rule['pattern'] == 'unspecified': - rule['pattern'] = 'None' - - pr = (rule['name'],rule['field'],rule['pattern']) - rule_set.append(pr) + if rule['pattern'].startswith('any'): + rule['pattern'] = '.*' + elif rule['pattern'] == 'unspecified': + rule['pattern'] = 'None' + pr = (rule['name'],rule['field'],rule['pattern']) + rule_set.append(pr) opt_value = tuple(rule_set) opts_dict['exclusion_rules'] = opt_value - exclusion_rules_processed = True else: opts_dict[c_name] = opt_value @@ -372,74 +366,6 @@ class PluginWidget(QWidget,Ui_Form): self.merge_after.setEnabled(False) self.include_hr.setEnabled(False) - def read_source_field_changed(self,new_index): - ''' - Process changes in the read_source_field combo box - Currently using QLineEdit for all field types - Possible to modify to switch QWidget type - ''' - new_source = unicode(self.read_source_field.currentText()) - read_source_spec = self.read_source_fields[new_source] - self.read_source_field_name = read_source_spec['field'] - - # Change pattern input widget to match the source field datatype - if read_source_spec['datatype'] in ['bool','composite','datetime','text']: - if not isinstance(self.read_pattern, QLineEdit): - self.read_spec_hl.removeWidget(self.read_pattern) - dw = QLineEdit(self) - dw.setObjectName('read_pattern') - dw.setToolTip('Pattern for read book') - self.read_pattern = dw - self.read_spec_hl.addWidget(dw) - - def exclude_source_field_changed(self,new_index): - ''' - Process changes in the exclude_source_field combo box - Currently using QLineEdit for all field types - Possible to modify to switch QWidget type - ''' - new_source = str(self.exclude_source_field.currentText()) - self.exclude_source_field_name = new_source - if new_source > '': - exclude_source_spec = self.exclude_source_fields[unicode(new_source)] - self.exclude_source_field_name = exclude_source_spec['field'] - self.exclude_pattern.setEnabled(True) - - # Change pattern input widget to match the source field datatype - if exclude_source_spec['datatype'] in ['bool','composite','datetime','text']: - if not isinstance(self.exclude_pattern, QLineEdit): - self.exclude_spec_hl.removeWidget(self.exclude_pattern) - dw = QLineEdit(self) - dw.setObjectName('exclude_pattern') - dw.setToolTip('Exclusion pattern') - self.exclude_pattern = dw - self.exclude_spec_hl.addWidget(dw) - else: - self.exclude_pattern.setEnabled(False) - - def generate_descriptions_changed(self,new_state): - ''' - Process changes to Descriptions section - 0: unchecked - 2: checked - ''' - - return - ''' - if new_state == 0: - # unchecked - self.merge_source_field.setEnabled(False) - self.merge_before.setEnabled(False) - self.merge_after.setEnabled(False) - self.include_hr.setEnabled(False) - elif new_state == 2: - # checked - self.merge_source_field.setEnabled(True) - self.merge_before.setEnabled(True) - self.merge_after.setEnabled(True) - self.include_hr.setEnabled(True) - ''' - def header_note_source_field_changed(self,new_index): ''' Process changes in the header_note_source_field combo box @@ -476,12 +402,6 @@ class PluginWidget(QWidget,Ui_Form): self.exclude_genre.setText(default[1]) break - def thumb_width_changed(self,new_value): - ''' - Process changes in the thumb_width spin box - ''' - pass - class CheckableTableWidgetItem(QTableWidgetItem): ''' @@ -540,13 +460,14 @@ class GenericRulesTable(QTableWidget): placeholders for basic methods to be overriden ''' - def __init__(self, parent_gb_hl, object_name, rules, eligible_custom_fields, db): + def __init__(self, parent_gb, object_name, rules, eligible_custom_fields, db): self.rules = rules self.eligible_custom_fields = eligible_custom_fields self.db = db QTableWidget.__init__(self) self.setObjectName(object_name) - self.layout = parent_gb_hl + self.layout = QHBoxLayout() + parent_gb.setLayout(self.layout) # Add ourselves to the layout #print("verticalHeader: %s" % dir(self.verticalHeader())) @@ -559,9 +480,11 @@ class GenericRulesTable(QTableWidget): self.setColumnCount(0) self.setRowCount(0) - self.layout.addWidget(self) + self.last_row_selected = self.currentRow() + self.last_rows_selected = self.selectionModel().selectedRows() + self._init_controls() def _init_controls(self): @@ -599,12 +522,12 @@ class GenericRulesTable(QTableWidget): def add_row(self): self.setFocus() - row = self.currentRow() + 1 + row = self.last_row_selected + 1 self.insertRow(row) self.populate_table_row(row, self.create_blank_row_data()) self.select_and_scroll_to_row(row) self.resizeColumnsToContents() - # Just in case table was empty + # In case table was empty self.horizontalHeader().setStretchLastSection(True) def convert_row_to_data(self): @@ -621,12 +544,16 @@ class GenericRulesTable(QTableWidget): def delete_row(self): self.setFocus() - rows = self.selectionModel().selectedRows() + rows = self.last_rows_selected if len(rows) == 0: return - message = '

Are you sure you want to delete this rule?' + + first = rows[0].row() + 1 + last = rows[-1].row() + 1 + + message = '

Are you sure you want to delete rule %d?' % first if len(rows) > 1: - message = '

Are you sure you want to delete the %d selected rules?'%len(rows) + message = '

Are you sure you want to delete rules %d-%d?' % (first, last) if not question_dialog(self, _('Are you sure?'), message, show_copy_button=False): return first_sel_row = self.currentRow() @@ -637,16 +564,18 @@ class GenericRulesTable(QTableWidget): elif self.rowCount() > 0: self.select_and_scroll_to_row(first_sel_row - 1) + def focusOutEvent(self,e): + # Override of QTableWidget method - clear selection when table loses focus + self.last_row_selected = self.currentRow() + self.last_rows_selected = self.selectionModel().selectedRows() + self.clearSelection() + def get_data(self): pass - def focusOutEvent(self,e): - # Override of QTableWidget method - self.clearSelection() - def move_row_down(self): self.setFocus() - rows = self.selectionModel().selectedRows() + rows = self.last_rows_selected if len(rows) == 0: return last_sel_row = rows[-1].row() @@ -673,11 +602,11 @@ class GenericRulesTable(QTableWidget): scroll_to_row = last_sel_row + 1 if scroll_to_row < self.rowCount() - 1: scroll_to_row = scroll_to_row + 1 - self.scrollToItem(self.item(scroll_to_row, 0)) + self.select_and_scroll_to_row(scroll_to_row) def move_row_up(self): self.setFocus() - rows = self.selectionModel().selectedRows() + rows = self.last_rows_selected if len(rows) == 0: return first_sel_row = rows[0].row() @@ -699,7 +628,7 @@ class GenericRulesTable(QTableWidget): scroll_to_row = first_sel_row - 1 if scroll_to_row > 0: scroll_to_row = scroll_to_row - 1 - self.scrollToItem(self.item(scroll_to_row, 0)) + self.select_and_scroll_to_row(scroll_to_row) def populate_table_row(self): ''' @@ -744,7 +673,7 @@ class ExclusionRules(GenericRulesTable): self.setSelectionBehavior(QAbstractItemView.SelectRows) def _initialize(self): - self.populate() + self.populate_table() self.resizeColumnsToContents() self.resize_name(1.5) self.horizontalHeader().setStretchLastSection(True) @@ -780,7 +709,7 @@ class ExclusionRules(GenericRulesTable): 'pattern':data['pattern']}) return data_items - def populate(self): + def populate_table(self): # Format of rules list is different if default values vs retrieved JSON # Hack to normalize list style rules = self.rules @@ -842,6 +771,8 @@ class ExclusionRules(GenericRulesTable): values = ['True','False','unspecified'] elif self.eligible_custom_fields[source_field]['datatype'] in ['composite']: values = ['any value','unspecified'] + elif self.eligible_custom_fields[source_field]['datatype'] in ['datetime']: + values = ['any date','unspecified'] values_combo = ComboBox(self, values, pattern) self.setCellWidget(row, 3, values_combo) @@ -862,7 +793,7 @@ class PrefixRules(GenericRulesTable): def _initialize(self): self.generate_prefix_list() - self.populate() + self.populate_table() self.resizeColumnsToContents() self.resize_name(1.5) self.horizontalHeader().setStretchLastSection(True) @@ -1036,7 +967,7 @@ class PrefixRules(GenericRulesTable): 'prefix':data['prefix']}) return data_items - def populate(self): + def populate_table(self): # Format of rules list is different if default values vs retrieved JSON # Hack to normalize list style rules = self.rules @@ -1108,10 +1039,10 @@ class PrefixRules(GenericRulesTable): values = sorted(values, key=sort_key) elif self.eligible_custom_fields[source_field]['datatype'] in ['bool']: values = ['True','False','unspecified'] - elif self.eligible_custom_fields[source_field]['datatype'] in ['datetime']: - values = ['any date','unspecified'] elif self.eligible_custom_fields[source_field]['datatype'] in ['composite']: values = ['any value','unspecified'] + elif self.eligible_custom_fields[source_field]['datatype'] in ['datetime']: + values = ['any date','unspecified'] values_combo = ComboBox(self, values, pattern) self.setCellWidget(row, 4, values_combo) diff --git a/src/calibre/gui2/catalog/catalog_epub_mobi.ui b/src/calibre/gui2/catalog/catalog_epub_mobi.ui index 784806c15e..bfe94a389f 100644 --- a/src/calibre/gui2/catalog/catalog_epub_mobi.ui +++ b/src/calibre/gui2/catalog/catalog_epub_mobi.ui @@ -35,7 +35,7 @@ - Sections to include in catalog. + Enabled sections will be included in the generated catalog. Included sections @@ -107,12 +107,8 @@ - <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> -<html><head><meta name="qrichtext" content="1" /><style type="text/css"> -p, li { white-space: pre-wrap; } -</style></head><body style=" font-family:'Lucida Grande'; font-size:13pt; font-weight:400; font-style:normal;"> -<p style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">A regular expression describing genres to be excluded from the generated catalog. Genres are derived from the tags applied to your books.</p> -<p style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">The default pattern \[.+\]|\+ excludes tags of the form [tag], e.g., [Project Gutenberg], and '+', the default tag for a read book.</p></body></html> + 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. Excluded genres @@ -209,16 +205,11 @@ p, li { white-space: pre-wrap; } - Matching books will not be included in generated catalog. + Books matching any of the exclusion rules will be excluded from the generated catalog. Excluded books - - - - - @@ -230,16 +221,11 @@ p, li { white-space: pre-wrap; } - The earliest enabled matching rule will be used to add a prefix to book listings in the generated catalog. + The first enabled matching rule will be used to add a prefix to book listings in the generated catalog. Prefix rules - - - - - @@ -305,7 +291,7 @@ p, li { white-space: pre-wrap; } - Size hint for Description cover thumbnails + Size hint for cover thumbnails included in Descriptions section. inch @@ -362,7 +348,7 @@ p, li { white-space: pre-wrap; } - Custom column source for note to include in Description header area + Custom column source for text to include in Description section. @@ -404,7 +390,7 @@ p, li { white-space: pre-wrap; } - Additional content merged with Comments during catalog generation + Custom column containing additional content to be merged with Comments metadata. @@ -418,7 +404,7 @@ p, li { white-space: pre-wrap; } - Merge additional content before Comments + Merge additional content before Comments metadata. &Before @@ -428,7 +414,7 @@ p, li { white-space: pre-wrap; } - Merge additional content after Comments + Merge additional content after Comments metadata. &After @@ -445,7 +431,7 @@ p, li { white-space: pre-wrap; } - Separate Comments and additional content with horizontal rule + Separate Comments metadata and additional content with a horizontal rule. &Separator diff --git a/src/calibre/library/catalogs/epub_mobi_builder.py b/src/calibre/library/catalogs/epub_mobi_builder.py index 63fe02d5f1..825778f0d5 100644 --- a/src/calibre/library/catalogs/epub_mobi_builder.py +++ b/src/calibre/library/catalogs/epub_mobi_builder.py @@ -470,6 +470,7 @@ class CatalogBuilder(object): return self.__output_profile.empty_ratings_char return property(fget=fget) @dynamic_property + def READ_PROGRESS_SYMBOL(self): def fget(self): return "▪" if self.generateForKindle else '+' @@ -672,7 +673,7 @@ Author '{0}': for record in data: matched = list(set(record['tags']) & set(exclude_tags)) if matched : - self.opts.log.info(" - %s (Exclusion rule %s)" % (record['title'], matched)) + self.opts.log.info(" - %s (Exclusion rule Tags: '%s')" % (record['title'], str(matched[0]))) search_phrase = '' if exclude_tags: @@ -4038,7 +4039,9 @@ Author '{0}': if re.search(pat, unicode(field_contents), re.IGNORECASE) is not None: if self.opts.verbose: - self.opts.log.info(" excluding '%s' (%s:%s)" % (record['title'], field, pat)) + field_md = self.db.metadata_for_field(field) + self.opts.log.info(" - %s (Exclusion rule '%s': %s:%s)" % + (record['title'], field_md['name'], field,pat)) exclusion_set.append(record) if record in filtered_data_set: filtered_data_set.remove(record)