Nicer interface for editing tweaks

This commit is contained in:
Kovid Goyal 2011-02-09 15:13:13 -07:00
parent aa19276219
commit 588e92348c
2 changed files with 372 additions and 33 deletions

View File

@ -5,37 +5,311 @@ __license__ = 'GPL v3'
__copyright__ = '2010, Kovid Goyal <kovid@kovidgoyal.net>' __copyright__ = '2010, Kovid Goyal <kovid@kovidgoyal.net>'
__docformat__ = 'restructuredtext en' __docformat__ = 'restructuredtext en'
import textwrap
from calibre.gui2.preferences import ConfigWidgetBase, test_widget, AbortCommit from calibre.gui2.preferences import ConfigWidgetBase, test_widget, AbortCommit
from calibre.gui2.preferences.tweaks_ui import Ui_Form from calibre.gui2.preferences.tweaks_ui import Ui_Form
from calibre.gui2 import error_dialog from calibre.gui2 import error_dialog, NONE
from calibre.utils.config import read_raw_tweaks, write_tweaks from calibre.utils.config import read_raw_tweaks, write_tweaks
from calibre.gui2.widgets import PythonHighlighter
from calibre import isbytestring
from PyQt4.Qt import QAbstractListModel, Qt, QStyledItemDelegate, QStyle, \
QStyleOptionViewItem, QFont, QDialogButtonBox, QDialog, \
QVBoxLayout, QPlainTextEdit, QLabel
class Delegate(QStyledItemDelegate): # {{{
def __init__(self, view):
QStyledItemDelegate.__init__(self, view)
self.view = view
def paint(self, p, opt, idx):
copy = QStyleOptionViewItem(opt)
copy.showDecorationSelected = True
if self.view.currentIndex() == idx:
copy.state |= QStyle.State_HasFocus
QStyledItemDelegate.paint(self, p, copy, idx)
# }}}
class Tweak(object): # {{{
def __init__(self, name, doc, var_names, defaults, custom):
self.name = name
self.doc = doc.strip()
self.var_names = var_names
self.default_values = {}
for x in var_names:
self.default_values[x] = defaults[x]
self.custom_values = {}
for x in var_names:
if x in custom:
self.custom_values[x] = custom[x]
def __str__(self):
ans = ['#: ' + self.name]
for line in self.doc.splitlines():
if line:
ans.append('# ' + line)
for key, val in self.default_values.iteritems():
val = self.custom_values.get(key, val)
ans.append('%s = %r'%(key, val))
ans = '\n'.join(ans)
if isinstance(ans, unicode):
ans = ans.encode('utf-8')
return ans
def __cmp__(self, other):
return cmp(self.is_customized, getattr(other, 'is_customized', False))
@property
def is_customized(self):
for x, val in self.default_values.iteritems():
if self.custom_values.get(x, val) != val:
return True
return False
@property
def edit_text(self):
ans = ['# %s'%self.name]
for x, val in self.default_values.iteritems():
val = self.custom_values.get(x, val)
ans.append('%s = %r'%(x, val))
return '\n\n'.join(ans)
def restore_to_default(self):
self.custom_values.clear()
def update(self, varmap):
self.custom_values.update(varmap)
# }}}
class Tweaks(QAbstractListModel): # {{{
def __init__(self, parent=None):
QAbstractListModel.__init__(self, parent)
raw_defaults, raw_custom = read_raw_tweaks()
self.parse_tweaks(raw_defaults, raw_custom)
def rowCount(self, *args):
return len(self.tweaks)
def data(self, index, role):
row = index.row()
try:
tweak = self.tweaks[row]
except:
return NONE
if role == Qt.DisplayRole:
return textwrap.fill(tweak.name, 40)
if role == Qt.FontRole and tweak.is_customized:
ans = QFont()
ans.setBold(True)
return ans
if role == Qt.ToolTipRole:
tt = _('This tweak has it default value')
if tweak.is_customized:
tt = _('This tweak has been customized')
return tt
if role == Qt.UserRole:
return tweak
return NONE
def parse_tweaks(self, defaults, custom):
l, g = {}, {}
try:
exec custom in g, l
except:
print 'Failed to load custom tweaks file'
import traceback
traceback.print_exc()
dl, dg = {}, {}
exec defaults in dg, dl
lines = defaults.splitlines()
pos = 0
self.tweaks = []
while pos < len(lines):
line = lines[pos]
if line.startswith('#:'):
pos = self.read_tweak(lines, pos, dl, l)
pos += 1
default_keys = set(dl.iterkeys())
custom_keys = set(l.iterkeys())
self.plugin_tweaks = {}
for key in custom_keys - default_keys:
self.plugin_tweaks[key] = l[key]
def read_tweak(self, lines, pos, defaults, custom):
name = lines[pos][2:].strip()
doc, var_names = [], []
while True:
pos += 1
line = lines[pos]
if not line.startswith('#'):
break
doc.append(line[1:].strip())
doc = '\n'.join(doc)
while True:
line = lines[pos]
if not line.strip():
break
spidx1 = line.find(' ')
spidx2 = line.find('=')
spidx = spidx1 if spidx1 > 0 and (spidx2 == 0 or spidx2 > spidx1) else spidx2
if spidx > 0:
var = line[:spidx]
if var not in defaults:
raise ValueError('%r not in default tweaks dict'%var)
var_names.append(var)
pos += 1
if not var_names:
raise ValueError('Failed to find any variables for %r'%name)
self.tweaks.append(Tweak(name, doc, var_names, defaults, custom))
#print '\n\n', self.tweaks[-1]
return pos
def restore_to_default(self, idx):
tweak = self.data(idx, Qt.UserRole)
if tweak is not NONE:
tweak.restore_to_default()
self.dataChanged.emit(idx, idx)
def restore_to_defaults(self):
for r in range(self.rowCount()):
self.restore_to_default(self.index(r))
def update_tweak(self, idx, varmap):
tweak = self.data(idx, Qt.UserRole)
if tweak is not NONE:
tweak.update(varmap)
self.dataChanged.emit(idx, idx)
def to_string(self):
ans = ['#!/usr/bin/env python',
'# vim:fileencoding=UTF-8:ts=4:sw=4:sta:et:sts=4:ai', '',
'# This file was automatically generated by calibre, do not'
' edit it unless you know what you are doing.', '',
]
for tweak in self.tweaks:
ans.extend(['', str(tweak), ''])
if self.plugin_tweaks:
ans.extend(['', '',
'# The following are tweaks for installed plugins', ''])
for key, val in self.plugin_tweaks.iteritems():
ans.extend(['%s = %r'%(key, val), '', ''])
return '\n'.join(ans)
@property
def plugin_tweaks_string(self):
ans = []
for key, val in self.plugin_tweaks.iteritems():
ans.extend(['%s = %r'%(key, val), '', ''])
ans = '\n'.join(ans)
if isbytestring(ans):
ans = ans.decode('utf-8')
return ans
def set_plugin_tweaks(self, d):
self.plugin_tweaks = d
# }}}
class PluginTweaks(QDialog): # {{{
def __init__(self, raw, parent=None):
QDialog.__init__(self, parent)
self.edit = QPlainTextEdit(self)
self.highlighter = PythonHighlighter(self.edit.document())
self.l = QVBoxLayout()
self.setLayout(self.l)
self.l.addWidget(QLabel(
_('Add/edit tweaks for any custom plugins you have installed.')))
self.l.addWidget(self.edit)
self.edit.setPlainText(raw)
self.bb = QDialogButtonBox(QDialogButtonBox.Ok|QDialogButtonBox.Cancel,
Qt.Horizontal, self)
self.bb.accepted.connect(self.accept)
self.bb.rejected.connect(self.reject)
self.l.addWidget(self.bb)
# }}}
class ConfigWidget(ConfigWidgetBase, Ui_Form): class ConfigWidget(ConfigWidgetBase, Ui_Form):
def genesis(self, gui): def genesis(self, gui):
self.gui = gui self.gui = gui
self.current_tweaks.textChanged.connect(self.changed) self.delegate = Delegate(self.tweaks_view)
self.tweaks_view.setItemDelegate(self.delegate)
self.tweaks_view.currentChanged = self.current_changed
self.highlighter = PythonHighlighter(self.edit_tweak.document())
self.restore_default_button.clicked.connect(self.restore_to_default)
self.apply_button.clicked.connect(self.apply_tweak)
self.plugin_tweaks_button.clicked.connect(self.plugin_tweaks)
def plugin_tweaks(self):
raw = self.tweaks.plugin_tweaks_string
d = PluginTweaks(raw, self)
if d.exec_() == d.Accepted:
g, l = {}, {}
try:
exec unicode(d.edit.toPlainText()) in g, l
except:
import traceback
return error_dialog(self, _('Failed'),
_('There was a syntax error in your tweak. Click '
'the show details button for details.'), show=True,
det_msg=traceback.format_exc())
self.tweaks.set_plugin_tweaks(l)
self.changed()
def current_changed(self, current, previous):
tweak = self.tweaks.data(current, Qt.UserRole)
self.help.setPlainText(tweak.doc)
self.edit_tweak.setPlainText(tweak.edit_text)
def changed(self, *args): def changed(self, *args):
self.changed_signal.emit() self.changed_signal.emit()
def initialize(self): def initialize(self):
deft, curt = read_raw_tweaks() self.tweaks = Tweaks()
self.current_tweaks.blockSignals(True) self.tweaks_view.setModel(self.tweaks)
self.current_tweaks.setPlainText(curt.decode('utf-8'))
self.current_tweaks.blockSignals(False)
self.default_tweaks.setPlainText(deft.decode('utf-8')) def restore_to_default(self, *args):
idx = self.tweaks_view.currentIndex()
if idx.isValid():
self.tweaks.restore_to_default(idx)
tweak = self.tweaks.data(idx, Qt.UserRole)
self.edit_tweak.setPlainText(tweak.edit_text)
self.changed()
def restore_defaults(self): def restore_defaults(self):
ConfigWidgetBase.restore_defaults(self) ConfigWidgetBase.restore_defaults(self)
deft, curt = read_raw_tweaks() self.tweaks.restore_to_defaults()
self.current_tweaks.setPlainText(deft.decode('utf-8')) self.changed()
def apply_tweak(self):
idx = self.tweaks_view.currentIndex()
if idx.isValid():
l, g = {}, {}
try:
exec unicode(self.edit_tweak.toPlainText()) in g, l
except:
import traceback
error_dialog(self.gui, _('Failed'),
_('There was a syntax error in your tweak. Click '
'the show details button for details.'),
det_msg=traceback.format_exc(), show=True)
return
self.tweaks.update_tweak(idx, l)
self.changed()
def commit(self): def commit(self):
raw = unicode(self.current_tweaks.toPlainText()).encode('utf-8') raw = self.tweaks.to_string()
try: try:
exec raw exec raw
except: except:
@ -54,5 +328,7 @@ class ConfigWidget(ConfigWidgetBase, Ui_Form):
if __name__ == '__main__': if __name__ == '__main__':
from PyQt4.Qt import QApplication from PyQt4.Qt import QApplication
app = QApplication([]) app = QApplication([])
#Tweaks()
#test_widget
test_widget('Advanced', 'Tweaks') test_widget('Advanced', 'Tweaks')

View File

@ -7,31 +7,70 @@
<x>0</x> <x>0</x>
<y>0</y> <y>0</y>
<width>660</width> <width>660</width>
<height>351</height> <height>531</height>
</rect> </rect>
</property> </property>
<property name="windowTitle"> <property name="windowTitle">
<string>Form</string> <string>Form</string>
</property> </property>
<layout class="QVBoxLayout" name="verticalLayout"> <layout class="QGridLayout" name="gridLayout_2">
<item> <item row="0" column="0" rowspan="2">
<widget class="QLabel" name="label_18"> <layout class="QVBoxLayout" name="verticalLayout_2">
<property name="text"> <item>
<string>Values for the tweaks are shown below. Edit them to change the behavior of calibre. Your changes will only take effect after a restart of calibre.</string> <widget class="QLabel" name="label_18">
</property> <property name="text">
<property name="wordWrap"> <string>Values for the tweaks are shown below. Edit them to change the behavior of calibre. Your changes will only take effect &lt;b&gt;after a restart&lt;/b&gt; of calibre.</string>
<bool>true</bool> </property>
</property> <property name="wordWrap">
</widget> <bool>true</bool>
</property>
</widget>
</item>
<item>
<widget class="QListView" name="tweaks_view">
<property name="sizePolicy">
<sizepolicy hsizetype="Minimum" vsizetype="Expanding">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="minimumSize">
<size>
<width>300</width>
<height>0</height>
</size>
</property>
<property name="alternatingRowColors">
<bool>true</bool>
</property>
<property name="spacing">
<number>5</number>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="plugin_tweaks_button">
<property name="toolTip">
<string>Edit tweaks for any custom plugins you have installed</string>
</property>
<property name="text">
<string>&amp;Plugin tweaks</string>
</property>
</widget>
</item>
</layout>
</item> </item>
<item> <item row="0" column="1">
<widget class="QGroupBox" name="groupBox_6"> <widget class="QGroupBox" name="groupBox">
<property name="title"> <property name="title">
<string>All available tweaks</string> <string>Help</string>
</property> </property>
<layout class="QGridLayout" name="gridLayout_11"> <layout class="QVBoxLayout" name="verticalLayout">
<item row="0" column="0"> <item>
<widget class="QPlainTextEdit" name="default_tweaks"> <widget class="QPlainTextEdit" name="help">
<property name="lineWrapMode">
<enum>QPlainTextEdit::NoWrap</enum>
</property>
<property name="readOnly"> <property name="readOnly">
<bool>true</bool> <bool>true</bool>
</property> </property>
@ -40,14 +79,38 @@
</layout> </layout>
</widget> </widget>
</item> </item>
<item> <item row="1" column="1">
<widget class="QGroupBox" name="groupBox_7"> <widget class="QGroupBox" name="groupBox_2">
<property name="title"> <property name="title">
<string>&amp;Current tweaks</string> <string>Edit tweak</string>
</property> </property>
<layout class="QGridLayout" name="gridLayout_10"> <layout class="QGridLayout" name="gridLayout">
<item row="0" column="0"> <item row="0" column="0" colspan="2">
<widget class="QPlainTextEdit" name="current_tweaks"/> <widget class="QPlainTextEdit" name="edit_tweak">
<property name="lineWrapMode">
<enum>QPlainTextEdit::NoWrap</enum>
</property>
</widget>
</item>
<item row="1" column="0">
<widget class="QPushButton" name="restore_default_button">
<property name="toolTip">
<string>Restore this tweak to its default value</string>
</property>
<property name="text">
<string>Restore &amp;default</string>
</property>
</widget>
</item>
<item row="1" column="1">
<widget class="QPushButton" name="apply_button">
<property name="toolTip">
<string>Apply any changes you made to this tweak</string>
</property>
<property name="text">
<string>&amp;Apply</string>
</property>
</widget>
</item> </item>
</layout> </layout>
</widget> </widget>