diff --git a/resources/catalog/stylesheet.css b/resources/catalog/stylesheet.css index f14b1aaa29..48edce76c8 100644 --- a/resources/catalog/stylesheet.css +++ b/resources/catalog/stylesheet.css @@ -74,7 +74,7 @@ p.date_read { p.author { font-size:large; margin-top:0em; - margin-bottom:0em; + margin-bottom:0.1em; text-align: center; text-indent: 0em; } @@ -122,7 +122,7 @@ p.genres { p.series { font-style:italic; - margin-top:0.25em; + margin-top:0.10em; margin-bottom:0em; margin-left:2em; text-align:left; diff --git a/src/calibre/gui2/catalog/catalog_epub_mobi.py b/src/calibre/gui2/catalog/catalog_epub_mobi.py index 42143eb506..185b6ab668 100644 --- a/src/calibre/gui2/catalog/catalog_epub_mobi.py +++ b/src/calibre/gui2/catalog/catalog_epub_mobi.py @@ -15,8 +15,9 @@ 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, QRadioButton, QSize, QSizePolicy, - QTableWidget, QTableWidgetItem, QToolButton, QVBoxLayout, QWidget) + QDoubleSpinBox, QIcon, QLineEdit, QObject, QRadioButton, QSize, QSizePolicy, + QTableWidget, QTableWidgetItem, QToolButton, QVBoxLayout, QWidget, + SIGNAL) class PluginWidget(QWidget,Ui_Form): @@ -454,6 +455,8 @@ class GenericRulesTable(QTableWidget): Add QTableWidget, controls to parent QGroupBox placeholders for basic methods to be overriden ''' + FOCUS_SWITCHING = True + DEBUG = False def __init__(self, parent_gb, object_name, rules, eligible_custom_fields, db): self.rules = rules @@ -476,11 +479,15 @@ class GenericRulesTable(QTableWidget): self.setRowCount(0) self.layout.addWidget(self) - self.last_row_selected = self.currentRow() - self.last_rows_selected = self.selectionModel().selectedRows() + if self.FOCUS_SWITCHING: + self.last_row_selected = self.currentRow() + self.last_rows_selected = self.selectionModel().selectedRows() self._init_controls() + # Hook check_box changes. Everything else is already hooked + QObject.connect(self, SIGNAL('cellChanged(int,int)'), self.enabled_state_changed) + def _init_controls(self): # Add the control set vbl = QVBoxLayout() @@ -516,7 +523,12 @@ class GenericRulesTable(QTableWidget): def add_row(self): self.setFocus() - row = self.last_row_selected + 1 + if self.FOCUS_SWITCHING: + row = self.last_row_selected + 1 + else: + row = self.currentRow() + 1 + if self.DEBUG and self.FOCUS_SWITCHING: + print("%s:add_row(): last_row_selected: %d, row: %d" % (self.objectName(), self.last_row_selected, row)) self.insertRow(row) self.populate_table_row(row, self.create_blank_row_data()) self.select_and_scroll_to_row(row) @@ -538,7 +550,10 @@ class GenericRulesTable(QTableWidget): def delete_row(self): self.setFocus() - rows = self.last_rows_selected + if self.FOCUS_SWITCHING: + rows = self.last_rows_selected + else: + rows = self.selectionModel().selectedRows() if len(rows) == 0: return @@ -558,11 +573,18 @@ class GenericRulesTable(QTableWidget): elif self.rowCount() > 0: self.select_and_scroll_to_row(first_sel_row - 1) + def focusInEvent(self,e): + if self.DEBUG: + print("%s:focusInEvent()" % self.objectName()) + 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() + if self.FOCUS_SWITCHING: + self.last_row_selected = self.currentRow() + self.last_rows_selected = self.selectionModel().selectedRows() + self.clearSelection() + if self.DEBUG: + print("%s:focusOutEvent(): self.last_row_selected: %d" % (self.objectName(),self.last_row_selected)) def get_data(self): ''' @@ -572,7 +594,10 @@ class GenericRulesTable(QTableWidget): def move_row_down(self): self.setFocus() - rows = self.last_rows_selected + if self.FOCUS_SWITCHING: + rows = self.last_rows_selected + else: + rows = self.selectionModel().selectedRows() if len(rows) == 0: return last_sel_row = rows[-1].row() @@ -598,13 +623,16 @@ class GenericRulesTable(QTableWidget): self.blockSignals(False) scroll_to_row = last_sel_row + 1 - if scroll_to_row < self.rowCount() - 1: - scroll_to_row = scroll_to_row + 1 + #if scroll_to_row < self.rowCount() - 1: + # scroll_to_row = scroll_to_row + 1 self.select_and_scroll_to_row(scroll_to_row) def move_row_up(self): self.setFocus() - rows = self.last_rows_selected + if self.FOCUS_SWITCHING: + rows = self.last_rows_selected + else: + rows = self.selectionModel().selectedRows() if len(rows) == 0: return first_sel_row = rows[0].row() @@ -623,10 +651,14 @@ class GenericRulesTable(QTableWidget): self.removeRow(selrow.row() - 1) self.blockSignals(False) - scroll_to_row = first_sel_row - 1 + scroll_to_row = first_sel_row if scroll_to_row > 0: scroll_to_row = scroll_to_row - 1 self.select_and_scroll_to_row(scroll_to_row) + if self.DEBUG: + print("%s:move_row_up(): first_sel_row: %d" % (self.objectName(), first_sel_row)) + print("%s:move_row_up(): scroll_to_row: %d" % (self.objectName(), scroll_to_row)) + print("%s move_row_down(): current_row: %d" % (self.objectName(), self.currentRow())) def populate_table_row(self): ''' @@ -642,13 +674,69 @@ class GenericRulesTable(QTableWidget): def rule_name_edited(self): current_row = self.currentRow() self.cellWidget(current_row,1).home(False) - self.setFocus() self.select_and_scroll_to_row(current_row) def select_and_scroll_to_row(self, row): + self.setFocus() self.selectRow(row) self.scrollToItem(self.currentItem()) + def _source_index_changed(self, combo): + # Figure out which row we're in + for row in range(self.rowCount()): + if self.cellWidget(row, self.COLUMNS['FIELD']['ordinal']) is combo: + break + + if self.DEBUG: + print("%s:_source_index_changed(): calling source_index_changed with row: %d " % + (self.objectName(), row)) + + self.source_index_changed(combo, row) + + def source_index_changed(self, combo, row, pattern=''): + # Populate the Pattern field based upon the Source field + + source_field = str(combo.currentText()) + if source_field == '': + values = [] + elif source_field == 'Tags': + values = sorted(self.db.all_tags(), key=sort_key) + else: + if self.eligible_custom_fields[unicode(source_field)]['datatype'] in ['enumeration', 'text']: + values = self.db.all_custom(self.db.field_metadata.key_to_label( + self.eligible_custom_fields[unicode(source_field)]['field'])) + values = sorted(values, key=sort_key) + elif self.eligible_custom_fields[unicode(source_field)]['datatype'] in ['bool']: + values = [_('True'),_('False'),_('unspecified')] + elif self.eligible_custom_fields[unicode(source_field)]['datatype'] in ['composite']: + values = [_('any value'),_('unspecified')] + elif self.eligible_custom_fields[unicode(source_field)]['datatype'] in ['datetime']: + values = [_('any date'),_('unspecified')] + + values_combo = ComboBox(self, values, pattern) + values_combo.currentIndexChanged.connect(partial(self.values_index_changed, values_combo)) + self.setCellWidget(row, self.COLUMNS['PATTERN']['ordinal'], values_combo) + self.select_and_scroll_to_row(row) + + def values_index_changed(self, combo): + # After edit, select row + for row in range(self.rowCount()): + if self.cellWidget(row, self.COLUMNS['PATTERN']['ordinal']) is combo: + self.select_and_scroll_to_row(row) + break + + if self.DEBUG: + print("%s:values_index_changed(): row %d " % + (self.objectName(), row)) + + def enabled_state_changed(self, row, col): + # After state change, select row + if col in [self.COLUMNS['ENABLED']['ordinal']]: + self.select_and_scroll_to_row(row) + if self.DEBUG: + print("%s:enabled_state_changed(): row %d col %d" % + (self.objectName(), row, col)) + class ExclusionRules(GenericRulesTable): COLUMNS = { 'ENABLED':{'ordinal': 0, 'name': ''}, @@ -658,6 +746,7 @@ class ExclusionRules(GenericRulesTable): def __init__(self, parent_gb_hl, object_name, rules, eligible_custom_fields, db): super(ExclusionRules, self).__init__(parent_gb_hl, object_name, rules, eligible_custom_fields, db) + self.setObjectName("exclusion_rules_table") self._init_table_widget() self._initialize() @@ -730,7 +819,7 @@ class ExclusionRules(GenericRulesTable): def set_source_field_in_row(row, col, field=''): source_combo = ComboBox(self, sorted(self.eligible_custom_fields.keys(), key=sort_key), field) - source_combo.currentIndexChanged.connect(partial(self.source_index_changed, source_combo, row)) + source_combo.currentIndexChanged.connect(partial(self._source_index_changed, source_combo)) self.setCellWidget(row, col, source_combo) return source_combo @@ -738,7 +827,8 @@ class ExclusionRules(GenericRulesTable): self.blockSignals(True) # Enabled - self.setItem(row, self.COLUMNS['ENABLED']['ordinal'], CheckableTableWidgetItem(data['enabled'])) + check_box = CheckableTableWidgetItem(data['enabled']) + self.setItem(row, self.COLUMNS['ENABLED']['ordinal'], check_box) # Rule name set_rule_name_in_row(row, self.COLUMNS['NAME']['ordinal'], name=data['name']) @@ -748,32 +838,10 @@ class ExclusionRules(GenericRulesTable): # Pattern # The contents of the Pattern field is driven by the Source field - self.source_index_changed(source_combo, row, self.COLUMNS['PATTERN']['ordinal'], pattern=data['pattern']) + self.source_index_changed(source_combo, row, pattern=data['pattern']) self.blockSignals(False) - def source_index_changed(self, combo, row, col, pattern=''): - # Populate the Pattern field based upon the Source field - source_field = str(combo.currentText()) - if source_field == '': - values = [] - elif source_field == 'Tags': - values = sorted(self.db.all_tags(), key=sort_key) - else: - if self.eligible_custom_fields[unicode(source_field)]['datatype'] in ['enumeration', 'text']: - values = self.db.all_custom(self.db.field_metadata.key_to_label( - self.eligible_custom_fields[unicode(source_field)]['field'])) - values = sorted(values, key=sort_key) - elif self.eligible_custom_fields[unicode(source_field)]['datatype'] in ['bool']: - values = ['True','False','unspecified'] - elif self.eligible_custom_fields[unicode(source_field)]['datatype'] in ['composite']: - values = ['any value','unspecified'] - elif self.eligible_custom_fields[unicode(source_field)]['datatype'] in ['datetime']: - values = ['any date','unspecified'] - - values_combo = ComboBox(self, values, pattern) - self.setCellWidget(row, self.COLUMNS['PATTERN']['ordinal'], values_combo) - class PrefixRules(GenericRulesTable): COLUMNS = { 'ENABLED':{'ordinal': 0, 'name': ''}, @@ -784,6 +852,7 @@ class PrefixRules(GenericRulesTable): def __init__(self, parent_gb_hl, object_name, rules, eligible_custom_fields, db): super(PrefixRules, self).__init__(parent_gb_hl, object_name, rules, eligible_custom_fields, db) + self.setObjectName("prefix_rules_table") self._init_table_widget() self._initialize() @@ -998,14 +1067,12 @@ class PrefixRules(GenericRulesTable): def set_source_field_in_row(row, col, field=''): source_combo = ComboBox(self, sorted(self.eligible_custom_fields.keys(), key=sort_key), field) - source_combo.currentIndexChanged.connect(partial(self.source_index_changed, source_combo, row)) + source_combo.currentIndexChanged.connect(partial(self._source_index_changed, source_combo)) self.setCellWidget(row, col, source_combo) return source_combo - # Entry point self.blockSignals(True) - #print("prefix_rules_populate_table_row processing rule:\n%s\n" % data) # Enabled self.setItem(row, self.COLUMNS['ENABLED']['ordinal'], CheckableTableWidgetItem(data['enabled'])) @@ -1021,31 +1088,7 @@ class PrefixRules(GenericRulesTable): # Pattern # The contents of the Pattern field is driven by the Source field - self.source_index_changed(source_combo, row, self.COLUMNS['PATTERN']['ordinal'], pattern=data['pattern']) + self.source_index_changed(source_combo, row, pattern=data['pattern']) self.blockSignals(False) - def source_index_changed(self, combo, row, col, pattern=''): - # Populate the Pattern field based upon the Source field - # row, col are the control that changed - - source_field = str(combo.currentText()) - if source_field == '': - values = [] - elif source_field == 'Tags': - values = sorted(self.db.all_tags(), key=sort_key) - else: - if self.eligible_custom_fields[unicode(source_field)]['datatype'] in ['enumeration', 'text']: - values = self.db.all_custom(self.db.field_metadata.key_to_label( - self.eligible_custom_fields[unicode(source_field)]['field'])) - values = sorted(values, key=sort_key) - elif self.eligible_custom_fields[unicode(source_field)]['datatype'] in ['bool']: - values = ['True','False','unspecified'] - elif self.eligible_custom_fields[unicode(source_field)]['datatype'] in ['composite']: - values = ['any value','unspecified'] - elif self.eligible_custom_fields[unicode(source_field)]['datatype'] in ['datetime']: - values = ['any date','unspecified'] - - values_combo = ComboBox(self, values, pattern) - self.setCellWidget(row, self.COLUMNS['PATTERN']['ordinal'], values_combo) - diff --git a/src/calibre/library/catalogs/epub_mobi_builder.py b/src/calibre/library/catalogs/epub_mobi_builder.py index c196db3ca5..1ebe710993 100644 --- a/src/calibre/library/catalogs/epub_mobi_builder.py +++ b/src/calibre/library/catalogs/epub_mobi_builder.py @@ -1191,8 +1191,7 @@ Author '{0}': if self.opts.generate_series: aTag = Tag(soup,'a') - aTag['href'] = "%s.html#%s_series" % ('BySeries', - re.sub('\s','',book['series']).lower()) + aTag['href'] = "%s.html#%s" % ('BySeries',self.generateSeriesAnchor(book['series'])) aTag.insert(0, book['series']) pSeriesTag.insert(0, aTag) else: @@ -1333,8 +1332,9 @@ Author '{0}': pSeriesTag['class'] = "series" if self.opts.generate_series: aTag = Tag(soup,'a') - aTag['href'] = "%s.html#%s_series" % ('BySeries', - re.sub('\W','',new_entry['series']).lower()) + + if self.letter_or_symbol(new_entry['series']) == self.SYMBOLS: + aTag['href'] = "%s.html#%s" % ('BySeries',self.generateSeriesAnchor(new_entry['series'])) aTag.insert(0, new_entry['series']) pSeriesTag.insert(0, aTag) else: @@ -1741,17 +1741,6 @@ Author '{0}': body = soup.find('body') btc = 0 - - pTag = Tag(soup, "p") - pTag['style'] = 'display:none' - ptc = 0 - aTag = Tag(soup,'a') - aTag['id'] = 'section_start' - pTag.insert(ptc, aTag) - ptc += 1 - body.insert(btc, pTag) - btc += 1 - divTag = Tag(soup, "div") dtc = 0 current_letter = "" @@ -1788,10 +1777,7 @@ Author '{0}': pSeriesTag = Tag(soup,'p') pSeriesTag['class'] = "series" aTag = Tag(soup, 'a') - if self.letter_or_symbol(book['series']): - aTag['id'] = "symbol_%s_series" % re.sub('\W','',book['series']).lower() - else: - aTag['id'] = "%s_series" % re.sub('\W','',book['series']).lower() + aTag['id'] = self.generateSeriesAnchor(book['series']) pSeriesTag.insert(0,aTag) pSeriesTag.insert(1,NavigableString('%s' % book['series'])) divTag.insert(dtc,pSeriesTag) @@ -1847,19 +1833,23 @@ Author '{0}': divTag.insert(dtc, pBookTag) dtc += 1 + pTag = Tag(soup, "p") + pTag['class'] = 'title' + ptc = 0 + aTag = Tag(soup,'a') + aTag['id'] = 'section_start' + pTag.insert(ptc, aTag) + ptc += 1 + if not self.__generateForKindle: # Insert the

tag with book_count at the head - #

By Series

- pTag = Tag(soup, "p") - pTag['class'] = 'title' aTag = Tag(soup, "a") anchor_name = friendly_name.lower() aTag['id'] = anchor_name.replace(" ","") pTag.insert(0,aTag) - #h2Tag.insert(1,NavigableString('%s (%d)' % (friendly_name, series_count))) pTag.insert(1,NavigableString('%s' % friendly_name)) - body.insert(btc,pTag) - btc += 1 + body.insert(btc,pTag) + btc += 1 # Add the divTag to the body body.insert(btc, divTag) @@ -3353,15 +3343,17 @@ Author '{0}': return codeTag else: spanTag = Tag(soup, "span") + #spanTag['class'] = "prefix" if prefix_char is None: spanTag['style'] = "color:white" prefix_char = self.defaultPrefix + #prefix_char = " " spanTag.insert(0,NavigableString(prefix_char)) return spanTag def generateAuthorAnchor(self, author): - # Strip white space to '' - return re.sub("\W","", author) + # Generate a legal XHTML id/href string + return re.sub("\W","", ascii_text(author)) def generateFormatArgs(self, book): series_index = str(book['series_index']) @@ -3438,8 +3430,7 @@ Author '{0}': pSeriesTag['class'] = "series" if self.opts.generate_series: aTag = Tag(soup,'a') - aTag['href'] = "%s.html#%s_series" % ('BySeries', - re.sub('\W','',book['series']).lower()) + aTag['href'] = "%s.html#%s" % ('BySeries', self.generateSeriesAnchor(book['series'])) aTag.insert(0, book['series']) pSeriesTag.insert(0, aTag) else: @@ -3641,12 +3632,7 @@ Author '{0}': if aTag: if book['series']: if self.opts.generate_series: - if self.letter_or_symbol(book['series']): - aTag['href'] = "%s.html#symbol_%s_series" % ('BySeries', - re.sub('\W','',book['series']).lower()) - else: - aTag['href'] = "%s.html#%s_series" % ('BySeries', - re.sub('\s','',book['series']).lower()) + aTag['href'] = "%s.html#%s" % ('BySeries',self.generateSeriesAnchor(book['series'])) else: aTag.extract() @@ -3780,6 +3766,14 @@ Author '{0}': pass return rating + def generateSeriesAnchor(self, series): + # Generate a legal XHTML id/href string + if self.letter_or_symbol(series) == self.SYMBOLS: + return "symbol_%s_series" % re.sub('\W','',series).lower() + else: + return "%s_series" % re.sub('\W','',ascii_text(series)).lower() + + def generateShortDescription(self, description, dest=None): # Truncate the description, on word boundaries if necessary # Possible destinations: