Improved behavior of GenericRulesTable when focus is lost, fixed some cross-referencing bugs in HTML, made anchors XHTML compliant.

This commit is contained in:
GRiker 2012-08-13 04:21:46 -06:00
parent 95aa079bbd
commit a7cbda6f66
3 changed files with 142 additions and 105 deletions

View File

@ -74,7 +74,7 @@ p.date_read {
p.author { p.author {
font-size:large; font-size:large;
margin-top:0em; margin-top:0em;
margin-bottom:0em; margin-bottom:0.1em;
text-align: center; text-align: center;
text-indent: 0em; text-indent: 0em;
} }
@ -122,7 +122,7 @@ p.genres {
p.series { p.series {
font-style:italic; font-style:italic;
margin-top:0.25em; margin-top:0.10em;
margin-bottom:0em; margin-bottom:0em;
margin-left:2em; margin-left:2em;
text-align:left; text-align:left;

View File

@ -15,8 +15,9 @@ from calibre.utils.icu import sort_key
from catalog_epub_mobi_ui import Ui_Form from catalog_epub_mobi_ui import Ui_Form
from PyQt4.Qt import (Qt, QAbstractItemView, QCheckBox, QComboBox, from PyQt4.Qt import (Qt, QAbstractItemView, QCheckBox, QComboBox,
QDoubleSpinBox, QIcon, QLineEdit, QRadioButton, QSize, QSizePolicy, QDoubleSpinBox, QIcon, QLineEdit, QObject, QRadioButton, QSize, QSizePolicy,
QTableWidget, QTableWidgetItem, QToolButton, QVBoxLayout, QWidget) QTableWidget, QTableWidgetItem, QToolButton, QVBoxLayout, QWidget,
SIGNAL)
class PluginWidget(QWidget,Ui_Form): class PluginWidget(QWidget,Ui_Form):
@ -454,6 +455,8 @@ class GenericRulesTable(QTableWidget):
Add QTableWidget, controls to parent QGroupBox Add QTableWidget, controls to parent QGroupBox
placeholders for basic methods to be overriden placeholders for basic methods to be overriden
''' '''
FOCUS_SWITCHING = True
DEBUG = False
def __init__(self, parent_gb, object_name, rules, eligible_custom_fields, db): def __init__(self, parent_gb, object_name, rules, eligible_custom_fields, db):
self.rules = rules self.rules = rules
@ -476,11 +479,15 @@ class GenericRulesTable(QTableWidget):
self.setRowCount(0) self.setRowCount(0)
self.layout.addWidget(self) self.layout.addWidget(self)
self.last_row_selected = self.currentRow() if self.FOCUS_SWITCHING:
self.last_rows_selected = self.selectionModel().selectedRows() self.last_row_selected = self.currentRow()
self.last_rows_selected = self.selectionModel().selectedRows()
self._init_controls() 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): def _init_controls(self):
# Add the control set # Add the control set
vbl = QVBoxLayout() vbl = QVBoxLayout()
@ -516,7 +523,12 @@ class GenericRulesTable(QTableWidget):
def add_row(self): def add_row(self):
self.setFocus() 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.insertRow(row)
self.populate_table_row(row, self.create_blank_row_data()) self.populate_table_row(row, self.create_blank_row_data())
self.select_and_scroll_to_row(row) self.select_and_scroll_to_row(row)
@ -538,7 +550,10 @@ class GenericRulesTable(QTableWidget):
def delete_row(self): def delete_row(self):
self.setFocus() 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: if len(rows) == 0:
return return
@ -558,11 +573,18 @@ class GenericRulesTable(QTableWidget):
elif self.rowCount() > 0: elif self.rowCount() > 0:
self.select_and_scroll_to_row(first_sel_row - 1) 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): def focusOutEvent(self,e):
# Override of QTableWidget method - clear selection when table loses focus # Override of QTableWidget method - clear selection when table loses focus
self.last_row_selected = self.currentRow() if self.FOCUS_SWITCHING:
self.last_rows_selected = self.selectionModel().selectedRows() self.last_row_selected = self.currentRow()
self.clearSelection() 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): def get_data(self):
''' '''
@ -572,7 +594,10 @@ class GenericRulesTable(QTableWidget):
def move_row_down(self): def move_row_down(self):
self.setFocus() 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: if len(rows) == 0:
return return
last_sel_row = rows[-1].row() last_sel_row = rows[-1].row()
@ -598,13 +623,16 @@ class GenericRulesTable(QTableWidget):
self.blockSignals(False) self.blockSignals(False)
scroll_to_row = last_sel_row + 1 scroll_to_row = last_sel_row + 1
if scroll_to_row < self.rowCount() - 1: #if scroll_to_row < self.rowCount() - 1:
scroll_to_row = scroll_to_row + 1 # scroll_to_row = scroll_to_row + 1
self.select_and_scroll_to_row(scroll_to_row) self.select_and_scroll_to_row(scroll_to_row)
def move_row_up(self): def move_row_up(self):
self.setFocus() 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: if len(rows) == 0:
return return
first_sel_row = rows[0].row() first_sel_row = rows[0].row()
@ -623,10 +651,14 @@ class GenericRulesTable(QTableWidget):
self.removeRow(selrow.row() - 1) self.removeRow(selrow.row() - 1)
self.blockSignals(False) self.blockSignals(False)
scroll_to_row = first_sel_row - 1 scroll_to_row = first_sel_row
if scroll_to_row > 0: if scroll_to_row > 0:
scroll_to_row = scroll_to_row - 1 scroll_to_row = scroll_to_row - 1
self.select_and_scroll_to_row(scroll_to_row) 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): def populate_table_row(self):
''' '''
@ -642,13 +674,69 @@ class GenericRulesTable(QTableWidget):
def rule_name_edited(self): def rule_name_edited(self):
current_row = self.currentRow() current_row = self.currentRow()
self.cellWidget(current_row,1).home(False) self.cellWidget(current_row,1).home(False)
self.setFocus()
self.select_and_scroll_to_row(current_row) self.select_and_scroll_to_row(current_row)
def select_and_scroll_to_row(self, row): def select_and_scroll_to_row(self, row):
self.setFocus()
self.selectRow(row) self.selectRow(row)
self.scrollToItem(self.currentItem()) 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): class ExclusionRules(GenericRulesTable):
COLUMNS = { 'ENABLED':{'ordinal': 0, 'name': ''}, 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): 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) super(ExclusionRules, self).__init__(parent_gb_hl, object_name, rules, eligible_custom_fields, db)
self.setObjectName("exclusion_rules_table")
self._init_table_widget() self._init_table_widget()
self._initialize() self._initialize()
@ -730,7 +819,7 @@ class ExclusionRules(GenericRulesTable):
def set_source_field_in_row(row, col, field=''): 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 = 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) self.setCellWidget(row, col, source_combo)
return source_combo return source_combo
@ -738,7 +827,8 @@ class ExclusionRules(GenericRulesTable):
self.blockSignals(True) self.blockSignals(True)
# Enabled # 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 # Rule name
set_rule_name_in_row(row, self.COLUMNS['NAME']['ordinal'], name=data['name']) set_rule_name_in_row(row, self.COLUMNS['NAME']['ordinal'], name=data['name'])
@ -748,32 +838,10 @@ class ExclusionRules(GenericRulesTable):
# Pattern # Pattern
# The contents of the Pattern field is driven by the Source field # 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) 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): class PrefixRules(GenericRulesTable):
COLUMNS = { 'ENABLED':{'ordinal': 0, 'name': ''}, 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): 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) super(PrefixRules, self).__init__(parent_gb_hl, object_name, rules, eligible_custom_fields, db)
self.setObjectName("prefix_rules_table")
self._init_table_widget() self._init_table_widget()
self._initialize() self._initialize()
@ -998,14 +1067,12 @@ class PrefixRules(GenericRulesTable):
def set_source_field_in_row(row, col, field=''): 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 = 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) self.setCellWidget(row, col, source_combo)
return source_combo return source_combo
# Entry point # Entry point
self.blockSignals(True) self.blockSignals(True)
#print("prefix_rules_populate_table_row processing rule:\n%s\n" % data)
# Enabled # Enabled
self.setItem(row, self.COLUMNS['ENABLED']['ordinal'], CheckableTableWidgetItem(data['enabled'])) self.setItem(row, self.COLUMNS['ENABLED']['ordinal'], CheckableTableWidgetItem(data['enabled']))
@ -1021,31 +1088,7 @@ class PrefixRules(GenericRulesTable):
# Pattern # Pattern
# The contents of the Pattern field is driven by the Source field # 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) 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)

