Make the book details panel completely user configurable

This commit is contained in:
Kovid Goyal 2011-04-24 19:44:17 -06:00
parent 4dbd7840d6
commit 978e1f826b
14 changed files with 717 additions and 704 deletions

View File

@ -266,26 +266,6 @@ max_content_server_tags_shown=5
content_server_will_display = ['*']
content_server_wont_display = []
#: Set custom metadata fields that the book details panel will or will not display.
# book_details_will_display is a list of custom fields to be displayed.
# book_details_wont_display is a list of custom fields not to be displayed.
# wont_display has priority over will_display.
# The special value '*' means all custom fields. The value [] means no entries.
# Defaults:
# book_details_will_display = ['*']
# book_details_wont_display = []
# Examples:
# To display only the custom fields #mytags and #genre:
# book_details_will_display = ['#mytags', '#genre']
# book_details_wont_display = []
# To display all fields except #mycomments:
# book_details_will_display = ['*']
# book_details_wont_display['#mycomments']
# As above, this tweak affects only display of custom fields. The standard
# fields are not affected
book_details_will_display = ['*']
book_details_wont_display = []
#: Set the maximum number of sort 'levels'
# Set the maximum number of sort 'levels' that calibre will use to resort the
# library after certain operations such as searches or device insertion. Each

View File

@ -0,0 +1,26 @@
a {
text-decoration: none;
color: blue
}
.comments {
margin-top: 0;
padding-top: 0;
text-indent: 0
}
table.fields {
margin-bottom: 0;
padding-bottom: 0;
}
table.fields td {
vertical-align: top
}
table.fields td.title {
font-weight: bold
}
.series_name {
font-style: italic
}

View File

@ -19,6 +19,9 @@ from calibre.utils.date import isoformat, format_date
from calibre.utils.icu import sort_key
from calibre.utils.formatter import TemplateFormatter
def human_readable(size, precision=2):
""" Convert a size in bytes into megabytes """
return ('%.'+str(precision)+'f'+ 'MB') % ((size/(1024.*1024.)),)
NULL_VALUES = {
'user_metadata': {},
@ -551,7 +554,8 @@ class Metadata(object):
def format_field_extended(self, key, series_with_index=True):
from calibre.ebooks.metadata import authors_to_string
'''
returns the tuple (field_name, formatted_value)
returns the tuple (field_name, formatted_value, original_value,
field_metadata)
'''
# Handle custom series index
@ -627,6 +631,8 @@ class Metadata(object):
res = format_date(res, fmeta['display'].get('date_format','dd MMM yyyy'))
elif datatype == 'rating':
res = res/2.0
elif key in ('book_size', 'size'):
res = human_readable(res)
return (name, unicode(res), orig_res, fmeta)
return (None, None, None, None)

View File

@ -80,6 +80,14 @@ gprefs.defaults['font'] = None
gprefs.defaults['tags_browser_partition_method'] = 'first letter'
gprefs.defaults['tags_browser_collapse_at'] = 100
gprefs.defaults['edit_metadata_single_layout'] = 'default'
gprefs.defaults['book_display_fields'] = [
('title', False), ('authors', False), ('formats', True),
('series', True), ('identifiers', True), ('tags', True),
('path', True), ('publisher', False), ('rating', False),
('author_sort', False), ('sort', False), ('timestamp', False),
('uuid', False), ('comments', True), ('id', False), ('pubdate', False),
('last_modified', False), ('size', False),
]
# }}}
@ -89,7 +97,7 @@ UNDEFINED_QDATE = QDate(UNDEFINED_DATE)
ALL_COLUMNS = ['title', 'ondevice', 'authors', 'size', 'timestamp', 'rating', 'publisher',
'tags', 'series', 'pubdate']
def _config():
def _config(): # {{{
c = Config('gui', 'preferences for the calibre GUI')
c.add_opt('send_to_storage_card_by_default', default=False,
help=_('Send file to storage card instead of main memory by default'))
@ -181,6 +189,8 @@ def _config():
return ConfigProxy(c)
config = _config()
# }}}
# Turn off DeprecationWarnings in windows GUI
if iswindows:
import warnings

View File

@ -30,5 +30,5 @@ class ShowBookDetailsAction(InterfaceAction):
index = self.gui.library_view.currentIndex()
if index.isValid():
BookInfo(self.gui, self.gui.library_view, index,
self.gui.iactions['View'].view_format_by_id).show()
self.gui.book_details.handle_click).show()

View File

@ -5,67 +5,151 @@ __license__ = 'GPL v3'
__copyright__ = '2010, Kovid Goyal <kovid@kovidgoyal.net>'
__docformat__ = 'restructuredtext en'
import collections, sys
from Queue import Queue
from PyQt4.Qt import QPixmap, QSize, QWidget, Qt, pyqtSignal, QUrl, \
QPropertyAnimation, QEasingCurve, QThread, QApplication, QFontInfo, \
QSizePolicy, QPainter, QRect, pyqtProperty, QLayout, QPalette, QMenu
from PyQt4.Qt import (QPixmap, QSize, QWidget, Qt, pyqtSignal, QUrl,
QPropertyAnimation, QEasingCurve, QApplication, QFontInfo,
QSizePolicy, QPainter, QRect, pyqtProperty, QLayout, QPalette, QMenu)
from PyQt4.QtWebKit import QWebView
from calibre import fit_image, prepare_string_for_xml
from calibre.gui2.dnd import dnd_has_image, dnd_get_image, dnd_get_files, \
IMAGE_EXTENSIONS, dnd_has_extension
from calibre import fit_image, force_unicode, prepare_string_for_xml
from calibre.gui2.dnd import (dnd_has_image, dnd_get_image, dnd_get_files,
IMAGE_EXTENSIONS, dnd_has_extension)
from calibre.ebooks import BOOK_EXTENSIONS
from calibre.constants import preferred_encoding
from calibre.ebooks.metadata.book.base import (field_metadata, Metadata)
from calibre.ebooks.metadata import fmt_sidx
from calibre.constants import filesystem_encoding
from calibre.library.comments import comments_to_html
from calibre.gui2 import config, open_local_file, open_url, pixmap_to_data
from calibre.gui2 import (config, open_local_file, open_url, pixmap_to_data,
gprefs)
from calibre.utils.icu import sort_key
# render_rows(data) {{{
WEIGHTS = collections.defaultdict(lambda : 100)
WEIGHTS[_('Path')] = 5
WEIGHTS[_('Formats')] = 1
WEIGHTS[_('Collections')] = 2
WEIGHTS[_('Series')] = 3
WEIGHTS[_('Tags')] = 4
def render_rows(data):
keys = data.keys()
# First sort by name. The WEIGHTS sort will preserve this sub-order
keys.sort(key=sort_key)
keys.sort(key=lambda x: WEIGHTS[x])
rows = []
for key in keys:
txt = data[key]
if key in ('id', _('Comments')) or not hasattr(txt, 'strip') or not txt.strip() or \
txt == 'None':
def render_html(mi, css, vertical, widget, all_fields=False): # {{{
table = render_data(mi, all_fields=all_fields,
use_roman_numbers=config['use_roman_numerals_for_series_number'])
def color_to_string(col):
ans = '#000000'
if col.isValid():
col = col.toRgb()
if col.isValid():
ans = unicode(col.name())
return ans
f = QFontInfo(QApplication.font(widget)).pixelSize()
c = color_to_string(QApplication.palette().color(QPalette.Normal,
QPalette.WindowText))
templ = u'''\
<html>
<head>
<style type="text/css">
body, td {background-color: transparent; font-size: %dpx; color: %s }
</style>
<style type="text/css">
%s
</style>
</head>
<body>
%%s
</body>
<html>
'''%(f, c, css)
comments = u''
if mi.comments:
comments = comments_to_html(force_unicode(mi.comments))
right_pane = u'<div id="comments" class="comments">%s</div>'%comments
if vertical:
ans = templ%(table+right_pane)
else:
ans = templ%(u'<table><tr><td valign="top" '
'style="padding-right:2em; width:40%%">%s</td><td valign="top">%s</td></tr></table>'
% (table, right_pane))
return ans
def get_field_list(fm):
fieldlist = list(gprefs['book_display_fields'])
names = frozenset([x[0] for x in fieldlist])
for field in fm.displayable_field_keys():
if field not in names:
fieldlist.append((field, True))
return fieldlist
def render_data(mi, use_roman_numbers=True, all_fields=False):
ans = []
isdevice = not hasattr(mi, 'id')
fm = getattr(mi, 'field_metadata', field_metadata)
for field, display in get_field_list(fm):
metadata = fm.get(field, None)
if all_fields:
display = True
if (not display or not metadata or mi.is_null(field) or
field == 'comments'):
continue
if isinstance(key, str):
key = key.decode(preferred_encoding, 'replace')
if isinstance(txt, str):
txt = txt.decode(preferred_encoding, 'replace')
if key.endswith(u':html'):
key = key[:-5]
txt = comments_to_html(txt)
elif '</font>' not in txt:
txt = prepare_string_for_xml(txt)
if 'id' in data:
if key == _('Path'):
txt = u'<a href="path:%s" title="%s">%s</a>'%(data['id'],
txt, _('Click to open'))
if key == _('Formats') and txt and txt != _('None'):
fmts = [x.strip() for x in txt.split(',')]
fmts = [u'<a href="format:%s:%s">%s</a>' % (data['id'], x, x) for x
in fmts]
txt = ', '.join(fmts)
name = metadata['name']
if not name:
name = field
name += ':'
if metadata['datatype'] == 'comments':
val = getattr(mi, field)
if val:
val = force_unicode(val)
ans.append((field,
u'<td class="comments" colspan="2">%s</td>'%comments_to_html(val)))
elif field == 'path':
if mi.path:
path = force_unicode(mi.path, filesystem_encoding)
scheme = u'devpath' if isdevice else u'path'
url = prepare_string_for_xml(path if isdevice else
unicode(mi.id), True)
link = u'<a href="%s:%s" title="%s">%s</a>' % (scheme, url,
prepare_string_for_xml(path, True), _('Click to open'))
ans.append((field, u'<td class="title">%s</td><td>%s</td>'%(name, link)))
elif field == 'formats':
if isdevice: continue
fmts = [u'<a href="format:%s:%s">%s</a>' % (mi.id, x, x) for x
in mi.formats]
ans.append((field, u'<td class="title">%s</td><td>%s</td>'%(name,
u', '.join(fmts))))
elif field == 'identifiers':
pass # TODO
else:
if key == _('Path'):
txt = u'<a href="devpath:%s">%s</a>'%(txt,
_('Click to open'))
val = mi.format_field(field)[-1]
if val is None:
continue
val = prepare_string_for_xml(val)
if metadata['datatype'] == 'series':
if metadata['is_custom']:
sidx = mi.get_extra(field)
else:
sidx = getattr(mi, field+'_index')
if sidx is None:
sidx = 1.0
val = _('Book %s of <span class="series_name">%s</span>')%(fmt_sidx(sidx,
use_roman=use_roman_numbers),
prepare_string_for_xml(getattr(mi, field)))
rows.append((key, txt))
return rows
ans.append((field, u'<td class="title">%s</td><td>%s</td>'%(name, val)))
dc = getattr(mi, 'device_collections', [])
if dc:
dc = u', '.join(sorted(dc, key=sort_key))
ans.append(('device_collections',
u'<td class="title">%s</td><td>%s</td>'%(
_('Collections')+':', dc)))
def classname(field):
try:
dt = fm[field]['datatype']
except:
dt = 'text'
return 'datatype_%s'%dt
ans = [u'<tr id="%s" class="%s">%s</tr>'%(field.replace('#', '_'),
classname(field), html) for field, html in ans]
# print '\n'.join(ans)
return u'<table class="fields">%s</table>'%(u'\n'.join(ans))
# }}}
@ -117,10 +201,10 @@ class CoverView(QWidget): # {{{
def show_data(self, data):
self.animation.stop()
same_item = data.get('id', True) == self.data.get('id', False)
same_item = getattr(data, 'id', True) == self.data.get('id', False)
self.data = {'id':data.get('id', None)}
if data.has_key('cover'):
self.pixmap = QPixmap.fromImage(data.pop('cover'))
if data.cover_data[1]:
self.pixmap = QPixmap.fromImage(data.cover_data[1])
if self.pixmap.isNull() or self.pixmap.width() < 5 or \
self.pixmap.height() < 5:
self.pixmap = self.default_pixmap
@ -188,32 +272,6 @@ class CoverView(QWidget): # {{{
# Book Info {{{
class RenderComments(QThread):
rdone = pyqtSignal(object, object)
def __init__(self, parent):
QThread.__init__(self, parent)
self.queue = Queue()
self.start()
def run(self):
while True:
try:
rows, comments = self.queue.get()
except:
break
import time
time.sleep(0.001)
oint = sys.getcheckinterval()
sys.setcheckinterval(5)
try:
self.rdone.emit(rows, comments_to_html(comments))
except:
pass
sys.setcheckinterval(oint)
class BookInfo(QWebView):
link_clicked = pyqtSignal(object)
@ -221,8 +279,6 @@ class BookInfo(QWebView):
def __init__(self, vertical, parent=None):
QWebView.__init__(self, parent)
self.vertical = vertical
self.renderer = RenderComments(self)
self.renderer.rdone.connect(self._show_data, type=Qt.QueuedConnection)
self.page().setLinkDelegationPolicy(self.page().DelegateAllLinks)
self.linkClicked.connect(self.link_activated)
self._link_clicked = False
@ -231,6 +287,7 @@ class BookInfo(QWebView):
self.setAcceptDrops(False)
palette.setBrush(QPalette.Base, Qt.transparent)
self.page().setPalette(palette)
self.css = P('templates/book_details.css', data=True).decode('utf-8')
def link_activated(self, link):
self._link_clicked = True
@ -240,56 +297,9 @@ class BookInfo(QWebView):
def turnoff_scrollbar(self, *args):
self.page().mainFrame().setScrollBarPolicy(Qt.Horizontal, Qt.ScrollBarAlwaysOff)
def show_data(self, data):
rows = render_rows(data)
rows = u'\n'.join([u'<tr><td valign="top"><b>%s:</b></td><td valign="top">%s</td></tr>'%(k,t) for
k, t in rows])
comments = data.get(_('Comments'), '')
if not comments or comments == u'None':
comments = ''
self.renderer.queue.put((rows, comments))
self._show_data(rows, '')
def _show_data(self, rows, comments):
def color_to_string(col):
ans = '#000000'
if col.isValid():
col = col.toRgb()
if col.isValid():
ans = unicode(col.name())
return ans
f = QFontInfo(QApplication.font(self.parent())).pixelSize()
c = color_to_string(QApplication.palette().color(QPalette.Normal,
QPalette.WindowText))
templ = u'''\
<html>
<head>
<style type="text/css">
body, td {background-color: transparent; font-size: %dpx; color: %s }
a { text-decoration: none; color: blue }
div.description { margin-top: 0; padding-top: 0; text-indent: 0 }
table { margin-bottom: 0; padding-bottom: 0; }
</style>
</head>
<body>
%%s
</body>
<html>
'''%(f, c)
if self.vertical:
extra = ''
if comments:
extra = u'<div class="description">%s</div>'%comments
self.setHtml(templ%(u'<table>%s</table>%s'%(rows, extra)))
else:
left_pane = u'<table>%s</table>'%rows
right_pane = u'<div>%s</div>'%comments
self.setHtml(templ%(u'<table><tr><td valign="top" '
'style="padding-right:2em; width:40%%">%s</td><td valign="top">%s</td></tr></table>'
% (left_pane, right_pane)))
def show_data(self, mi):
html = render_html(mi, self.css, self.vertical, self.parent())
self.setHtml(html)
def mouseDoubleClickEvent(self, ev):
swidth = self.page().mainFrame().scrollBarGeometry(Qt.Vertical).width()
@ -457,10 +467,10 @@ class BookDetails(QWidget): # {{{
self._layout.addWidget(self.cover_view)
self.book_info = BookInfo(vertical, self)
self._layout.addWidget(self.book_info)
self.book_info.link_clicked.connect(self._link_clicked)
self.book_info.link_clicked.connect(self.handle_click)
self.setCursor(Qt.PointingHandCursor)
def _link_clicked(self, link):
def handle_click(self, link):
typ, _, val = link.partition(':')
if typ == 'path':
self.open_containing_folder.emit(int(val))
@ -484,7 +494,7 @@ class BookDetails(QWidget): # {{{
def show_data(self, data):
self.book_info.show_data(data)
self.cover_view.show_data(data)
self.current_path = data.get(_('Path'), '')
self.current_path = getattr(data, u'path', u'')
self.update_layout()
def update_layout(self):
@ -500,7 +510,7 @@ class BookDetails(QWidget): # {{{
)
def reset_info(self):
self.show_data({})
self.show_data(Metadata(_('Unknown')))
# }}}

