/
preferences.py
219 lines (178 loc) · 6.78 KB
/
preferences.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
# -*- coding: utf-8 -*-
#
# Copyright © Spyder Project Contributors
# Licensed under the terms of the MIT License
# (see spyder/__init__.py for details)
"""
API to create an entry in Spyder Preferences associated to a given plugin.
"""
# Standard library imports
import types
from typing import Set
# Local imports
from spyder.config.manager import CONF
from spyder.config.types import ConfigurationKey
from spyder.api.utils import PrefixedTuple
from spyder.plugins.preferences.api import SpyderConfigPage, BaseConfigTab
OptionSet = Set[ConfigurationKey]
class SpyderPreferencesTab(BaseConfigTab):
"""
Widget that represents a tab on a preference page.
All calls to :class:`SpyderConfigPage` attributes are resolved
via delegation.
"""
# Name of the tab to display on the configuration page.
TITLE = None
def __init__(self, parent: SpyderConfigPage):
super().__init__(parent)
self.parent = parent
if self.TITLE is None or not isinstance(self.TITLE, str):
raise ValueError("TITLE must be a str")
def apply_settings(self) -> OptionSet:
"""
Hook called to manually apply settings that cannot be automatically
applied.
Reimplement this if the configuration tab has complex widgets that
cannot be created with any of the `self.create_*` calls.
"""
return set({})
def is_valid(self) -> bool:
"""
Return True if the tab contents are valid.
This method can be overriden to perform complex checks.
"""
return True
def __getattr__(self, attr):
this_class_dir = dir(self)
if attr not in this_class_dir:
return getattr(self.parent, attr)
else:
return super().__getattr__(attr)
class PluginConfigPage(SpyderConfigPage):
"""
Widget to expose the options a plugin offers for configuration as
an entry in Spyder's Preferences dialog.
"""
# TODO: Temporal attribute to handle which appy_settings method to use
# the one of the conf page or the one in the plugin, while the config
# dialog system is updated.
APPLY_CONF_PAGE_SETTINGS = False
def __init__(self, plugin, parent):
self.plugin = plugin
self.main = parent.main
if hasattr(plugin, 'CONF_SECTION'):
self.CONF_SECTION = plugin.CONF_SECTION
if hasattr(plugin, 'get_font'):
self.get_font = plugin.get_font
if not self.APPLY_CONF_PAGE_SETTINGS:
self._patch_apply_settings(plugin)
SpyderConfigPage.__init__(self, parent)
def _wrap_apply_settings(self, func):
"""
Wrap apply_settings call to ensure that a user-defined custom call
is called alongside the Spyder Plugin API configuration propagation
call.
"""
def wrapper(self, options):
opts = self.previous_apply_settings() or set({})
opts |= options
self.aggregate_sections_partials(opts)
func(opts)
return types.MethodType(wrapper, self)
def _patch_apply_settings(self, plugin):
self.previous_apply_settings = self.apply_settings
try:
# New API
self.apply_settings = self._wrap_apply_settings(plugin.apply_conf)
self.get_option = plugin.get_conf
self.set_option = plugin.set_conf
self.remove_option = plugin.remove_conf
except AttributeError:
# Old API
self.apply_settings = self._wrap_apply_settings(
plugin.apply_plugin_settings)
self.get_option = plugin.get_option
self.set_option = plugin.set_option
self.remove_option = plugin.remove_option
def aggregate_sections_partials(self, opts):
"""Aggregate options by sections in order to notify observers."""
to_update = {}
for opt in opts:
if isinstance(opt, tuple):
# This is necessary to filter tuple options that do not
# belong to a section.
if len(opt) == 2 and opt[0] is None:
opt = opt[1]
section = self.CONF_SECTION
if opt in self.cross_section_options:
section = self.cross_section_options[opt]
section_options = to_update.get(section, [])
section_options.append(opt)
to_update[section] = section_options
for section in to_update:
section_prefix = PrefixedTuple()
# Notify section observers
CONF.notify_observers(section, '__section',
recursive_notification=False)
for opt in to_update[section]:
if isinstance(opt, tuple):
opt = opt[:-1]
section_prefix.add_path(opt)
# Notify prefixed observers
for prefix in section_prefix:
try:
CONF.notify_observers(section, prefix,
recursive_notification=False)
except Exception:
# Prevent unexpected failures on tests
pass
def get_name(self):
"""
Return plugin name to use in preferences page title, and
message boxes.
Normally you do not have to reimplement it, as soon as the
plugin name in preferences page will be the same as the plugin
title.
"""
try:
# New API
name = self.plugin.get_name()
except AttributeError:
# Old API
name = self.plugin.get_plugin_title()
return name
def get_icon(self):
"""
Return plugin icon to use in preferences page.
Normally you do not have to reimplement it, as soon as the
plugin icon in preferences page will be the same as the plugin
icon.
"""
try:
# New API
icon = self.plugin.get_icon()
except AttributeError:
# Old API
icon = self.plugin.get_plugin_icon()
return icon
def setup_page(self):
"""
Setup configuration page widget
You should implement this method and set the layout of the
preferences page.
layout = QVBoxLayout()
layout.addWidget(...)
...
self.setLayout(layout)
"""
raise NotImplementedError
def apply_settings(self) -> OptionSet:
"""
Hook called to manually apply settings that cannot be automatically
applied.
Reimplement this if the configuration page has complex widgets that
cannot be created with any of the `self.create_*` calls.
This call should return a set containing the configuration options that
changed.
"""
return set({})