1
# -*- coding: utf-8 -*-
3
# Copyright © 2009-2010 Pierre Raybaut
4
# Licensed under the terms of the MIT License
5
# (see spyderlib/__init__.py for details)
7
"""Configuration dialog / Preferences"""
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
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)
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())
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
51
Initialize configuration page:
53
* load settings and change widgets accordingly
59
"""Return page name"""
60
raise NotImplementedError
63
"""Return page icon"""
64
raise NotImplementedError
67
"""Setup configuration page widget"""
68
raise NotImplementedError
70
def set_modified(self, state):
71
self.is_modified = state
72
self.emit(SIGNAL("apply_button_enabled(bool)"), state)
75
"""Return True if all widget contents are valid"""
76
raise NotImplementedError
78
def apply_changes(self):
79
"""Apply changes callback"""
82
if self.apply_callback is not None:
84
self.set_modified(False)
86
def load_from_conf(self):
87
"""Load settings from configuration file"""
88
raise NotImplementedError
90
def save_to_conf(self):
91
"""Save settings to configuration file"""
92
raise NotImplementedError
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)
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)
107
self.contents_widget = QListWidget()
108
self.contents_widget.setMovement(QListView.Static)
109
self.contents_widget.setSpacing(1)
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*)"),
119
self.pages_widget = QStackedWidget()
120
self.connect(self.pages_widget, SIGNAL("currentChanged(int)"),
121
self.current_page_changed)
123
self.connect(self.contents_widget, SIGNAL("currentRowChanged(int)"),
124
self.pages_widget.setCurrentIndex)
125
self.contents_widget.setCurrentRow(0)
127
hsplitter = QSplitter()
128
hsplitter.addWidget(self.contents_widget)
129
hsplitter.addWidget(self.pages_widget)
131
btnlayout = QHBoxLayout()
132
btnlayout.addStretch(1)
133
btnlayout.addWidget(bbox)
135
vlayout = QVBoxLayout()
136
vlayout.addWidget(hsplitter)
137
vlayout.addLayout(btnlayout)
139
self.setLayout(vlayout)
141
self.setWindowTitle(_("Preferences"))
142
self.setWindowIcon(get_icon("configure.png"))
144
def get_current_index(self):
145
"""Return current page index"""
146
return self.contents_widget.currentRow()
148
def set_current_index(self, index):
149
"""Set current page index"""
150
self.contents_widget.setCurrentRow(index)
152
def get_page(self, index=None):
153
"""Return page widget"""
155
widget = self.pages_widget.currentWidget()
157
widget = self.pages_widget.widget(index)
158
return widget.widget()
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():
166
configpage.apply_changes()
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():
175
configpage.apply_changes()
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)
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))
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()'))
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))
212
self.radiobuttons = {}
214
self.validate_data = {}
220
self.changed_options = set()
221
self.default_button_group = None
223
def apply_settings(self, options):
224
raise NotImplementedError
226
def check_settings(self):
227
"""This method is called to check settings after configuration
228
dialog has been shown"""
231
def set_modified(self, state):
232
ConfigPage.set_modified(self, state)
234
self.changed_options = set()
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),
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):
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())
284
property = 'plugin_font'
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)
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))
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))
343
def has_been_modified(self, option):
344
self.set_modified(True)
345
self.changed_options.add(option)
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)
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)
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)
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)
390
def create_lineedit(self, text, option, default=NoDefault,
391
tip=None, alignment=Qt.Vertical):
393
label.setWordWrap(True)
395
layout = QVBoxLayout() if alignment == Qt.Vertical else QHBoxLayout()
396
layout.addWidget(label)
397
layout.addWidget(edit)
398
layout.setContentsMargins(0, 0, 0, 0)
401
self.lineedits[edit] = (option, default)
402
widget = QWidget(self)
403
widget.setLayout(layout)
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):
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)
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)
434
edit.setText(directory)
436
def create_browsefile(self, text, option, default=NoDefault, tip=None,
438
widget = self.create_lineedit(text, option, default,
439
alignment=Qt.Horizontal)
440
for edit in self.lineedits:
441
if widget.isAncestorOf(edit):
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)
457
def select_file(self, edit, filters=None):
459
basedir = osp.dirname(unicode(edit.text()))
460
if not osp.isdir(basedir):
461
basedir = os.getcwdu()
463
filters = _("All files (*)")
464
title = _("Select file")
465
filename, _selfilter = getopenfilename(self, title, basedir, filters)
467
edit.setText(filename)
469
def create_spinbox(self, prefix, suffix, option, default=NoDefault,
470
min_=None, max_=None, step=None, tip=None):
472
plabel = QLabel(prefix)
476
slabel = QLabel(suffix)
481
spinbox.setMinimum(min_)
483
spinbox.setMaximum(max_)
485
spinbox.setSingleStep(step)
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)
494
layout.setContentsMargins(0, 0, 0, 0)
495
widget = QWidget(self)
496
widget.setLayout(layout)
499
def create_coloredit(self, text, option, default=NoDefault, tip=None,
500
without_layout=False):
502
clayout = ColorLayout(QColor(Qt.black), self)
503
clayout.lineedit.setMaximumWidth(80)
505
clayout.setToolTip(tip)
506
self.coloredits[clayout] = (option, default)
508
return label, clayout
509
layout = QHBoxLayout()
510
layout.addWidget(label)
511
layout.addLayout(clayout)
513
layout.setContentsMargins(0, 0, 0, 0)
514
widget = QWidget(self)
515
widget.setLayout(layout)
518
def create_scedit(self, text, option, default=NoDefault, tip=None,
519
without_layout=False):
521
clayout = ColorLayout(QColor(Qt.black), self)
522
clayout.lineedit.setMaximumWidth(80)
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)
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)
541
layout.setContentsMargins(0, 0, 0, 0)
542
widget = QWidget(self)
543
widget.setLayout(layout)
546
def create_combobox(self, text, choices, option, default=NoDefault,
548
"""choices: couples (name, key)"""
550
combobox = QComboBox()
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)
560
layout.setContentsMargins(0, 0, 0, 0)
561
widget = QWidget(self)
562
widget.setLayout(layout)
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: "))
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)
581
text = _("Font style")
582
group = QGroupBox(text)
583
group.setLayout(layout)
585
group.setToolTip(tip)
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))
595
def create_tab(self, *widgets):
596
"""Create simple tab widget page: widgets added in a vertical layout"""
598
layout = QVBoxLayout()
600
layout.addWidget(widg)
602
widget.setLayout(layout)
606
class GeneralConfigPage(SpyderConfigPage):
608
def __init__(self, parent, main):
609
SpyderConfigPage.__init__(self, parent)
612
def set_option(self, option, value):
613
CONF.set(self.CONF_SECTION, option, value)
615
def get_option(self, option, default=NoDefault):
616
return CONF.get(self.CONF_SECTION, option, default)
618
def apply_settings(self, options):
619
raise NotImplementedError
622
class MainConfigPage(GeneralConfigPage):
623
CONF_SECTION = "main"
628
return get_icon("genprefs.png")
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,
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"),
642
animated_box = newcb(_("Animated toolbars and dockwidgets"),
644
margin_box = newcb(_("Custom dockwidget margin:"),
646
margin_spin = self.create_spinbox("", "pixels", 'custom_margin',
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)
655
single_instance_box = newcb(_("Use a single instance"),
657
tip=_("Set this to open external<br> "
658
"python, .spy and .mat files in an "
659
"already running instance (Requires "
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)
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())
694
sbar_layout = QVBoxLayout()
695
sbar_layout.addLayout(memory_layout)
696
sbar_layout.addLayout(cpu_layout)
697
sbar_group.setLayout(sbar_layout)
699
debug_group = QGroupBox(_("Debugging"))
700
popup_console_box = newcb(_("Pop up internal console when errors "
702
'show_internal_console_if_traceback',
705
debug_layout = QVBoxLayout()
706
debug_layout.addWidget(popup_console_box)
707
debug_group.setLayout(debug_layout)
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)
716
def apply_settings(self, options):
717
self.main.apply_settings()
720
class ColorSchemeConfigPage(GeneralConfigPage):
721
CONF_SECTION = "color_schemes"
723
return _("Syntax coloring")
726
return get_icon("genprefs.png")
728
def setup_page(self):
730
names = self.get_option("names")
731
names.pop(names.index(CUSTOM_COLOR_SCHEME_NAME))
732
names.insert(0, CUSTOM_COLOR_SCHEME_NAME)
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:"),
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,
763
label.setAlignment(Qt.AlignRight|Qt.AlignVCenter)
764
cs_layout.addWidget(label, row+1, 0)
765
cs_layout.addLayout(clayout, row+1, 1)
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)
780
tabs.addTab(self.create_tab(cs_group), tabname)
782
vlayout = QVBoxLayout()
783
vlayout.addWidget(tabs)
784
self.setLayout(vlayout)
787
def reset_to_default(self, name):
788
set_default_color_scheme(name, replace=True)
789
self.load_from_conf()
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 -*-
3
# Copyright © 2009-2010 Pierre Raybaut
4
# Licensed under the terms of the MIT License
5
# (see spyderlib/__init__.py for details)
7
"""Configuration dialog / Preferences"""
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
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)
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())
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
51
Initialize configuration page:
53
* load settings and change widgets accordingly
59
"""Return page name"""
60
raise NotImplementedError
63
"""Return page icon"""
64
raise NotImplementedError
67
"""Setup configuration page widget"""
68
raise NotImplementedError
70
def set_modified(self, state):
71
self.is_modified = state
72
self.emit(SIGNAL("apply_button_enabled(bool)"), state)
75
"""Return True if all widget contents are valid"""
76
raise NotImplementedError
78
def apply_changes(self):
79
"""Apply changes callback"""
82
if self.apply_callback is not None:
84
self.set_modified(False)
86
def load_from_conf(self):
87
"""Load settings from configuration file"""
88
raise NotImplementedError
90
def save_to_conf(self):
91
"""Save settings to configuration file"""
92
raise NotImplementedError
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)
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)
107
self.contents_widget = QListWidget()
108
self.contents_widget.setMovement(QListView.Static)
109
self.contents_widget.setSpacing(1)
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*)"),
119
self.pages_widget = QStackedWidget()
120
self.connect(self.pages_widget, SIGNAL("currentChanged(int)"),
121
self.current_page_changed)
123
self.connect(self.contents_widget, SIGNAL("currentRowChanged(int)"),
124
self.pages_widget.setCurrentIndex)
125
self.contents_widget.setCurrentRow(0)
127
hsplitter = QSplitter()
128
hsplitter.addWidget(self.contents_widget)
129
hsplitter.addWidget(self.pages_widget)
131
btnlayout = QHBoxLayout()
132
btnlayout.addStretch(1)
133
btnlayout.addWidget(bbox)
135
vlayout = QVBoxLayout()
136
vlayout.addWidget(hsplitter)
137
vlayout.addLayout(btnlayout)
139
self.setLayout(vlayout)
141
self.setWindowTitle(_("Preferences"))
142
self.setWindowIcon(get_icon("configure.png"))
144
def get_current_index(self):
145
"""Return current page index"""
146
return self.contents_widget.currentRow()
148
def set_current_index(self, index):
149
"""Set current page index"""
150
self.contents_widget.setCurrentRow(index)
152
def get_page(self, index=None):
153
"""Return page widget"""
155
widget = self.pages_widget.currentWidget()
157
widget = self.pages_widget.widget(index)
158
return widget.widget()
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():
166
configpage.apply_changes()
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():
175
configpage.apply_changes()
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)
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))
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()'))
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))
212
self.radiobuttons = {}
214
self.validate_data = {}
220
self.changed_options = set()
221
self.default_button_group = None
223
def apply_settings(self, options):
224
raise NotImplementedError
226
def check_settings(self):
227
"""This method is called to check settings after configuration
228
dialog has been shown"""
231
def set_modified(self, state):
232
ConfigPage.set_modified(self, state)
234
self.changed_options = set()
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),
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):
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())
284
property = 'plugin_font'
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)
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))
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))
343
def has_been_modified(self, option):
344
self.set_modified(True)
345
self.changed_options.add(option)
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)
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)
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)
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)
390
def create_lineedit(self, text, option, default=NoDefault,
391
tip=None, alignment=Qt.Vertical):
393
label.setWordWrap(True)
395
layout = QVBoxLayout() if alignment == Qt.Vertical else QHBoxLayout()
396
layout.addWidget(label)
397
layout.addWidget(edit)
398
layout.setContentsMargins(0, 0, 0, 0)
401
self.lineedits[edit] = (option, default)
402
widget = QWidget(self)
403
widget.setLayout(layout)
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):
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)
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)
434
edit.setText(directory)
436
def create_browsefile(self, text, option, default=NoDefault, tip=None,
438
widget = self.create_lineedit(text, option, default,
439
alignment=Qt.Horizontal)
440
for edit in self.lineedits:
441
if widget.isAncestorOf(edit):
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)
457
def select_file(self, edit, filters=None):
459
basedir = osp.dirname(unicode(edit.text()))
460
if not osp.isdir(basedir):
461
basedir = os.getcwdu()
463
filters = _("All files (*)")
464
title = _("Select file")
465
filename, _selfilter = getopenfilename(self, title, basedir, filters)
467
edit.setText(filename)
469
def create_spinbox(self, prefix, suffix, option, default=NoDefault,
470
min_=None, max_=None, step=None, tip=None):
472
plabel = QLabel(prefix)
476
slabel = QLabel(suffix)
481
spinbox.setMinimum(min_)
483
spinbox.setMaximum(max_)
485
spinbox.setSingleStep(step)
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)
494
layout.setContentsMargins(0, 0, 0, 0)
495
widget = QWidget(self)
496
widget.setLayout(layout)
499
def create_coloredit(self, text, option, default=NoDefault, tip=None,
500
without_layout=False):
502
clayout = ColorLayout(QColor(Qt.black), self)
503
clayout.lineedit.setMaximumWidth(80)
505
clayout.setToolTip(tip)
506
self.coloredits[clayout] = (option, default)
508
return label, clayout
509
layout = QHBoxLayout()
510
layout.addWidget(label)
511
layout.addLayout(clayout)
513
layout.setContentsMargins(0, 0, 0, 0)
514
widget = QWidget(self)
515
widget.setLayout(layout)
518
def create_scedit(self, text, option, default=NoDefault, tip=None,
519
without_layout=False):
521
clayout = ColorLayout(QColor(Qt.black), self)
522
clayout.lineedit.setMaximumWidth(80)
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)
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)
541
layout.setContentsMargins(0, 0, 0, 0)
542
widget = QWidget(self)
543
widget.setLayout(layout)
546
def create_combobox(self, text, choices, option, default=NoDefault,
548
"""choices: couples (name, key)"""
550
combobox = QComboBox()
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)
560
layout.setContentsMargins(0, 0, 0, 0)
561
widget = QWidget(self)
562
widget.setLayout(layout)
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: "))
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)
581
text = _("Font style")
582
group = QGroupBox(text)
583
group.setLayout(layout)
585
group.setToolTip(tip)
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))
595
def create_tab(self, *widgets):
596
"""Create simple tab widget page: widgets added in a vertical layout"""
598
layout = QVBoxLayout()
600
layout.addWidget(widg)
602
widget.setLayout(layout)
606
class GeneralConfigPage(SpyderConfigPage):
608
def __init__(self, parent, main):
609
SpyderConfigPage.__init__(self, parent)
612
def set_option(self, option, value):
613
CONF.set(self.CONF_SECTION, option, value)
615
def get_option(self, option, default=NoDefault):
616
return CONF.get(self.CONF_SECTION, option, default)
618
def apply_settings(self, options):
619
raise NotImplementedError
622
class MainConfigPage(GeneralConfigPage):
623
CONF_SECTION = "main"
628
return get_icon("genprefs.png")
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,
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"),
642
animated_box = newcb(_("Animated toolbars and dockwidgets"),
644
margin_box = newcb(_("Custom dockwidget margin:"),
646
margin_spin = self.create_spinbox("", "pixels", 'custom_margin',
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)
655
single_instance_box = newcb(_("Use a single instance"),
657
tip=_("Set this to open external<br> "
658
"python, .spy and .mat files in an "
659
"already running instance (Requires "
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)
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())
694
sbar_layout = QVBoxLayout()
695
sbar_layout.addLayout(memory_layout)
696
sbar_layout.addLayout(cpu_layout)
697
sbar_group.setLayout(sbar_layout)
699
debug_group = QGroupBox(_("Debugging"))
700
popup_console_box = newcb(_("Pop up internal console when errors "
702
'show_internal_console_if_traceback',
705
debug_layout = QVBoxLayout()
706
debug_layout.addWidget(popup_console_box)
707
debug_group.setLayout(debug_layout)
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)
716
def apply_settings(self, options):
717
self.main.apply_settings()
720
class ColorSchemeConfigPage(GeneralConfigPage):
721
CONF_SECTION = "color_schemes"
723
return _("Syntax coloring")
726
return get_icon("genprefs.png")
728
def setup_page(self):
730
names = self.get_option("names")
731
names.pop(names.index(CUSTOM_COLOR_SCHEME_NAME))
732
names.insert(0, CUSTOM_COLOR_SCHEME_NAME)
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:"),
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,
763
label.setAlignment(Qt.AlignRight|Qt.AlignVCenter)
764
cs_layout.addWidget(label, row+1, 0)
765
cs_layout.addLayout(clayout, row+1, 1)
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)
780
tabs.addTab(self.create_tab(cs_group), tabname)
782
vlayout = QVBoxLayout()
783
vlayout.addWidget(tabs)
784
self.setLayout(vlayout)
787
def reset_to_default(self, name):
788
set_default_color_scheme(name, replace=True)
789
self.load_from_conf()
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'])