View File

@ -3,30 +3,33 @@ __license__ = 'GPL v3'
__copyright__ = '2008, Kovid Goyal kovid@kovidgoyal.net'
__docformat__ = 'restructuredtext en'
import textwrap, os, re
from PyQt4.Qt import QCoreApplication, SIGNAL, QModelIndex, QTimer, Qt, \
QDialog, QPixmap, QIcon, QSize
from PyQt4.Qt import (QCoreApplication, SIGNAL, QModelIndex, QTimer, Qt,
QDialog, QPixmap, QIcon, QSize, QPalette)
from calibre.gui2.dialogs.book_info_ui import Ui_BookInfo
from calibre.gui2 import dynamic, open_local_file, open_url
from calibre.gui2 import dynamic
from calibre import fit_image
from calibre.library.comments import comments_to_html
from calibre.utils.icu import sort_key
from calibre.gui2.book_details import render_html
class BookInfo(QDialog, Ui_BookInfo):
def __init__(self, parent, view, row, view_func):
def __init__(self, parent, view, row, link_delegate):
QDialog.__init__(self, parent)
Ui_BookInfo.__init__(self)
self.setupUi(self)
self.gui = parent
self.cover_pixmap = None
self.comments.sizeHint = self.comments_size_hint
self.comments.page().setLinkDelegationPolicy(self.comments.page().DelegateAllLinks)
self.comments.linkClicked.connect(self.link_clicked)
self.view_func = view_func
self.details.sizeHint = self.details_size_hint
self.details.page().setLinkDelegationPolicy(self.details.page().DelegateAllLinks)
self.details.linkClicked.connect(self.link_clicked)
self.css = P('templates/book_details.css', data=True).decode('utf-8')
self.link_delegate = link_delegate
self.details.setAttribute(Qt.WA_OpaquePaintEvent, False)
palette = self.details.palette()
self.details.setAcceptDrops(False)
palette.setBrush(QPalette.Base, Qt.transparent)
self.details.page().setPalette(palette)
self.view = view
@ -37,7 +40,6 @@ class BookInfo(QDialog, Ui_BookInfo):
self.connect(self.view.selectionModel(), SIGNAL('currentChanged(QModelIndex,QModelIndex)'), self.slave)
self.connect(self.next_button, SIGNAL('clicked()'), self.next)
self.connect(self.previous_button, SIGNAL('clicked()'), self.previous)
self.connect(self.text, SIGNAL('linkActivated(QString)'), self.open_book_path)
self.fit_cover.stateChanged.connect(self.toggle_cover_fit)
self.cover.resizeEvent = self.cover_view_resized
self.cover.cover_changed.connect(self.cover_changed)
@ -46,6 +48,10 @@ class BookInfo(QDialog, Ui_BookInfo):
screen_height = desktop.availableGeometry().height() - 100
self.resize(self.size().width(), screen_height)
def link_clicked(self, qurl):
link = unicode(qurl.toString())
self.link_delegate(link)
def cover_changed(self, data):
if self.current_row is not None:
id_ = self.view.model().id(self.current_row)
@ -60,11 +66,8 @@ class BookInfo(QDialog, Ui_BookInfo):
if self.fit_cover.isChecked():
self.resize_cover()
def link_clicked(self, url):
open_url(url)
def comments_size_hint(self):
return QSize(350, 250)
def details_size_hint(self):
return QSize(350, 550)
def toggle_cover_fit(self, state):
dynamic.set('book_info_dialog_fit_cover', self.fit_cover.isChecked())
@ -77,13 +80,6 @@ class BookInfo(QDialog, Ui_BookInfo):
row = current.row()
self.refresh(row)
def open_book_path(self, path):
path = unicode(path)
if os.sep in path:
open_local_file(path)
else:
self.view_func(self.view.model().id(self.current_row), path)
def next(self):
row = self.view.currentIndex().row()
ni = self.view.model().index(row+1, 0)
@ -117,8 +113,8 @@ class BookInfo(QDialog, Ui_BookInfo):
row = row.row()
if row == self.current_row:
return
info = self.view.model().get_book_info(row)
if info is None:
mi = self.view.model().get_book_display_info(row)
if mi is None:
# Indicates books was deleted from library, or row numbers have
# changed
return
@ -126,40 +122,11 @@ class BookInfo(QDialog, Ui_BookInfo):
self.previous_button.setEnabled(False if row == 0 else True)
self.next_button.setEnabled(False if row == self.view.model().rowCount(QModelIndex())-1 else True)
self.current_row = row
self.setWindowTitle(info[_('Title')])
self.title.setText('<b>'+info.pop(_('Title')))
comments = info.pop(_('Comments'), '')
if comments:
comments = comments_to_html(comments)
if re.search(r'<[a-zA-Z]+>', comments) is None:
lines = comments.splitlines()
lines = [x if x.strip() else '<br><br>' for x in lines]
comments = '\n'.join(lines)
self.comments.setHtml('<div>%s</div>' % comments)
self.comments.page().setLinkDelegationPolicy(self.comments.page().DelegateAllLinks)
cdata = info.pop('cover', '')
self.cover_pixmap = QPixmap.fromImage(cdata)
self.setWindowTitle(mi.title)
self.title.setText('<b>'+mi.title)
mi.title = _('Unknown')
self.cover_pixmap = QPixmap.fromImage(mi.cover_data[1])
self.resize_cover()
html = render_html(mi, self.css, True, self, all_fields=True)
self.details.setHtml(html)
rows = u''
self.text.setText('')
self.data = info
if _('Path') in info.keys():
p = info[_('Path')]
info[_('Path')] = '<a href="%s">%s</a>'%(p, p)
if _('Formats') in info.keys():
formats = info[_('Formats')].split(',')
info[_('Formats')] = ''
for f in formats:
f = f.strip()
info[_('Formats')] += '<a href="%s">%s</a>, '%(f,f)
for key in sorted(info.keys(), key=sort_key):
if key == 'id': continue
txt = info[key]
if key.endswith(':html'):
key = key[:-5]
txt = comments_to_html(txt)
if key != _('Path'):
txt = u'<br />\n'.join(textwrap.wrap(txt, 120))
rows += u'<tr><td><b>%s:</b></td><td>%s</td></tr>'%(key, txt)
self.text.setText(u'<table>'+rows+'</table>')