View File

@ -1191,8 +1191,7 @@ Author '{0}':
if self.opts.generate_series: if self.opts.generate_series:
aTag = Tag(soup,'a') aTag = Tag(soup,'a')
aTag['href'] = "%s.html#%s_series" % ('BySeries', aTag['href'] = "%s.html#%s" % ('BySeries',self.generateSeriesAnchor(book['series']))
re.sub('\s','',book['series']).lower())
aTag.insert(0, book['series']) aTag.insert(0, book['series'])
pSeriesTag.insert(0, aTag) pSeriesTag.insert(0, aTag)
else: else:
@ -1333,8 +1332,9 @@ Author '{0}':
pSeriesTag['class'] = "series" pSeriesTag['class'] = "series"
if self.opts.generate_series: if self.opts.generate_series:
aTag = Tag(soup,'a') 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']) aTag.insert(0, new_entry['series'])
pSeriesTag.insert(0, aTag) pSeriesTag.insert(0, aTag)
else: else:
@ -1741,17 +1741,6 @@ Author '{0}':
body = soup.find('body') body = soup.find('body')
btc = 0 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") divTag = Tag(soup, "div")
dtc = 0 dtc = 0
current_letter = "" current_letter = ""
@ -1788,10 +1777,7 @@ Author '{0}':
pSeriesTag = Tag(soup,'p') pSeriesTag = Tag(soup,'p')
pSeriesTag['class'] = "series" pSeriesTag['class'] = "series"
aTag = Tag(soup, 'a') aTag = Tag(soup, 'a')
if self.letter_or_symbol(book['series']): aTag['id'] = self.generateSeriesAnchor(book['series'])
aTag['id'] = "symbol_%s_series" % re.sub('\W','',book['series']).lower()
else:
aTag['id'] = "%s_series" % re.sub('\W','',book['series']).lower()
pSeriesTag.insert(0,aTag) pSeriesTag.insert(0,aTag)
pSeriesTag.insert(1,NavigableString('%s' % book['series'])) pSeriesTag.insert(1,NavigableString('%s' % book['series']))
divTag.insert(dtc,pSeriesTag) divTag.insert(dtc,pSeriesTag)
@ -1847,19 +1833,23 @@ Author '{0}':
divTag.insert(dtc, pBookTag) divTag.insert(dtc, pBookTag)
dtc += 1 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: if not self.__generateForKindle:
# Insert the <h2> tag with book_count at the head # Insert the <h2> tag with book_count at the head
#<h2><a name="byseries" id="byseries"></a>By Series</h2>
pTag = Tag(soup, "p")
pTag['class'] = 'title'
aTag = Tag(soup, "a") aTag = Tag(soup, "a")
anchor_name = friendly_name.lower() anchor_name = friendly_name.lower()
aTag['id'] = anchor_name.replace(" ","") aTag['id'] = anchor_name.replace(" ","")
pTag.insert(0,aTag) pTag.insert(0,aTag)
#h2Tag.insert(1,NavigableString('%s (%d)' % (friendly_name, series_count)))
pTag.insert(1,NavigableString('%s' % friendly_name)) pTag.insert(1,NavigableString('%s' % friendly_name))
body.insert(btc,pTag) body.insert(btc,pTag)
btc += 1 btc += 1
# Add the divTag to the body # Add the divTag to the body
body.insert(btc, divTag) body.insert(btc, divTag)
@ -3353,15 +3343,17 @@ Author '{0}':
return codeTag return codeTag
else: else:
spanTag = Tag(soup, "span") spanTag = Tag(soup, "span")
#spanTag['class'] = "prefix"
if prefix_char is None: if prefix_char is None:
spanTag['style'] = "color:white" spanTag['style'] = "color:white"
prefix_char = self.defaultPrefix prefix_char = self.defaultPrefix
#prefix_char = "&nbsp;"
spanTag.insert(0,NavigableString(prefix_char)) spanTag.insert(0,NavigableString(prefix_char))
return spanTag return spanTag
def generateAuthorAnchor(self, author): def generateAuthorAnchor(self, author):
# Strip white space to '' # Generate a legal XHTML id/href string
return re.sub("\W","", author) return re.sub("\W","", ascii_text(author))
def generateFormatArgs(self, book): def generateFormatArgs(self, book):
series_index = str(book['series_index']) series_index = str(book['series_index'])
@ -3438,8 +3430,7 @@ Author '{0}':
pSeriesTag['class'] = "series" pSeriesTag['class'] = "series"
if self.opts.generate_series: if self.opts.generate_series:
aTag = Tag(soup,'a') aTag = Tag(soup,'a')
aTag['href'] = "%s.html#%s_series" % ('BySeries', aTag['href'] = "%s.html#%s" % ('BySeries', self.generateSeriesAnchor(book['series']))
re.sub('\W','',book['series']).lower())
aTag.insert(0, book['series']) aTag.insert(0, book['series'])
pSeriesTag.insert(0, aTag) pSeriesTag.insert(0, aTag)
else: else:
@ -3641,12 +3632,7 @@ Author '{0}':
if aTag: if aTag:
if book['series']: if book['series']:
if self.opts.generate_series: if self.opts.generate_series:
if self.letter_or_symbol(book['series']): aTag['href'] = "%s.html#%s" % ('BySeries',self.generateSeriesAnchor(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())
else: else:
aTag.extract() aTag.extract()
@ -3780,6 +3766,14 @@ Author '{0}':
pass pass
return rating 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): def generateShortDescription(self, description, dest=None):
# Truncate the description, on word boundaries if necessary # Truncate the description, on word boundaries if necessary
# Possible destinations: # Possible destinations: