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_will_display = ['*']
content_server_wont_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'
# Set the maximum number of sort 'levels' that calibre will use to resort the # 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 # 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.icu import sort_key
from calibre.utils.formatter import TemplateFormatter 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 = { NULL_VALUES = {
'user_metadata': {}, 'user_metadata': {},
@ -551,7 +554,8 @@ class Metadata(object):
def format_field_extended(self, key, series_with_index=True): def format_field_extended(self, key, series_with_index=True):
from calibre.ebooks.metadata import authors_to_string 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 # Handle custom series index
@ -627,6 +631,8 @@ class Metadata(object):
res = format_date(res, fmeta['display'].get('date_format','dd MMM yyyy')) res = format_date(res, fmeta['display'].get('date_format','dd MMM yyyy'))
elif datatype == 'rating': elif datatype == 'rating':
res = res/2.0 res = res/2.0
elif key in ('book_size', 'size'):
res = human_readable(res)
return (name, unicode(res), orig_res, fmeta) return (name, unicode(res), orig_res, fmeta)
return (None, None, None, None) 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_partition_method'] = 'first letter'
gprefs.defaults['tags_browser_collapse_at'] = 100 gprefs.defaults['tags_browser_collapse_at'] = 100
gprefs.defaults['edit_metadata_single_layout'] = 'default' 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', ALL_COLUMNS = ['title', 'ondevice', 'authors', 'size', 'timestamp', 'rating', 'publisher',
'tags', 'series', 'pubdate'] 'tags', 'series', 'pubdate']
def _config(): def _config(): # {{{
c = Config('gui', 'preferences for the calibre GUI') c = Config('gui', 'preferences for the calibre GUI')
c.add_opt('send_to_storage_card_by_default', default=False, c.add_opt('send_to_storage_card_by_default', default=False,
help=_('Send file to storage card instead of main memory by default')) help=_('Send file to storage card instead of main memory by default'))
@ -181,6 +189,8 @@ def _config():
return ConfigProxy(c) return ConfigProxy(c)
config = _config() config = _config()
# }}}
# Turn off DeprecationWarnings in windows GUI # Turn off DeprecationWarnings in windows GUI
if iswindows: if iswindows:
import warnings import warnings

View File