View File

@ -20,6 +20,12 @@
<layout class="QGridLayout" name="gridLayout">
<item row="0" column="0" colspan="2">
<widget class="QLabel" name="title">
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Maximum">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="font">
<font>
<weight>75</weight>
@ -34,82 +40,17 @@
</property>
</widget>
</item>
<item row="1" column="0" rowspan="3">
<item row="2" column="0" rowspan="3">
<widget class="CoverView" name="cover"/>
</item>
<item row="1" column="1">
<widget class="QScrollArea" name="scrollArea">
<property name="frameShape">
<enum>QFrame::NoFrame</enum>
</property>
<property name="widgetResizable">
<bool>true</bool>
</property>
<widget class="QWidget" name="scrollAreaWidgetContents">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>435</width>
<height>670</height>
</rect>
</property>
<layout class="QVBoxLayout" name="verticalLayout">
<item>
<widget class="QLabel" name="text">
<property name="text">
<string>TextLabel</string>
</property>
<property name="alignment">
<set>Qt::AlignLeading|Qt::AlignLeft|Qt::AlignTop</set>
</property>
<property name="wordWrap">
<bool>true</bool>
</property>
</widget>
</item>
<item>
<widget class="QGroupBox" name="groupBox">
<property name="title">
<string>Comments</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout_2">
<item>
<widget class="QWebView" name="comments">
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Expanding">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="maximumSize">
<size>
<width>350</width>
<height>16777215</height>
</size>
</property>
<property name="url">
<url>
<string>about:blank</string>
</url>
</property>
</widget>
</item>
</layout>
</widget>
</item>
</layout>
</widget>
</widget>
</item>
<item row="2" column="1">
<item row="3" column="1">
<widget class="QCheckBox" name="fit_cover">
<property name="text">
<string>Fit &amp;cover within view</string>
</property>
</widget>
</item>
<item row="3" column="1">
<item row="4" column="1">
<layout class="QHBoxLayout" name="horizontalLayout">
<item>
<widget class="QPushButton" name="previous_button">
@ -135,6 +76,15 @@
</item>
</layout>
</item>
<item row="2" column="1">
<widget class="QWebView" name="details">
<property name="url">
<url>
<string>about:blank</string>
</url>
</property>
</widget>
</item>
</layout>
</widget>
<customwidgets>

View File

@ -8,20 +8,20 @@ __docformat__ = 'restructuredtext en'
import shutil, functools, re, os, traceback
from contextlib import closing
from PyQt4.Qt import QAbstractTableModel, Qt, pyqtSignal, QIcon, QImage, \
QModelIndex, QVariant, QDate, QColor
from PyQt4.Qt import (QAbstractTableModel, Qt, pyqtSignal, QIcon, QImage,
QModelIndex, QVariant, QDate, QColor)
from calibre.gui2 import NONE, config, UNDEFINED_QDATE
from calibre.gui2 import NONE, UNDEFINED_QDATE
from calibre.utils.pyparsing import ParseException
from calibre.ebooks.metadata import fmt_sidx, authors_to_string, string_to_authors
from calibre.ptempfile import PersistentTemporaryFile
from calibre.utils.config import tweaks, prefs
from calibre.utils.date import dt_factory, qt_to_dt, isoformat
from calibre.utils.date import dt_factory, qt_to_dt
from calibre.utils.icu import sort_key
from calibre.utils.search_query_parser import SearchQueryParser
from calibre.library.caches import _match, CONTAINS_MATCH, EQUALS_MATCH, \
REGEXP_MATCH, MetadataBackup, force_to_bool
from calibre import strftime, isbytestring, prepare_string_for_xml
from calibre.library.caches import (_match, CONTAINS_MATCH, EQUALS_MATCH,
REGEXP_MATCH, MetadataBackup, force_to_bool)
from calibre import strftime, isbytestring
from calibre.constants import filesystem_encoding, DEBUG
from calibre.gui2.library import DEFAULT_SORT
@ -114,7 +114,7 @@ class BooksModel(QAbstractTableModel): # {{{
return cc_label in self.custom_columns
def read_config(self):
self.use_roman_numbers = config['use_roman_numerals_for_series_number']
pass
def set_device_connected(self, is_connected):
self.device_connected = is_connected
@ -355,63 +355,13 @@ class BooksModel(QAbstractTableModel): # {{{
return self.rowCount(None)
def get_book_display_info(self, idx):
def custom_keys_to_display():
ans = getattr(self, '_custom_fields_in_book_info', None)
if ans is None:
cfkeys = set(self.db.custom_field_keys())
yes_fields = set(tweaks['book_details_will_display'])
no_fields = set(tweaks['book_details_wont_display'])
if '*' in yes_fields:
yes_fields = cfkeys
if '*' in no_fields:
no_fields = cfkeys
ans = frozenset(yes_fields - no_fields)
setattr(self, '_custom_fields_in_book_info', ans)
return ans
data = {}
cdata = self.cover(idx)
if cdata:
data['cover'] = cdata
tags = list(self.db.get_tags(self.db.id(idx)))
if tags:
tags.sort(key=sort_key)
tags = ', '.join(tags)
else:
tags = _('None')
data[_('Tags')] = tags
formats = self.db.formats(idx)
if formats:
formats = formats.replace(',', ', ')
else:
formats = _('None')
data[_('Formats')] = formats
data[_('Path')] = self.db.abspath(idx)
data['id'] = self.id(idx)
comments = self.db.comments(idx)
if not comments:
comments = _('None')
data[_('Comments')] = comments
series = self.db.series(idx)
if series:
sidx = self.db.series_index(idx)
sidx = fmt_sidx(sidx, use_roman = self.use_roman_numbers)
data[_('Series')] = \
_('Book %s of %s.')%\
(sidx, prepare_string_for_xml(series))
mi = self.db.get_metadata(idx)
cf_to_display = custom_keys_to_display()
for key in mi.custom_field_keys():
if key not in cf_to_display:
continue
name, val = mi.format_field(key)
if mi.metadata_for_field(key)['datatype'] == 'comments':
name += ':html'
if val and name not in data:
data[name] = val
return data
mi.size = mi.book_size
mi.cover_data = ('jpg', self.cover(idx))
mi.id = self.db.id(idx)
mi.field_metadata = self.db.field_metadata
mi.path = self.db.abspath(idx, create_dirs=False)
return mi
def current_changed(self, current, previous, emit_signal=True):
if current.isValid():
@ -425,16 +375,8 @@ class BooksModel(QAbstractTableModel): # {{{
def get_book_info(self, index):
if isinstance(index, int):
index = self.index(index, 0)
# If index is not valid returns None
data = self.current_changed(index, None, False)
if data is None:
return data
row = index.row()
data[_('Title')] = self.db.title(row)
au = self.db.authors(row)
if not au:
au = _('Unknown')
au = authors_to_string([a.strip().replace('|', ',') for a in au.split(',')])
data[_('Author(s)')] = au
return data
def metadata_for(self, ids):
@ -1189,39 +1131,46 @@ class DeviceBooksModel(BooksModel): # {{{
img = self.default_image
return img
def current_changed(self, current, previous):
data = {}
item = self.db[self.map[current.row()]]
cover = self.cover(current.row())
if cover is not self.default_image:
data['cover'] = cover
type = _('Unknown')
def get_book_display_info(self, idx):
from calibre.ebooks.metadata.book.base import Metadata
item = self.db[self.map[idx]]
cover = self.cover(idx)
if cover is self.default_image:
cover = None
title = item.title
if not title:
title = _('Unknown')
au = item.authors
if not au:
au = [_('Unknown')]
mi = Metadata(title, au)
mi.cover_data = ('jpg', cover)
fmt = _('Unknown')
ext = os.path.splitext(item.path)[1]
if ext:
type = ext[1:].lower()
data[_('Format')] = type
data[_('Path')] = item.path
fmt = ext[1:].lower()
mi.formats = [fmt]
mi.path = (item.path if item.path else None)
dt = dt_factory(item.datetime, assume_utc=True)
data[_('Timestamp')] = isoformat(dt, sep=' ', as_utc=False)
data[_('Collections')] = ', '.join(item.device_collections)
tags = getattr(item, 'tags', None)
if tags:
tags = u', '.join(tags)
else:
tags = _('None')
data[_('Tags')] = tags
comments = getattr(item, 'comments', None)
if not comments:
comments = _('None')
data[_('Comments')] = comments
mi.timestamp = dt
mi.device_collections = list(item.device_collections)
mi.tags = list(getattr(item, 'tags', []))
mi.comments = getattr(item, 'comments', None)
series = getattr(item, 'series', None)
if series:
sidx = getattr(item, 'series_index', 0)
sidx = fmt_sidx(sidx, use_roman = self.use_roman_numbers)
data[_('Series')] = _('Book <font face="serif">%s</font> of %s.')%(sidx, series)
mi.series = series
mi.series_index = sidx
return mi
self.new_bookdisplay_data.emit(data)
def current_changed(self, current, previous, emit_signal=True):
if current.isValid():
idx = current.row()
data = self.get_book_display_info(idx)
if emit_signal:
self.new_bookdisplay_data.emit(data)
else:
return data
def paths(self, rows):
return [self.db[self.map[r.row()]].path for r in rows ]
@ -1281,7 +1230,7 @@ class DeviceBooksModel(BooksModel): # {{{
elif cname == 'authors':
au = self.db[self.map[row]].authors
if not au:
au = self.unknown
au = [_('Unknown')]
return QVariant(authors_to_string(au))
elif cname == 'size':
size = self.db[self.map[row]].size

View File

@ -5,16 +5,22 @@ __license__ = 'GPL v3'
__copyright__ = '2010, Kovid Goyal <kovid@kovidgoyal.net>'
__docformat__ = 'restructuredtext en'
from PyQt4.Qt import QApplication, QFont, QFontInfo, QFontDialog
from PyQt4.Qt import (QApplication, QFont, QFontInfo, QFontDialog,
QAbstractListModel)
from calibre.gui2.preferences import ConfigWidgetBase, test_widget, CommaSeparatedList
from calibre.gui2.preferences.look_feel_ui import Ui_Form
from calibre.gui2 import config, gprefs, qt_app
from calibre.utils.localization import available_translations, \
get_language, get_lang
from calibre.utils.localization import (available_translations,
get_language, get_lang)
from calibre.utils.config import prefs
from calibre.utils.icu import sort_key
class DisplayedFields(QAbstractListModel):
def __init__(self, parent=None):
QAbstractListModel.__init__(self, parent)
class ConfigWidget(ConfigWidgetBase, Ui_Form):
def genesis(self, gui):

