mirror of
https://github.com/kovidgoyal/calibre.git
synced 2025-07-09 03:04:10 -04:00
Start work on custom theme support for the editor
This commit is contained in:
parent
4f6e3ea1b5
commit
1b8697391b
@ -47,6 +47,7 @@ d['saved_searches'] = []
|
|||||||
d['insert_tag_mru'] = ['p', 'div', 'li', 'h1', 'h2', 'h3', 'h4', 'em', 'strong', 'td', 'tr']
|
d['insert_tag_mru'] = ['p', 'div', 'li', 'h1', 'h2', 'h3', 'h4', 'em', 'strong', 'td', 'tr']
|
||||||
d['spell_check_case_sensitive_sort'] = False
|
d['spell_check_case_sensitive_sort'] = False
|
||||||
d['inline_spell_check'] = True
|
d['inline_spell_check'] = True
|
||||||
|
d['custom_themes'] = {}
|
||||||
|
|
||||||
del d
|
del d
|
||||||
|
|
||||||
|
@ -8,9 +8,16 @@ __copyright__ = '2013, Kovid Goyal <kovid at kovidgoyal.net>'
|
|||||||
|
|
||||||
from collections import namedtuple
|
from collections import namedtuple
|
||||||
|
|
||||||
from PyQt4.Qt import (QColor, QBrush, QFont, QApplication, QPalette)
|
from PyQt4.Qt import (
|
||||||
|
QColor, QBrush, QFont, QApplication, QPalette, QComboBox,
|
||||||
|
QPushButton, QIcon, QFormLayout, QLineEdit, QWidget, QScrollArea,
|
||||||
|
QVBoxLayout, Qt, QHBoxLayout, pyqtSignal, QPixmap, QColorDialog,
|
||||||
|
QToolButton, QCheckBox, QSize, QLabel)
|
||||||
|
|
||||||
|
from calibre.gui2 import error_dialog
|
||||||
|
from calibre.gui2.tweak_book import tprefs
|
||||||
from calibre.gui2.tweak_book.editor import SyntaxTextCharFormat
|
from calibre.gui2.tweak_book.editor import SyntaxTextCharFormat
|
||||||
|
from calibre.gui2.tweak_book.widgets import Dialog
|
||||||
|
|
||||||
underline_styles = {'single', 'dash', 'dot', 'dash_dot', 'dash_dot_dot', 'wave', 'spell'}
|
underline_styles = {'single', 'dash', 'dot', 'dash_dot', 'dash_dot_dot', 'wave', 'spell'}
|
||||||
|
|
||||||
@ -226,11 +233,24 @@ def u(x):
|
|||||||
return x + 'Underline'
|
return x + 'Underline'
|
||||||
underline_styles = {x:getattr(SyntaxTextCharFormat, u(x)) for x in underline_styles}
|
underline_styles = {x:getattr(SyntaxTextCharFormat, u(x)) for x in underline_styles}
|
||||||
|
|
||||||
|
def to_highlight(data):
|
||||||
|
data = data.copy()
|
||||||
|
for c in ('fg', 'bg', 'uc'):
|
||||||
|
data[c] = read_color(data[c]) if c in data else None
|
||||||
|
return Highlight(**data)
|
||||||
|
|
||||||
def get_theme(name):
|
def get_theme(name):
|
||||||
try:
|
try:
|
||||||
return THEMES[name]
|
return THEMES[name]
|
||||||
|
except KeyError:
|
||||||
|
try:
|
||||||
|
ans = tprefs['custom_themes'][name]
|
||||||
except KeyError:
|
except KeyError:
|
||||||
return THEMES[default_theme()]
|
return THEMES[default_theme()]
|
||||||
|
else:
|
||||||
|
dt = THEMES[default_theme()].copy()
|
||||||
|
dt.update({k:to_highlight(v) for k, v in ans.iteritems()})
|
||||||
|
return dt
|
||||||
|
|
||||||
def highlight_to_char_format(h):
|
def highlight_to_char_format(h):
|
||||||
ans = SyntaxTextCharFormat()
|
ans = SyntaxTextCharFormat()
|
||||||
@ -260,3 +280,269 @@ def theme_format(theme, name):
|
|||||||
except KeyError:
|
except KeyError:
|
||||||
h = THEMES[default_theme()][name]
|
h = THEMES[default_theme()][name]
|
||||||
return highlight_to_char_format(h)
|
return highlight_to_char_format(h)
|
||||||
|
|
||||||
|
def custom_theme_names():
|
||||||
|
return tuple(tprefs['custom_themes'].iterkeys())
|
||||||
|
|
||||||
|
def builtin_theme_names():
|
||||||
|
return tuple(THEMES.iterkeys())
|
||||||
|
|
||||||
|
def all_theme_names():
|
||||||
|
return builtin_theme_names() + custom_theme_names()
|
||||||
|
|
||||||
|
class CreateNewTheme(Dialog):
|
||||||
|
|
||||||
|
def __init__(self, parent=None):
|
||||||
|
Dialog.__init__(self, _('Create custom theme'), 'custom-theme-create', parent=parent)
|
||||||
|
|
||||||
|
def setup_ui(self):
|
||||||
|
self.l = l = QFormLayout(self)
|
||||||
|
self.setLayout(l)
|
||||||
|
|
||||||
|
self._name = n = QLineEdit(self)
|
||||||
|
l.addRow(_('&Name of custom theme:'), n)
|
||||||
|
|
||||||
|
self.base = b = QComboBox(self)
|
||||||
|
b.addItems(sorted(builtin_theme_names()))
|
||||||
|
l.addRow(_('&Builtin theme to base on:'), b)
|
||||||
|
idx = b.findText(tprefs['editor_theme'] or default_theme())
|
||||||
|
if idx == -1:
|
||||||
|
idx = b.findText(default_theme())
|
||||||
|
b.setCurrentIndex(idx)
|
||||||
|
|
||||||
|
l.addRow(self.bb)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def theme_name(self):
|
||||||
|
return unicode(self._name.text()).strip()
|
||||||
|
|
||||||
|
def accept(self):
|
||||||
|
if not self.theme_name:
|
||||||
|
return error_dialog(self, _('No name specified'), _(
|
||||||
|
'You must specify a name for your theme'), show=True)
|
||||||
|
if '*' + self.theme_name in custom_theme_names():
|
||||||
|
return error_dialog(self, _('Name already used'), _(
|
||||||
|
'A custom theme with the name %s already exists') % self.theme_name, show=True)
|
||||||
|
return Dialog.accept(self)
|
||||||
|
|
||||||
|
def col_to_string(color):
|
||||||
|
return '%02X%02X%02X' % color.getRgb()[:3]
|
||||||
|
|
||||||
|
class ColorButton(QPushButton):
|
||||||
|
|
||||||
|
changed = pyqtSignal()
|
||||||
|
|
||||||
|
def __init__(self, data, name, text, parent):
|
||||||
|
QPushButton.__init__(self, text, parent)
|
||||||
|
self.ic = QPixmap(self.iconSize())
|
||||||
|
color = data[name]
|
||||||
|
self.data, self.name = data, name
|
||||||
|
if color is not None:
|
||||||
|
self.current_color = read_color(color).color()
|
||||||
|
self.ic.fill(self.current_color)
|
||||||
|
else:
|
||||||
|
self.ic.fill(Qt.transparent)
|
||||||
|
self.current_color = color
|
||||||
|
self.update_tooltip()
|
||||||
|
self.setIcon(QIcon(self.ic))
|
||||||
|
self.clicked.connect(self.choose_color)
|
||||||
|
|
||||||
|
def clear(self):
|
||||||
|
self.current_color = None
|
||||||
|
self.update_tooltip()
|
||||||
|
self.ic.fill(Qt.transparent)
|
||||||
|
self.setIcon(QIcon(self.ic))
|
||||||
|
self.data[self.name] = self.value
|
||||||
|
self.changed.emit()
|
||||||
|
|
||||||
|
def choose_color(self):
|
||||||
|
col = QColorDialog.getColor(self.current_color or Qt.black, self, _('Choose color'))
|
||||||
|
if col.isValid():
|
||||||
|
self.current_color = col
|
||||||
|
self.update_tooltip()
|
||||||
|
self.ic.fill(col)
|
||||||
|
self.setIcon(QIcon(self.ic))
|
||||||
|
self.data[self.name] = self.value
|
||||||
|
self.changed.emit()
|
||||||
|
|
||||||
|
def update_tooltip(self):
|
||||||
|
self.setToolTip(_('Red: {0} Green: {1} Blue: {2}').format(*self.current_color.getRgb()[:3]) if self.current_color else _('No color'))
|
||||||
|
|
||||||
|
@property
|
||||||
|
def value(self):
|
||||||
|
if self.current_color is None:
|
||||||
|
return None
|
||||||
|
return col_to_string(self.current_color)
|
||||||
|
|
||||||
|
class Bool(QCheckBox):
|
||||||
|
|
||||||
|
changed = pyqtSignal()
|
||||||
|
|
||||||
|
def __init__(self, data, key, text, parent):
|
||||||
|
QCheckBox.__init__(self, text, parent)
|
||||||
|
self.data, self.key = data, key
|
||||||
|
self.setChecked(data.get(key, False))
|
||||||
|
self.stateChanged.connect(self._changed)
|
||||||
|
|
||||||
|
def _changed(self, state):
|
||||||
|
self.data[self.key] = self.value
|
||||||
|
self.changed.emit()
|
||||||
|
|
||||||
|
@property
|
||||||
|
def value(self):
|
||||||
|
return self.checkState() == Qt.Checked
|
||||||
|
|
||||||
|
class Property(QWidget):
|
||||||
|
|
||||||
|
changed = pyqtSignal()
|
||||||
|
|
||||||
|
def __init__(self, name, data, parent=None):
|
||||||
|
QWidget.__init__(self, parent)
|
||||||
|
self.l = l = QHBoxLayout(self)
|
||||||
|
self.setLayout(l)
|
||||||
|
self.label = QLabel(name)
|
||||||
|
l.addWidget(self.label)
|
||||||
|
self.data = data
|
||||||
|
|
||||||
|
def create_color_button(key, text):
|
||||||
|
b = ColorButton(data, key, text, self)
|
||||||
|
b.changed.connect(self.changed), l.addWidget(b)
|
||||||
|
bc = QToolButton(self)
|
||||||
|
bc.setIcon(QIcon(I('clear_left.png')))
|
||||||
|
bc.setToolTip(_('Remove color'))
|
||||||
|
bc.clicked.connect(b.clear)
|
||||||
|
h = QHBoxLayout()
|
||||||
|
h.addWidget(b), h.addWidget(bc)
|
||||||
|
return h
|
||||||
|
|
||||||
|
for k, text in (('fg', _('&Foreground')), ('bg', _('&Background'))):
|
||||||
|
h = create_color_button(k, text)
|
||||||
|
l.addLayout(h)
|
||||||
|
|
||||||
|
for k, text in (('bold', _('B&old')), ('italic', _('&Italic'))):
|
||||||
|
w = Bool(data, k, text, self)
|
||||||
|
w.changed.connect(self.changed)
|
||||||
|
l.addWidget(w)
|
||||||
|
|
||||||
|
self.underline = us = QComboBox(self)
|
||||||
|
us.addItems(sorted(tuple(underline_styles) + ('',)))
|
||||||
|
idx = us.findText(data.get('underline', '') or '')
|
||||||
|
us.setCurrentIndex(max(idx, 0))
|
||||||
|
us.currentIndexChanged.connect(self.us_changed)
|
||||||
|
self.la = la = QLabel(_('&Underline:'))
|
||||||
|
la.setBuddy(us)
|
||||||
|
h = QHBoxLayout()
|
||||||
|
h.addWidget(la), h.addWidget(us), l.addLayout(h)
|
||||||
|
|
||||||
|
h = create_color_button('underline_color', _('Color'))
|
||||||
|
l.addLayout(h)
|
||||||
|
l.addStretch(1)
|
||||||
|
|
||||||
|
def us_changed(self):
|
||||||
|
self.data['underline'] = unicode(self.underline.currentText())
|
||||||
|
self.changed.emit()
|
||||||
|
|
||||||
|
class ThemeEditor(Dialog):
|
||||||
|
|
||||||
|
def __init__(self, parent=None):
|
||||||
|
Dialog.__init__(self, _('Create/edit custom theme'), 'custom-theme-editor', parent=parent)
|
||||||
|
|
||||||
|
def setup_ui(self):
|
||||||
|
self.block_show = False
|
||||||
|
self.properties = []
|
||||||
|
self.l = l = QVBoxLayout(self)
|
||||||
|
self.setLayout(l)
|
||||||
|
h = QHBoxLayout()
|
||||||
|
l.addLayout(h)
|
||||||
|
self.la = la = QLabel(_('&Edit theme:'))
|
||||||
|
h.addWidget(la)
|
||||||
|
self.theme = t = QComboBox(self)
|
||||||
|
la.setBuddy(t)
|
||||||
|
t.addItems(sorted(custom_theme_names()))
|
||||||
|
t.setMinimumWidth(200)
|
||||||
|
if t.count() > 0:
|
||||||
|
t.setCurrentIndex(0)
|
||||||
|
t.currentIndexChanged[int].connect(self.show_theme)
|
||||||
|
h.addWidget(t)
|
||||||
|
|
||||||
|
self.add_button = b = QPushButton(QIcon(I('plus.png')), _('Add &new theme'), self)
|
||||||
|
b.clicked.connect(self.create_new_theme)
|
||||||
|
h.addWidget(b)
|
||||||
|
h.addStretch(1)
|
||||||
|
|
||||||
|
self.scroll = s = QScrollArea(self)
|
||||||
|
self.w = w = QWidget(self)
|
||||||
|
s.setWidget(w), s.setWidgetResizable(True)
|
||||||
|
|
||||||
|
self.cl = cl = QVBoxLayout()
|
||||||
|
w.setLayout(cl)
|
||||||
|
l.addWidget(s)
|
||||||
|
|
||||||
|
self.bb.clear()
|
||||||
|
self.bb.addButton(self.bb.Close)
|
||||||
|
l.addWidget(self.bb)
|
||||||
|
|
||||||
|
if self.theme.count() > 0:
|
||||||
|
self.show_theme()
|
||||||
|
|
||||||
|
def update_theme(self, name):
|
||||||
|
data = tprefs['custom_themes'][name]
|
||||||
|
extra = set(data.iterkeys()) - set(THEMES[default_theme()].iterkeys())
|
||||||
|
missing = set(THEMES[default_theme()].iterkeys()) - set(data.iterkeys())
|
||||||
|
for k in extra:
|
||||||
|
data.pop(k)
|
||||||
|
for k in missing:
|
||||||
|
data[k] = dict(THEMES[default_theme()][k]._asdict())
|
||||||
|
if extra or missing:
|
||||||
|
tprefs['custom_themes'][name] = data
|
||||||
|
return data
|
||||||
|
|
||||||
|
def show_theme(self):
|
||||||
|
if self.block_show:
|
||||||
|
return
|
||||||
|
for c in self.properties:
|
||||||
|
c.changed.disconnect()
|
||||||
|
self.cl.removeWidget(c)
|
||||||
|
c.setParent(None)
|
||||||
|
c.deleteLater()
|
||||||
|
self.properties = []
|
||||||
|
name = unicode(self.theme.currentText())
|
||||||
|
data = self.update_theme(name)
|
||||||
|
maxw = 0
|
||||||
|
for k in sorted(data):
|
||||||
|
w = Property(k, data[k], parent=self)
|
||||||
|
w.changed.connect(self.changed)
|
||||||
|
self.properties.append(w)
|
||||||
|
maxw = max(maxw, w.label.sizeHint().width())
|
||||||
|
self.cl.addWidget(w)
|
||||||
|
for p in self.properties:
|
||||||
|
p.label.setMinimumWidth(maxw), p.label.setMaximumWidth(maxw)
|
||||||
|
|
||||||
|
def changed(self):
|
||||||
|
pass
|
||||||
|
|
||||||
|
def create_new_theme(self):
|
||||||
|
d = CreateNewTheme(self)
|
||||||
|
if d.exec_() == d.Accepted:
|
||||||
|
name = '*' + d.theme_name
|
||||||
|
base = unicode(d.base.currentText())
|
||||||
|
theme = {}
|
||||||
|
for key, val in THEMES[base].iteritems():
|
||||||
|
theme[key] = {k:col_to_string(v.color()) if isinstance(v, QBrush) else v for k, v in val._asdict().iteritems()}
|
||||||
|
tprefs['custom_themes'][name] = theme
|
||||||
|
tprefs['custom_themes'] = tprefs['custom_themes']
|
||||||
|
t = self.theme
|
||||||
|
self.block_show = True
|
||||||
|
t.clear(), t.addItems(sorted(custom_theme_names()))
|
||||||
|
t.setCurrentIndex(t.findText(name))
|
||||||
|
self.block_show = False
|
||||||
|
self.show_theme()
|
||||||
|
|
||||||
|
def sizeHint(self):
|
||||||
|
return QSize(1000, 650)
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
app = QApplication([])
|
||||||
|
d = ThemeEditor()
|
||||||
|
d.exec_()
|
||||||
|
del app
|
||||||
|
Loading…
x
Reference in New Issue
Block a user