mirror of
https://github.com/kovidgoyal/calibre.git
synced 2025-07-09 03:04:10 -04:00
GUI for creating custom list templates
This commit is contained in:
parent
72c82aeb05
commit
e7af7af508
@ -2,6 +2,8 @@
|
|||||||
# vim:fileencoding=UTF-8:ts=4:sw=4:sta:et:sts=4:ai
|
# vim:fileencoding=UTF-8:ts=4:sw=4:sta:et:sts=4:ai
|
||||||
# License: GPLv3 Copyright: 2010, Kovid Goyal <kovid at kovidgoyal.net>
|
# License: GPLv3 Copyright: 2010, Kovid Goyal <kovid at kovidgoyal.net>
|
||||||
|
|
||||||
|
import errno
|
||||||
|
import json
|
||||||
import os
|
import os
|
||||||
import textwrap
|
import textwrap
|
||||||
import time
|
import time
|
||||||
@ -19,6 +21,8 @@ from calibre.gui2 import (
|
|||||||
config, error_dialog, gprefs, info_dialog, open_url, warning_dialog
|
config, error_dialog, gprefs, info_dialog, open_url, warning_dialog
|
||||||
)
|
)
|
||||||
from calibre.gui2.preferences import AbortCommit, ConfigWidgetBase, test_widget
|
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.library_broker import load_gui_libraries
|
||||||
from calibre.srv.opts import change_settings, options, server_config
|
from calibre.srv.opts import change_settings, options, server_config
|
||||||
from calibre.srv.users import (
|
from calibre.srv.users import (
|
||||||
@ -26,7 +30,6 @@ from calibre.srv.users import (
|
|||||||
)
|
)
|
||||||
from calibre.utils.icu import primary_sort_key
|
from calibre.utils.icu import primary_sort_key
|
||||||
|
|
||||||
|
|
||||||
# Advanced {{{
|
# 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):
|
class ConfigWidget(ConfigWidgetBase):
|
||||||
|
|
||||||
def __init__(self, *args, **kw):
|
def __init__(self, *args, **kw):
|
||||||
@ -750,6 +851,10 @@ class ConfigWidget(ConfigWidgetBase):
|
|||||||
sa = QScrollArea(self)
|
sa = QScrollArea(self)
|
||||||
sa.setWidget(a), sa.setWidgetResizable(True)
|
sa.setWidget(a), sa.setWidgetResizable(True)
|
||||||
t.addTab(sa, _('&Advanced'))
|
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:
|
for tab in self.tabs:
|
||||||
if hasattr(tab, 'changed_signal'):
|
if hasattr(tab, 'changed_signal'):
|
||||||
tab.changed_signal.connect(self.changed_signal.emit)
|
tab.changed_signal.connect(self.changed_signal.emit)
|
||||||
@ -882,6 +987,8 @@ class ConfigWidget(ConfigWidgetBase):
|
|||||||
)
|
)
|
||||||
self.tabs_widget.setCurrentWidget(self.users_tab)
|
self.tabs_widget.setCurrentWidget(self.users_tab)
|
||||||
return False
|
return False
|
||||||
|
if not self.custom_list_tab.commit():
|
||||||
|
return False
|
||||||
ConfigWidgetBase.commit(self)
|
ConfigWidgetBase.commit(self)
|
||||||
change_settings(**settings)
|
change_settings(**settings)
|
||||||
UserManager().user_data = users
|
UserManager().user_data = users
|
||||||
@ -902,6 +1009,7 @@ class ConfigWidget(ConfigWidgetBase):
|
|||||||
def refresh_gui(self, gui):
|
def refresh_gui(self, gui):
|
||||||
if self.server:
|
if self.server:
|
||||||
self.server.user_manager.refresh()
|
self.server.user_manager.refresh()
|
||||||
|
self.server.ctx.custom_list_template = custom_list_template()
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
|
@ -4,11 +4,12 @@
|
|||||||
from __future__ import absolute_import, division, print_function, unicode_literals
|
from __future__ import absolute_import, division, print_function, unicode_literals
|
||||||
|
|
||||||
import errno
|
import errno
|
||||||
|
import json
|
||||||
import os
|
import os
|
||||||
from threading import Thread
|
from threading import Thread
|
||||||
|
|
||||||
from calibre import as_unicode
|
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.bonjour import BonJour
|
||||||
from calibre.srv.handler import Handler
|
from calibre.srv.handler import Handler
|
||||||
from calibre.srv.http_response import create_http_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):
|
class Server(object):
|
||||||
|
|
||||||
loop = current_thread = exception = None
|
loop = current_thread = exception = None
|
||||||
@ -46,6 +61,11 @@ class Server(object):
|
|||||||
self.opts = opts
|
self.opts = opts
|
||||||
self.log, self.access_log = log, access_log
|
self.log, self.access_log = log, access_log
|
||||||
self.handler.set_log(self.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
|
@property
|
||||||
def user_manager(self):
|
def user_manager(self):
|
||||||
|
@ -187,7 +187,7 @@ def render_part(part, template, book_id, metadata):
|
|||||||
if not n:
|
if not n:
|
||||||
break
|
break
|
||||||
rendered = E.span()
|
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 '}':
|
if field[0] is '{' and field[-1] is '}':
|
||||||
count += 1
|
count += 1
|
||||||
val = render_field(field[1:-1], metadata, book_id)
|
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'
|
height = f'{template.thumbnail_height}px'
|
||||||
else:
|
else:
|
||||||
if template.height is 'auto':
|
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:
|
else:
|
||||||
height = template.height
|
height = template.height
|
||||||
if jstype(height) is 'number':
|
if jstype(height) is 'number':
|
||||||
|
Loading…
x
Reference in New Issue
Block a user