~ubuntu-branches/debian/experimental/spyder/experimental

« back to all changes in this revision

Viewing changes to spyderlib/plugins/configdialog.py

  • Committer: Package Import Robot
  • Author(s): Picca Frédéric-Emmanuel
  • Date: 2013-02-27 09:51:28 UTC
  • mfrom: (1.1.18)
  • Revision ID: package-import@ubuntu.com-20130227095128-wtx1irpvf4vl79lj
Tags: 2.2.0~beta3+dfsg-1
* Imported Upstream version 2.2.0~beta3+dfsg
* debian /patches
  - 0002-feature-forwarded-add-icon-to-desktop-file.patch (deleted)
    this patch was integrated by the upstream.

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
# -*- coding: utf-8 -*-
2
 
#
3
 
# Copyright © 2009-2010 Pierre Raybaut
4
 
# Licensed under the terms of the MIT License
5
 
# (see spyderlib/__init__.py for details)
6
 
 
7
 
"""Configuration dialog / Preferences"""
8
 
 
9
 
import os
10
 
import os.path as osp
11
 
 
12
 
from spyderlib.baseconfig import _
13
 
from spyderlib.config import CONF
14
 
from spyderlib.guiconfig import (get_icon, CUSTOM_COLOR_SCHEME_NAME,
15
 
                                 set_default_color_scheme, COLOR_SCHEME_NAMES)
16
 
from spyderlib.utils.qthelpers import get_std_icon
17
 
from spyderlib.userconfig import NoDefault
18
 
from spyderlib.widgets.colors import ColorLayout
19
 
 
20
 
from spyderlib.qt.QtGui import (QWidget, QDialog, QListWidget, QListWidgetItem,
21
 
                                QVBoxLayout, QStackedWidget, QListView,
22
 
                                QHBoxLayout, QDialogButtonBox, QCheckBox,
23
 
                                QMessageBox, QLabel, QLineEdit, QSpinBox,
24
 
                                QPushButton, QFontComboBox, QGroupBox,
25
 
                                QComboBox, QColor, QGridLayout, QTabWidget,
26
 
                                QRadioButton, QButtonGroup, QSplitter,
27
 
                                QStyleFactory, QScrollArea)
28
 
from spyderlib.qt.QtCore import Qt, QSize, SIGNAL, SLOT, Slot
29
 
from spyderlib.qt.compat import (to_qvariant, from_qvariant,
30
 
                                 getexistingdirectory, getopenfilename)
31
 
 
32
 
 
33
 
class SizeMixin(object):
34
 
    """Mixin to keep widget size accessible
35
 
    even when C++ object has been deleted"""
36
 
    def resizeEvent(self, event):
37
 
        """Reimplement Qt method"""
38
 
        QDialog.resizeEvent(self, event)
39
 
        self.emit(SIGNAL("size_change(QSize)"), self.size())
40
 
 
41
 
 
42
 
class ConfigPage(QWidget):
43
 
    """Configuration page base class"""
44
 
    def __init__(self, parent, apply_callback=None):
45
 
        QWidget.__init__(self, parent)
46
 
        self.apply_callback = apply_callback
47
 
        self.is_modified = False
48
 
        
49
 
    def initialize(self):
50
 
        """
51
 
        Initialize configuration page:
52
 
            * setup GUI widgets
53
 
            * load settings and change widgets accordingly
54
 
        """
55
 
        self.setup_page()
56
 
        self.load_from_conf()
57
 
        
58
 
    def get_name(self):
59
 
        """Return page name"""
60
 
        raise NotImplementedError
61
 
    
62
 
    def get_icon(self):
63
 
        """Return page icon"""
64
 
        raise NotImplementedError
65
 
    
66
 
    def setup_page(self):
67
 
        """Setup configuration page widget"""
68
 
        raise NotImplementedError
69
 
        
70
 
    def set_modified(self, state):
71
 
        self.is_modified = state
72
 
        self.emit(SIGNAL("apply_button_enabled(bool)"), state)
73
 
        
74
 
    def is_valid(self):
75
 
        """Return True if all widget contents are valid"""
76
 
        raise NotImplementedError
77
 
    
78
 
    def apply_changes(self):
79
 
        """Apply changes callback"""
80
 
        if self.is_modified:
81
 
            self.save_to_conf()
82
 
            if self.apply_callback is not None:
83
 
                self.apply_callback()
84
 
            self.set_modified(False)
85
 
    
86
 
    def load_from_conf(self):
87
 
        """Load settings from configuration file"""
88
 
        raise NotImplementedError
89
 
    
90
 
    def save_to_conf(self):
91
 
        """Save settings to configuration file"""
92
 
        raise NotImplementedError
93
 
 
94
 
 
95
 
class ConfigDialog(QDialog, SizeMixin):
96
 
    """Spyder configuration ('Preferences') dialog box"""
97
 
    def __init__(self, parent=None):
98
 
        QDialog.__init__(self, parent)
99
 
        SizeMixin.__init__(self)
100
 
        
101
 
        # Destroying the C++ object right after closing the dialog box,
102
 
        # otherwise it may be garbage-collected in another QThread
103
 
        # (e.g. the editor's analysis thread in Spyder), thus leading to
104
 
        # a segmentation fault on UNIX or an application crash on Windows
105
 
        self.setAttribute(Qt.WA_DeleteOnClose)
106
 
 
107
 
        self.contents_widget = QListWidget()
108
 
        self.contents_widget.setMovement(QListView.Static)
109
 
        self.contents_widget.setSpacing(1)
110
 
 
111
 
        bbox = QDialogButtonBox(QDialogButtonBox.Ok|QDialogButtonBox.Apply
112
 
                                |QDialogButtonBox.Cancel)
113
 
        self.apply_btn = bbox.button(QDialogButtonBox.Apply)
114
 
        self.connect(bbox, SIGNAL("accepted()"), SLOT("accept()"))
115
 
        self.connect(bbox, SIGNAL("rejected()"), SLOT("reject()"))
116
 
        self.connect(bbox, SIGNAL("clicked(QAbstractButton*)"),
117
 
                     self.button_clicked)
118
 
 
119
 
        self.pages_widget = QStackedWidget()
120
 
        self.connect(self.pages_widget, SIGNAL("currentChanged(int)"),
121
 
                     self.current_page_changed)
122
 
 
123
 
        self.connect(self.contents_widget, SIGNAL("currentRowChanged(int)"),
124
 
                     self.pages_widget.setCurrentIndex)
125
 
        self.contents_widget.setCurrentRow(0)
126
 
 
127
 
        hsplitter = QSplitter()
128
 
        hsplitter.addWidget(self.contents_widget)
129
 
        hsplitter.addWidget(self.pages_widget)
130
 
 
131
 
        btnlayout = QHBoxLayout()
132
 
        btnlayout.addStretch(1)
133
 
        btnlayout.addWidget(bbox)
134
 
 
135
 
        vlayout = QVBoxLayout()
136
 
        vlayout.addWidget(hsplitter)
137
 
        vlayout.addLayout(btnlayout)
138
 
 
139
 
        self.setLayout(vlayout)
140
 
 
141
 
        self.setWindowTitle(_("Preferences"))
142
 
        self.setWindowIcon(get_icon("configure.png"))
143
 
        
144
 
    def get_current_index(self):
145
 
        """Return current page index"""
146
 
        return self.contents_widget.currentRow()
147
 
        
148
 
    def set_current_index(self, index):
149
 
        """Set current page index"""
150
 
        self.contents_widget.setCurrentRow(index)
151
 
        
152
 
    def get_page(self, index=None):
153
 
        """Return page widget"""
154
 
        if index is None:
155
 
            widget = self.pages_widget.currentWidget()
156
 
        else:
157
 
            widget = self.pages_widget.widget(index)
158
 
        return widget.widget()
159
 
        
160
 
    def accept(self):
161
 
        """Reimplement Qt method"""
162
 
        for index in range(self.pages_widget.count()):
163
 
            configpage = self.get_page(index)
164
 
            if not configpage.is_valid():
165
 
                return
166
 
            configpage.apply_changes()
167
 
        QDialog.accept(self)
168
 
        
169
 
    def button_clicked(self, button):
170
 
        if button is self.apply_btn:
171
 
            # Apply button was clicked
172
 
            configpage = self.get_page()
173
 
            if not configpage.is_valid():
174
 
                return
175
 
            configpage.apply_changes()
176
 
            
177
 
    def current_page_changed(self, index):
178
 
        widget = self.get_page(index)
179
 
        self.apply_btn.setVisible(widget.apply_callback is not None)
180
 
        self.apply_btn.setEnabled(widget.is_modified)
181
 
        
182
 
    def add_page(self, widget):
183
 
        self.connect(self, SIGNAL('check_settings()'), widget.check_settings)
184
 
        self.connect(widget, SIGNAL('show_this_page()'),
185
 
                     lambda row=self.contents_widget.count():
186
 
                     self.contents_widget.setCurrentRow(row))
187
 
        self.connect(widget, SIGNAL("apply_button_enabled(bool)"),
188
 
                     self.apply_btn.setEnabled)
189
 
        scrollarea = QScrollArea(self)
190
 
        scrollarea.setWidgetResizable(True)
191
 
        scrollarea.setWidget(widget)
192
 
        self.pages_widget.addWidget(scrollarea)
193
 
        item = QListWidgetItem(self.contents_widget)
194
 
        item.setIcon(widget.get_icon())
195
 
        item.setText(widget.get_name())
196
 
        item.setFlags(Qt.ItemIsSelectable|Qt.ItemIsEnabled)
197
 
        item.setSizeHint(QSize(0, 25))
198
 
        
199
 
    def check_all_settings(self):
200
 
        """This method is called to check all configuration page settings
201
 
        after configuration dialog has been shown"""
202
 
        self.emit(SIGNAL('check_settings()'))
