5
5
__copyright__ = '2010, Kovid Goyal <kovid@kovidgoyal.net>'
6
6
__docformat__ = 'restructuredtext en'
8
10
from calibre.gui2.preferences import ConfigWidgetBase, test_widget, AbortCommit
9
11
from calibre.gui2.preferences.tweaks_ui import Ui_Form
10
from calibre.gui2 import error_dialog
12
from calibre.gui2 import error_dialog, NONE
11
13
from calibre.utils.config import read_raw_tweaks, write_tweaks
14
from calibre.gui2.widgets import PythonHighlighter
15
from calibre import isbytestring
17
from PyQt4.Qt import QAbstractListModel, Qt, QStyledItemDelegate, QStyle, \
18
QStyleOptionViewItem, QFont, QDialogButtonBox, QDialog, \
19
QVBoxLayout, QPlainTextEdit, QLabel
21
class Delegate(QStyledItemDelegate): # {{{
22
def __init__(self, view):
23
QStyledItemDelegate.__init__(self, view)
26
def paint(self, p, opt, idx):
27
copy = QStyleOptionViewItem(opt)
28
copy.showDecorationSelected = True
29
if self.view.currentIndex() == idx:
30
copy.state |= QStyle.State_HasFocus
31
QStyledItemDelegate.paint(self, p, copy, idx)
35
class Tweak(object): # {{{
37
def __init__(self, name, doc, var_names, defaults, custom):
39
self.doc = doc.strip()
40
self.var_names = var_names
41
self.default_values = {}
43
self.default_values[x] = defaults[x]
44
self.custom_values = {}
47
self.custom_values[x] = custom[x]
50
ans = ['#: ' + self.name]
51
for line in self.doc.splitlines():
53
ans.append('# ' + line)
54
for key, val in self.default_values.iteritems():
55
val = self.custom_values.get(key, val)
56
ans.append('%s = %r'%(key, val))
58
if isinstance(ans, unicode):
59
ans = ans.encode('utf-8')
62
def __cmp__(self, other):
63
return -1 * cmp(self.is_customized,
64
getattr(other, 'is_customized', False))
67
def is_customized(self):
68
for x, val in self.default_values.iteritems():
69
if self.custom_values.get(x, val) != val:
75
ans = ['# %s'%self.name]
76
for x, val in self.default_values.iteritems():
77
val = self.custom_values.get(x, val)
78
ans.append('%s = %r'%(x, val))
79
return '\n\n'.join(ans)
81
def restore_to_default(self):
82
self.custom_values.clear()
84
def update(self, varmap):
85
self.custom_values.update(varmap)
89
class Tweaks(QAbstractListModel): # {{{
91
def __init__(self, parent=None):
92
QAbstractListModel.__init__(self, parent)
93
raw_defaults, raw_custom = read_raw_tweaks()
95
self.parse_tweaks(raw_defaults, raw_custom)
97
def rowCount(self, *args):
98
return len(self.tweaks)
100
def data(self, index, role):
103
tweak = self.tweaks[row]
106
if role == Qt.DisplayRole:
107
return textwrap.fill(tweak.name, 40)
108
if role == Qt.FontRole and tweak.is_customized:
112
if role == Qt.ToolTipRole:
113
tt = _('This tweak has it default value')
114
if tweak.is_customized:
115
tt = '<p>'+_('This tweak has been customized')
117
for varn, val in tweak.custom_values.iteritems():
118
tt += '%s = %r\n\n'%(varn, val)
120
if role == Qt.UserRole:
124
def parse_tweaks(self, defaults, custom):
129
print 'Failed to load custom tweaks file'
131
traceback.print_exc()
133
exec defaults in dg, dl
134
lines = defaults.splitlines()
137
while pos < len(lines):
139
if line.startswith('#:'):
140
pos = self.read_tweak(lines, pos, dl, l)
144
default_keys = set(dl.iterkeys())
145
custom_keys = set(l.iterkeys())
147
self.plugin_tweaks = {}
148
for key in custom_keys - default_keys:
149
self.plugin_tweaks[key] = l[key]
151
def read_tweak(self, lines, pos, defaults, custom):
152
name = lines[pos][2:].strip()
153
doc, var_names = [], []
157
if not line.startswith('#'):
159
doc.append(line[1:].strip())
165
spidx1 = line.find(' ')
166
spidx2 = line.find('=')
167
spidx = spidx1 if spidx1 > 0 and (spidx2 == 0 or spidx2 > spidx1) else spidx2
170
if var not in defaults:
171
raise ValueError('%r not in default tweaks dict'%var)
172
var_names.append(var)
175
raise ValueError('Failed to find any variables for %r'%name)
176
self.tweaks.append(Tweak(name, doc, var_names, defaults, custom))
177
#print '\n\n', self.tweaks[-1]
180
def restore_to_default(self, idx):
181
tweak = self.data(idx, Qt.UserRole)
182
if tweak is not NONE:
183
tweak.restore_to_default()
184
self.dataChanged.emit(idx, idx)
186
def restore_to_defaults(self):
187
for r in range(self.rowCount()):
188
self.restore_to_default(self.index(r))
190
def update_tweak(self, idx, varmap):
191
tweak = self.data(idx, Qt.UserRole)
192
if tweak is not NONE:
194
self.dataChanged.emit(idx, idx)
197
ans = ['#!/usr/bin/env python',
198
'# vim:fileencoding=UTF-8:ts=4:sw=4:sta:et:sts=4:ai', '',
199
'# This file was automatically generated by calibre, do not'
200
' edit it unless you know what you are doing.', '',
202
for tweak in self.tweaks:
203
ans.extend(['', str(tweak), ''])
205
if self.plugin_tweaks:
207
'# The following are tweaks for installed plugins', ''])
208
for key, val in self.plugin_tweaks.iteritems():
209
ans.extend(['%s = %r'%(key, val), '', ''])
210
return '\n'.join(ans)
213
def plugin_tweaks_string(self):
215
for key, val in self.plugin_tweaks.iteritems():
216
ans.extend(['%s = %r'%(key, val), '', ''])
218
if isbytestring(ans):
219
ans = ans.decode('utf-8')
222
def set_plugin_tweaks(self, d):
223
self.plugin_tweaks = d
227
class PluginTweaks(QDialog): # {{{
229
def __init__(self, raw, parent=None):
230
QDialog.__init__(self, parent)
231
self.edit = QPlainTextEdit(self)
232
self.highlighter = PythonHighlighter(self.edit.document())
233
self.l = QVBoxLayout()
234
self.setLayout(self.l)
236
_('Add/edit tweaks for any custom plugins you have installed. '
237
'Documentation for these tweaks should be available '
238
'on the website from where you downloaded the plugins.'))
239
self.msg.setWordWrap(True)
240
self.l.addWidget(self.msg)
241
self.l.addWidget(self.edit)
242
self.edit.setPlainText(raw)
243
self.bb = QDialogButtonBox(QDialogButtonBox.Ok|QDialogButtonBox.Cancel,
245
self.bb.accepted.connect(self.accept)
246
self.bb.rejected.connect(self.reject)
247
self.l.addWidget(self.bb)
248
self.resize(550, 300)
14
252
class ConfigWidget(ConfigWidgetBase, Ui_Form):
16
254
def genesis(self, gui):
18
self.current_tweaks.textChanged.connect(self.changed)
256
self.delegate = Delegate(self.tweaks_view)
257
self.tweaks_view.setItemDelegate(self.delegate)
258
self.tweaks_view.currentChanged = self.current_changed
259
self.highlighter = PythonHighlighter(self.edit_tweak.document())
260
self.restore_default_button.clicked.connect(self.restore_to_default)
261
self.apply_button.clicked.connect(self.apply_tweak)
262
self.plugin_tweaks_button.clicked.connect(self.plugin_tweaks)
263
self.splitter.setStretchFactor(0, 1)
264
self.splitter.setStretchFactor(1, 100)
267
def plugin_tweaks(self):
268
raw = self.tweaks.plugin_tweaks_string
269
d = PluginTweaks(raw, self)
270
if d.exec_() == d.Accepted:
273
exec unicode(d.edit.toPlainText()) in g, l
276
return error_dialog(self, _('Failed'),
277
_('There was a syntax error in your tweak. Click '
278
'the show details button for details.'), show=True,
279
det_msg=traceback.format_exc())
280
self.tweaks.set_plugin_tweaks(l)
283
def current_changed(self, current, previous):
284
tweak = self.tweaks.data(current, Qt.UserRole)
285
self.help.setPlainText(tweak.doc)
286
self.edit_tweak.setPlainText(tweak.edit_text)
20
288
def changed(self, *args):
21
289
self.changed_signal.emit()
23
291
def initialize(self):
24
deft, curt = read_raw_tweaks()
25
self.current_tweaks.blockSignals(True)
26
self.current_tweaks.setPlainText(curt.decode('utf-8'))
27
self.current_tweaks.blockSignals(False)
292
self.tweaks = Tweaks()
293
self.tweaks_view.setModel(self.tweaks)
29
self.default_tweaks.setPlainText(deft.decode('utf-8'))
295
def restore_to_default(self, *args):
296
idx = self.tweaks_view.currentIndex()
298
self.tweaks.restore_to_default(idx)
299
tweak = self.tweaks.data(idx, Qt.UserRole)
300
self.edit_tweak.setPlainText(tweak.edit_text)
31
303
def restore_defaults(self):
32
304
ConfigWidgetBase.restore_defaults(self)
33
deft, curt = read_raw_tweaks()
34
self.current_tweaks.setPlainText(deft.decode('utf-8'))
305
self.tweaks.restore_to_defaults()
308
def apply_tweak(self):
309
idx = self.tweaks_view.currentIndex()
313
exec unicode(self.edit_tweak.toPlainText()) in g, l
316
error_dialog(self.gui, _('Failed'),
317
_('There was a syntax error in your tweak. Click '
318
'the show details button for details.'),
319
det_msg=traceback.format_exc(), show=True)
321
self.tweaks.update_tweak(idx, l)
38
raw = unicode(self.current_tweaks.toPlainText()).encode('utf-8')
325
raw = self.tweaks.to_string()