View File

@ -14,280 +14,421 @@
<string>Form</string>
</property>
<layout class="QGridLayout" name="gridLayout_2">
<item row="0" column="0">
<widget class="QLabel" name="label_17">
<property name="text">
<string>User Interface &amp;layout (needs restart):</string>
</property>
<property name="buddy">
<cstring>opt_gui_layout</cstring>
</property>
</widget>
</item>
<item row="0" column="1">
<widget class="QComboBox" name="opt_gui_layout">
<property name="maximumSize">
<size>
<width>250</width>
<height>16777215</height>
</size>
</property>
<property name="sizeAdjustPolicy">
<enum>QComboBox::AdjustToMinimumContentsLengthWithIcon</enum>
</property>
<property name="minimumContentsLength">
<number>20</number>
</property>
</widget>
</item>
<item row="1" column="0">
<widget class="QLabel" name="label_6">
<property name="text">
<string>&amp;Number of covers to show in browse mode (needs restart):</string>
</property>
<property name="buddy">
<cstring>opt_cover_flow_queue_length</cstring>
</property>
</widget>
</item>
<item row="1" column="1">
<widget class="QSpinBox" name="opt_cover_flow_queue_length"/>
</item>
<item row="2" column="0">
<widget class="QLabel" name="label_7">
<property name="text">
<string>Choose &amp;language (requires restart):</string>
</property>
<property name="buddy">
<cstring>opt_language</cstring>
</property>
</widget>
</item>
<item row="2" column="1">
<widget class="QComboBox" name="opt_language">
<property name="sizeAdjustPolicy">
<enum>QComboBox::AdjustToMinimumContentsLengthWithIcon</enum>
</property>
<property name="minimumContentsLength">
<number>20</number>
</property>
</widget>
</item>
<item row="3" column="0">
<widget class="QCheckBox" name="opt_show_avg_rating">
<property name="text">
<string>Show &amp;average ratings in the tags browser</string>
</property>
<property name="checked">
<bool>true</bool>
</property>
</widget>
</item>
<item row="3" column="1">
<widget class="QCheckBox" name="opt_disable_animations">
<property name="toolTip">
<string>Disable all animations. Useful if you have a slow/old computer.</string>
</property>
<property name="text">
<string>Disable &amp;animations</string>
</property>
</widget>
</item>
<item row="4" column="0">
<widget class="QCheckBox" name="opt_systray_icon">
<property name="text">
<string>Enable system &amp;tray icon (needs restart)</string>
</property>
</widget>
</item>
<item row="4" column="1">
<widget class="QCheckBox" name="opt_show_splash_screen">
<property name="text">
<string>Show &amp;splash screen at startup</string>
</property>
</widget>
</item>
<item row="5" column="0">
<widget class="QCheckBox" name="opt_disable_tray_notification">
<property name="text">
<string>Disable &amp;notifications in system tray</string>
</property>
</widget>
</item>
<item row="5" column="1">
<widget class="QCheckBox" name="opt_use_roman_numerals_for_series_number">
<property name="text">
<string>Use &amp;Roman numerals for series</string>
</property>
<property name="checked">
<bool>true</bool>
</property>
</widget>
</item>
<item row="6" column="0" colspan="2">
<widget class="QCheckBox" name="opt_separate_cover_flow">
<property name="text">
<string>Show cover &amp;browser in a separate window (needs restart)</string>
</property>
</widget>
</item>
<item row="7" column="0" colspan="2">
<layout class="QHBoxLayout">
<item>
<widget class="QLabel" name="label_6">
<property name="text">
<string>Tags browser category &amp;partitioning method:</string>
</property>
<property name="buddy">
<cstring>opt_tags_browser_partition_method</cstring>
</property>
</widget>
</item>
<item>
<widget class="QComboBox" name="opt_tags_browser_partition_method">
<property name="toolTip">
<string>Choose how tag browser subcategories are displayed when
<item row="0" column="0" colspan="2">
<widget class="QToolBox" name="toolBox">
<widget class="QWidget" name="page">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>682</width>
<height>254</height>
</rect>
</property>
<attribute name="label">
<string>Main interface</string>
</attribute>
<layout class="QGridLayout" name="gridLayout_4">
<item row="0" column="0">
<widget class="QLabel" name="label_17">
<property name="text">
<string>User Interface &amp;layout (needs restart):</string>
</property>
<property name="buddy">
<cstring>opt_gui_layout</cstring>
</property>
</widget>
</item>
<item row="0" column="1">
<widget class="QComboBox" name="opt_gui_layout">
<property name="maximumSize">
<size>
<width>250</width>
<height>16777215</height>
</size>
</property>
<property name="sizeAdjustPolicy">
<enum>QComboBox::AdjustToMinimumContentsLengthWithIcon</enum>
</property>
<property name="minimumContentsLength">
<number>20</number>
</property>
</widget>
</item>
<item row="1" column="0">
<widget class="QLabel" name="label_7">
<property name="text">
<string>Choose &amp;language (requires restart):</string>
</property>
<property name="buddy">
<cstring>opt_language</cstring>
</property>
</widget>
</item>
<item row="1" column="1">
<widget class="QComboBox" name="opt_language">
<property name="sizeAdjustPolicy">
<enum>QComboBox::AdjustToMinimumContentsLengthWithIcon</enum>
</property>
<property name="minimumContentsLength">
<number>20</number>
</property>
</widget>
</item>
<item row="2" column="1">
<widget class="QCheckBox" name="opt_disable_animations">
<property name="toolTip">
<string>Disable all animations. Useful if you have a slow/old computer.</string>
</property>
<property name="text">
<string>Disable &amp;animations</string>
</property>
</widget>
</item>
<item row="3" column="1">
<widget class="QCheckBox" name="opt_show_splash_screen">
<property name="text">
<string>Show &amp;splash screen at startup</string>
</property>
</widget>
</item>
<item row="5" column="0" colspan="2">
<widget class="QGroupBox" name="groupBox_2">
<property name="title">
<string>&amp;Toolbar</string>
</property>
<layout class="QGridLayout" name="gridLayout">
<item row="0" column="1">
<widget class="QComboBox" name="opt_toolbar_icon_size"/>
</item>
<item row="0" column="0">
<widget class="QLabel" name="label">
<property name="text">
<string>&amp;Icon size:</string>
</property>
<property name="buddy">
<cstring>opt_toolbar_icon_size</cstring>
</property>
</widget>
</item>
<item row="1" column="1">
<widget class="QComboBox" name="opt_toolbar_text"/>
</item>
<item row="1" column="0">
<widget class="QLabel" name="label_4">
<property name="text">
<string>Show &amp;text under icons:</string>
</property>
<property name="buddy">
<cstring>opt_toolbar_text</cstring>
</property>
</widget>
</item>
</layout>
</widget>
</item>
<item row="8" column="0">
<spacer name="verticalSpacer_3">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
<height>40</height>
</size>
</property>
</spacer>
</item>
<item row="6" column="0">
<layout class="QHBoxLayout" name="horizontalLayout">
<item>
<widget class="QLabel" name="label_2">
<property name="text">
<string>Interface font:</string>
</property>
<property name="buddy">
<cstring>font_display</cstring>
</property>
</widget>
</item>
<item>
<widget class="QLineEdit" name="font_display">
<property name="readOnly">
<bool>true</bool>
</property>
</widget>
</item>
</layout>
</item>
<item row="6" column="1">
<widget class="QPushButton" name="change_font_button">
<property name="text">
<string>Change &amp;font (needs restart)</string>
</property>
</widget>
</item>
<item row="2" column="0">
<widget class="QCheckBox" name="opt_systray_icon">
<property name="text">
<string>Enable system &amp;tray icon (needs restart)</string>
</property>
</widget>
</item>
<item row="3" column="0">
<widget class="QCheckBox" name="opt_disable_tray_notification">
<property name="text">
<string>Disable &amp;notifications in system tray</string>
</property>
</widget>
</item>
</layout>
</widget>
<widget class="QWidget" name="page_2">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>699</width>
<height>151</height>
</rect>
</property>
<attribute name="label">
<string>Tag Browser</string>
</attribute>
<layout class="QGridLayout" name="gridLayout_5">
<item row="0" column="0" colspan="2">
<widget class="QCheckBox" name="opt_show_avg_rating">
<property name="text">
<string>Show &amp;average ratings in the tags browser</string>
</property>
<property name="checked">
<bool>true</bool>
</property>
</widget>
</item>
<item row="1" column="0" colspan="2">
<layout class="QHBoxLayout">
<item>
<widget class="QLabel" name="label_6">
<property name="text">
<string>Tags browser category &amp;partitioning method:</string>
</property>
<property name="buddy">
<cstring>opt_tags_browser_partition_method</cstring>
</property>
</widget>
</item>
<item>
<widget class="QComboBox" name="opt_tags_browser_partition_method">
<property name="toolTip">
<string>Choose how tag browser subcategories are displayed when
there are more items than the limit. Select by first
letter to see an A, B, C list. Choose partitioned to
have a list of fixed-sized groups. Set to disabled
if you never want subcategories</string>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="label_6">
<property name="text">
<string>&amp;Collapse when more items than:</string>
</property>
<property name="buddy">
<cstring>opt_tags_browser_collapse_at</cstring>
</property>
</widget>
</item>
<item>
<widget class="QSpinBox" name="opt_tags_browser_collapse_at">
<property name="toolTip">
<string>If a Tag Browser category has more than this number of items, it is divided
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="label_6">
<property name="text">
<string>&amp;Collapse when more items than:</string>
</property>
<property name="buddy">
<cstring>opt_tags_browser_collapse_at</cstring>
</property>
</widget>
</item>
<item>
<widget class="QSpinBox" name="opt_tags_browser_collapse_at">
<property name="toolTip">
<string>If a Tag Browser category has more than this number of items, it is divided
up into sub-categories. If the partition method is set to disable, this value is ignored.</string>
</property>
<property name="maximum">
<number>10000</number>
</property>
</widget>
</item>
<item>
<spacer name="verticalSpacer">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
<height>5</height>
</size>
</property>
</spacer>
</item>
</layout>
</item>
<item row="8" column="0">
<widget class="QLabel" name="label_81">
<property name="text">
<string>Categories with &amp;hierarchical items:</string>
</property>
<property name="buddy">
<cstring>opt_categories_using_hierarchy</cstring>
</property>
</widget>
</item>
<item row="8" column="1">
<widget class="MultiCompleteLineEdit" name="opt_categories_using_hierarchy">
<property name="toolTip">
<string>A comma-separated list of columns in which items containing
</property>
<property name="maximum">
<number>10000</number>
</property>
</widget>
</item>
<item>
<spacer name="verticalSpacer">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
<height>5</height>
</size>
</property>
</spacer>
</item>
</layout>
</item>
<item row="2" column="0">
<widget class="QLabel" name="label_81">
<property name="text">
<string>Categories with &amp;hierarchical items:</string>
</property>
<property name="buddy">
<cstring>opt_categories_using_hierarchy</cstring>
</property>
</widget>
</item>
<item row="2" column="1">
<widget class="MultiCompleteLineEdit" name="opt_categories_using_hierarchy">
<property name="toolTip">
<string>A comma-separated list of columns in which items containing
periods are displayed in the tag browser trees. For example, if
this box contains 'tags' then tags of the form 'Mystery.English'
and 'Mystery.Thriller' will be displayed with English and Thriller
both under 'Mystery'. If 'tags' is not in this box,
then the tags will be displayed each on their own line.</string>
</property>
</property>
</widget>
</item>
<item row="3" column="0" colspan="2">
<spacer name="verticalSpacer_2">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
<height>40</height>
</size>
</property>
</spacer>
</item>
</layout>
</widget>
<widget class="QWidget" name="page_3">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>699</width>
<height>306</height>
</rect>
</property>
<attribute name="label">
<string>Cover Browser</string>
</attribute>
<layout class="QGridLayout" name="gridLayout_6">
<item row="0" column="0" colspan="2">
<widget class="QCheckBox" name="opt_separate_cover_flow">
<property name="text">
<string>Show cover &amp;browser in a separate window (needs restart)</string>
</property>
</widget>
</item>
<item row="1" column="0">
<widget class="QLabel" name="label_6">
<property name="text">
<string>&amp;Number of covers to show in browse mode (needs restart):</string>
</property>
<property name="buddy">
<cstring>opt_cover_flow_queue_length</cstring>
</property>
</widget>
</item>
<item row="1" column="1">
<widget class="QSpinBox" name="opt_cover_flow_queue_length"/>
</item>
<item row="2" column="0" colspan="2">
<spacer name="verticalSpacer_4">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
<height>40</height>
</size>
</property>
</spacer>
</item>
</layout>
</widget>
<widget class="QWidget" name="page_4">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>699</width>
<height>306</height>
</rect>
</property>
<attribute name="label">
<string>Book Details</string>
</attribute>
<layout class="QGridLayout" name="gridLayout_7">
<item row="0" column="1">
<widget class="QCheckBox" name="opt_use_roman_numerals_for_series_number">
<property name="text">
<string>Use &amp;Roman numerals for series</string>
</property>
<property name="checked">
<bool>true</bool>
</property>
</widget>
</item>
<item row="0" column="0">
<widget class="QGroupBox" name="groupBox">
<property name="title">
<string>Select displayed metadata</string>
</property>
<layout class="QGridLayout" name="gridLayout_3">
<item row="0" column="0" rowspan="3">
<widget class="QListView" name="field_display_order">
<property name="alternatingRowColors">
<bool>true</bool>
</property>
</widget>
</item>
<item row="0" column="1">
<widget class="QToolButton" name="toolButton">
<property name="toolTip">
<string>Move up</string>
</property>
<property name="icon">
<iconset resource="../../../../resources/images.qrc">
<normaloff>:/images/arrow-up.png</normaloff>:/images/arrow-up.png</iconset>
</property>
</widget>
</item>
<item row="2" column="1">
<widget class="QToolButton" name="toolButton_2">
<property name="toolTip">
<string>Move down</string>
</property>
<property name="icon">
<iconset resource="../../../../resources/images.qrc">
<normaloff>:/images/arrow-down.png</normaloff>:/images/arrow-down.png</iconset>
</property>
</widget>
</item>
<item row="1" column="1">
<spacer name="verticalSpacer_5">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
<height>40</height>
</size>
</property>
</spacer>
</item>
</layout>
</widget>
</item>
</layout>
</widget>
</widget>
</item>
<item row="15" column="0" colspan="2">
<widget class="QGroupBox" name="groupBox_2">
<property name="title">
<string>&amp;Toolbar</string>
</property>
<layout class="QGridLayout" name="gridLayout">
<item row="0" column="1">
<widget class="QComboBox" name="opt_toolbar_icon_size"/>
</item>
<item row="0" column="0">
<widget class="QLabel" name="label">
<property name="text">
<string>&amp;Icon size:</string>
</property>
<property name="buddy">
<cstring>opt_toolbar_icon_size</cstring>
</property>
</widget>
</item>
<item row="1" column="1">
<widget class="QComboBox" name="opt_toolbar_text"/>
</item>
<item row="1" column="0">
<widget class="QLabel" name="label_4">
<property name="text">
<string>Show &amp;text under icons:</string>
</property>
<property name="buddy">
<cstring>opt_toolbar_text</cstring>
</property>
</widget>
</item>
</layout>
</widget>
</item>
<item row="16" column="0">
<layout class="QHBoxLayout" name="horizontalLayout">
<item>
<widget class="QLabel" name="label_2">
<property name="text">
<string>Interface font:</string>
</property>
<property name="buddy">
<cstring>font_display</cstring>
</property>
</widget>
</item>
<item>
<widget class="QLineEdit" name="font_display">
<property name="readOnly">
<bool>true</bool>
</property>
</widget>
</item>
</layout>
</item>
<item row="16" column="1">
<widget class="QPushButton" name="change_font_button">
<property name="text">
<string>Change &amp;font (needs restart)</string>
</property>
</widget>
</item>
<item row="17" column="0" colspan="2">
<spacer name="verticalSpacer">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
<height>40</height>
</size>
</property>
</spacer>
</item>
</layout>
</widget>
<customwidgets>
@ -297,6 +438,8 @@ then the tags will be displayed each on their own line.</string>
<header>calibre/gui2/complete.h</header>
</customwidget>
</customwidgets>
<resources/>
<resources>
<include location="../../../../resources/images.qrc"/>
</resources>
<connections/>
</ui>