203
 
 
204
 
 
205
 
class SpyderConfigPage(ConfigPage):
206
 
    """Plugin configuration dialog box page widget"""
207
 
    def __init__(self, parent):
208
 
        ConfigPage.__init__(self, parent,
209
 
                            apply_callback=lambda:
210
 
                            self.apply_settings(self.changed_options))
211
 
        self.checkboxes = {}
212
 
        self.radiobuttons = {}
213
 
        self.lineedits = {}
214
 
        self.validate_data = {}
215
 
        self.spinboxes = {}
216
 
        self.comboboxes = {}
217
 
        self.fontboxes = {}
218
 
        self.coloredits = {}
219
 
        self.scedits = {}
220
 
        self.changed_options = set()
221
 
        self.default_button_group = None
222
 
        
223
 
    def apply_settings(self, options):
224
 
        raise NotImplementedError
225
 
    
226
 
    def check_settings(self):
227
 
        """This method is called to check settings after configuration 
228
 
        dialog has been shown"""
229
 
        pass
230
 
        
231
 
    def set_modified(self, state):
232
 
        ConfigPage.set_modified(self, state)
233
 
        if not state:
234
 
            self.changed_options = set()
235
 
        
236
 
    def is_valid(self):
237
 
        """Return True if all widget contents are valid"""
238
 
        for lineedit in self.lineedits:
239
 
            if lineedit in self.validate_data and lineedit.isEnabled():
240
 
                validator, invalid_msg = self.validate_data[lineedit]
241
 
                text = unicode(lineedit.text())
242
 
                if not validator(text):
243
 
                    QMessageBox.critical(self, self.get_name(),
244
 
                                     "%s:<br><b>%s</b>" % (invalid_msg, text),
245
 
                                     QMessageBox.Ok)
246
 
                    return False
247
 
        return True
248
 
        
249
 
    def load_from_conf(self):
250
 
        """Load settings from configuration file"""
251
 
        for checkbox, (option, default) in self.checkboxes.items():
252
 
            checkbox.setChecked(self.get_option(option, default))
253
 
            self.connect(checkbox, SIGNAL("clicked(bool)"),
254
 
                         lambda _foo, opt=option: self.has_been_modified(opt))
255
 
        for radiobutton, (option, default) in self.radiobuttons.items():
256
 
            radiobutton.setChecked(self.get_option(option, default))
257
 
            self.connect(radiobutton, SIGNAL("toggled(bool)"),
258
 
                         lambda _foo, opt=option: self.has_been_modified(opt))
259
 
        for lineedit, (option, default) in self.lineedits.items():
260
 
            lineedit.setText(self.get_option(option, default))
261
 
            self.connect(lineedit, SIGNAL("textChanged(QString)"),
262
 
                         lambda _foo, opt=option: self.has_been_modified(opt))
263
 
        for spinbox, (option, default) in self.spinboxes.items():
264
 
            spinbox.setValue(self.get_option(option, default))
265
 
            self.connect(spinbox, SIGNAL('valueChanged(int)'),
266
 
                         lambda _foo, opt=option: self.has_been_modified(opt))
267
 
        for combobox, (option, default) in self.comboboxes.items():
268
 
            value = self.get_option(option, default)
269
 
            for index in range(combobox.count()):
270
 
                data = from_qvariant(combobox.itemData(index), unicode)
271
 
                # For PyQt API v2, it is necessary to convert `data` to 
272
 
                # unicode in case the original type was not a string, like an 
273
 
                # integer for example (see spyderlib.qt.compat.from_qvariant):
274
 
                if unicode(data) == unicode(value):
275
 
                    break
276
 
            combobox.setCurrentIndex(index)
277
 
            self.connect(combobox, SIGNAL('currentIndexChanged(int)'),
278
 
                         lambda _foo, opt=option: self.has_been_modified(opt))
279
 
        for (fontbox, sizebox), option in self.fontboxes.items():
280
 
            font = self.get_font(option)
281
 
            fontbox.setCurrentFont(font)
282
 
            sizebox.setValue(font.pointSize())
283
 
            if option is None:
284
 
                property = 'plugin_font'
285
 
            else:
286
 
                property = option
287
 
            self.connect(fontbox, SIGNAL('currentIndexChanged(int)'),
288
 
                         lambda _foo, opt=property: self.has_been_modified(opt))
289
 
            self.connect(sizebox, SIGNAL('valueChanged(int)'),
290
 
                         lambda _foo, opt=property: self.has_been_modified(opt))
291
 
        for clayout, (option, default) in self.coloredits.items():
292
 
            property = to_qvariant(option)
293
 
            edit = clayout.lineedit
294
 
            btn = clayout.colorbtn
295
 
            edit.setText(self.get_option(option, default))
296
 
            self.connect(btn, SIGNAL('clicked()'),
297
 
                         lambda opt=option: self.has_been_modified(opt))
298
 
            self.connect(edit, SIGNAL("textChanged(QString)"),
299
 
                         lambda _foo, opt=option: self.has_been_modified(opt))
300
 
        for (clayout, cb_bold, cb_italic
301
 
             ), (option, default) in self.scedits.items():
302
 
            edit = clayout.lineedit
303
 
            btn = clayout.colorbtn
304
 
            color, bold, italic = self.get_option(option, default)
305
 
            edit.setText(color)
306
 
            cb_bold.setChecked(bold)
307
 
            cb_italic.setChecked(italic)
308
 
            self.connect(btn, SIGNAL('clicked()'),
309
 
                         lambda opt=option: self.has_been_modified(opt))
310
 
            self.connect(edit, SIGNAL("textChanged(QString)"),
311
 
                         lambda _foo, opt=option: self.has_been_modified(opt))
312
 
            self.connect(cb_bold, SIGNAL("clicked(bool)"),
313
 
                         lambda _foo, opt=option: self.has_been_modified(opt))
314
 
            self.connect(cb_italic, SIGNAL("clicked(bool)"),
315
 
                         lambda _foo, opt=option: self.has_been_modified(opt))
316
 
    
317
 
    def save_to_conf(self):
318
 
        """Save settings to configuration file"""
319
 
        for checkbox, (option, _default) in self.checkboxes.items():
320
 
            self.set_option(option, checkbox.isChecked())
321
 
        for radiobutton, (option, _default) in self.radiobuttons.items():
322
 
            self.set_option(option, radiobutton.isChecked())
323
 
        for lineedit, (option, _default) in self.lineedits.items():
324
 
            self.set_option(option, unicode(lineedit.text()))
325
 
        for spinbox, (option, _default) in self.spinboxes.items():
326
 
            self.set_option(option, spinbox.value())
327
 
        for combobox, (option, _default) in self.comboboxes.items():
328
 
            data = combobox.itemData(combobox.currentIndex())
329
 
            self.set_option(option, from_qvariant(data, unicode))
330
 
        for (fontbox, sizebox), option in self.fontboxes.items():
331
 
            font = fontbox.currentFont()
332
 
            font.setPointSize(sizebox.value())
333
 
            self.set_font(font, option)
334
 
        for clayout, (option, _default) in self.coloredits.items():
335
 
            self.set_option(option, unicode(clayout.lineedit.text()))
336
 
        for (clayout, cb_bold, cb_italic), (option, _default) in self.scedits.items():
337
 
            color = unicode(clayout.lineedit.text())
338
 
            bold = cb_bold.isChecked()
339
 
            italic = cb_italic.isChecked()
340
 
            self.set_option(option, (color, bold, italic))
341
 
    
342
 
    @Slot(str)
343
 
    def has_been_modified(self, option):
344
 
        self.set_modified(True)
345
 
        self.changed_options.add(option)
346
 
    
347
 
    def create_checkbox(self, text, option, default=NoDefault,
348
 
                        tip=None, msg_warning=None, msg_info=None,
349
 
                        msg_if_enabled=False):
350
 
        checkbox = QCheckBox(text)
351
 
        if tip is not None:
352
 
            checkbox.setToolTip(tip)
353
 
        self.checkboxes[checkbox] = (option, default)
354
 
        if msg_warning is not None or msg_info is not None:
355
 
            def show_message(is_checked):
356
 
                if is_checked or not msg_if_enabled:
357
 
                    if msg_warning is not None:
358
 
                        QMessageBox.warning(self, self.get_name(),
359
 
                                            msg_warning, QMessageBox.Ok)
360
 
                    if msg_info is not None:
361
 
                        QMessageBox.information(self, self.get_name(),
362
 
                                                msg_info, QMessageBox.Ok)
363
 
            self.connect(checkbox, SIGNAL("clicked(bool)"), show_message)
364
 
        return checkbox
365
 
    
366
 
    def create_radiobutton(self, text, option, default=NoDefault,
367
 
                           tip=None, msg_warning=None, msg_info=None,
368
 
                           msg_if_enabled=False, button_group=None):
369
 
        radiobutton = QRadioButton(text)
370
 
        if button_group is None:
371
 
            if self.default_button_group is None:
372
 
                self.default_button_group = QButtonGroup(self)
373
 
            button_group = self.default_button_group
374
 
        button_group.addButton(radiobutton)
375
 
        if tip is not None:
376
 
            radiobutton.setToolTip(tip)
377
 
        self.radiobuttons[radiobutton] = (option, default)
378
 
        if msg_warning is not None or msg_info is not None:
379
 
            def show_message(is_checked):
380
 
                if is_checked or not msg_if_enabled:
381
 
                    if msg_warning is not None:
382
 
                        QMessageBox.warning(self, self.get_name(),
383
 
                                            msg_warning, QMessageBox.Ok)
384
 
                    if msg_info is not None:
385
 
                        QMessageBox.information(self, self.get_name(),
386
 
                                                msg_info, QMessageBox.Ok)
387
 
            self.connect(radiobutton, SIGNAL("toggled(bool)"), show_message)
