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 {
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;

View file

@ -15,8 +15,9 @@
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 @@ def __init__(self, parent_gb, object_name, rules, eligible_custom_fields, db):
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 @@ def _init_controls(self):
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 @@ def create_blank_row_data(self):
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 @@ def delete_row(self):
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 @@ def get_data(self):
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 @@ def move_row_down(self):
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 @@ def move_row_up(self):
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 @@ def resize_name(self, scale):
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 @@ def set_rule_name_in_row(row, col, name=''):
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 @@ def set_source_field_in_row(row, col, field=''):
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 @@ def set_source_field_in_row(row, col, field=''):
# 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 @@ def set_rule_name_in_row(row, col, name=''):
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 @@ def set_source_field_in_row(row, col, field=''):
# 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)

View file

@ -1191,8 +1191,7 @@ def generateHTMLByAuthor(self):
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 @@ def add_books_to_HTML_by_month(this_months_list, dtc):
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 @@ def generateHTMLBySeries(self):
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 @@ def generateHTMLBySeries(self):
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 @@ def generateHTMLBySeries(self):
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 <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")
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 @@ def formatPrefix(self,prefix_char,soup):
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 = "&nbsp;"
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 @@ def generateHTMLByGenre(self, genre, section_head, books, outfile):
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 @@ def generate_html():
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 @@ def generateRatingString(self, book):
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: