Start work on custom theme support for the editor

This commit is contained in:
Kovid Goyal 2014-05-27 21:22:21 +05:30
parent 4f6e3ea1b5
commit 1b8697391b
2 changed files with 289 additions and 2 deletions

View File

@ -47,6 +47,7 @@ d['saved_searches'] = []
d['insert_tag_mru'] = ['p', 'div', 'li', 'h1', 'h2', 'h3', 'h4', 'em', 'strong', 'td', 'tr']
d['spell_check_case_sensitive_sort'] = False
d['inline_spell_check'] = True
d['custom_themes'] = {}
del d

View File

@ -8,9 +8,16 @@ __copyright__ = '2013, Kovid Goyal <kovid at kovidgoyal.net>'
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.widgets import Dialog
underline_styles = {'single', 'dash', 'dot', 'dash_dot', 'dash_dot_dot', 'wave', 'spell'}
@ -226,11 +233,24 @@ def u(x):
return x + 'Underline'
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):
try:
return THEMES[name]
except KeyError:
try:
ans = tprefs['custom_themes'][name]
except KeyError:
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):
ans = SyntaxTextCharFormat()
@ -260,3 +280,269 @@ def theme_format(theme, name):
except KeyError:
h = THEMES[default_theme()][name]
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