388
 
        return radiobutton
389
 
    
390
 
    def create_lineedit(self, text, option, default=NoDefault,
391
 
                        tip=None, alignment=Qt.Vertical):
392
 
        label = QLabel(text)
393
 
        label.setWordWrap(True)
394
 
        edit = QLineEdit()
395
 
        layout = QVBoxLayout() if alignment == Qt.Vertical else QHBoxLayout()
396
 
        layout.addWidget(label)
397
 
        layout.addWidget(edit)
398
 
        layout.setContentsMargins(0, 0, 0, 0)
399
 
        if tip:
400
 
            edit.setToolTip(tip)
401
 
        self.lineedits[edit] = (option, default)
402
 
        widget = QWidget(self)
403
 
        widget.setLayout(layout)
404
 
        return widget
405
 
    
406
 
    def create_browsedir(self, text, option, default=NoDefault, tip=None):
407
 
        widget = self.create_lineedit(text, option, default,
408
 
                                      alignment=Qt.Horizontal)
409
 
        for edit in self.lineedits:
410
 
            if widget.isAncestorOf(edit):
411
 
                break
412
 
        msg = _("Invalid directory path")
413
 
        self.validate_data[edit] = (osp.isdir, msg)
414
 
        browse_btn = QPushButton(get_std_icon('DirOpenIcon'), "", self)
415
 
        browse_btn.setToolTip(_("Select directory"))
416
 
        self.connect(browse_btn, SIGNAL("clicked()"),
417
 
                     lambda: self.select_directory(edit))
418
 
        layout = QHBoxLayout()
419
 
        layout.addWidget(widget)
420
 
        layout.addWidget(browse_btn)
421
 
        layout.setContentsMargins(0, 0, 0, 0)
422
 
        browsedir = QWidget(self)
423
 
        browsedir.setLayout(layout)
424
 
        return browsedir
425
 
 
426
 
    def select_directory(self, edit):
427
 
        """Select directory"""
428
 
        basedir = unicode(edit.text())
429
 
        if not osp.isdir(basedir):
430
 
            basedir = os.getcwdu()
431
 
        title = _("Select directory")
432
 
        directory = getexistingdirectory(self, title, basedir)
433
 
        if directory:
434
 
            edit.setText(directory)
435
 
    
436
 
    def create_browsefile(self, text, option, default=NoDefault, tip=None,
437
 
                          filters=None):
438
 
        widget = self.create_lineedit(text, option, default,
439
 
                                      alignment=Qt.Horizontal)
440
 
        for edit in self.lineedits:
441
 
            if widget.isAncestorOf(edit):
442
 
                break
443
 
        msg = _("Invalid file path")
444
 
        self.validate_data[edit] = (osp.isfile, msg)
445
 
        browse_btn = QPushButton(get_std_icon('FileIcon'), "", self)
446
 
        browse_btn.setToolTip(_("Select file"))
447
 
        self.connect(browse_btn, SIGNAL("clicked()"),
448
 
                     lambda: self.select_file(edit, filters))
449
 
        layout = QHBoxLayout()
450
 
        layout.addWidget(widget)
451
 
        layout.addWidget(browse_btn)
452
 
        layout.setContentsMargins(0, 0, 0, 0)
453
 
        browsedir = QWidget(self)
454
 
        browsedir.setLayout(layout)
455
 
        return browsedir
456
 
 
457
 
    def select_file(self, edit, filters=None):
458
 
        """Select File"""
459
 
        basedir = osp.dirname(unicode(edit.text()))
460
 
        if not osp.isdir(basedir):
461
 
            basedir = os.getcwdu()
462
 
        if filters is None:
463
 
            filters = _("All files (*)")
464
 
        title = _("Select file")
465
 
        filename, _selfilter = getopenfilename(self, title, basedir, filters)
466
 
        if filename:
467
 
            edit.setText(filename)
468
 
    
469
 
    def create_spinbox(self, prefix, suffix, option, default=NoDefault,
470
 
                       min_=None, max_=None, step=None, tip=None):
471
 
        if prefix:
472
 
            plabel = QLabel(prefix)
473
 
        else:
474
 
            plabel = None
475
 
        if suffix:
476
 
            slabel = QLabel(suffix)
477
 
        else:
478
 
            slabel = None
479
 
        spinbox = QSpinBox()
480
 
        if min_ is not None:
481
 
            spinbox.setMinimum(min_)
482
 
        if max_ is not None:
483
 
            spinbox.setMaximum(max_)
484
 
        if step is not None:
485
 
            spinbox.setSingleStep(step)
486
 
        if tip is not None:
487
 
            spinbox.setToolTip(tip)
488
 
        self.spinboxes[spinbox] = (option, default)
489
 
        layout = QHBoxLayout()
490
 
        for subwidget in (plabel, spinbox, slabel):
491
 
            if subwidget is not None:
492
 
                layout.addWidget(subwidget)
493
 
        layout.addStretch(1)
494
 
        layout.setContentsMargins(0, 0, 0, 0)
495
 
        widget = QWidget(self)
496
 
        widget.setLayout(layout)
497
 
        return widget
498
 
    
499
 
    def create_coloredit(self, text, option, default=NoDefault, tip=None,
500
 
                         without_layout=False):
501
 
        label = QLabel(text)
502
 
        clayout = ColorLayout(QColor(Qt.black), self)
503
 
        clayout.lineedit.setMaximumWidth(80)
504
 
        if tip is not None:
505
 
            clayout.setToolTip(tip)
506
 
        self.coloredits[clayout] = (option, default)
507
 
        if without_layout:
508
 
            return label, clayout
509
 
        layout = QHBoxLayout()
510
 
        layout.addWidget(label)
511
 
        layout.addLayout(clayout)
512
 
        layout.addStretch(1)
513
 
        layout.setContentsMargins(0, 0, 0, 0)
514
 
        widget = QWidget(self)
515
 
        widget.setLayout(layout)
516
 
        return widget
517
 
    
518
 
    def create_scedit(self, text, option, default=NoDefault, tip=None,
519
 
                      without_layout=False):
520
 
        label = QLabel(text)
521
 
        clayout = ColorLayout(QColor(Qt.black), self)
522
 
        clayout.lineedit.setMaximumWidth(80)
523
 
        if tip is not None:
524
 
            clayout.setToolTip(tip)
525
 
        cb_bold = QCheckBox()
526
 
        cb_bold.setIcon(get_icon("bold.png"))
527
 
        cb_bold.setToolTip(_("Bold"))
528
 
        cb_italic = QCheckBox()
529
 
        cb_italic.setIcon(get_icon("italic.png"))
530
 
        cb_italic.setToolTip(_("Italic"))
531
 
        self.scedits[(clayout, cb_bold, cb_italic)] = (option, default)
532
 
        if without_layout:
533
 
            return label, clayout, cb_bold, cb_italic
534
 
        layout = QHBoxLayout()
535
 
        layout.addWidget(label)
536
 
        layout.addLayout(clayout)
537
 
        layout.addSpacing(10)
538
 
        layout.addWidget(cb_bold)
539
 
        layout.addWidget(cb_italic)
540
 
        layout.addStretch(1)
541
 
        layout.setContentsMargins(0, 0, 0, 0)
542
 
        widget = QWidget(self)
543
 
        widget.setLayout(layout)
544
 
        return widget
545
 
    
546
 
    def create_combobox(self, text, choices, option, default=NoDefault,
547
 
                        tip=None):
548
 
        """choices: couples (name, key)"""
549
 
        label = QLabel(text)
550
 
        combobox = QComboBox()
551
 
        if tip is not None:
552
 
            combobox.setToolTip(tip)
553
 
        for name, key in choices:
554
 
            combobox.addItem(name, to_qvariant(key))
555
 
        self.comboboxes[combobox] = (option, default)
556
 
        layout = QHBoxLayout()
557
 
        for subwidget in (label, combobox):
558
 
            layout.addWidget(subwidget)
559
 
        layout.addStretch(1)
560
 
        layout.setContentsMargins(0, 0, 0, 0)
561
 
        widget = QWidget(self)
562
 
        widget.setLayout(layout)
563
 
        return widget
564
 
    
565
 
    def create_fontgroup(self, option=None, text=None,
566
 
                         tip=None, fontfilters=None):
567
 
        """Option=None -> setting plugin font"""
568
 
        fontlabel = QLabel(_("Font: "))
569
 
        fontbox = QFontComboBox()
570
 
        if fontfilters is not None:
571
 
            fontbox.setFontFilters(fontfilters)
572
 
        sizelabel = QLabel("  "+_("Size: "))
573
 
        sizebox = QSpinBox()
574
 
        sizebox.setRange(7, 100)
575
 
        self.fontboxes[(fontbox, sizebox)] = option
576
 
        layout = QHBoxLayout()
577
 
        for subwidget in (fontlabel, fontbox, sizelabel, sizebox):
578
 
            layout.addWidget(subwidget)
579
 
        layout.addStretch(1)
580
 
        if text is None:
581
 
            text = _("Font style")
582
 
        group = QGroupBox(text)
583
 
        group.setLayout(layout)
584
 
        if tip is not None:
585
 
            group.setToolTip(tip)
586
 
        return group
587
 
    
588
 
    def create_button(self, text, callback):
589
 
        btn = QPushButton(text)
590
 
        self.connect(btn, SIGNAL('clicked()'), callback)
591
 
        self.connect(btn, SIGNAL('clicked()'),
592
 
                     lambda opt='': self.has_been_modified(opt))
593
 
        return btn
594
 
    
595
 
    def create_tab(self, *widgets):
596
 
        """Create simple tab widget page: widgets added in a vertical layout"""
597
 
        widget = QWidget()
598
 
        layout = QVBoxLayout()
599
 
        for widg in widgets:
600
 
            layout.addWidget(widg)
601
 
        layout.addStretch(1)
602
 
        widget.setLayout(layout)
603
 
        return widget
604
 
 
605
 
 
606
 
class GeneralConfigPage(SpyderConfigPage):
607
 
    CONF_SECTION = None
608
 
    def __init__(self, parent, main):
609
 
        SpyderConfigPage.__init__(self, parent)
610
 
        self.main = main
611
 
 
612
 
    def set_option(self, option, value):
613
 
        CONF.set(self.CONF_SECTION, option, value)
614
 
 
615
 
    def get_option(self, option, default=NoDefault):
616
 
        return CONF.get(self.CONF_SECTION, option, default)
617
 
            
618
 
    def apply_settings(self, options):
619
 
        raise NotImplementedError
620
 
 
621
 
 
622
 
class MainConfigPage(GeneralConfigPage):
623
 
    CONF_SECTION = "main"
624
 
    def get_name(self):
625
 
        return _("General")
626
 
    
627
 
    def get_icon(self):
628
 
        return get_icon("genprefs.png")
629
 
    
630
 
    def setup_page(self):
631
 
        interface_group = QGroupBox(_("Interface"))
632
 
        styles = [str(txt) for txt in QStyleFactory.keys()]
633
 
        choices = zip(styles, [style.lower() for style in styles])
634
 
        style_combo = self.create_combobox(_('Qt windows style'), choices,
635
 
                                           'windows_style',
636
 
                                           default=self.main.default_style)
637
 
        newcb = self.create_checkbox
638
 
        vertdock_box = newcb(_("Vertical dockwidget title bars"),
639
 
                             'vertical_dockwidget_titlebars')
640
 
        verttabs_box = newcb(_("Vertical dockwidget tabs"),
641
 
                             'vertical_tabs')
642
 
        animated_box = newcb(_("Animated toolbars and dockwidgets"),
643
 
                             'animated_docks')
644
 
        margin_box = newcb(_("Custom dockwidget margin:"),
645
 
                           'use_custom_margin')
646
 
        margin_spin = self.create_spinbox("", "pixels", 'custom_margin',
647
 
                                          0, 0, 30)
648
 
        self.connect(margin_box, SIGNAL("toggled(bool)"),
649
 
                     margin_spin.setEnabled)
650
 
        margin_spin.setEnabled(self.get_option('use_custom_margin'))
651
 
        margins_layout = QHBoxLayout()
652
 
        margins_layout.addWidget(margin_box)
653
 
        margins_layout.addWidget(margin_spin)
654
 
        
655
 
        single_instance_box = newcb(_("Use a single instance"),
656
 
                                    'single_instance',
657
 
                                    tip=_("Set this to open external<br> "
658
 
                                          "python, .spy and .mat files in an "
659
 
                                          "already running instance (Requires "
660
 
                                          "a restart)"))
661
 
        
662
 
        interface_layout = QVBoxLayout()
663
 
        interface_layout.addWidget(style_combo)
664
 
        interface_layout.addWidget(single_instance_box)
665
 
        interface_layout.addWidget(vertdock_box)
666
 
        interface_layout.addWidget(verttabs_box)
667
 
        interface_layout.addWidget(animated_box)
668
 
        interface_layout.addLayout(margins_layout)
669
 
        interface_group.setLayout(interface_layout)
670
 
 
671
 
        sbar_group = QGroupBox(_("Status bar"))
672
 
        memory_box = newcb(_("Show memory usage every"), 'memory_usage/enable',
673
 
                           tip=self.main.mem_status.toolTip())
674
 
        memory_spin = self.create_spinbox("", " ms", 'memory_usage/timeout',
675
 
                                          min_=100, max_=1000000, step=100)
676
 
        self.connect(memory_box, SIGNAL("toggled(bool)"),
677
 
                     memory_spin.setEnabled)
678
 
        memory_spin.setEnabled(self.get_option('memory_usage/enable'))
679
 
        memory_layout = QHBoxLayout()
680
 
        memory_layout.addWidget(memory_box)
681
 
        memory_layout.addWidget(memory_spin)
682
 
        memory_layout.setEnabled(self.main.mem_status.is_supported())
683
 
        cpu_box = newcb(_("Show CPU usage every"), 'cpu_usage/enable',
684
 
                        tip=self.main.cpu_status.toolTip())
685
 
        cpu_spin = self.create_spinbox("", " ms", 'cpu_usage/timeout',
686
 
                                       min_=100, max_=1000000, step=100)
687
 
        self.connect(cpu_box, SIGNAL("toggled(bool)"), cpu_spin.setEnabled)
688
 
        cpu_spin.setEnabled(self.get_option('cpu_usage/enable'))
689
 
        cpu_layout = QHBoxLayout()
690
 
        cpu_layout.addWidget(cpu_box)
691
 
        cpu_layout.addWidget(cpu_spin)
692
 
        cpu_layout.setEnabled(self.main.cpu_status.is_supported())
693
 
        
694
 
        sbar_layout = QVBoxLayout()
695
 
        sbar_layout.addLayout(memory_layout)
696
 
        sbar_layout.addLayout(cpu_layout)
697
 
        sbar_group.setLayout(sbar_layout)
698
 
        
699
 
        debug_group = QGroupBox(_("Debugging"))
700
 
        popup_console_box = newcb(_("Pop up internal console when errors "
701
 
                                    "were intercepted"),
702
 
                                  'show_internal_console_if_traceback',
703
 
                                  default=False)
704
 
        
705
 
        debug_layout = QVBoxLayout()
706
 
        debug_layout.addWidget(popup_console_box)
707
 
        debug_group.setLayout(debug_layout)
708
 
        
709
 
        vlayout = QVBoxLayout()
710
 
        vlayout.addWidget(interface_group)
711
 
        vlayout.addWidget(sbar_group)
712
 
        vlayout.addWidget(debug_group)
713
 
        vlayout.addStretch(1)
714
 
        self.setLayout(vlayout)
715
 
        
716
 
    def apply_settings(self, options):
717
 
        self.main.apply_settings()
718
 
 
719
 
 
720
 
class ColorSchemeConfigPage(GeneralConfigPage):
721
 
    CONF_SECTION = "color_schemes"
722
 
    def get_name(self):
723
 
        return _("Syntax coloring")
724
 
    
725
 
    def get_icon(self):
726
 
        return get_icon("genprefs.png")
727
 
    
728
 
    def setup_page(self):
729
 
        tabs = QTabWidget()
730
 
        names = self.get_option("names")
731
 
        names.pop(names.index(CUSTOM_COLOR_SCHEME_NAME))
732
 
        names.insert(0, CUSTOM_COLOR_SCHEME_NAME)
733
 
        fieldnames = {
734
 
                      "background":     _("Background:"),
735
 
                      "currentline":    _("Current line:"),
736
 
                      "occurence":      _("Occurence:"),
737
 
                      "ctrlclick":      _("Link:"),
738
 
                      "sideareas":      _("Side areas:"),
739
 
                      "matched_p":      _("Matched parentheses:"),
740
 
                      "unmatched_p":    _("Unmatched parentheses:"),
741
 
                      "normal":         _("Normal text:"),
742
 
                      "keyword":        _("Keyword:"),
743
 
                      "builtin":        _("Builtin:"),
744
 
                      "definition":     _("Definition:"),
745
 
                      "comment":        _("Comment:"),
746
 
                      "string":         _("String:"),
747
 
                      "number":         _("Number:"),
748
 
                      "instance":       _("Instance:"),
749
 
                      }
750
 
        from spyderlib.widgets.sourcecode import syntaxhighlighters
751
 
        assert all([key in fieldnames
752
 
                    for key in syntaxhighlighters.COLOR_SCHEME_KEYS])
753
 
        for tabname in names:
754
 
            cs_group = QGroupBox(_("Color scheme"))
755
 
            cs_layout = QGridLayout()
756
 
            for row, key in enumerate(syntaxhighlighters.COLOR_SCHEME_KEYS):
757
 
                option = "%s/%s" % (tabname, key)
758
 
                value = self.get_option(option)
759
 
                name = fieldnames[key]
760
 
                if isinstance(value, basestring):
761
 
                    label, clayout = self.create_coloredit(name, option,
762
 
                                                           without_layout=True)
763
 
                    label.setAlignment(Qt.AlignRight|Qt.AlignVCenter)
764
 
                    cs_layout.addWidget(label, row+1, 0)
765
 
                    cs_layout.addLayout(clayout, row+1, 1)
766
 
                else:
767
 
                    label, clayout, cb_bold, cb_italic = self.create_scedit(
768
 
                                            name, option, without_layout=True)
769
 
                    label.setAlignment(Qt.AlignRight|Qt.AlignVCenter)
770
 
                    cs_layout.addWidget(label, row+1, 0)
771
 
                    cs_layout.addLayout(clayout, row+1, 1)
772
 
                    cs_layout.addWidget(cb_bold, row+1, 2)
773
 
                    cs_layout.addWidget(cb_italic, row+1, 3)
774
 
            cs_group.setLayout(cs_layout)
775
 
            if tabname in COLOR_SCHEME_NAMES:
776
 
                def_btn = self.create_button(_("Reset to default values"),
777
 
                                         lambda: self.reset_to_default(tabname))
778
 
                tabs.addTab(self.create_tab(cs_group, def_btn), tabname)
779
 
            else:
780
 
                tabs.addTab(self.create_tab(cs_group), tabname)
781
 
        
782
 
        vlayout = QVBoxLayout()
783
 
        vlayout.addWidget(tabs)
784
 
        self.setLayout(vlayout)
785
 
        
786
 
    @Slot(str)
787
 
    def reset_to_default(self, name):
788
 
        set_default_color_scheme(name, replace=True)
789
 
        self.load_from_conf()
790
 
            
791
 
    def apply_settings(self, options):
792
 
        self.main.editor.apply_plugin_settings(['color_scheme_name'])
793
 
        if self.main.historylog is not None:
794
 
            self.main.historylog.apply_plugin_settings(['color_scheme_name'])
795
 
        if self.main.inspector is not None:
796
 
            self.main.inspector.apply_plugin_settings(['color_scheme_name'])
 
1
# -*- coding: utf-8 -*-
 
2
#
 
3
# Copyright © 2009-2010 Pierre Raybaut
 
4
# Licensed under the terms of the MIT License
 
5
# (see spyderlib/__init__.py for details)
 
6
 
 
7
"""Configuration dialog / Preferences"""
 
8
 
 
9
import os
 
10
import os.path as osp
 
11
 
 
12
from spyderlib.baseconfig import _
 
13
from spyderlib.config import CONF
 
14
from spyderlib.guiconfig import (CUSTOM_COLOR_SCHEME_NAME,
 
15
                                 set_default_color_scheme, COLOR_SCHEME_NAMES)
 
16
from spyderlib.utils.qthelpers import get_icon, get_std_icon
 
17
from spyderlib.userconfig import NoDefault
 
18
from spyderlib.widgets.colors import ColorLayout
 
19
 
 
20
from spyderlib.qt.QtGui import (QWidget, QDialog, QListWidget, QListWidgetItem,
 
21
                                QVBoxLayout, QStackedWidget, QListView,
 
22
                                QHBoxLayout, QDialogButtonBox, QCheckBox,
 
23
                                QMessageBox, QLabel, QLineEdit, QSpinBox,
 
24
                                QPushButton, QFontComboBox, QGroupBox,
 
25
                                QComboBox, QColor, QGridLayout, QTabWidget,
 
26
                                QRadioButton, QButtonGroup, QSplitter,
 
27
                                QStyleFactory, QScrollArea)
 
28
from spyderlib.qt.QtCore import Qt, QSize, SIGNAL, SLOT, Slot
 
29
from spyderlib.qt.compat import (to_qvariant, from_qvariant,
 
30
                                 getexistingdirectory, getopenfilename)
 
31
 
 
32
 
 
33
class SizeMixin(object):
 
34
    """Mixin to keep widget size accessible
 
35
    even when C++ object has been deleted"""
 
36
    def resizeEvent(self, event):
 
37
        """Reimplement Qt method"""
 
38
        QDialog.resizeEvent(self, event)
 
39
        self.emit(SIGNAL("size_change(QSize)"), self.size())
 
40
 
 
41
 
 
42
class ConfigPage(QWidget):
 
43
    """Configuration page base class"""
 
44
    def __init__(self, parent, apply_callback=None):
 
45
        QWidget.__init__(self, parent)
 
46
        self.apply_callback = apply_callback
 
47
        self.is_modified = False
 
48
        
 
49
    def initialize(self):
 
50
        """
 
51
        Initialize configuration page:
 
52
            * setup GUI widgets
 
53
            * load settings and change widgets accordingly
 
54
        """
 
55
        self.setup_page()
 
56
        self.load_from_conf()
 
57
        
 
58
    def get_name(self):
 
59
        """Return page name"""
 
60
        raise NotImplementedError
 
61
    
 
62
    def get_icon(self):
 
63
        """Return page icon"""
 
64
        raise NotImplementedError
 
65
    
 
66
    def setup_page(self):
 
67
        """Setup configuration page widget"""
 
68
        raise NotImplementedError
 
69
        
 
70
    def set_modified(self, state):
 
71
        self.is_modified = state
 
72
        self.emit(SIGNAL("apply_button_enabled(bool)"), state)
 
73
        
 
74
    def is_valid(self):
 
75
        """Return True if all widget contents are valid"""
 
76
        raise NotImplementedError
 
77
    
 
78
    def apply_changes(self):
 
79
        """Apply changes callback"""
 
80
        if self.is_modified:
 
81
            self.save_to_conf()
 
82
            if self.apply_callback is not None:
 
83
                self.apply_callback()
 
84
            self.set_modified(False)
 
85
    
 
86
    def load_from_conf(self):
 
87
        """Load settings from configuration file"""
 
88
        raise NotImplementedError
 
89
    
 
90
    def save_to_conf(self):
 
91
        """Save settings to configuration file"""
 
92
        raise NotImplementedError
 
93
 
 
94
 
 
95
class ConfigDialog(QDialog, SizeMixin):
 
96
    """Spyder configuration ('Preferences') dialog box"""
 
97
    def __init__(self, parent=None):
 
98
        QDialog.__init__(self, parent)
 
99
        SizeMixin.__init__(self)
 
100
        
 
101
        # Destroying the C++ object right after closing the dialog box,
 
102
        # otherwise it may be garbage-collected in another QThread
 
103
        # (e.g. the editor's analysis thread in Spyder), thus leading to
 
104
        # a segmentation fault on UNIX or an application crash on Windows
 
105
        self.setAttribute(Qt.WA_DeleteOnClose)
 
106
 
 
107
        self.contents_widget = QListWidget()
 
108
        self.contents_widget.setMovement(QListView.Static)
 
109
        self.contents_widget.setSpacing(1)
 
110
 
 
111
        bbox = QDialogButtonBox(QDialogButtonBox.Ok|QDialogButtonBox.Apply
 
112
                                |QDialogButtonBox.Cancel)
 
113
        self.apply_btn = bbox.button(QDialogButtonBox.Apply)
 
114
        self.connect(bbox, SIGNAL("accepted()"), SLOT("accept()"))
 
115
        self.connect(bbox, SIGNAL("rejected()"), SLOT("reject()"))
 
116
        self.connect(bbox, SIGNAL("clicked(QAbstractButton*)"),
 
117
                     self.button_clicked)
 
118
 
 
119
        self.pages_widget = QStackedWidget()
 
120
        self.connect(self.pages_widget, SIGNAL("currentChanged(int)"),
 
121
                     self.current_page_changed)
 
122
 
 
123
        self.connect(self.contents_widget, SIGNAL("currentRowChanged(int)"),
 
124
                     self.pages_widget.setCurrentIndex)
 
125
        self.contents_widget.setCurrentRow(0)
 
126
 
 
127
        hsplitter = QSplitter()
 
128
        hsplitter.addWidget(self.contents_widget)
 
129
        hsplitter.addWidget(self.pages_widget)
 
130
 
 
131
        btnlayout = QHBoxLayout()
 
132
        btnlayout.addStretch(1)
 
133
        btnlayout.addWidget(bbox)
 
134
 
 
135
        vlayout = QVBoxLayout()
 
136
        vlayout.addWidget(hsplitter)
 
137
        vlayout.addLayout(btnlayout)
 
138
 
 
139
        self.setLayout(vlayout)
 
140
 
 
141
        self.setWindowTitle(_("Preferences"))
 
142
        self.setWindowIcon(get_icon("configure.png"))
 
143
        
 
144
    def get_current_index(self):
 
145
        """Return current page index"""
 
146
        return self.contents_widget.currentRow()
 
147
        
 
148
    def set_current_index(self, index):
 
149
        """Set current page index"""
 
150
        self.contents_widget.setCurrentRow(index)
 
151
        
 
152
    def get_page(self, index=None):
 
153
        """Return page widget"""
 
154
        if index is None:
 
155
            widget = self.pages_widget.currentWidget()
 
156
        else:
 
157
            widget = self.pages_widget.widget(index)
 
158
        return widget.widget()
 
159
        
 
160
    def accept(self):
 
161
        """Reimplement Qt method"""
 
162
        for index in range(self.pages_widget.count()):
 
163
            configpage = self.get_page(index)
 
164
            if not configpage.is_valid():
 
165
                return
 
166
            configpage.apply_changes()
 
167
        QDialog.accept(self)
 
168
        
 
169
    def button_clicked(self, button):
 
170
        if button is self.apply_btn:
 
171
            # Apply button was clicked
 
172
            configpage = self.get_page()
 
173
            if not configpage.is_valid():
 
174
                return
 
175
            configpage.apply_changes()
 
176
            
 
177
    def current_page_changed(self, index):
 
178
        widget = self.get_page(index)
 
179
        self.apply_btn.setVisible(widget.apply_callback is not None)
 
180
        self.apply_btn.setEnabled(widget.is_modified)
 
181
        
 
182
    def add_page(self, widget):
 
183
        self.connect(self, SIGNAL('check_settings()'), widget.check_settings)
 
184
        self.connect(widget, SIGNAL('show_this_page()'),
 
185
                     lambda row=self.contents_widget.count():
 
186
                     self.contents_widget.setCurrentRow(row))
 
187
        self.connect(widget, SIGNAL("apply_button_enabled(bool)"),
 
188
                     self.apply_btn.setEnabled)
 
189
        scrollarea = QScrollArea(self)
 
190
        scrollarea.setWidgetResizable(True)
 
191
        scrollarea.setWidget(widget)
 
192
        self.pages_widget.addWidget(scrollarea)
 
193
        item = QListWidgetItem(self.contents_widget)
 
194
        item.setIcon(widget.get_icon())
 
195
        item.setText(widget.get_name())
 
196
        item.setFlags(Qt.ItemIsSelectable|Qt.ItemIsEnabled)
 
197
        item.setSizeHint(QSize(0, 25))
 
198
        
 
199
    def check_all_settings(self):
 
200
        """This method is called to check all configuration page settings
 
201
        after configuration dialog has been shown"""
 
202
        self.emit(SIGNAL('check_settings()'))
 
203
 
 
204
 
 
205
class SpyderConfigPage(ConfigPage):
 
206
    """Plugin configuration dialog box page widget"""
 
207
    def __init__(self, parent):
 
208
        ConfigPage.__init__(self, parent,
 
209
                            apply_callback=lambda:
 
210
                            self.apply_settings(self.changed_options))
 