View File

@ -1,9 +0,0 @@
#!/usr/bin/env python
# vim:fileencoding=UTF-8:ts=4:sw=4:sta:et:sts=4:ai
__license__ = 'GPL v3'
__copyright__ = '2010, Kovid Goyal <kovid@kovidgoyal.net>'
__docformat__ = 'restructuredtext en'

View File

@ -1,37 +0,0 @@
#!/usr/bin/env python
# vim:fileencoding=UTF-8:ts=4:sw=4:sta:et:sts=4:ai
__license__ = 'GPL v3'
__copyright__ = '2010, Kovid Goyal <kovid@kovidgoyal.net>'
__docformat__ = 'restructuredtext en'
''' Design documentation {{{
Storage paradigm {{{
* Agnostic to storage paradigm (i.e. no book per folder assumptions)
* Two separate concepts: A store and collection
A store is a backend, like a sqlite database associated with a path on
the local filesystem, or a cloud based storage solution.
A collection is a user defined group of stores. Most of the logic for
data manipulation sorting/searching/restrictions should be in the collection
class. The collection class should transparently handle the
conversion from store name + id to row number in the collection.
* Not sure how feasible it is to allow many-many maps between stores
and collections.
}}}
Event system {{{
* Comprehensive event system that other components can subscribe to
* Subscribers should be able to temporarily block receiving events
* Should event dispatch be asynchronous?
* Track last modified time for metadata and each format
}}}
}}}'''
# Imports {{{
# }}}

