mirror of
https://github.com/kovidgoyal/calibre.git
synced 2025-07-09 03:04:10 -04:00
A new 'authors type' custom column
This commit is contained in:
commit
ca2d9875bd
@ -575,6 +575,9 @@ class Metadata(object):
|
||||
orig_res = res
|
||||
datatype = cmeta['datatype']
|
||||
if datatype == 'text' and cmeta['is_multiple']:
|
||||
if cmeta['display'].get('is_names', False):
|
||||
res = u' & '.join(res)
|
||||
else:
|
||||
res = u', '.join(sorted(res, key=sort_key))
|
||||
elif datatype == 'series' and series_with_index:
|
||||
if self.get_extra(key) is not None:
|
||||
|
@ -226,10 +226,18 @@ class Comments(Base):
|
||||
class Text(Base):
|
||||
|
||||
def setup_ui(self, parent):
|
||||
if self.col_metadata['display'].get('is_names', False):
|
||||
self.sep = u' & '
|
||||
else:
|
||||
self.sep = u', '
|
||||
values = self.all_values = list(self.db.all_custom(num=self.col_id))
|
||||
values.sort(key=sort_key)
|
||||
if self.col_metadata['is_multiple']:
|
||||
w = MultiCompleteLineEdit(parent)
|
||||
w.set_separator(self.sep.strip())
|
||||
if self.sep == u' & ':
|
||||
w.set_space_before_sep(True)
|
||||
w.set_add_separator(tweaks['authors_completer_append_separator'])
|
||||
w.update_items_cache(values)
|
||||
w.setSizePolicy(QSizePolicy.Minimum, QSizePolicy.Preferred)
|
||||
else:
|
||||
@ -261,12 +269,12 @@ class Text(Base):
|
||||
if self.col_metadata['is_multiple']:
|
||||
if not val:
|
||||
val = []
|
||||
self.widgets[1].setText(u', '.join(val))
|
||||
self.widgets[1].setText(self.sep.join(val))
|
||||
|
||||
def getter(self):
|
||||
if self.col_metadata['is_multiple']:
|
||||
val = unicode(self.widgets[1].text()).strip()
|
||||
ans = [x.strip() for x in val.split(',') if x.strip()]
|
||||
ans = [x.strip() for x in val.split(self.sep.strip()) if x.strip()]
|
||||
if not ans:
|
||||
ans = None
|
||||
return ans
|
||||
@ -847,13 +855,20 @@ class BulkText(BulkBase):
|
||||
self.main_widget.setSizePolicy(QSizePolicy.Minimum, QSizePolicy.Preferred)
|
||||
self.adding_widget = self.main_widget
|
||||
|
||||
if not self.col_metadata['display'].get('is_names', False):
|
||||
w = RemoveTags(parent, values)
|
||||
self.widgets.append(QLabel('&'+self.col_metadata['name']+': ' +
|
||||
_('tags to remove'), parent))
|
||||
self.widgets.append(w)
|
||||
self.removing_widget = w
|
||||
self.main_widget.set_separator(',')
|
||||
w.tags_box.textChanged.connect(self.a_c_checkbox_changed)
|
||||
w.checkbox.stateChanged.connect(self.a_c_checkbox_changed)
|
||||
else:
|
||||
self.main_widget.set_separator('&')
|
||||
self.main_widget.set_space_before_sep(True)
|
||||
self.main_widget.set_add_separator(
|
||||
tweaks['authors_completer_append_separator'])
|
||||
else:
|
||||
self.make_widgets(parent, MultiCompleteComboBox)
|
||||
self.main_widget.set_separator(None)
|
||||
@ -882,6 +897,11 @@ class BulkText(BulkBase):
|
||||
if not self.a_c_checkbox.isChecked():
|
||||
return
|
||||
if self.col_metadata['is_multiple']:
|
||||
if self.col_metadata['display'].get('is_names', False):
|
||||
val = self.gui_val
|
||||
add = [v.strip() for v in val.split('&') if v.strip()]
|
||||
self.db.set_custom_bulk(book_ids, add, num=self.col_id)
|
||||
else:
|
||||
remove_all, adding, rtext = self.gui_val
|
||||
remove = set()
|
||||
if remove_all:
|
||||
@ -895,8 +915,8 @@ class BulkText(BulkBase):
|
||||
add = set([v.strip() for v in txt.split(',')])
|
||||
else:
|
||||
add = set()
|
||||
self.db.set_custom_bulk_multiple(book_ids, add=add, remove=remove,
|
||||
num=self.col_id)
|
||||
self.db.set_custom_bulk_multiple(book_ids, add=add,
|
||||
remove=remove, num=self.col_id)
|
||||
else:
|
||||
val = self.gui_val
|
||||
val = self.normalize_ui_val(val)
|
||||
@ -905,10 +925,11 @@ class BulkText(BulkBase):
|
||||
|
||||
def getter(self):
|
||||
if self.col_metadata['is_multiple']:
|
||||
if not self.col_metadata['display'].get('is_names', False):
|
||||
return self.removing_widget.checkbox.isChecked(), \
|
||||
unicode(self.adding_widget.text()), \
|
||||
unicode(self.removing_widget.tags_box.text())
|
||||
|
||||
return unicode(self.adding_widget.text())
|
||||
val = unicode(self.main_widget.currentText()).strip()
|
||||
if not val:
|
||||
val = None
|
||||
|
@ -653,7 +653,10 @@ class MetadataBulkDialog(ResizableDialog, Ui_MetadataBulkDialog):
|
||||
|
||||
if self.destination_field_fm['is_multiple']:
|
||||
if self.comma_separated.isChecked():
|
||||
if dest == 'authors':
|
||||
if dest == 'authors' or \
|
||||
(self.destination_field_fm['is_custom'] and
|
||||
self.destination_field_fm['datatype'] == 'text' and
|
||||
self.destination_field_fm['display'].get('is_names', False)):
|
||||
splitter = ' & '
|
||||
else:
|
||||
splitter = ','
|
||||
|
@ -640,18 +640,18 @@ class BooksModel(QAbstractTableModel): # {{{
|
||||
return self.bool_yes_icon
|
||||
return self.bool_blank_icon
|
||||
|
||||
def text_type(r, mult=False, idx=-1):
|
||||
def text_type(r, mult=None, idx=-1):
|
||||
text = self.db.data[r][idx]
|
||||
if text and mult:
|
||||
return QVariant(', '.join(sorted(text.split('|'),key=sort_key)))
|
||||
if text and mult is not None:
|
||||
if mult:
|
||||
return QVariant(u' & '.join(text.split('|')))
|
||||
return QVariant(u', '.join(sorted(text.split('|'),key=sort_key)))
|
||||
return QVariant(text)
|
||||
|
||||
def decorated_text_type(r, mult=False, idx=-1):
|
||||
def decorated_text_type(r, idx=-1):
|
||||
text = self.db.data[r][idx]
|
||||
if force_to_bool(text) is not None:
|
||||
return None
|
||||
if text and mult:
|
||||
return QVariant(', '.join(sorted(text.split('|'),key=sort_key)))
|
||||
return QVariant(text)
|
||||
|
||||
def number_type(r, idx=-1):
|
||||
@ -659,7 +659,7 @@ class BooksModel(QAbstractTableModel): # {{{
|
||||
|
||||
self.dc = {
|
||||
'title' : functools.partial(text_type,
|
||||
idx=self.db.field_metadata['title']['rec_index'], mult=False),
|
||||
idx=self.db.field_metadata['title']['rec_index'], mult=None),
|
||||
'authors' : functools.partial(authors,
|
||||
idx=self.db.field_metadata['authors']['rec_index']),
|
||||
'size' : functools.partial(size,
|
||||
@ -671,14 +671,14 @@ class BooksModel(QAbstractTableModel): # {{{
|
||||
'rating' : functools.partial(rating_type,
|
||||
idx=self.db.field_metadata['rating']['rec_index']),
|
||||
'publisher': functools.partial(text_type,
|
||||
idx=self.db.field_metadata['publisher']['rec_index'], mult=False),
|
||||
idx=self.db.field_metadata['publisher']['rec_index'], mult=None),
|
||||
'tags' : functools.partial(tags,
|
||||
idx=self.db.field_metadata['tags']['rec_index']),
|
||||
'series' : functools.partial(series_type,
|
||||
idx=self.db.field_metadata['series']['rec_index'],
|
||||
siix=self.db.field_metadata['series_index']['rec_index']),
|
||||
'ondevice' : functools.partial(text_type,
|
||||
idx=self.db.field_metadata['ondevice']['rec_index'], mult=False),
|
||||
idx=self.db.field_metadata['ondevice']['rec_index'], mult=None),
|
||||
}
|
||||
|
||||
self.dc_decorator = {
|
||||
@ -692,11 +692,12 @@ class BooksModel(QAbstractTableModel): # {{{
|
||||
datatype = self.custom_columns[col]['datatype']
|
||||
if datatype in ('text', 'comments', 'composite', 'enumeration'):
|
||||
mult=self.custom_columns[col]['is_multiple']
|
||||
if mult is not None:
|
||||
mult = self.custom_columns[col]['display'].get('is_names', False)
|
||||
self.dc[col] = functools.partial(text_type, idx=idx, mult=mult)
|
||||
if datatype in ['text', 'composite', 'enumeration'] and not mult:
|
||||
if self.custom_columns[col]['display'].get('use_decorations', False):
|
||||
self.dc[col] = functools.partial(decorated_text_type,
|
||||
idx=idx, mult=mult)
|
||||
self.dc[col] = functools.partial(decorated_text_type, idx=idx)
|
||||
self.dc_decorator[col] = functools.partial(
|
||||
bool_type_decorator, idx=idx,
|
||||
bool_cols_are_tristate=
|
||||
|
@ -78,6 +78,7 @@ class BooksView(QTableView): # {{{
|
||||
self.pubdate_delegate = PubDateDelegate(self)
|
||||
self.tags_delegate = CompleteDelegate(self, ',', 'all_tags')
|
||||
self.authors_delegate = CompleteDelegate(self, '&', 'all_author_names', True)
|
||||
self.cc_names_delegate = CompleteDelegate(self, '&', 'all_custom', True)
|
||||
self.series_delegate = TextDelegate(self)
|
||||
self.publisher_delegate = TextDelegate(self)
|
||||
self.text_delegate = TextDelegate(self)
|
||||
@ -410,6 +411,7 @@ class BooksView(QTableView): # {{{
|
||||
self.save_state()
|
||||
self._model.set_database(db)
|
||||
self.tags_delegate.set_database(db)
|
||||
self.cc_names_delegate.set_database(db)
|
||||
self.authors_delegate.set_database(db)
|
||||
self.series_delegate.set_auto_complete_function(db.all_series)
|
||||
self.publisher_delegate.set_auto_complete_function(db.all_publishers)
|
||||
@ -431,12 +433,17 @@ class BooksView(QTableView): # {{{
|
||||
self.setItemDelegateForColumn(cm.index(colhead), delegate)
|
||||
elif cc['datatype'] == 'comments':
|
||||
self.setItemDelegateForColumn(cm.index(colhead), self.cc_comments_delegate)
|
||||
elif cc['datatype'] in ('text', 'series'):
|
||||
elif cc['datatype'] == 'text':
|
||||
if cc['is_multiple']:
|
||||
self.setItemDelegateForColumn(cm.index(colhead), self.tags_delegate)
|
||||
if cc['display'].get('is_names', False):
|
||||
self.setItemDelegateForColumn(cm.index(colhead),
|
||||
self.cc_names_delegate)
|
||||
else:
|
||||
self.setItemDelegateForColumn(cm.index(colhead),
|
||||
self.tags_delegate)
|
||||
else:
|
||||
self.setItemDelegateForColumn(cm.index(colhead), self.cc_text_delegate)
|
||||
elif cc['datatype'] in ('int', 'float'):
|
||||
elif cc['datatype'] in ('series', 'int', 'float'):
|
||||
self.setItemDelegateForColumn(cm.index(colhead), self.cc_text_delegate)
|
||||
elif cc['datatype'] == 'bool':
|
||||
self.setItemDelegateForColumn(cm.index(colhead), self.cc_bool_delegate)
|
||||
|
@ -125,6 +125,8 @@ class CreateCustomColumn(QDialog, Ui_QCreateCustomColumn):
|
||||
self.datatype_changed()
|
||||
if ct in ['text', 'composite', 'enumeration']:
|
||||
self.use_decorations.setChecked(c['display'].get('use_decorations', False))
|
||||
elif ct == '*text':
|
||||
self.is_names.setChecked(c['display'].get('is_names', False))
|
||||
self.exec_()
|
||||
|
||||
def shortcut_activated(self, url):
|
||||
@ -167,6 +169,7 @@ class CreateCustomColumn(QDialog, Ui_QCreateCustomColumn):
|
||||
for x in ('box', 'default_label', 'label'):
|
||||
getattr(self, 'enum_'+x).setVisible(col_type == 'enumeration')
|
||||
self.use_decorations.setVisible(col_type in ['text', 'composite', 'enumeration'])
|
||||
self.is_names.setVisible(col_type == '*text')
|
||||
|
||||
def accept(self):
|
||||
col = unicode(self.column_name_box.text()).strip()
|
||||
@ -241,6 +244,8 @@ class CreateCustomColumn(QDialog, Ui_QCreateCustomColumn):
|
||||
return self.simple_error('', _('The value "{0}" is in the '
|
||||
'list more than once').format(l[i]))
|
||||
display_dict = {'enum_values': l}
|
||||
elif col_type == 'text' and is_multiple:
|
||||
display_dict = {'is_names': self.is_names.isChecked()}
|
||||
|
||||
if col_type in ['text', 'composite', 'enumeration']:
|
||||
display_dict['use_decorations'] = self.use_decorations.checkState()
|
||||
|
@ -120,6 +120,16 @@ Everything else will show nothing.</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QCheckBox" name="is_names">
|
||||
<property name="text">
|
||||
<string>Contains names</string>
|
||||
</property>
|
||||
<property name="toolTip">
|
||||
<string>Check this box if this column contains names, like the authors column.</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<spacer name="horizontalSpacer_27">
|
||||
<property name="orientation">
|
||||
|
@ -65,7 +65,8 @@ class ConfigWidget(ConfigWidgetBase, Ui_Form):
|
||||
|
||||
choices = set([k for k in db.field_metadata.all_field_keys()
|
||||
if db.field_metadata[k]['is_category'] and
|
||||
db.field_metadata[k]['datatype'] in ['text', 'series', 'enumeration']])
|
||||
(db.field_metadata[k]['datatype'] in ['text', 'series', 'enumeration']) and
|
||||
not db.field_metadata[k]['display'].get('is_names', False)])
|
||||
choices -= set(['authors', 'publisher', 'formats', 'news', 'identifiers'])
|
||||
choices |= set(['search'])
|
||||
self.opt_categories_using_hierarchy.update_items_cache(choices)
|
||||
|
@ -658,8 +658,7 @@ class TagTreeItem(object): # {{{
|
||||
|
||||
def tag_data(self, role):
|
||||
tag = self.tag
|
||||
if tag.category == 'authors' and \
|
||||
tweaks['categories_use_field_for_author_name'] == 'author_sort':
|
||||
if tag.use_sort_as_name:
|
||||
name = tag.sort
|
||||
tt_author = True
|
||||
else:
|
||||
@ -1275,6 +1274,7 @@ class TagsModel(QAbstractItemModel): # {{{
|
||||
if len(components) == 0 or '.'.join(components) != tag.original_name:
|
||||
components = [tag.original_name]
|
||||
if (not tag.is_hierarchical) and (in_uc or
|
||||
(fm['is_custom'] and fm['display'].get('is_names', False)) or
|
||||
key in ['authors', 'publisher', 'news', 'formats', 'rating'] or
|
||||
key not in self.db.prefs.get('categories_using_hierarchy', []) or
|
||||
len(components) == 1):
|
||||
|
@ -15,7 +15,7 @@ from calibre.utils.config import tweaks, prefs
|
||||
from calibre.utils.date import parse_date, now, UNDEFINED_DATE
|
||||
from calibre.utils.search_query_parser import SearchQueryParser
|
||||
from calibre.utils.pyparsing import ParseException
|
||||
from calibre.ebooks.metadata import title_sort
|
||||
from calibre.ebooks.metadata import title_sort, author_to_author_sort
|
||||
from calibre.ebooks.metadata.opf2 import metadata_to_opf
|
||||
from calibre import prints
|
||||
|
||||
@ -1023,6 +1023,10 @@ class SortKeyGenerator(object):
|
||||
if val:
|
||||
sep = fm['is_multiple']
|
||||
if sep:
|
||||
if fm['display'].get('is_names', False):
|
||||
val = sep.join(
|
||||
[author_to_author_sort(v) for v in val.split(sep)])
|
||||
else:
|
||||
val = sep.join(sorted(val.split(sep),
|
||||
key=self.string_sort_key))
|
||||
val = self.string_sort_key(val)
|
||||
|
@ -117,7 +117,7 @@ class CustomColumns(object):
|
||||
if x is None:
|
||||
return []
|
||||
if isinstance(x, (str, unicode, bytes)):
|
||||
x = x.split(',')
|
||||
x = x.split('&' if d['display'].get('is_names', False) else',')
|
||||
x = [y.strip() for y in x if y.strip()]
|
||||
x = [y.decode(preferred_encoding, 'replace') if not isinstance(y,
|
||||
unicode) else y for y in x]
|
||||
@ -482,8 +482,11 @@ class CustomColumns(object):
|
||||
set_val = val if data['is_multiple'] else [val]
|
||||
existing = getter()
|
||||
if not existing:
|
||||
existing = []
|
||||
for x in set(set_val) - set(existing):
|
||||
existing = set([])
|
||||
else:
|
||||
existing = set(existing)
|
||||
# preserve the order in set_val
|
||||
for x in [v for v in set_val if v not in existing]:
|
||||
# normalized types are text and ratings, so we can do this check
|
||||
# to see if we need to re-add the value
|
||||
if not x:
|
||||
|
@ -48,7 +48,7 @@ class Tag(object):
|
||||
|
||||
def __init__(self, name, id=None, count=0, state=0, avg=0, sort=None,
|
||||
tooltip=None, icon=None, category=None, id_set=None,
|
||||
is_editable = True, is_searchable=True):
|
||||
is_editable = True, is_searchable=True, use_sort_as_name=False):
|
||||
self.name = self.original_name = name
|
||||
self.id = id
|
||||
self.count = count
|
||||
@ -59,6 +59,7 @@ class Tag(object):
|
||||
self.id_set = id_set if id_set is not None else set([])
|
||||
self.avg_rating = avg/2.0 if avg is not None else 0
|
||||
self.sort = sort
|
||||
self.use_sort_as_name = use_sort_as_name
|
||||
if self.avg_rating > 0:
|
||||
if tooltip:
|
||||
tooltip = tooltip + ': '
|
||||
@ -1323,6 +1324,11 @@ class LibraryDatabase2(LibraryDatabase, SchemaUpgrade, CustomColumns):
|
||||
for l in list:
|
||||
(id, val) = (l[0], l[1])
|
||||
tids[category][val] = (id, '{0:05.2f}'.format(val))
|
||||
elif cat['datatype'] == 'text' and cat['is_multiple'] and \
|
||||
cat['display'].get('is_names', False):
|
||||
for l in list:
|
||||
(id, val) = (l[0], l[1])
|
||||
tids[category][val] = (id, author_to_author_sort(val))
|
||||
else:
|
||||
for l in list:
|
||||
(id, val) = (l[0], l[1])
|
||||
@ -1480,11 +1486,20 @@ class LibraryDatabase2(LibraryDatabase, SchemaUpgrade, CustomColumns):
|
||||
reverse=True
|
||||
items.sort(key=kf, reverse=reverse)
|
||||
|
||||
if tweaks['categories_use_field_for_author_name'] == 'author_sort' and\
|
||||
(category == 'authors' or
|
||||
(cat['display'].get('is_names', False) and
|
||||
cat['is_custom'] and cat['is_multiple'] and
|
||||
cat['datatype'] == 'text')):
|
||||
use_sort_as_name = True
|
||||
else:
|
||||
use_sort_as_name = False
|
||||
is_editable = category not in ['news', 'rating']
|
||||
categories[category] = [tag_class(formatter(r.n), count=r.c, id=r.id,
|
||||
avg=avgr(r), sort=r.s, icon=icon,
|
||||
tooltip=tooltip, category=category,
|
||||
id_set=r.id_set, is_editable=is_editable)
|
||||
id_set=r.id_set, is_editable=is_editable,
|
||||
use_sort_as_name=use_sort_as_name)
|
||||
for r in items]
|
||||
|
||||
#print 'end phase "tags list":', time.clock() - last, 'seconds'
|
||||
|
@ -15,7 +15,7 @@ from calibre import isbytestring, force_unicode, fit_image, \
|
||||
prepare_string_for_xml
|
||||
from calibre.utils.ordered_dict import OrderedDict
|
||||
from calibre.utils.filenames import ascii_filename
|
||||
from calibre.utils.config import prefs, tweaks
|
||||
from calibre.utils.config import prefs
|
||||
from calibre.utils.icu import sort_key
|
||||
from calibre.utils.magick import Image
|
||||
from calibre.library.comments import comments_to_html
|
||||
@ -155,8 +155,7 @@ def get_category_items(category, items, restriction, datatype, prefix): # {{{
|
||||
'<div>{1}</div>'
|
||||
'<div>{2}</div></div>')
|
||||
rating, rstring = render_rating(i.avg_rating, prefix)
|
||||
if i.category == 'authors' and \
|
||||
tweaks['categories_use_field_for_author_name'] == 'author_sort':
|
||||
if i.use_sort_as_name:
|
||||
name = xml(i.sort)
|
||||
else:
|
||||
name = xml(i.name)
|
||||
@ -696,7 +695,10 @@ class BrowseServer(object):
|
||||
xml(href, True),
|
||||
xml(val if len(dbtags) == 1 else tag.name),
|
||||
xml(key, True)))
|
||||
join = ' & ' if key == 'authors' else ', '
|
||||
join = ' & ' if key == 'authors' or \
|
||||
(fm['is_custom'] and
|
||||
fm['display'].get('is_names', False)) \
|
||||
else ', '
|
||||
args[key] = join.join(vals)
|
||||
added_key = True
|
||||
if not added_key:
|
||||
|
@ -22,7 +22,6 @@ from calibre.library.server.utils import format_tag_string, Offsets
|
||||
from calibre import guess_type, prepare_string_for_xml as xml
|
||||
from calibre.utils.icu import sort_key
|
||||
from calibre.utils.ordered_dict import OrderedDict
|
||||
from calibre.utils.config import tweaks
|
||||
|
||||
BASE_HREFS = {
|
||||
0 : '/stanza',
|
||||
@ -126,8 +125,7 @@ def CATALOG_ENTRY(item, item_kind, base_href, version, updated,
|
||||
count = (_('%d books') if item.count > 1 else _('%d book'))%item.count
|
||||
if ignore_count:
|
||||
count = ''
|
||||
if item.category == 'authors' and \
|
||||
tweaks['categories_use_field_for_author_name'] == 'author_sort':
|
||||
if item.use_sort_as_name:
|
||||
name = item.sort
|
||||
else:
|
||||
name = item.name
|
||||
|
Loading…
x
Reference in New Issue
Block a user