211
        self.checkboxes = {}
 
212
        self.radiobuttons = {}
 
213
        self.lineedits = {}
 
214
        self.validate_data = {}
 
215
        self.spinboxes = {}
 
216
        self.comboboxes = {}
 
217
        self.fontboxes = {}
 
218
        self.coloredits = {}
 
219
        self.scedits = {}
 
220
        self.changed_options = set()
 
221
        self.default_button_group = None
 
222
        
 
223
    def apply_settings(self, options):
 
224
        raise NotImplementedError
 
225
    
 
226
    def check_settings(self):
 
227
        """This method is called to check settings after configuration 
 
228
        dialog has been shown"""
 
229
        pass
 
230
        
 
231
    def set_modified(self, state):
 
232
        ConfigPage.set_modified(self, state)
 
233
        if not state:
 
234
            self.changed_options = set()
 
235
        
 
236
    def is_valid(self):
 
237
        """Return True if all widget contents are valid"""
 
238
        for lineedit in self.lineedits:
 
239
            if lineedit in self.validate_data and lineedit.isEnabled():
 
240
                validator, invalid_msg = self.validate_data[lineedit]
 
241
                text = unicode(lineedit.text())
 
242
                if not validator(text):
 
243
                    QMessageBox.critical(self, self.get_name(),
 
244
                                     "%s:<br><b>%s</b>" % (invalid_msg, text),
 
245
                                     QMessageBox.Ok)
 
246
                    return False
 
247
        return True
 
248
        
 
249
    def load_from_conf(self):
 
250
        """Load settings from configuration file"""
 
251
        for checkbox, (option, default) in self.checkboxes.items():
 
252
            checkbox.setChecked(self.get_option(option, default))
 
253
            self.connect(checkbox, SIGNAL("clicked(bool)"),
 
254
                         lambda _foo, opt=option: self.has_been_modified(opt))
 
255
        for radiobutton, (option, default) in self.radiobuttons.items():
 
256
            radiobutton.setChecked(self.get_option(option, default))
 
257
            self.connect(radiobutton, SIGNAL("toggled(bool)"),
 
258
                         lambda _foo, opt=option: self.has_been_modified(opt))
 
259
        for lineedit, (option, default) in self.lineedits.items():
 
260
            lineedit.setText(self.get_option(option, default))
 
261
            self.connect(lineedit, SIGNAL("textChanged(QString)"),
 
262
                         lambda _foo, opt=option: self.has_been_modified(opt))
 
263
        for spinbox, (option, default) in self.spinboxes.items():
 
264
            spinbox.setValue(self.get_option(option, default))
 
265
            self.connect(spinbox, SIGNAL('valueChanged(int)'),
 
266
                         lambda _foo, opt=option: self.has_been_modified(opt))
 
267
        for combobox, (option, default) in self.comboboxes.items():
 
268
            value = self.get_option(option, default)
 
269
            for index in range(combobox.count()):
 
270
                data = from_qvariant(combobox.itemData(index), unicode)
 
271
                # For PyQt API v2, it is necessary to convert `data` to 
 
272
                # unicode in case the original type was not a string, like an 
 
273
                # integer for example (see spyderlib.qt.compat.from_qvariant):
 
274
                if unicode(data) == unicode(value):
 
275
                    break
 
276
            combobox.setCurrentIndex(index)
 
277
            self.connect(combobox, SIGNAL('currentIndexChanged(int)'),
 
278
                         lambda _foo, opt=option: self.has_been_modified(opt))
 
279
        for (fontbox, sizebox), option in self.fontboxes.items():
 
280
            font = self.get_font(option)
 
281
            fontbox.setCurrentFont(font)
 
282
            sizebox.setValue(font.pointSize())
 
283
            if option is None:
 
284
                property = 'plugin_font'
 
285
            else:
 
286
                property = option
 
287
            self.connect(fontbox, SIGNAL('currentIndexChanged(int)'),
 
288
                         lambda _foo, opt=property: self.has_been_modified(opt))
 
289
            self.connect(sizebox, SIGNAL('valueChanged(int)'),
 
290
                         lambda _foo, opt=property: self.has_been_modified(opt))
 
291
        for clayout, (option, default) in self.coloredits.items():
 
292
            property = to_qvariant(option)
 
293
            edit = clayout.lineedit
 
294
            btn = clayout.colorbtn
 
295
            edit.setText(self.get_option(option, default))
 
296
            self.connect(btn, SIGNAL('clicked()'),
 
297
                         lambda opt=option: self.has_been_modified(opt))
 
298
            self.connect(edit, SIGNAL("textChanged(QString)"),
 
299
                         lambda _foo, opt=option: self.has_been_modified(opt))
 
300
        for (clayout, cb_bold, cb_italic
 
301
             ), (option, default) in self.scedits.items():
 
302
            edit = clayout.lineedit
 
303
            btn = clayout.colorbtn
 
304
            color, bold, italic = self.get_option(option, default)
 
305
            edit.setText(color)
 
306
            cb_bold.setChecked(bold)
 
307
            cb_italic.setChecked(italic)
 
308
            self.connect(btn, SIGNAL('clicked()'),
 
309
                         lambda opt=option: self.has_been_modified(opt))
 
310
            self.connect(edit, SIGNAL("textChanged(QString)"),
 
311
                         lambda _foo, opt=option: self.has_been_modified(opt))
 
312
            self.connect(cb_bold, SIGNAL("clicked(bool)"),
 
313
                         lambda _foo, opt=option: self.has_been_modified(opt))
 
314
            self.connect(cb_italic, SIGNAL("clicked(bool)"),
 
315
                         lambda _foo, opt=option: self.has_been_modified(opt))
 
316
    
 
317
    def save_to_conf(self):
 
318
        """Save settings to configuration file"""
 
319
        for checkbox, (option, _default) in self.checkboxes.items():
 
320
            self.set_option(option, checkbox.isChecked())
 
321
        for radiobutton, (option, _default) in self.radiobuttons.items():
 
322
            self.set_option(option, radiobutton.isChecked())
 
323
        for lineedit, (option, _default) in self.lineedits.items():
 
324
            self.set_option(option, unicode(lineedit.text()))
 
325
        for spinbox, (option, _default) in self.spinboxes.items():
 
326
            self.set_option(option, spinbox.value())
 
327
        for combobox, (option, _default) in self.comboboxes.items():
 
328
            data = combobox.itemData(combobox.currentIndex())
 
329
            self.set_option(option, from_qvariant(data, unicode))
 
330
        for (fontbox, sizebox), option in self.fontboxes.items():
 
331
            font = fontbox.currentFont()
 
332
            font.setPointSize(sizebox.value())
 
333
            self.set_font(font, option)
 
334
        for clayout, (option, _default) in self.coloredits.items():
 
335
            self.set_option(option, unicode(clayout.lineedit.text()))
 
336
        for (clayout, cb_bold, cb_italic), (option, _default) in self.scedits.items():
 
337
            color = unicode(clayout.lineedit.text())
 
338
            bold = cb_bold.isChecked()
 
339
            italic = cb_italic.isChecked()
 
340
            self.set_option(option, (color, bold, italic))
 
341
    
 
342
    @Slot(str)
 
343
    def has_been_modified(self, option):
 
344
        self.set_modified(True)
 
345
        self.changed_options.add(option)
 
346
    
 
347
    def create_checkbox(self, text, option, default=NoDefault,
 
348
                        tip=None, msg_warning=None, msg_info=None,
 
349
                        msg_if_enabled=False):
 
350
        checkbox = QCheckBox(text)
 
351
        if tip is not None:
 
352
            checkbox.setToolTip(tip)
 
353
        self.checkboxes[checkbox] = (option, default)
 
354
        if msg_warning is not None or msg_info is not None:
 
355
            def show_message(is_checked):
 
356
                if is_checked or not msg_if_enabled:
 
357
                    if msg_warning is not None:
 
358
                        QMessageBox.warning(self, self.get_name(),
 
359
                                            msg_warning, QMessageBox.Ok)
 
360
                    if msg_info is not None:
 
361
                        QMessageBox.information(self, self.get_name(),
 
362
                                                msg_info, QMessageBox.Ok)
 
363
            self.connect(checkbox, SIGNAL("clicked(bool)"), show_message)
 
364
        return checkbox
 
365
    
 
366
    def create_radiobutton(self, text, option, default=NoDefault,
 
367
                           tip=None, msg_warning=None, msg_info=None,
 
368
                           msg_if_enabled=False, button_group=None):
 
369
        radiobutton = QRadioButton(text)
 
370
        if button_group is None:
 
371
            if self.default_button_group is None:
 
372
                self.default_button_group = QButtonGroup(self)
 
373
            button_group = self.default_button_group
 
374
        button_group.addButton(radiobutton)
 
375
        if tip is not None:
 
376
            radiobutton.setToolTip(tip)
 
377
        self.radiobuttons[radiobutton] = (option, default)
 
378
        if msg_warning is not None or msg_info is not None:
 
379
            def show_message(is_checked):
 
380
                if is_checked or not msg_if_enabled:
 
381
                    if msg_warning is not None:
 
382
                        QMessageBox.warning(self, self.get_name(),
 
383
                                            msg_warning, QMessageBox.Ok)
 
384
                    if msg_info is not None:
 
385
                        QMessageBox.information(self, self.get_name(),
 
386
                                                msg_info, QMessageBox.Ok)
 
387
            self.connect(radiobutton, SIGNAL("toggled(bool)"), show_message)
 
388
        return radiobutton
 
389
    
 
390
    def create_lineedit(self, text, option, default=NoDefault,
 
391
                        tip=None, alignment=Qt.Vertical):
 
392
        label = QLabel(text)
 
393
        label.setWordWrap(True)
 
394
        edit = QLineEdit()
 