View File

@ -188,7 +188,7 @@ class FieldMetadata(dict):
'datatype':'text',
'is_multiple':None,
'kind':'field',
'name':None,
'name':_('Author Sort'),
'search_terms':['author_sort'],
'is_custom':False,
'is_category':False,
@ -238,7 +238,7 @@ class FieldMetadata(dict):
'datatype':'datetime',
'is_multiple':None,
'kind':'field',
'name':_('Date'),
'name':_('Modified'),
'search_terms':['last_modified'],
'is_custom':False,
'is_category':False,
@ -258,7 +258,7 @@ class FieldMetadata(dict):
'datatype':'text',
'is_multiple':None,
'kind':'field',
'name':None,
'name':_('Path'),
'search_terms':[],
'is_custom':False,
'is_category':False,
@ -308,7 +308,7 @@ class FieldMetadata(dict):
'datatype':'float',
'is_multiple':None,
'kind':'field',
'name':_('Size (MB)'),
'name':_('Size'),
'search_terms':['size'],
'is_custom':False,
'is_category':False,
@ -399,6 +399,13 @@ class FieldMetadata(dict):
if self._tb_cats[k]['kind']=='field' and
self._tb_cats[k]['datatype'] is not None]
def displayable_field_keys(self):
return [k for k in self._tb_cats.keys()
if self._tb_cats[k]['kind']=='field' and
self._tb_cats[k]['datatype'] is not None and
k not in ('au_map', 'marked', 'ondevice', 'cover') and
not self.is_series_index(k)]
def standard_field_keys(self):
return [k for k in self._tb_cats.keys()
if self._tb_cats[k]['kind']=='field' and
@ -442,6 +449,11 @@ class FieldMetadata(dict):
def is_custom_field(self, key):
return key.startswith(self.custom_field_prefix)
def is_series_index(self, key):
m = self[key]
return (m['datatype'] == 'float' and key.endswith('_index') and
key[:-6] in self)
def key_to_label(self, key):
if 'label' not in self._tb_cats[key]:
return key