~stub/ubuntu/precise/calibre/devel

« back to all changes in this revision

Viewing changes to src/calibre/gui2/preferences/tweaks.py

  • Committer: Bazaar Package Importer
  • Author(s): Martin Pitt
  • Date: 2011-04-12 11:29:25 UTC
  • mfrom: (42.1.2 sid)
  • Revision ID: james.westby@ubuntu.com-20110412112925-c7171kt2bb5rmft4
Tags: 0.7.50+dfsg-2
* debian/control: Build with libpodofo-dev to enable PDF metadata.
  (Closes: #619632)
* debian/control: Add libboost1.42-dev build dependency. Apparently it is
  needed in some setups. (Closes: #619807)
* debian/rules: Call dh_sip to generate a proper sip API dependency, to
  prevent crashes like #616372 for partial upgrades.
* debian/control: Bump python-qt4 dependency to >= 4.8.3-2, which reportedly
  fixes crashes on startup. (Closes: #619701, #620125)

Show diffs side-by-side

added added

removed removed

Lines of Context:
5
5
__copyright__ = '2010, Kovid Goyal <kovid@kovidgoyal.net>'
6
6
__docformat__ = 'restructuredtext en'
7
7
 
 
8
import textwrap
 
9
 
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
12
 
 
 
14
from calibre.gui2.widgets import PythonHighlighter
 
15
from calibre import isbytestring
 
16
 
 
17
from PyQt4.Qt import QAbstractListModel, Qt, QStyledItemDelegate, QStyle, \
 
18
    QStyleOptionViewItem, QFont, QDialogButtonBox, QDialog, \
 
19
    QVBoxLayout, QPlainTextEdit, QLabel
 
20
 
 
21
class Delegate(QStyledItemDelegate): # {{{
 
22
    def __init__(self, view):
 
23
        QStyledItemDelegate.__init__(self, view)
 
24
        self.view = view
 
25
 
 
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)
 
32
 
 
33
# }}}
 
34
 
 
35
class Tweak(object): # {{{
 
36
 
 
37
    def __init__(self, name, doc, var_names, defaults, custom):
 
38
        self.name = name
 
39
        self.doc = doc.strip()
 
40
        self.var_names = var_names
 
41
        self.default_values = {}
 
42
        for x in var_names:
 
43
            self.default_values[x] = defaults[x]
 
44
        self.custom_values = {}
 
45
        for x in var_names:
 
46
            if x in custom:
 
47
                self.custom_values[x] = custom[x]
 
48
 
 
49
    def __str__(self):
 
50
        ans = ['#: ' + self.name]
 
51
        for line in self.doc.splitlines():
 
52
            if line:
 
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))
 
57
        ans = '\n'.join(ans)
 
58
        if isinstance(ans, unicode):
 
59
            ans = ans.encode('utf-8')
 
60
        return ans
 
61
 
 
62
    def __cmp__(self, other):
 
63
        return -1 * cmp(self.is_customized,
 
64
                            getattr(other, 'is_customized', False))
 
65
 
 
66
    @property
 
67
    def is_customized(self):
 
68
        for x, val in self.default_values.iteritems():
 
69
            if self.custom_values.get(x, val) != val:
 
70
                return True
 
71
        return False
 
72
 
 
73
    @property
 
74
    def edit_text(self):
 
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)
 
80
 
 
81
    def restore_to_default(self):
 
82
        self.custom_values.clear()
 
83
 
 
84
    def update(self, varmap):
 
85
        self.custom_values.update(varmap)
 
86
 
 
87
# }}}
 
88
 
 
89
class Tweaks(QAbstractListModel): # {{{
 
90
 
 
91
    def __init__(self, parent=None):
 
92
        QAbstractListModel.__init__(self, parent)
 
93
        raw_defaults, raw_custom = read_raw_tweaks()
 
94
 
 
95
        self.parse_tweaks(raw_defaults, raw_custom)
 
96
 
 
97
    def rowCount(self, *args):
 
98
        return len(self.tweaks)
 
99
 
 
100
    def data(self, index, role):
 
101
        row = index.row()
 
102
        try:
 
103
            tweak = self.tweaks[row]
 
104
        except:
 
105
            return NONE
 
106
        if role == Qt.DisplayRole:
 
107
            return textwrap.fill(tweak.name, 40)
 
108
        if role == Qt.FontRole and tweak.is_customized:
 
109
            ans = QFont()
 
110
            ans.setBold(True)
 
111
            return ans
 
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')
 
116
                tt += '<pre>'
 
117
                for varn, val in tweak.custom_values.iteritems():
 
118
                    tt += '%s = %r\n\n'%(varn, val)
 
119
            return tt
 
120
        if role == Qt.UserRole:
 
121
            return tweak
 
122
        return NONE
 
123
 
 
124
    def parse_tweaks(self, defaults, custom):
 
125
        l, g = {}, {}
 
126
        try:
 
127
            exec custom in g, l
 
128
        except:
 
129
            print 'Failed to load custom tweaks file'
 
130
            import traceback
 
131
            traceback.print_exc()
 
132
        dl, dg = {}, {}
 
133
        exec defaults in dg, dl
 
134
        lines = defaults.splitlines()
 
135
        pos = 0
 
136
        self.tweaks = []
 
137
        while pos < len(lines):
 
138
            line = lines[pos]
 
139
            if line.startswith('#:'):
 
