GUI for creating custom list templates

This commit is contained in:
Kovid Goyal 2017-07-20 20:23:50 +05:30
parent 72c82aeb05
commit e7af7af508
No known key found for this signature in database
GPG Key ID: 06BC317B515ACE7C
3 changed files with 133 additions and 4 deletions

View File

@ -2,6 +2,8 @@
# vim:fileencoding=UTF-8:ts=4:sw=4:sta:et:sts=4:ai
# License: GPLv3 Copyright: 2010, Kovid Goyal <kovid at kovidgoyal.net>
import errno
import json
import os
import textwrap
import time
@ -19,6 +21,8 @@ from calibre.gui2 import (
config, error_dialog, gprefs, info_dialog, open_url, warning_dialog
)
from calibre.gui2.preferences import AbortCommit, ConfigWidgetBase, test_widget
from calibre.srv.code import custom_list_template as default_custom_list_template
from calibre.srv.embedded import custom_list_template
from calibre.srv.library_broker import load_gui_libraries
from calibre.srv.opts import change_settings, options, server_config
from calibre.srv.users import (
@ -26,7 +30,6 @@ from calibre.srv.users import (
)
from calibre.utils.icu import primary_sort_key
# Advanced {{{
@ -729,6 +732,104 @@ class Users(QWidget):
# }}}
class CustomList(QWidget): # {{{
changed_signal = pyqtSignal()
def __init__(self, parent):
QWidget.__init__(self, parent)
self.default_template = default_custom_list_template()
self.l = l = QFormLayout(self)
l.setFieldGrowthPolicy(l.AllNonFixedFieldsGrow)
self.la = la = QLabel('<p>' + _(
'Here you can create a template to control what data is shown when'
' using the <i>Custom list</i> mode for the book list'))
la.setWordWrap(True)
l.addRow(la)
self.thumbnail = t = QCheckBox(_('Show a cover &thumbnail'))
self.thumbnail_height = th = QSpinBox(self)
th.setSuffix(' px'), th.setRange(60, 600)
self.entry_height = eh = QLineEdit(self)
l.addRow(t), l.addRow(_('Thumbnail &height:'), th)
l.addRow(_('Entry &height:'), eh)
t.stateChanged.connect(self.changed_signal)
th.valueChanged.connect(self.changed_signal)
eh.textChanged.connect(self.changed_signal)
eh.setToolTip(textwrap.fill(_(
'The height for each entry. The special value "auto" causes a height to be calculated'
' based on the number of lines in the template. Otherwise, use a CSS length, such as'
' 100px or 15ex')))
t.stateChanged.connect(self.thumbnail_state_changed)
th.setVisible(False)
self.comments_fields = cf = QLineEdit(self)
l.addRow(_('&Long text fields:'), cf)
cf.setToolTip(textwrap.fill(_(
'A comma separated list of fields that will be added at the bottom of every entry.'
' These fields are interpreted as containing HTML, not plain text.')))
cf.textChanged.connect(self.changed_signal)
self.la1 = la = QLabel('<p>' + _(
'The template below will be interpreted as HTML and all {{fields}} will be replaced'
' by the actual metadata, if available. You can use {0} as a separator'
' to split a line into multiple columns.').format('|||'))
la.setWordWrap(True)
l.addRow(la)
self.template = t = QPlainTextEdit(self)
l.addRow(t)
t.textChanged.connect(self.changed_signal)
def thumbnail_state_changed(self):
is_enabled = bool(self.thumbnail.isChecked())
for w, x in [(self.thumbnail_height, True), (self.entry_height, False)]:
w.setVisible(is_enabled is x)
self.layout().labelForField(w).setVisible(is_enabled is x)
def genesis(self):
self.current_template = custom_list_template() or self.default_template
@property
def current_template(self):
return {
'thumbnail': self.thumbnail.isChecked(),
'thumbnail_height': self.thumbnail_height.value(),
'height': self.entry_height.text().strip() or 'auto',
'comments_fields': [x.strip() for x in self.comments_fields.text().split(',') if x.strip()],
'lines': [x.strip() for x in self.template.toPlainText().splitlines()]
}
@current_template.setter
def current_template(self, template):
self.thumbnail.setChecked(bool(template.get('thumbnail')))
try:
th = int(template['thumbnail_height'])
except Exception:
th = self.default_template['thumbnail_height']
self.thumbnail_height.setValue(th)
self.entry_height.setText(template.get('height') or 'auto')
self.comments_fields.setText(', '.join(template.get('comments_fields') or ()))
self.template.setPlainText('\n'.join(template.get('lines') or ()))
def restore_defaults(self):
self.current_template = self.default_template
def commit(self):
template = self.current_template
if template == self.default_template:
try:
os.remove(custom_list_template.path)
except EnvironmentError as err:
if err.errno != errno.ENOENT:
raise
else:
raw = json.dumps(template, sort_keys=True, indent=4, separators=(',', ': '))
with lopen(custom_list_template.path, 'wb') as f:
f.write(raw)
return True
# }}}
class ConfigWidget(ConfigWidgetBase):
def __init__(self, *args, **kw):
@ -750,6 +851,10 @@ class ConfigWidget(ConfigWidgetBase):
sa = QScrollArea(self)
sa.setWidget(a), sa.setWidgetResizable(True)
t.addTab(sa, _('&Advanced'))
self.custom_list_tab = clt = CustomList(self)
sa = QScrollArea(self)
sa.setWidget(clt), sa.setWidgetResizable(True)
t.addTab(sa, _('Book &list template'))
for tab in self.tabs:
if hasattr(tab, 'changed_signal'):
tab.changed_signal.connect(self.changed_signal.emit)
@ -882,6 +987,8 @@ class ConfigWidget(ConfigWidgetBase):
)
self.tabs_widget.setCurrentWidget(self.users_tab)
return False
if not self.custom_list_tab.commit():
return False
ConfigWidgetBase.commit(self)
change_settings(**settings)
UserManager().user_data = users
@ -902,6 +1009,7 @@ class ConfigWidget(ConfigWidgetBase):
def refresh_gui(self, gui):
if self.server:
self.server.user_manager.refresh()
self.server.ctx.custom_list_template = custom_list_template()
if __name__ == '__main__':

View File

@ -4,11 +4,12 @@
from __future__ import absolute_import, division, print_function, unicode_literals
import errno
import json
import os
from threading import Thread
from calibre import as_unicode
from calibre.constants import cache_dir, is_running_from_develop
from calibre.constants import cache_dir, config_dir, is_running_from_develop
from calibre.srv.bonjour import BonJour
from calibre.srv.handler import Handler
from calibre.srv.http_response import create_http_handler
@ -23,6 +24,20 @@ def log_paths():
)
def custom_list_template():
try:
with lopen(custom_list_template.path, 'rb') as f:
raw = f.read()
except EnvironmentError as err:
if err.errno != errno.ENOENT:
raise
return
return json.loads(raw)
custom_list_template.path = os.path.join(config_dir, 'server-custom-list-template.json')
class Server(object):
loop = current_thread = exception = None
@ -46,6 +61,11 @@ class Server(object):
self.opts = opts
self.log, self.access_log = log, access_log
self.handler.set_log(self.log)
self.handler.router.ctx.custom_list_template = custom_list_template()
@property
def ctx(self):
return self.handler.router.ctx
@property
def user_manager(self):

View File

@ -187,7 +187,7 @@ def render_part(part, template, book_id, metadata):
if not n:
break
rendered = E.span()
for field in n.nodeValue.split(/({[_a-z0-9]+})/):
for field in n.nodeValue.split(/({#?[_a-z0-9]+})/):
if field[0] is '{' and field[-1] is '}':
count += 1
val = render_field(field[1:-1], metadata, book_id)
@ -272,7 +272,8 @@ def create_item(book_id, metadata, create_image, show_book_details):
height = f'{template.thumbnail_height}px'
else:
if template.height is 'auto':
height = (template.lines.length * 2.5 + 1) + 'ex'
extra = 5 if template.comments_fields.length else 1
height = (template.lines.length * 2.5 + extra) + 'ex'
else:
height = template.height
if jstype(height) is 'number':