395
        layout = QVBoxLayout() if alignment == Qt.Vertical else QHBoxLayout()
 
396
        layout.addWidget(label)
 
397
        layout.addWidget(edit)
 
398
        layout.setContentsMargins(0, 0, 0, 0)
 
399
        if tip:
 
400
            edit.setToolTip(tip)
 
401
        self.lineedits[edit] = (option, default)
 
402
        widget = QWidget(self)
 
403
        widget.setLayout(layout)
 
404
        return widget
 
405
    
 
406
    def create_browsedir(self, text, option, default=NoDefault, tip=None):
 
407
        widget = self.create_lineedit(text, option, default,
 
408
                                      alignment=Qt.Horizontal)
 
409
        for edit in self.lineedits:
 
410
            if widget.isAncestorOf(edit):
 
411
                break
 
412
        msg = _("Invalid directory path")
 
413
        self.validate_data[edit] = (osp.isdir, msg)
 
414
        browse_btn = QPushButton(get_std_icon('DirOpenIcon'), "", self)
 
415
        browse_btn.setToolTip(_("Select directory"))
 
416
        self.connect(browse_btn, SIGNAL("clicked()"),
 
417
                     lambda: self.select_directory(edit))
 
418
        layout = QHBoxLayout()
 
419
        layout.addWidget(widget)
 
420
        layout.addWidget(browse_btn)
 
421
        layout.setContentsMargins(0, 0, 0, 0)
 
422
        browsedir = QWidget(self)
 
423
        browsedir.setLayout(layout)
 
424
        return browsedir
 
425
 
 
426
    def select_directory(self, edit):
 
427
        """Select directory"""
 
428
        basedir = unicode(edit.text())
 
429
        if not osp.isdir(basedir):
 
430
            basedir = os.getcwdu()
 
431
        title = _("Select directory")
 
432
        directory = getexistingdirectory(self, title, basedir)
 
433
        if directory:
 
434
            edit.setText(directory)
 
435
    
 
436
    def create_browsefile(self, text, option, default=NoDefault, tip=None,
 
437
                          filters=None):
 
438
        widget = self.create_lineedit(text, option, default,
 
439
                                      alignment=Qt.Horizontal)
 
440
        for edit in self.lineedits:
 
441
            if widget.isAncestorOf(edit):
 
442
                break
 
443
        msg = _("Invalid file path")
 
444
        self.validate_data[edit] = (osp.isfile, msg)
 
445
        browse_btn = QPushButton(get_std_icon('FileIcon'), "", self)
 
446
        browse_btn.setToolTip(_("Select file"))
 
447
        self.connect(browse_btn, SIGNAL("clicked()"),
 
448
                     lambda: self.select_file(edit, filters))
 
449
        layout = QHBoxLayout()
 
450
        layout.addWidget(widget)
 
451
        layout.addWidget(browse_btn)
 
452
        layout.setContentsMargins(0, 0, 0, 0)
 
453
        browsedir = QWidget(self)
 
454
        browsedir.setLayout(layout)
 
455
        return browsedir
 
456
 
 
457
    def select_file(self, edit, filters=None):
 
458
        """Select File"""
 
459
        basedir = osp.dirname(unicode(edit.text()))
 
460
        if not osp.isdir(basedir):
 
461
            basedir = os.getcwdu()
 
462
        if filters is None:
 
463
            filters = _("All files (*)")
 
464
        title = _("Select file")
 
465
        filename, _selfilter = getopenfilename(self, title, basedir, filters)
 
466
        if filename:
 
467
            edit.setText(filename)
 
468
    
 
469
    def create_spinbox(self, prefix, suffix, option, default=NoDefault,
 
470
                       min_=None, max_=None, step=None, tip=None):
 
471
        if prefix:
 
472
            plabel = QLabel(prefix)
 
473
        else:
 
474
            plabel = None
 
475
        if suffix:
 
476
            slabel = QLabel(suffix)
 
477
        else:
 
478
            slabel = None
 
479
        spinbox = QSpinBox()
 
480
        if min_ is not None:
 
481
            spinbox.setMinimum(min_)
 
482
        if max_ is not None:
 
483
            spinbox.setMaximum(max_)
 
484
        if step is not None:
 
485
            spinbox.setSingleStep(step)
 
486
        if tip is not None:
 
487
            spinbox.setToolTip(tip)
 
488
        self.spinboxes[spinbox] = (option, default)
 
489
        layout = QHBoxLayout()
 
490
        for subwidget in (plabel, spinbox, slabel):
 
491
            if subwidget is not None:
 
492
                layout.addWidget(subwidget)
 
493
        layout.addStretch(1)
 
494
        layout.setContentsMargins(0, 0, 0, 0)
 
495
        widget = QWidget(self)
 
496
        widget.setLayout(layout)
 
497
        return widget
 
498
    
 
499
    def create_coloredit(self, text, option, default=NoDefault, tip=None,
 
500
                         without_layout=False):
 
501
        label = QLabel(text)
 
502
        clayout = ColorLayout(QColor(Qt.black), self)
 
503
        clayout.lineedit.setMaximumWidth(80)
 
504
        if tip is not None:
 
505
            clayout.setToolTip(tip)
 
506
        self.coloredits[clayout] = (option, default)
 
507
        if without_layout:
 
508
            return label, clayout
 
509
        layout = QHBoxLayout()
 
510
        layout.addWidget(label)
 
511
        layout.addLayout(clayout)
 
512
        layout.addStretch(1)
 
513
        layout.setContentsMargins(0, 0, 0, 0)
 
514
        widget = QWidget(self)
 
515
        widget.setLayout(layout)
 
516
        return widget
 
517
    
 
518
    def create_scedit(self, text, option, default=NoDefault, tip=None,
 
519
                      without_layout=False):
 
520
        label = QLabel(text)
 
521
        clayout = ColorLayout(QColor(Qt.black), self)
 
522
        clayout.lineedit.setMaximumWidth(80)
 
523
        if tip is not None:
 
524
            clayout.setToolTip(tip)
 
525
        cb_bold = QCheckBox()
 
526
        cb_bold.setIcon(get_icon("bold.png"))
 
527
        cb_bold.setToolTip(_("Bold"))
 
528
        cb_italic = QCheckBox()
 
529
        cb_italic.setIcon(get_icon("italic.png"))
 
530
        cb_italic.setToolTip(_("Italic"))
 
531
        self.scedits[(clayout, cb_bold, cb_italic)] = (option, default)
 
532
        if without_layout:
 
533
            return label, clayout, cb_bold, cb_italic
 
534
        layout = QHBoxLayout()
 
535
        layout.addWidget(label)
 
536
        layout.addLayout(clayout)
 
537
        layout.addSpacing(10)
 
538
        layout.addWidget(cb_bold)
 
539
        layout.addWidget(cb_italic)
 
540
        layout.addStretch(1)
 
541
        layout.setContentsMargins(0, 0, 0, 0)
 
542
        widget = QWidget(self)
 
543
        widget.setLayout(layout)
 
544
        return widget
 
545
    
 
546
    def create_combobox(self, text, choices, option, default=NoDefault,
 
547
                        tip=None):
 
548
        """choices: couples (name, key)"""
 
549
        label = QLabel(text)
 
550
        combobox = QComboBox()
 
551
        if tip is not None:
 
552
            combobox.setToolTip(tip)
 
553
        for name, key in choices:
 
554
            combobox.addItem(name, to_qvariant(key))
 
555
        self.comboboxes[combobox] = (option, default)
 
556
        layout = QHBoxLayout()
 
557
        for subwidget in (label, combobox):
 
558
            layout.addWidget(subwidget)
 
559
        layout.addStretch(1)
 
560
        layout.setContentsMargins(0, 0, 0, 0)
 
561
        widget = QWidget(self)
 
562
        widget.setLayout(layout)
 
563
        return widget
 
564
    
 
565
    def create_fontgroup(self, option=None, text=None,
 
566
                         tip=None, fontfilters=None):
 
567
        """Option=None -> setting plugin font"""
 
568
        fontlabel = QLabel(_("Font: "))
 
569
        fontbox = QFontComboBox()
 
570
        if fontfilters is not None:
 
571
            fontbox.setFontFilters(fontfilters)
 
572
        sizelabel = QLabel("  "+_("Size: "))
 
573
        sizebox = QSpinBox()
 
574
        sizebox.setRange(7, 100)
 
575
        self.fontboxes[(fontbox, sizebox)] = option
 
576
        layout = QHBoxLayout()
 
577
        for subwidget in (fontlabel, fontbox, sizelabel, sizebox):
 
578
            layout.addWidget(subwidget)
 
579
        layout.addStretch(1)
 
580
        if text is None:
 
581
            text = _("Font style")
 
582
        group = QGroupBox(text)
 
583
        group.setLayout(layout)
 
584
        if tip is not None:
 
585
            group.setToolTip(tip)
 
586
        return group
 
587
    
 
588
    def create_button(self, text, callback):
 
589
        btn = QPushButton(text)
 
590
        self.connect(btn, SIGNAL('clicked()'), callback)
 
591
        self.connect(btn, SIGNAL('clicked()'),
 
592
                     lambda opt='': self.has_been_modified(opt))
 
593
        return btn
 
594
    
 
595
    def create_tab(self, *widgets):
 
596
        """Create simple tab widget page: widgets added in a vertical layout"""
 
597
        widget = QWidget()
 
598
        layout = QVBoxLayout()
 
599
        for widg in widgets:
 
600
            layout.addWidget(widg)
 
601
        layout.addStretch(1)
 
602
        widget.setLayout(layout)
 
603
        return widget
 
604
 
 
605
 
 
606
class GeneralConfigPage(SpyderConfigPage):
 
607
    CONF_SECTION = None
 
608
    def __init__(self, parent, main):
 
609
        SpyderConfigPage.__init__(self, parent)
 