140
                pos = self.read_tweak(lines, pos, dl, l)
 
141
            pos += 1
 
142
 
 
143
        self.tweaks.sort()
 
144
        default_keys = set(dl.iterkeys())
 
145
        custom_keys = set(l.iterkeys())
 
146
 
 
147
        self.plugin_tweaks = {}
 
148
        for key in custom_keys - default_keys:
 
149
            self.plugin_tweaks[key] = l[key]
 
150
 
 
151
    def read_tweak(self, lines, pos, defaults, custom):
 
152
        name = lines[pos][2:].strip()
 
153
        doc, var_names = [], []
 
154
        while True:
 
155
            pos += 1
 
156
            line = lines[pos]
 
157
            if not line.startswith('#'):
 
158
                break
 
159
            doc.append(line[1:].strip())
 
160
        doc = '\n'.join(doc)
 
161
        while True:
 
162
            line = lines[pos]
 
163
            if not line.strip():
 
164
                break
 
165
            spidx1 = line.find(' ')
 
166
            spidx2 = line.find('=')
 
167
            spidx = spidx1 if spidx1 > 0 and (spidx2 == 0 or spidx2 > spidx1) else spidx2
 
168
            if spidx > 0:
 
169
                var = line[:spidx]
 
170
                if var not in defaults:
 
171
                    raise ValueError('%r not in default tweaks dict'%var)
 
172
                var_names.append(var)
 
173
            pos += 1
 
174
        if not var_names:
 
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]
 
178
        return pos
 
179
 
 
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)
 
185
 
 
186
    def restore_to_defaults(self):
 
187
        for r in range(self.rowCount()):
 
188
            self.restore_to_default(self.index(r))
 
189
 
 
190
    def update_tweak(self, idx, varmap):
 
191
        tweak = self.data(idx, Qt.UserRole)
 
192
        if tweak is not NONE:
 
193
            tweak.update(varmap)
 
194
            self.dataChanged.emit(idx, idx)
 
195
 
 
196
    def to_string(self):
 
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.', '',
 
201
            ]
 
202
        for tweak in self.tweaks:
 
203
            ans.extend(['', str(tweak), ''])
 
204
 
 
205
        if self.plugin_tweaks:
 
206
            ans.extend(['', '',
 
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)
 
211
 
 
212
    @property
 
213
    def plugin_tweaks_string(self):
 
214
        ans = []
 
215
        for key, val in self.plugin_tweaks.iteritems():
 
216
            ans.extend(['%s = %r'%(key, val), '', ''])
 
217
        ans = '\n'.join(ans)
 
218
        if isbytestring(ans):
 
219
            ans = ans.decode('utf-8')
 
220
        return ans
 
221
 
 
222
    def set_plugin_tweaks(self, d):
 
223
        self.plugin_tweaks = d
 
224
 
 
225
# }}}
 
226
 
 
227
class PluginTweaks(QDialog): # {{{
 
228
 
 
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)
 
235
        self.msg = QLabel(
 
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,
 
244
                Qt.Horizontal, self)
 
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)
 
249
 
 
250
# }}}
13
251
 
14
252
class ConfigWidget(ConfigWidgetBase, Ui_Form):
15
253
 
16
254
    def genesis(self, gui):
17
255
        self.gui = 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)
 
265
 
 
266
 
 
267
    def plugin_tweaks(self):
 
268
        raw = self.tweaks.plugin_tweaks_string
 
269
        d = PluginTweaks(raw, self)
 
270
        if d.exec_() == d.Accepted:
 
271
            g, l = {}, {}
 
272
            try:
 
273
                exec unicode(d.edit.toPlainText()) in g, l
 
274
            except:
 
275
                import traceback
 
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)
 
281
            self.changed()
 
282
 
 
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)
19
287
 
20
288
    def changed(self, *args):
21
289
        self.changed_signal.emit()
22
290
 
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)
28
294
 
29
 
        self.default_tweaks.setPlainText(deft.decode('utf-8'))
 
295
    def restore_to_default(self, *args):
 
296
        idx = self.tweaks_view.currentIndex()
 
297
        if idx.isValid():
 
298
            self.tweaks.restore_to_default(idx)
 
299
            tweak = self.tweaks.data(idx, Qt.UserRole)
 
300
            self.edit_tweak.setPlainText(tweak.edit_text)
 
301
            self.changed()
30
302
 
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()
 
306
        self.changed()
35
307
 
 
308
    def apply_tweak(self):
 
309
        idx = self.tweaks_view.currentIndex()
 
310
        if idx.isValid():
 
311
            l, g = {}, {}
 
312
            try:
 
313
                exec unicode(self.edit_tweak.toPlainText()) in g, l
 
314
            except:
 
315
                import traceback
 
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)
 
320
                return
 
321
            self.tweaks.update_tweak(idx, l)
 
322
            self.changed()
36
323
 
37
324
    def commit(self):
38
 
        raw = unicode(self.current_tweaks.toPlainText()).encode('utf-8')
 
325
        raw = self.tweaks.to_string()
39
326
        try:
40
327
            exec raw
41
328
        except:
54
341
if __name__ == '__main__':
55
342
    from PyQt4.Qt import QApplication
56
343
    app = QApplication([])
 
344
    #Tweaks()
 
345
    #test_widget
57
346
    test_widget('Advanced', 'Tweaks')
58
347