@ -30,5 +30,5 @@ class ShowBookDetailsAction(InterfaceAction):
index = self.gui.library_view.currentIndex() index = self.gui.library_view.currentIndex()
if index.isValid(): if index.isValid():
BookInfo(self.gui, self.gui.library_view, index, 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>' __copyright__ = '2010, Kovid Goyal <kovid@kovidgoyal.net>'
__docformat__ = 'restructuredtext en' __docformat__ = 'restructuredtext en'
import collections, sys
from Queue import Queue
from PyQt4.Qt import QPixmap, QSize, QWidget, Qt, pyqtSignal, QUrl, \ from PyQt4.Qt import (QPixmap, QSize, QWidget, Qt, pyqtSignal, QUrl,
QPropertyAnimation, QEasingCurve, QThread, QApplication, QFontInfo, \ QPropertyAnimation, QEasingCurve, QApplication, QFontInfo,
QSizePolicy, QPainter, QRect, pyqtProperty, QLayout, QPalette, QMenu QSizePolicy, QPainter, QRect, pyqtProperty, QLayout, QPalette, QMenu)
from PyQt4.QtWebKit import QWebView from PyQt4.QtWebKit import QWebView
from calibre import fit_image, prepare_string_for_xml 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, \ from calibre.gui2.dnd import (dnd_has_image, dnd_get_image, dnd_get_files,
IMAGE_EXTENSIONS, dnd_has_extension IMAGE_EXTENSIONS, dnd_has_extension)
from calibre.ebooks import BOOK_EXTENSIONS 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.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 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): def render_html(mi, css, vertical, widget, all_fields=False): # {{{
keys = data.keys() table = render_data(mi, all_fields=all_fields,
# First sort by name. The WEIGHTS sort will preserve this sub-order use_roman_numbers=config['use_roman_numerals_for_series_number'])
keys.sort(key=sort_key)
keys.sort(key=lambda x: WEIGHTS[x]) def color_to_string(col):
rows = [] ans = '#000000'
for key in keys: if col.isValid():
txt = data[key] col = col.toRgb()
if key in ('id', _('Comments')) or not hasattr(txt, 'strip') or not txt.strip() or \ if col.isValid():
txt == 'None': 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 continue
if isinstance(key, str): name = metadata['name']
key = key.decode(preferred_encoding, 'replace') if not name:
if isinstance(txt, str): name = field
txt = txt.decode(preferred_encoding, 'replace') name += ':'
if key.endswith(u':html'): if metadata['datatype'] == 'comments':
key = key[:-5] val = getattr(mi, field)
txt = comments_to_html(txt) if val:
elif '</font>' not in txt: val = force_unicode(val)
txt = prepare_string_for_xml(txt) ans.append((field,
if 'id' in data: u'<td class="comments" colspan="2">%s</td>'%comments_to_html(val)))
if key == _('Path'): elif field == 'path':
txt = u'<a href="path:%s" title="%s">%s</a>'%(data['id'], if mi.path:
txt, _('Click to open')) path = force_unicode(mi.path, filesystem_encoding)
if key == _('Formats') and txt and txt != _('None'): scheme = u'devpath' if isdevice else u'path'
fmts = [x.strip() for x in txt.split(',')] url = prepare_string_for_xml(path if isdevice else
fmts = [u'<a href="format:%s:%s">%s</a>' % (data['id'], x, x) for x unicode(mi.id), True)
in fmts] link = u'<a href="%s:%s" title="%s">%s</a>' % (scheme, url,
txt = ', '.join(fmts) 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: else:
if key == _('Path'): val = mi.format_field(field)[-1]
txt = u'<a href="devpath:%s">%s</a>'%(txt, if val is None:
_('Click to open')) 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)) ans.append((field, u'<td class="title">%s</td><td>%s</td>'%(name, val)))
return rows
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): def show_data(self, data):
self.animation.stop() 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)} self.data = {'id':data.get('id', None)}
if data.has_key('cover'): if data.cover_data[1]:
self.pixmap = QPixmap.fromImage(data.pop('cover')) self.pixmap = QPixmap.fromImage(data.cover_data[1])
if self.pixmap.isNull() or self.pixmap.width() < 5 or \ if self.pixmap.isNull() or self.pixmap.width() < 5 or \
self.pixmap.height() < 5: self.pixmap.height() < 5:
self.pixmap = self.default_pixmap self.pixmap = self.default_pixmap
@ -188,32 +272,6 @@ class CoverView(QWidget): # {{{
# Book Info {{{ # 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): class BookInfo(QWebView):
link_clicked = pyqtSignal(object) link_clicked = pyqtSignal(object)
@ -221,8 +279,6 @@ class BookInfo(QWebView):
def __init__(self, vertical, parent=None): def __init__(self, vertical, parent=None):
QWebView.__init__(self, parent) QWebView.__init__(self, parent)
self.vertical = vertical self.vertical = vertical
self.renderer = RenderComments(self)
self.renderer.rdone.connect(self._show_data, type=Qt.QueuedConnection)
self.page().setLinkDelegationPolicy(self.page().DelegateAllLinks) self.page().setLinkDelegationPolicy(self.page().DelegateAllLinks)
self.linkClicked.connect(self.link_activated) self.linkClicked.connect(self.link_activated)
self._link_clicked = False self._link_clicked = False
@ -231,6 +287,7 @@ class BookInfo(QWebView):
self.setAcceptDrops(False) self.setAcceptDrops(False)
palette.setBrush(QPalette.Base, Qt.transparent) palette.setBrush(QPalette.Base, Qt.transparent)
self.page().setPalette(palette) self.page().setPalette(palette)
self.css = P('templates/book_details.css', data=True).decode('utf-8')
def link_activated(self, link): def link_activated(self, link):
self._link_clicked = True self._link_clicked = True
@ -240,56 +297,9 @@ class BookInfo(QWebView):
def turnoff_scrollbar(self, *args): def turnoff_scrollbar(self, *args):
self.page().mainFrame().setScrollBarPolicy(Qt.Horizontal, Qt.ScrollBarAlwaysOff) self.page().mainFrame().setScrollBarPolicy(Qt.Horizontal, Qt.ScrollBarAlwaysOff)
def show_data(self, data): def show_data(self, mi):
rows = render_rows(data) html = render_html(mi, self.css, self.vertical, self.parent())
rows = u'\n'.join([u'<tr><td valign="top"><b>%s:</b></td><td valign="top">%s</td></tr>'%(k,t) for self.setHtml(html)
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 mouseDoubleClickEvent(self, ev): def mouseDoubleClickEvent(self, ev):
swidth = self.page().mainFrame().scrollBarGeometry(Qt.Vertical).width() swidth = self.page().mainFrame().scrollBarGeometry(Qt.Vertical).width()
@ -457,10 +467,10 @@ class BookDetails(QWidget): # {{{
self._layout.addWidget(self.cover_view) self._layout.addWidget(self.cover_view)
self.book_info = BookInfo(vertical, self) self.book_info = BookInfo(vertical, self)
self._layout.addWidget(self.book_info) 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) self.setCursor(Qt.PointingHandCursor)
def _link_clicked(self, link): def handle_click(self, link):
typ, _, val = link.partition(':') typ, _, val = link.partition(':')
if typ == 'path': if typ == 'path':
self.open_containing_folder.emit(int(val)) self.open_containing_folder.emit(int(val))
@ -484,7 +494,7 @@ class BookDetails(QWidget): # {{{
def show_data(self, data): def show_data(self, data):
self.book_info.show_data(data) self.book_info.show_data(data)
self.cover_view.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() self.update_layout()
def update_layout(self): def update_layout(self):
@ -500,7 +510,7 @@ class BookDetails(QWidget): # {{{
) )
def reset_info(self): 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' __copyright__ = '2008, Kovid Goyal kovid@kovidgoyal.net'
__docformat__ = 'restructuredtext en' __docformat__ = 'restructuredtext en'
import textwrap, os, re
from PyQt4.Qt import QCoreApplication, SIGNAL, QModelIndex, QTimer, Qt, \ from PyQt4.Qt import (QCoreApplication, SIGNAL, QModelIndex, QTimer, Qt,
QDialog, QPixmap, QIcon, QSize QDialog, QPixmap, QIcon, QSize, QPalette)
from calibre.gui2.dialogs.book_info_ui import Ui_BookInfo 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 import fit_image
from calibre.library.comments import comments_to_html from calibre.gui2.book_details import render_html
from calibre.utils.icu import sort_key
class BookInfo(QDialog, Ui_BookInfo): 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) QDialog.__init__(self, parent)
Ui_BookInfo.__init__(self) Ui_BookInfo.__init__(self)
self.setupUi(self) self.setupUi(self)
self.gui = parent self.gui = parent
self.cover_pixmap = None self.cover_pixmap = None
self.comments.sizeHint = self.comments_size_hint self.details.sizeHint = self.details_size_hint
self.comments.page().setLinkDelegationPolicy(self.comments.page().DelegateAllLinks) self.details.page().setLinkDelegationPolicy(self.details.page().DelegateAllLinks)
self.comments.linkClicked.connect(self.link_clicked) self.details.linkClicked.connect(self.link_clicked)
self.view_func = view_func 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 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.view.selectionModel(), SIGNAL('currentChanged(QModelIndex,QModelIndex)'), self.slave)
self.connect(self.next_button, SIGNAL('clicked()'), self.next) self.connect(self.next_button, SIGNAL('clicked()'), self.next)
self.connect(self.previous_button, SIGNAL('clicked()'), self.previous) 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.fit_cover.stateChanged.connect(self.toggle_cover_fit)
self.cover.resizeEvent = self.cover_view_resized self.cover.resizeEvent = self.cover_view_resized
self.cover.cover_changed.connect(self.cover_changed) self.cover.cover_changed.connect(self.cover_changed)
@ -46,6 +48,10 @@ class BookInfo(QDialog, Ui_BookInfo):
screen_height = desktop.availableGeometry().height() - 100 screen_height = desktop.availableGeometry().height() - 100
self.resize(self.size().width(), screen_height) 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): def cover_changed(self, data):
if self.current_row is not None: if self.current_row is not None:
id_ = self.view.model().id(self.current_row) id_ = self.view.model().id(self.current_row)
@ -60,11 +66,8 @@ class BookInfo(QDialog, Ui_BookInfo):
if self.fit_cover.isChecked(): if self.fit_cover.isChecked():
self.resize_cover() self.resize_cover()
def link_clicked(self, url): def details_size_hint(self):
open_url(url) return QSize(350, 550)
def comments_size_hint(self):
return QSize(350, 250)
def toggle_cover_fit(self, state): def toggle_cover_fit(self, state):
dynamic.set('book_info_dialog_fit_cover', self.fit_cover.isChecked()) dynamic.set('book_info_dialog_fit_cover', self.fit_cover.isChecked())
@ -77,13 +80,6 @@ class BookInfo(QDialog, Ui_BookInfo):
row = current.row() row = current.row()
self.refresh(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): def next(self):
row = self.view.currentIndex().row() row = self.view.currentIndex().row()
ni = self.view.model().index(row+1, 0) ni = self.view.model().index(row+1, 0)
@ -117,8 +113,8 @@ class BookInfo(QDialog, Ui_BookInfo):
row = row.row() row = row.row()
if row == self.current_row: if row == self.current_row:
return return
info = self.view.model().get_book_info(row) mi = self.view.model().get_book_display_info(row)
if info is None: if mi is None:
# Indicates books was deleted from library, or row numbers have # Indicates books was deleted from library, or row numbers have
# changed # changed
return return
@ -126,40 +122,11 @@ class BookInfo(QDialog, Ui_BookInfo):
self.previous_button.setEnabled(False if row == 0 else True) 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.next_button.setEnabled(False if row == self.view.model().rowCount(QModelIndex())-1 else True)
self.current_row = row self.current_row = row
self.setWindowTitle(info[_('Title')]) self.setWindowTitle(mi.title)
self.title.setText('<b>'+info.pop(_('Title'))) self.title.setText('<b>'+mi.title)
comments = info.pop(_('Comments'), '') mi.title = _('Unknown')
if comments: self.cover_pixmap = QPixmap.fromImage(mi.cover_data[1])
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.resize_cover() 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"> <layout class="QGridLayout" name="gridLayout">
<item row="0" column="0" colspan="2"> <item row="0" column="0" colspan="2">
<widget class="QLabel" name="title"> <widget class="QLabel" name="title">
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Maximum">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="font"> <property name="font">
<font> <font>
<weight>75</weight> <weight>75</weight>
@ -34,82 +40,17 @@
</property> </property>
</widget> </widget>
</item> </item>
<item row="1" column="0" rowspan="3"> <item row="2" column="0" rowspan="3">
<widget class="CoverView" name="cover"/> <widget class="CoverView" name="cover"/>
</item> </item>
<item row="1" column="1"> <item row="3" 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">
<widget class="QCheckBox" name="fit_cover"> <widget class="QCheckBox" name="fit_cover">
<property name="text"> <property name="text">
<string>Fit &amp;cover within view</string> <string>Fit &amp;cover within view</string>
</property> </property>
</widget> </widget>
</item> </item>
<item row="3" column="1"> <item row="4" column="1">
<layout class="QHBoxLayout" name="horizontalLayout"> <layout class="QHBoxLayout" name="horizontalLayout">
<item> <item>
<widget class="QPushButton" name="previous_button"> <widget class="QPushButton" name="previous_button">
@ -135,6 +76,15 @@
</item> </item>
</layout> </layout>
</item> </item>
<item row="2" column="1">
<widget class="QWebView" name="details">
<property name="url">
<url>
<string>about:blank</string>
</url>
</property>
</widget>
</item>
</layout> </layout>
</widget> </widget>
<customwidgets> <customwidgets>