610
        self.main = main
 
611
 
 
612
    def set_option(self, option, value):
 
613
        CONF.set(self.CONF_SECTION, option, value)
 
614
 
 
615
    def get_option(self, option, default=NoDefault):
 
616
        return CONF.get(self.CONF_SECTION, option, default)
 
617
            
 
618
    def apply_settings(self, options):
 
619
        raise NotImplementedError
 
620
 
 
621
 
 
622
class MainConfigPage(GeneralConfigPage):
 
623
    CONF_SECTION = "main"
 
624
    def get_name(self):
 
625
        return _("General")
 
626
    
 
627
    def get_icon(self):
 
628
        return get_icon("genprefs.png")
 
629
    
 
630
    def setup_page(self):
 
631
        interface_group = QGroupBox(_("Interface"))
 
632
        styles = [str(txt) for txt in QStyleFactory.keys()]
 
633
        choices = zip(styles, [style.lower() for style in styles])
 
634
        style_combo = self.create_combobox(_('Qt windows style'), choices,
 
635
                                           'windows_style',
 
636
                                           default=self.main.default_style)
 
637
        newcb = self.create_checkbox
 
638
        vertdock_box = newcb(_("Vertical dockwidget title bars"),
 
639
                             'vertical_dockwidget_titlebars')
 
640
        verttabs_box = newcb(_("Vertical dockwidget tabs"),
 
641
                             'vertical_tabs')
 
642
        animated_box = newcb(_("Animated toolbars and dockwidgets"),
 
643
                             'animated_docks')
 
644
        margin_box = newcb(_("Custom dockwidget margin:"),
 
645
                           'use_custom_margin')
 
646
        margin_spin = self.create_spinbox("", "pixels", 'custom_margin',
 
647
                                          0, 0, 30)
 
648
        self.connect(margin_box, SIGNAL("toggled(bool)"),
 
649
                     margin_spin.setEnabled)
 
650
        margin_spin.setEnabled(self.get_option('use_custom_margin'))
 
651
        margins_layout = QHBoxLayout()
 
652
        margins_layout.addWidget(margin_box)
 
653
        margins_layout.addWidget(margin_spin)
 
654
        
 
655
        single_instance_box = newcb(_("Use a single instance"),
 
656
                                    'single_instance',
 
657
                                    tip=_("Set this to open external<br> "
 
658
                                          "python, .spy and .mat files in an "
 
659
                                          "already running instance (Requires "
 
660
                                          "a restart)"))
 
661
        
 
662
        interface_layout = QVBoxLayout()
 
663
        interface_layout.addWidget(style_combo)
 
664
        interface_layout.addWidget(single_instance_box)
 
665
        interface_layout.addWidget(vertdock_box)
 
666
        interface_layout.addWidget(verttabs_box)
 
667
        interface_layout.addWidget(animated_box)
 
668
        interface_layout.addLayout(margins_layout)
 
669
        interface_group.setLayout(interface_layout)
 
670
 
 
671
        sbar_group = QGroupBox(_("Status bar"))
 
672
        memory_box = newcb(_("Show memory usage every"), 'memory_usage/enable',
 
673
                           tip=self.main.mem_status.toolTip())
 
674
        memory_spin = self.create_spinbox("", " ms", 'memory_usage/timeout',
 
675
                                          min_=100, max_=1000000, step=100)
 
676
        self.connect(memory_box, SIGNAL("toggled(bool)"),
 
677
                     memory_spin.setEnabled)
 
678
        memory_spin.setEnabled(self.get_option('memory_usage/enable'))
 
679
        memory_layout = QHBoxLayout()
 
680
        memory_layout.addWidget(memory_box)
 
681
        memory_layout.addWidget(memory_spin)
 
682
        memory_layout.setEnabled(self.main.mem_status.is_supported())
 
683
        cpu_box = newcb(_("Show CPU usage every"), 'cpu_usage/enable',
 
684
                        tip=self.main.cpu_status.toolTip())
 
685
        cpu_spin = self.create_spinbox("", " ms", 'cpu_usage/timeout',
 
686
                                       min_=100, max_=1000000, step=100)
 
687
        self.connect(cpu_box, SIGNAL("toggled(bool)"), cpu_spin.setEnabled)
 
688
        cpu_spin.setEnabled(self.get_option('cpu_usage/enable'))
 
689
        cpu_layout = QHBoxLayout()
 
690
        cpu_layout.addWidget(cpu_box)
 
691
        cpu_layout.addWidget(cpu_spin)
 
692
        cpu_layout.setEnabled(self.main.cpu_status.is_supported())
 
693
        
 
694
        sbar_layout = QVBoxLayout()
 
695
        sbar_layout.addLayout(memory_layout)
 
696
        sbar_layout.addLayout(cpu_layout)
 
697
        sbar_group.setLayout(sbar_layout)
 
698
        
 
699
        debug_group = QGroupBox(_("Debugging"))
 
700
        popup_console_box = newcb(_("Pop up internal console when errors "
 
701
                                    "were intercepted"),
 
702
                                  'show_internal_console_if_traceback',
 
703
                                  default=True)
 
704
        
 
705
        debug_layout = QVBoxLayout()
 
706
        debug_layout.addWidget(popup_console_box)
 
707
        debug_group.setLayout(debug_layout)
 
708
        
 
709
        vlayout = QVBoxLayout()
 
710
        vlayout.addWidget(interface_group)
 
711
        vlayout.addWidget(sbar_group)
 
712
        vlayout.addWidget(debug_group)
 
713
        vlayout.addStretch(1)
 
714
        self.setLayout(vlayout)
 
715
        
 
716
    def apply_settings(self, options):
 
717
        self.main.apply_settings()
 
718
 
 
719
 
 
720
class ColorSchemeConfigPage(GeneralConfigPage):
 
721
    CONF_SECTION = "color_schemes"
 
722
    def get_name(self):
 
723
        return _("Syntax coloring")
 
724
    
 
725
    def get_icon(self):
 
726
        return get_icon("genprefs.png")
 
727
    
 
728
    def setup_page(self):
 
729
        tabs = QTabWidget()
 
730
        names = self.get_option("names")
 
731
        names.pop(names.index(CUSTOM_COLOR_SCHEME_NAME))
 
732
        names.insert(0, CUSTOM_COLOR_SCHEME_NAME)
 
733
        fieldnames = {
 
734
                      "background":     _("Background:"),
 
735
                      "currentline":    _("Current line:"),
 
736
                      "occurence":      _("Occurence:"),
 
737
                      "ctrlclick":      _("Link:"),
 
738
                      "sideareas":      _("Side areas:"),
 
739
                      "matched_p":      _("Matched parentheses:"),
 
740
                      "unmatched_p":    _("Unmatched parentheses:"),
 
741
                      "normal":         _("Normal text:"),
 
742
                      "keyword":        _("Keyword:"),
 
743
                      "builtin":        _("Builtin:"),
 
744
                      "definition":     _("Definition:"),
 
745
                      "comment":        _("Comment:"),
 
746
                      "string":         _("String:"),
 
747
                      "number":         _("Number:"),
 
748
                      "instance":       _("Instance:"),
 
749
                      }
 
750
        from spyderlib.widgets.sourcecode import syntaxhighlighters
 
751
        assert all([key in fieldnames
 
752
                    for key in syntaxhighlighters.COLOR_SCHEME_KEYS])
 
753
        for tabname in names:
 
754
            cs_group = QGroupBox(_("Color scheme"))
 
755
            cs_layout = QGridLayout()
 
756
            for row, key in enumerate(syntaxhighlighters.COLOR_SCHEME_KEYS):
 
757
                option = "%s/%s" % (tabname, key)
 
758
                value = self.get_option(option)
 
759
                name = fieldnames[key]
 
760
                if isinstance(value, basestring):
 
761
                    label, clayout = self.create_coloredit(name, option,
 
762
                                                           without_layout=True)
 
763
                    label.setAlignment(Qt.AlignRight|Qt.AlignVCenter)
 
764
                    cs_layout.addWidget(label, row+1, 0)
 
765
                    cs_layout.addLayout(clayout, row+1, 1)
 
766
                else:
 
767
                    label, clayout, cb_bold, cb_italic = self.create_scedit(
 
768
                                            name, option, without_layout=True)
 
769
                    label.setAlignment(Qt.AlignRight|Qt.AlignVCenter)
 
770
                    cs_layout.addWidget(label, row+1, 0)
 
771
                    cs_layout.addLayout(clayout, row+1, 1)
 
772
                    cs_layout.addWidget(cb_bold, row+1, 2)
 
773
                    cs_layout.addWidget(cb_italic, row+1, 3)
 
774
            cs_group.setLayout(cs_layout)
 
775
            if tabname in COLOR_SCHEME_NAMES:
 
776
                def_btn = self.create_button(_("Reset to default values"),
 
777
                                         lambda: self.reset_to_default(tabname))
 
778
                tabs.addTab(self.create_tab(cs_group, def_btn), tabname)
 
779
            else:
 
780
                tabs.addTab(self.create_tab(cs_group), tabname)
 
781
        
 
782
        vlayout = QVBoxLayout()
 
783
        vlayout.addWidget(tabs)
 
784
        self.setLayout(vlayout)
 
785
        
 
786
    @Slot(str)
 
787
    def reset_to_default(self, name):
 
788
        set_default_color_scheme(name, replace=True)
 
789
        self.load_from_conf()
 
790
            
 
791
    def apply_settings(self, options):
 
792
        self.main.editor.apply_plugin_settings(['color_scheme_name'])
 
793
        if self.main.historylog is not None:
 
794
            self.main.historylog.apply_plugin_settings(['color_scheme_name'])
 
795
        if self.main.inspector is not None:
 
796
            self.main.inspector.apply_plugin_settings(['color_scheme_name'])