View File

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

View File

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

View File

@ -14,280 +14,421 @@
<string>Form</string> <string>Form</string>
</property> </property>
<layout class="QGridLayout" name="gridLayout_2"> <layout class="QGridLayout" name="gridLayout_2">
<item row="0" column="0"> <item row="0" column="0" colspan="2">
<widget class="QLabel" name="label_17"> <widget class="QToolBox" name="toolBox">
<property name="text"> <widget class="QWidget" name="page">
<string>User Interface &amp;layout (needs restart):</string> <property name="geometry">
</property> <rect>
<property name="buddy"> <x>0</x>
<cstring>opt_gui_layout</cstring> <y>0</y>
</property> <width>682</width>
</widget> <height>254</height>
</item> </rect>
<item row="0" column="1"> </property>
<widget class="QComboBox" name="opt_gui_layout"> <attribute name="label">
<property name="maximumSize"> <string>Main interface</string>
<size> </attribute>
<width>250</width> <layout class="QGridLayout" name="gridLayout_4">
<height>16777215</height> <item row="0" column="0">
</size> <widget class="QLabel" name="label_17">
</property> <property name="text">
<property name="sizeAdjustPolicy"> <string>User Interface &amp;layout (needs restart):</string>
<enum>QComboBox::AdjustToMinimumContentsLengthWithIcon</enum> </property>
</property> <property name="buddy">
<property name="minimumContentsLength"> <cstring>opt_gui_layout</cstring>
<number>20</number> </property>
</property> </widget>
</widget> </item>
</item> <item row="0" column="1">
<item row="1" column="0"> <widget class="QComboBox" name="opt_gui_layout">
<widget class="QLabel" name="label_6"> <property name="maximumSize">
<property name="text"> <size>
<string>&amp;Number of covers to show in browse mode (needs restart):</string> <width>250</width>
</property> <height>16777215</height>
<property name="buddy"> </size>
<cstring>opt_cover_flow_queue_length</cstring> </property>
</property> <property name="sizeAdjustPolicy">
</widget> <enum>QComboBox::AdjustToMinimumContentsLengthWithIcon</enum>
</item> </property>
<item row="1" column="1"> <property name="minimumContentsLength">
<widget class="QSpinBox" name="opt_cover_flow_queue_length"/> <number>20</number>
</item> </property>
<item row="2" column="0"> </widget>
<widget class="QLabel" name="label_7"> </item>
<property name="text"> <item row="1" column="0">
<string>Choose &amp;language (requires restart):</string> <widget class="QLabel" name="label_7">
</property> <property name="text">
<property name="buddy"> <string>Choose &amp;language (requires restart):</string>
<cstring>opt_language</cstring> </property>
</property> <property name="buddy">
</widget> <cstring>opt_language</cstring>
</item> </property>
<item row="2" column="1"> </widget>
<widget class="QComboBox" name="opt_language"> </item>
<property name="sizeAdjustPolicy"> <item row="1" column="1">
<enum>QComboBox::AdjustToMinimumContentsLengthWithIcon</enum> <widget class="QComboBox" name="opt_language">
</property> <property name="sizeAdjustPolicy">
<property name="minimumContentsLength"> <enum>QComboBox::AdjustToMinimumContentsLengthWithIcon</enum>
<number>20</number> </property>
</property> <property name="minimumContentsLength">
</widget> <number>20</number>
</item> </property>
<item row="3" column="0"> </widget>
<widget class="QCheckBox" name="opt_show_avg_rating"> </item>
<property name="text"> <item row="2" column="1">
<string>Show &amp;average ratings in the tags browser</string> <widget class="QCheckBox" name="opt_disable_animations">
</property> <property name="toolTip">
<property name="checked"> <string>Disable all animations. Useful if you have a slow/old computer.</string>
<bool>true</bool> </property>
</property> <property name="text">
</widget> <string>Disable &amp;animations</string>
</item> </property>
<item row="3" column="1"> </widget>
<widget class="QCheckBox" name="opt_disable_animations"> </item>
<property name="toolTip"> <item row="3" column="1">
<string>Disable all animations. Useful if you have a slow/old computer.</string> <widget class="QCheckBox" name="opt_show_splash_screen">
</property> <property name="text">
<property name="text"> <string>Show &amp;splash screen at startup</string>
<string>Disable &amp;animations</string> </property>
</property> </widget>
</widget> </item>
</item> <item row="5" column="0" colspan="2">
<item row="4" column="0"> <widget class="QGroupBox" name="groupBox_2">
<widget class="QCheckBox" name="opt_systray_icon"> <property name="title">
<property name="text"> <string>&amp;Toolbar</string>
<string>Enable system &amp;tray icon (needs restart)</string> </property>
</property> <layout class="QGridLayout" name="gridLayout">
</widget> <item row="0" column="1">
</item> <widget class="QComboBox" name="opt_toolbar_icon_size"/>
<item row="4" column="1"> </item>
<widget class="QCheckBox" name="opt_show_splash_screen"> <item row="0" column="0">
<property name="text"> <widget class="QLabel" name="label">
<string>Show &amp;splash screen at startup</string> <property name="text">
</property> <string>&amp;Icon size:</string>
</widget> </property>
</item> <property name="buddy">
<item row="5" column="0"> <cstring>opt_toolbar_icon_size</cstring>
<widget class="QCheckBox" name="opt_disable_tray_notification"> </property>
<property name="text"> </widget>
<string>Disable &amp;notifications in system tray</string> </item>
</property> <item row="1" column="1">
</widget> <widget class="QComboBox" name="opt_toolbar_text"/>
</item> </item>
<item row="5" column="1"> <item row="1" column="0">
<widget class="QCheckBox" name="opt_use_roman_numerals_for_series_number"> <widget class="QLabel" name="label_4">
<property name="text"> <property name="text">
<string>Use &amp;Roman numerals for series</string> <string>Show &amp;text under icons:</string>
</property> </property>
<property name="checked"> <property name="buddy">
<bool>true</bool> <cstring>opt_toolbar_text</cstring>
</property> </property>
</widget> </widget>
</item> </item>
<item row="6" column="0" colspan="2"> </layout>
<widget class="QCheckBox" name="opt_separate_cover_flow"> </widget>
<property name="text"> </item>
<string>Show cover &amp;browser in a separate window (needs restart)</string> <item row="8" column="0">
</property> <spacer name="verticalSpacer_3">
</widget> <property name="orientation">
</item> <enum>Qt::Vertical</enum>
<item row="7" column="0" colspan="2"> </property>
<layout class="QHBoxLayout"> <property name="sizeHint" stdset="0">
<item> <size>
<widget class="QLabel" name="label_6"> <width>20</width>
<property name="text"> <height>40</height>
<string>Tags browser category &amp;partitioning method:</string> </size>
</property> </property>
<property name="buddy"> </spacer>
<cstring>opt_tags_browser_partition_method</cstring> </item>
</property> <item row="6" column="0">
</widget> <layout class="QHBoxLayout" name="horizontalLayout">
</item> <item>
<item> <widget class="QLabel" name="label_2">
<widget class="QComboBox" name="opt_tags_browser_partition_method"> <property name="text">
<property name="toolTip"> <string>Interface font:</string>
<string>Choose how tag browser subcategories are displayed when </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 there are more items than the limit. Select by first
letter to see an A, B, C list. Choose partitioned to letter to see an A, B, C list. Choose partitioned to
have a list of fixed-sized groups. Set to disabled have a list of fixed-sized groups. Set to disabled
if you never want subcategories</string> if you never want subcategories</string>
</property> </property>
</widget> </widget>
</item> </item>
<item> <item>
<widget class="QLabel" name="label_6"> <widget class="QLabel" name="label_6">
<property name="text"> <property name="text">
<string>&amp;Collapse when more items than:</string> <string>&amp;Collapse when more items than:</string>
</property> </property>
<property name="buddy"> <property name="buddy">
<cstring>opt_tags_browser_collapse_at</cstring> <cstring>opt_tags_browser_collapse_at</cstring>
</property> </property>
</widget> </widget>
</item> </item>
<item> <item>
<widget class="QSpinBox" name="opt_tags_browser_collapse_at"> <widget class="QSpinBox" name="opt_tags_browser_collapse_at">
<property name="toolTip"> <property name="toolTip">
<string>If a Tag Browser category has more than this number of items, it is divided <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> up into sub-categories. If the partition method is set to disable, this value is ignored.</string>
</property> </property>
<property name="maximum"> <property name="maximum">
<number>10000</number> <number>10000</number>
</property> </property>
</widget> </widget>
</item> </item>
<item> <item>
<spacer name="verticalSpacer"> <spacer name="verticalSpacer">
<property name="orientation"> <property name="orientation">
<enum>Qt::Horizontal</enum> <enum>Qt::Horizontal</enum>
</property> </property>
<property name="sizeHint" stdset="0"> <property name="sizeHint" stdset="0">
<size> <size>
<width>20</width> <width>20</width>
<height>5</height> <height>5</height>
</size> </size>
</property> </property>
</spacer> </spacer>
</item> </item>
</layout> </layout>
</item> </item>
<item row="8" column="0"> <item row="2" column="0">
<widget class="QLabel" name="label_81"> <widget class="QLabel" name="label_81">
<property name="text"> <property name="text">
<string>Categories with &amp;hierarchical items:</string> <string>Categories with &amp;hierarchical items:</string>
</property> </property>
<property name="buddy"> <property name="buddy">
<cstring>opt_categories_using_hierarchy</cstring> <cstring>opt_categories_using_hierarchy</cstring>
</property> </property>
</widget> </widget>
</item> </item>
<item row="8" column="1"> <item row="2" column="1">
<widget class="MultiCompleteLineEdit" name="opt_categories_using_hierarchy"> <widget class="MultiCompleteLineEdit" name="opt_categories_using_hierarchy">
<property name="toolTip"> <property name="toolTip">
<string>A comma-separated list of columns in which items containing <string>A comma-separated list of columns in which items containing
periods are displayed in the tag browser trees. For example, if periods are displayed in the tag browser trees. For example, if
this box contains 'tags' then tags of the form 'Mystery.English' this box contains 'tags' then tags of the form 'Mystery.English'
and 'Mystery.Thriller' will be displayed with English and Thriller and 'Mystery.Thriller' will be displayed with English and Thriller
both under 'Mystery'. If 'tags' is not in this box, both under 'Mystery'. If 'tags' is not in this box,
then the tags will be displayed each on their own line.</string> 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> </widget>
</item> </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> </layout>
</widget> </widget>
<customwidgets> <customwidgets>
@ -297,6 +438,8 @@ then the tags will be displayed each on their own line.</string>
<header>calibre/gui2/complete.h</header> <header>calibre/gui2/complete.h</header>
</customwidget> </customwidget>
</customwidgets> </customwidgets>
<resources/> <resources>
<include location="../../../../resources/images.qrc"/>
</resources>
<connections/> <connections/>
</ui> </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', 'datatype':'text',
'is_multiple':None, 'is_multiple':None,
'kind':'field', 'kind':'field',
'name':None, 'name':_('Author Sort'),
'search_terms':['author_sort'], 'search_terms':['author_sort'],
'is_custom':False, 'is_custom':False,
'is_category':False, 'is_category':False,
@ -238,7 +238,7 @@ class FieldMetadata(dict):
'datatype':'datetime', 'datatype':'datetime',
'is_multiple':None, 'is_multiple':None,
'kind':'field', 'kind':'field',
'name':_('Date'), 'name':_('Modified'),
'search_terms':['last_modified'], 'search_terms':['last_modified'],
'is_custom':False, 'is_custom':False,
'is_category':False, 'is_category':False,
@ -258,7 +258,7 @@ class FieldMetadata(dict):
'datatype':'text', 'datatype':'text',
'is_multiple':None, 'is_multiple':None,
'kind':'field', 'kind':'field',
'name':None, 'name':_('Path'),
'search_terms':[], 'search_terms':[],
'is_custom':False, 'is_custom':False,
'is_category':False, 'is_category':False,
@ -308,7 +308,7 @@ class FieldMetadata(dict):
'datatype':'float', 'datatype':'float',
'is_multiple':None, 'is_multiple':None,
'kind':'field', 'kind':'field',
'name':_('Size (MB)'), 'name':_('Size'),
'search_terms':['size'], 'search_terms':['size'],
'is_custom':False, 'is_custom':False,
'is_category':False, 'is_category':False,
@ -399,6 +399,13 @@ class FieldMetadata(dict):
if self._tb_cats[k]['kind']=='field' and if self._tb_cats[k]['kind']=='field' and
self._tb_cats[k]['datatype'] is not None] 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): def standard_field_keys(self):
return [k for k in self._tb_cats.keys() return [k for k in self._tb_cats.keys()
if self._tb_cats[k]['kind']=='field' and if self._tb_cats[k]['kind']=='field' and
@ -442,6 +449,11 @@ class FieldMetadata(dict):
def is_custom_field(self, key): def is_custom_field(self, key):
return key.startswith(self.custom_field_prefix) 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): def key_to_label(self, key):
if 'label' not in self._tb_cats[key]: if 'label' not in self._tb_cats[key]:
return key return key