Package renamed: lightdm_gtk_greeter_settings
Andrew P.
10 years ago
0 | ||
1 | from collections import namedtuple | |
2 | import configparser | |
3 | from glob import iglob | |
4 | from locale import gettext as _ | |
5 | import os | |
6 | import sys | |
7 | ||
8 | from gi.repository import Gtk | |
9 | ||
10 | from gtk_greeter_settings import OptionEntry | |
11 | from gtk_greeter_settings import helpers | |
12 | ||
13 | ||
14 | __all__ = ['GtkGreeterSettingsWindow'] | |
15 | ||
16 | ||
17 | BindingValue = namedtuple('BindingValue', ('option', 'default')) | |
18 | ||
19 | ||
20 | OPTIONS_BINDINGS = \ | |
21 | { | |
22 | 'greeter': | |
23 | { | |
24 | # key: class, base widgets name, default value | |
25 | ||
26 | # Theme | |
27 | 'theme-name': (OptionEntry.StringEntry, 'gtk_theme', None), | |
28 | 'icon-theme-name': (OptionEntry.StringEntry, 'icons_theme', None), | |
29 | 'font-name': (OptionEntry.FontEntry, 'font', 'Sans 10'), | |
30 | 'xft-antialias': (OptionEntry.BooleanEntry, 'antialias', False), | |
31 | 'xft-dpi': (OptionEntry.StringEntry, 'dpi', None), | |
32 | 'background': (OptionEntry.BackgroundEntry, 'background', None), | |
33 | 'default-user-image': (OptionEntry.IconEntry, 'user_image', '#avatar-default'), | |
34 | # Panel | |
35 | 'show-clock': (OptionEntry.BooleanEntry, 'show_clock', False), | |
36 | 'clock-format': (OptionEntry.ClockFormatEntry, 'clock_format', '%a, %H:%M'), | |
37 | 'show-indicators': (OptionEntry.IndicatorsEntry, 'indicators', None), | |
38 | # Position | |
39 | 'position': (OptionEntry.PositionEntry, 'position', '50%,center'), | |
40 | } | |
41 | } | |
42 | ||
43 | ||
44 | class BuilderWrapper: | |
45 | def __init__(self, builder, base): | |
46 | self._builder = builder | |
47 | self._base = base | |
48 | ||
49 | def __getitem__(self, key): | |
50 | return self._builder.get_object('%s_%s' % (self._base, key)) | |
51 | ||
52 | ||
53 | class GtkGreeterSettingsWindow(Gtk.Window): | |
54 | ||
55 | __gtype_name__ = 'GtkGreeterSettingsWindow' | |
56 | ||
57 | BUILDER_WIDGETS = ('dialog_buttons', 'apply_button', | |
58 | 'gtk_theme_values', 'icons_theme_values') | |
59 | ||
60 | def __new__(cls): | |
61 | builder = Gtk.Builder() | |
62 | builder.add_from_file(helpers.get_data_path('%s.ui' % cls.__name__)) | |
63 | window = builder.get_object("settings_window") | |
64 | window._builder = builder | |
65 | window.__dict__.update(('_' + w, builder.get_object(w)) | |
66 | for w in cls.BUILDER_WIDGETS) | |
67 | builder.connect_signals(window) | |
68 | window._init_window() | |
69 | return window | |
70 | ||
71 | def _init_window(self): | |
72 | self._bindings = {section: {key: BindingValue(cls(BuilderWrapper(self._builder, base_name)), default) | |
73 | for key, (cls, base_name, default) in keys.items()} | |
74 | for section, keys in OPTIONS_BINDINGS.items()} | |
75 | ||
76 | self._config_path = helpers.get_config_path() | |
77 | if not self._has_access_to_write(self._config_path): | |
78 | self._apply_button.props.sensitive = False | |
79 | ||
80 | helpers.show_message(text=_('No permissions to save configuration'), | |
81 | secondary_text=_( | |
82 | 'It seems that you don\'t have permissions to write to file:\n\ | |
83 | %s\n\nTry to run this program using "sudo" or "pkexec"') % self._config_path, | |
84 | message_type=Gtk.MessageType.WARNING) | |
85 | ||
86 | self._config = configparser.RawConfigParser(strict=False, allow_no_value=True) | |
87 | ||
88 | try: | |
89 | if not self._config.read(self._config_path): | |
90 | helpers.show_message(text=_('Failed to read configuration file: %s') % self._config_path, | |
91 | message_type=Gtk.MessageType.ERROR) | |
92 | except (configparser.DuplicateSectionError, configparser.MissingSectionHeaderError): | |
93 | pass | |
94 | ||
95 | for theme in iglob(os.path.join(sys.prefix, 'share', 'themes', '*', 'gtk-3.0')): | |
96 | self._gtk_theme_values.append_text(theme.split(os.path.sep)[-2]) | |
97 | ||
98 | for theme in iglob(os.path.join(sys.prefix, 'share', 'icons', '*', 'index.theme')): | |
99 | self._icons_theme_values.append_text(theme.split(os.path.sep)[-2]) | |
100 | ||
101 | self._read() | |
102 | ||
103 | def _has_access_to_write(self, path): | |
104 | if os.path.exists(path) and os.access(self._config_path, os.W_OK): | |
105 | return True | |
106 | return os.access(os.path.dirname(self._config_path), os.W_OK | os.X_OK) | |
107 | ||
108 | def _read(self): | |
109 | for section, keys in self._bindings.items(): | |
110 | for key, binding in keys.items(): | |
111 | binding.option.value = self._config.get(section, key, fallback=binding.default) | |
112 | ||
113 | def _write(self): | |
114 | for section, keys in self._bindings.items(): | |
115 | if not self._config.has_section(section): | |
116 | self._config.add_section(section) | |
117 | for key, binding in keys.items(): | |
118 | value = binding.option.value | |
119 | if value is None: | |
120 | self._config.remove_option(section, key) | |
121 | else: | |
122 | self._config.set(section, key, value) | |
123 | ||
124 | try: | |
125 | with open(self._config_path + '_', 'w') as file: | |
126 | self._config.write(file) | |
127 | except OSError as e: | |
128 | helpers.show_message(e, Gtk.MessageType.ERROR) | |
129 | ||
130 | def on_destroy(self, *args): | |
131 | Gtk.main_quit() | |
132 | ||
133 | def on_apply_clicked(self, *args): | |
134 | self._write() | |
135 | ||
136 | def on_reset_clicked(self, *args): | |
137 | self._read() | |
138 | ||
139 | def on_close_clicked(self, *args): | |
140 | self.destroy() |
0 | ||
1 | from glob import iglob | |
2 | import os | |
3 | import sys | |
4 | ||
5 | from gi.repository import Gtk | |
6 | from gtk_greeter_settings import helpers | |
7 | ||
8 | ||
9 | __all__ = ['IndicatorChooserDialog'] | |
10 | ||
11 | ||
12 | class IndicatorChooserDialog(Gtk.Dialog): | |
13 | ||
14 | __gtype_name__ = 'IndicatorChooserDialog' | |
15 | ||
16 | BUILDER_WIDGETS = ('short_choice', 'short_value', 'short_model', | |
17 | 'path_choice', 'path_value', | |
18 | 'add_button', 'ok_button', 'infobar', 'message') | |
19 | ||
20 | def __new__(cls): | |
21 | builder = Gtk.Builder() | |
22 | builder.add_from_file(helpers.get_data_path('%s.ui' % cls.__name__)) | |
23 | window = builder.get_object('indicator_chooser_dialog') | |
24 | window._builder = builder | |
25 | window.__dict__.update(('_' + w, builder.get_object(w)) | |
26 | for w in cls.BUILDER_WIDGETS) | |
27 | ||
28 | builder.connect_signals(window) | |
29 | window._init_window() | |
30 | return window | |
31 | ||
32 | def _init_window(self): | |
33 | self._add_callback = None | |
34 | self._check_callback = None | |
35 | ||
36 | for path in iglob(os.path.join(sys.prefix, 'share', 'unity', 'indicators', '*')): | |
37 | name = os.path.basename(path) | |
38 | parts = name.rsplit('.', maxsplit=1) | |
39 | if len(parts) == 2 and parts[0] == 'com.canonical.indicator': | |
40 | name = parts[1] | |
41 | self._short_model.append((name,)) | |
42 | ||
43 | for path in iglob(os.path.join(sys.prefix, 'lib', 'indicators3', '7', '*.so')): | |
44 | self._short_model.append((os.path.basename(path),)) | |
45 | ||
46 | def _get_current_value(self): | |
47 | if self._short_choice.props.active: | |
48 | return self._short_value.props.text | |
49 | else: | |
50 | return self._path_value.get_filename() | |
51 | ||
52 | def _update_state(self, force_valid=None): | |
53 | message = None | |
54 | if force_valid is None: | |
55 | valid = False | |
56 | if self._check_callback is not None: | |
57 | check = self._check_callback(self._get_current_value()) | |
58 | if isinstance(check, str): | |
59 | message = check | |
60 | else: | |
61 | valid = bool(check) | |
62 | else: | |
63 | valid = True | |
64 | else: | |
65 | valid = force_valid | |
66 | ||
67 | self._infobar.props.visible = message is not None | |
68 | if message is not None: | |
69 | self._message.props.label = message | |
70 | ||
71 | self._ok_button.props.sensitive = valid | |
72 | self._add_button.props.sensitive = valid | |
73 | ||
74 | def on_short_value_changed(self, widget): | |
75 | if not self._short_choice.props.active: | |
76 | self._short_choice.props.active = True | |
77 | else: | |
78 | self._update_state() | |
79 | ||
80 | def on_path_value_changed(self, widget): | |
81 | self._path_choice.props.active = True | |
82 | self._update_state() | |
83 | ||
84 | def on_short_choice_toggled(self, widget): | |
85 | self._update_state() | |
86 | ||
87 | def on_add_clicked(self, *args): | |
88 | value = self._get_current_value() | |
89 | if value: | |
90 | self._add_callback(value) | |
91 | self._update_state(False) | |
92 | ||
93 | def get_indicator(self, check_callback=None, add_callback=None): | |
94 | self._check_callback = check_callback | |
95 | self._add_callback = add_callback | |
96 | self._add_button.props.visible = add_callback is not None | |
97 | ||
98 | self._update_state() | |
99 | response = self.run() | |
100 | self.hide() | |
101 | if response == Gtk.ResponseType.OK: | |
102 | return self._get_current_value() | |
103 | return None |
0 | ||
1 | from _functools import partial | |
2 | from builtins import isinstance | |
3 | from collections import namedtuple, OrderedDict | |
4 | from itertools import product | |
5 | from locale import gettext as _ | |
6 | import time | |
7 | ||
8 | from gi.repository import Gtk, Gdk, GObject | |
9 | ||
10 | from gtk_greeter_settings import IndicatorChooserDialog | |
11 | ||
12 | ||
13 | __all__ = ['BaseEntry', 'BooleanEntry', 'StringEntry', 'ClockFormatEntry', | |
14 | 'BackgroundEntry', 'IconEntry', 'IndicatorsEntry', 'PositionEntry'] | |
15 | ||
16 | ||
17 | class GtkSignalBlocker: | |
18 | def __init__(self, widget, handler): | |
19 | if hasattr(handler, '__call__'): | |
20 | self._block = partial(widget.handler_block_by_func, handler) | |
21 | self._unblock = partial(widget.handler_unblock_by_func, handler) | |
22 | elif isinstance(handler, int): | |
23 | self._block = partial(widget.handler_block, handler) | |
24 | self._unblock = partial(widget.handler_unblock, handler) | |
25 | else: | |
26 | self._block = None | |
27 | self._unblock = None | |
28 | ||
29 | def __enter__(self): | |
30 | if self._block: | |
31 | self._block() | |
32 | return self | |
33 | ||
34 | def __exit__(self, *args): | |
35 | if self._unblock: | |
36 | self._unblock() | |
37 | return False | |
38 | ||
39 | ||
40 | class BaseEntry: | |
41 | ||
42 | @property | |
43 | def value(self): | |
44 | return self._get_value() | |
45 | ||
46 | @value.setter | |
47 | def value(self, value): | |
48 | return self._set_value(value) | |
49 | ||
50 | def __repr__(self): | |
51 | try: | |
52 | value = self._get_value() | |
53 | except NotImplemented: | |
54 | value = '<Undefined>' | |
55 | return '%s(%s)' % (self.__class__.__name__, value) | |
56 | ||
57 | def _get_value(self): | |
58 | raise NotImplementedError(self.__class__) | |
59 | ||
60 | def _set_value(self, value): | |
61 | raise NotImplementedError(self.__class__) | |
62 | ||
63 | ||
64 | class BooleanEntry(BaseEntry): | |
65 | ||
66 | def __init__(self, widgets): | |
67 | self._widget = widgets['value'] | |
68 | ||
69 | def _get_value(self): | |
70 | return 'true' if self._widget.props.active else 'false' | |
71 | ||
72 | def _set_value(self, value): | |
73 | self._widget.props.active = value and value.lower() not in ('false', 'no', '0') | |
74 | ||
75 | ||
76 | class StringEntry(BaseEntry): | |
77 | ||
78 | def __init__(self, widgets): | |
79 | self._widget = widgets['value'] | |
80 | if isinstance(self._widget, Gtk.ComboBox): | |
81 | self._widget = self._widget.get_child() | |
82 | ||
83 | def _get_value(self): | |
84 | return self._widget.props.text | |
85 | ||
86 | def _set_value(self, value): | |
87 | self._widget.props.text = value or '' | |
88 | ||
89 | ||
90 | class ClockFormatEntry(StringEntry): | |
91 | ||
92 | def __init__(self, widgets): | |
93 | super().__init__(widgets) | |
94 | self._preview = widgets['preview'] | |
95 | self._widget.connect('changed', self._on_changed) | |
96 | GObject.timeout_add_seconds(1, self._on_changed, self._widget) | |
97 | ||
98 | def _on_changed(self, entry): | |
99 | self._preview.props.label = time.strftime(self._widget.props.text) | |
100 | return True | |
101 | ||
102 | ||
103 | class BackgroundEntry(BaseEntry): | |
104 | ||
105 | def __init__(self, widgets): | |
106 | self._image_choice = widgets['image_choice'] | |
107 | self._color_choice = widgets['color_choice'] | |
108 | self._image_value = widgets['image_value'] | |
109 | self._color_value = widgets['color_value'] | |
110 | ||
111 | def _get_value(self): | |
112 | if self._image_choice.props.active: | |
113 | return self._image_value.get_filename() | |
114 | else: | |
115 | return self._color_value.props.color.to_string() | |
116 | ||
117 | def _set_value(self, value): | |
118 | if value is None: | |
119 | value = '' | |
120 | ||
121 | color = Gdk.color_parse(value) | |
122 | ||
123 | self._color_choice.props.active = color is not None | |
124 | self._image_choice.props.active = color is None | |
125 | ||
126 | if color is not None: | |
127 | self._color_value.props.color = color | |
128 | self._image_value.unselect_all() | |
129 | else: | |
130 | if value: | |
131 | self._image_value.select_filename(value) | |
132 | else: | |
133 | self._image_value.unselect_all() | |
134 | ||
135 | ||
136 | class FontEntry(BaseEntry): | |
137 | ||
138 | def __init__(self, widgets): | |
139 | self._widget = widgets['value'] | |
140 | ||
141 | def _get_value(self): | |
142 | return self._widget.get_font_name() | |
143 | ||
144 | def _set_value(self, value): | |
145 | self._widget.props.font_name = value or '' | |
146 | ||
147 | ||
148 | class IconEntry(BaseEntry): | |
149 | ||
150 | def __init__(self, widgets): | |
151 | self._image = widgets['image'] | |
152 | self._button = widgets['button'] | |
153 | ||
154 | def _get_value(self): | |
155 | pass | |
156 | ||
157 | def _set_value(self, value): | |
158 | pass | |
159 | ||
160 | ||
161 | class IndicatorsEntry(BaseEntry): | |
162 | NAMES_DELIMITER = ';' | |
163 | # It's the only one place where model columns order defined | |
164 | ModelRow = namedtuple('ModelRow', ('enabled', 'name', 'builtin', 'external')) | |
165 | ||
166 | def __init__(self, widgets): | |
167 | # Map ModelRow fields to self._model_[field-name] = [field-index] | |
168 | for i, field in enumerate(IndicatorsEntry.ModelRow._fields): | |
169 | setattr(self, '_model_' + field, i) | |
170 | ||
171 | self._use = widgets['use'] | |
172 | self._toolbar = widgets['toolbar'] | |
173 | self._treeview = widgets['treeview'] | |
174 | self._selection = widgets['selection'] | |
175 | self._state_renderer = widgets['state_renderer'] | |
176 | self._name_column = widgets['name_column'] | |
177 | self._name_renderer = widgets['name_renderer'] | |
178 | self._add = widgets['add'] | |
179 | self._remove = widgets['remove'] | |
180 | self._up = widgets['up'] | |
181 | self._down = widgets['down'] | |
182 | self._model = widgets['model'] | |
183 | ||
184 | self._initial_items = OrderedDict((item.name, item) | |
185 | for item in map(self.ModelRow._make, self._model)) | |
186 | self._indicators_dialog = None | |
187 | ||
188 | self._treeview.connect("key-press-event", self._on_key_press) | |
189 | self._selection.connect("changed", self._on_selection_changed) | |
190 | self._state_renderer.connect("toggled", self._on_state_toggled) | |
191 | self._name_renderer.connect("edited", self._on_name_edited) | |
192 | self._add.connect("clicked", self._on_add) | |
193 | self._remove.connect("clicked", self._on_remove) | |
194 | self._up.connect("clicked", self._on_up) | |
195 | self._down.connect("clicked", self._on_down) | |
196 | self._use.connect("notify::active", self._on_use_toggled) | |
197 | ||
198 | def _get_value(self): | |
199 | if self._use.props.active: | |
200 | return self.NAMES_DELIMITER.join(item.name for item in map(self.ModelRow._make, self._model) | |
201 | if (item.builtin and item.enabled) or item.external) | |
202 | else: | |
203 | return None | |
204 | ||
205 | def _set_value(self, value): | |
206 | with GtkSignalBlocker(self._use, self._on_use_toggled): | |
207 | self._use.set_active(value is not None) | |
208 | ||
209 | self._model.clear() | |
210 | last_options = self._initial_items.copy() | |
211 | if value: | |
212 | for name in value.split(self.NAMES_DELIMITER): | |
213 | try: | |
214 | self._model.append(last_options.pop(name)._replace(enabled=True)) | |
215 | except KeyError: | |
216 | self._model.append(self.ModelRow(name=name, external=True, | |
217 | builtin=False, enabled=False)) | |
218 | for i in last_options.values(): | |
219 | self._model.append(i) | |
220 | ||
221 | self._toolbar.props.sensitive = value is not None | |
222 | self._treeview.props.sensitive = value is not None | |
223 | ||
224 | self._selection.select_path(0) | |
225 | ||
226 | def _remove_selection(self): | |
227 | model, rowiter = self._selection.get_selected() | |
228 | if rowiter: | |
229 | previter = model.iter_previous(rowiter) | |
230 | model.remove(rowiter) | |
231 | if previter: | |
232 | self._selection.select_iter(previter) | |
233 | ||
234 | def _move_selection(self, move_up): | |
235 | model, rowiter = self._selection.get_selected() | |
236 | if rowiter: | |
237 | if move_up: | |
238 | model.swap(rowiter, model.iter_previous(rowiter)) | |
239 | else: | |
240 | model.swap(rowiter, model.iter_next(rowiter)) | |
241 | self._on_selection_changed(self._selection) | |
242 | ||
243 | def _check_indicator(self, name): | |
244 | ''' Returns True if name is valid, error message or False otherwise ''' | |
245 | if not name: | |
246 | return False | |
247 | else: | |
248 | if any(row[self._model_name] == name for row in self._model): | |
249 | return _('Indicator "%s" is already in the list') % name | |
250 | return True | |
251 | ||
252 | def _add_indicator(self, name): | |
253 | if name: | |
254 | rowiter = self._model.append(self.ModelRow(name=name, external=True, | |
255 | builtin=False, enabled=False)) | |
256 | self._selection.select_iter(rowiter) | |
257 | self._treeview.grab_focus() | |
258 | ||
259 | def _on_key_press(self, treeview, event): | |
260 | if Gdk.keyval_name(event.keyval) == 'Delete': | |
261 | self._remove_selection() | |
262 | elif Gdk.keyval_name(event.keyval) == 'F2': | |
263 | model, rowiter = self._selection.get_selected() | |
264 | if rowiter and model[rowiter][self._model_external]: | |
265 | self._treeview.set_cursor(model.get_path(rowiter), self._name_column, True) | |
266 | else: | |
267 | return False | |
268 | return True | |
269 | ||
270 | def _on_state_toggled(self, renderer, path): | |
271 | self._model[path][self._model_enabled] = not self._model[path][self._model_enabled] | |
272 | ||
273 | def _on_name_edited(self, renderer, path, name): | |
274 | check = self._check_indicator(name) | |
275 | if not isinstance(check, str) and check: | |
276 | self._model[path][self._model_name] = name | |
277 | ||
278 | def _on_use_toggled(self, *args): | |
279 | if self._use.props.active: | |
280 | self._set_value([]) | |
281 | else: | |
282 | self._set_value(None) | |
283 | ||
284 | def _on_selection_changed(self, selection): | |
285 | model, rowiter = selection.get_selected() | |
286 | self._remove.props.sensitive = (rowiter is not None) and self.ModelRow._make(model[rowiter]).external | |
287 | self._down.props.sensitive = (rowiter is not None) and model.iter_next(rowiter) is not None | |
288 | self._up.props.sensitive = (rowiter is not None) and model.iter_previous(rowiter) is not None | |
289 | if rowiter is not None: | |
290 | self._treeview.scroll_to_cell(model.get_path(rowiter)) | |
291 | ||
292 | def _on_add(self, *args): | |
293 | if not self._indicators_dialog: | |
294 | self._indicators_dialog = IndicatorChooserDialog.IndicatorChooserDialog() | |
295 | name = self._indicators_dialog.get_indicator(check_callback=self._check_indicator, | |
296 | add_callback=self._add_indicator) | |
297 | if name: | |
298 | self._add_indicator(name) | |
299 | ||
300 | def _on_remove(self, *args): | |
301 | self._remove_selection() | |
302 | ||
303 | def _on_up(self, *args): | |
304 | self._move_selection(move_up=True) | |
305 | ||
306 | def _on_down(self, *args): | |
307 | self._move_selection(move_up=False) | |
308 | ||
309 | ||
310 | class PositionEntry(BaseEntry): | |
311 | ||
312 | class Dimension: | |
313 | def __init__(self, name, widgets, anchors, on_changed): | |
314 | self.__dict__.update(('_%s' % w, widgets['%s_%s' % (name, w)]) | |
315 | for w in ('value', 'percents', 'mirror', 'adjustment')) | |
316 | self._name = name | |
317 | self._on_changed = on_changed | |
318 | self._anchor = '' | |
319 | ||
320 | self._percents.connect('toggled', self._on_percents_toggled) | |
321 | self._mirror.connect('toggled', self._on_mirror_toggled) | |
322 | self._adjustment.connect('value-changed', self._on_value_changed) | |
323 | ||
324 | for (x, y), widget in anchors.items(): | |
325 | widget.connect('toggled', self._on_anchor_toggled, self, | |
326 | x if self._name == 'x' else y) | |
327 | ||
328 | @property | |
329 | def value(self): | |
330 | return '%s%d%s,%s' % ('-' if self._mirror.props.active else '', | |
331 | int(self._value.props.value), | |
332 | '%' if self._percents.props.active else '', | |
333 | 'start' if self._name == 'x' else 'end') | |
334 | ||
335 | @value.setter | |
336 | def value(self, dim_value): | |
337 | value, _, anchor = dim_value.partition(',') | |
338 | ||
339 | percents = value and value[-1] == '%' | |
340 | if percents: | |
341 | value = value[:-1] | |
342 | ||
343 | try: | |
344 | p = int(value) | |
345 | except ValueError: | |
346 | p = 0 | |
347 | ||
348 | negative = (p < 0) or (p == 0 and value and value[0] == '-') | |
349 | ||
350 | if not anchor or anchor not in ('start', 'center', 'end'): | |
351 | if negative: | |
352 | anchor = 'end' | |
353 | else: | |
354 | anchor = 'start' | |
355 | self._anchor = anchor | |
356 | ||
357 | with GtkSignalBlocker(self._percents, self._on_percents_toggled): | |
358 | self._percents.props.active = percents | |
359 | self._adjustment.props.upper = 100 if self._percents.props.active else 10000 | |
360 | with GtkSignalBlocker(self._mirror, self._on_mirror_toggled): | |
361 | self._mirror.props.active = negative | |
362 | with GtkSignalBlocker(self._adjustment, self._on_value_changed): | |
363 | self._adjustment.props.value = -p if negative else p | |
364 | ||
365 | @property | |
366 | def anchor(self): | |
367 | return self._anchor | |
368 | ||
369 | def get_scaled_position(self, screen, window, scale): | |
370 | screen_size = screen[0] if self._name == 'x' else screen[1] | |
371 | window_size = window[0] if self._name == 'x' else window[1] | |
372 | ||
373 | p = int(self._adjustment.props.value) | |
374 | if self._percents.props.active: | |
375 | p = screen_size * p / 100 | |
376 | else: | |
377 | p *= scale | |
378 | ||
379 | if self._mirror.props.active: | |
380 | p = screen_size - p | |
381 | ||
382 | if self._anchor == 'center': | |
383 | p -= window_size / 2 | |
384 | elif self._anchor == 'end': | |
385 | p -= window_size | |
386 | ||
387 | p = int(p) | |
388 | ||
389 | if p + window_size > screen_size: | |
390 | p = screen_size - window_size | |
391 | if p < 0: | |
392 | p = 0 | |
393 | ||
394 | return p | |
395 | ||
396 | def _on_value_changed(self, widget): | |
397 | self._on_changed(self) | |
398 | ||
399 | def _on_percents_toggled(self, toggle): | |
400 | self._adjustment.props.upper = 100 if toggle.props.active else 10000 | |
401 | self._on_changed(self) | |
402 | ||
403 | def _on_mirror_toggled(self, toggle): | |
404 | self._on_changed(self) | |
405 | ||
406 | def _on_anchor_toggled(self, toggle, dimension, anchor): | |
407 | if dimension == self and toggle.props.active and anchor != self._anchor: | |
408 | self._anchor = anchor | |
409 | self._on_changed(self) | |
410 | ||
411 | REAL_WINDOW_SIZE = 430, 210 | |
412 | ||
413 | def __init__(self, widgets): | |
414 | self._screen = widgets['screen'] | |
415 | self._window = widgets['window'] | |
416 | self._screen_pos = (0, 0) | |
417 | self._screen_size = (0, 0) | |
418 | ||
419 | self._anchors = {(x, y): widgets['base_%s_%s' % (x, y)] | |
420 | for x, y in product(('start', 'center', 'end'), repeat=2)} | |
421 | ||
422 | self._screen.connect('size-allocate', self._on_resize) | |
423 | self._screen.connect('draw', self._on_draw_screen_border) | |
424 | ||
425 | self._x = PositionEntry.Dimension('x', widgets, self._anchors, self._on_dimension_changed) | |
426 | self._y = PositionEntry.Dimension('y', widgets, self._anchors, self._on_dimension_changed) | |
427 | ||
428 | def _on_draw_screen_border(self, widget, cr): | |
429 | width, height = self._screen_size | |
430 | x, y = self._screen_pos | |
431 | line_width = 2 | |
432 | width -= line_width | |
433 | height -= line_width | |
434 | ||
435 | x += line_width / 2 | |
436 | y += line_width / 2 | |
437 | cr.set_source_rgba(0.2, 0.1, 0.2, 0.8) | |
438 | cr.set_line_width(line_width) | |
439 | ||
440 | cr.move_to(x, y) | |
441 | cr.line_to(x + width, y) | |
442 | cr.line_to(x + width, y + height) | |
443 | cr.line_to(x, y + height) | |
444 | cr.line_to(x, y - line_width / 2) | |
445 | cr.stroke_preserve() | |
446 | ||
447 | return False | |
448 | ||
449 | def _get_value(self): | |
450 | return self._x.value + ' ' + self._y.value | |
451 | ||
452 | def _set_value(self, value): | |
453 | if value: | |
454 | x, _, y = value.partition(' ') | |
455 | self._x.value = x | |
456 | self._y.value = y or x | |
457 | self._anchors[self._x.anchor, self._y.anchor].props.active = True | |
458 | self._update_layout() | |
459 | ||
460 | def _update_layout(self): | |
461 | screen = self._screen.get_toplevel().get_screen() | |
462 | geometry = screen.get_monitor_geometry(screen.get_primary_monitor()) | |
463 | window_allocation = self._window.get_allocation() | |
464 | window_size = window_allocation.width, window_allocation.height | |
465 | scale = self._screen_size[0] / geometry.width | |
466 | ||
467 | x = self._screen_pos[0] + self._x.get_scaled_position(self._screen_size, window_size, scale) | |
468 | y = self._screen_pos[1] + self._y.get_scaled_position(self._screen_size, window_size, scale) | |
469 | ||
470 | self._screen.move(self._window, x, y) | |
471 | self._screen.check_resize() | |
472 | ||
473 | def _on_resize(self, widget, allocation): | |
474 | screen = self._screen.get_toplevel().get_screen() | |
475 | geometry = screen.get_monitor_geometry(screen.get_primary_monitor()) | |
476 | screen_scale = geometry.height / geometry.width | |
477 | ||
478 | width = allocation.width | |
479 | height = int(width * screen_scale) | |
480 | ||
481 | if height > allocation.height: | |
482 | height = allocation.height | |
483 | width = min(width, int(height / screen_scale)) | |
484 | self._screen_pos = int((allocation.width - width) / 2), 0 | |
485 | self._screen_size = (width, height) | |
486 | ||
487 | with GtkSignalBlocker(self._screen, self._on_resize): | |
488 | scale = width / geometry.width | |
489 | self._window.set_size_request(PositionEntry.REAL_WINDOW_SIZE[0] * scale, | |
490 | PositionEntry.REAL_WINDOW_SIZE[1] * scale) | |
491 | self._update_layout() | |
492 | ||
493 | def _on_dimension_changed(self, dimension): | |
494 | with GtkSignalBlocker(self._screen, self._on_resize): | |
495 | self._update_layout() |
0 | #!/usr/bin/env python3 | |
1 | ||
2 | from gi.repository import Gtk | |
3 | ||
4 | ||
5 | def main(): | |
6 | from gtk_greeter_settings import GtkGreeterSettingsWindow | |
7 | window = GtkGreeterSettingsWindow.GtkGreeterSettingsWindow() | |
8 | window.show() | |
9 | Gtk.main() | |
10 | ||
11 | ||
12 | if __name__ == "__main__": | |
13 | import sys, os | |
14 | sys.path.append(os.path.join(os.path.dirname(__file__), '..')) | |
15 | window = __import__('GtkGreeterSettingsWindow').GtkGreeterSettingsWindow() | |
16 | window.show() | |
17 | Gtk.main() |
0 | ||
1 | import os | |
2 | from gi.repository import Gtk | |
3 | ||
4 | ||
5 | __license__ = 'GPL-3' | |
6 | __version__ = '0.1' | |
7 | __data_directory__ = '../data/' | |
8 | __config_path__ = 'lightdm-gtk-greeter.conf' | |
9 | ||
10 | ||
11 | try: | |
12 | from _override_config import * | |
13 | except ImportError: | |
14 | pass | |
15 | ||
16 | ||
17 | __all__ = ['get_data_path', 'get_config_path', 'show_message'] | |
18 | ||
19 | ||
20 | def get_data_path(*parts): | |
21 | return os.path.abspath(os.path.join(os.path.dirname(__file__), | |
22 | __data_directory__, *parts)) | |
23 | ||
24 | ||
25 | def get_config_path(): | |
26 | return os.path.abspath(__config_path__) | |
27 | ||
28 | ||
29 | def show_message(**kwargs): | |
30 | dialog = Gtk.MessageDialog(buttons=Gtk.ButtonsType.CLOSE, **kwargs) | |
31 | dialog.run() | |
32 | dialog.destroy() |
0 | ||
1 | from collections import namedtuple | |
2 | import configparser | |
3 | from glob import iglob | |
4 | from locale import gettext as _ | |
5 | import os | |
6 | import sys | |
7 | ||
8 | from gi.repository import Gtk | |
9 | ||
10 | from lightdm_gtk_greeter_settings import OptionEntry | |
11 | from lightdm_gtk_greeter_settings import helpers | |
12 | ||
13 | ||
14 | __all__ = ['GtkGreeterSettingsWindow'] | |
15 | ||
16 | ||
17 | BindingValue = namedtuple('BindingValue', ('option', 'default')) | |
18 | ||
19 | ||
20 | OPTIONS_BINDINGS = \ | |
21 | { | |
22 | 'greeter': | |
23 | { | |
24 | # key: class, base widgets name, default value | |
25 | ||
26 | # Theme | |
27 | 'theme-name': (OptionEntry.StringEntry, 'gtk_theme', None), | |
28 | 'icon-theme-name': (OptionEntry.StringEntry, 'icons_theme', None), | |
29 | 'font-name': (OptionEntry.FontEntry, 'font', 'Sans 10'), | |
30 | 'xft-antialias': (OptionEntry.BooleanEntry, 'antialias', False), | |
31 | 'xft-dpi': (OptionEntry.StringEntry, 'dpi', None), | |
32 | 'background': (OptionEntry.BackgroundEntry, 'background', None), | |
33 | 'default-user-image': (OptionEntry.IconEntry, 'user_image', '#avatar-default'), | |
34 | # Panel | |
35 | 'show-clock': (OptionEntry.BooleanEntry, 'show_clock', False), | |
36 | 'clock-format': (OptionEntry.ClockFormatEntry, 'clock_format', '%a, %H:%M'), | |
37 | 'show-indicators': (OptionEntry.IndicatorsEntry, 'indicators', None), | |
38 | # Position | |
39 | 'position': (OptionEntry.PositionEntry, 'position', '50%,center'), | |
40 | } | |
41 | } | |
42 | ||
43 | ||
44 | class BuilderWrapper: | |
45 | def __init__(self, builder, base): | |
46 | self._builder = builder | |
47 | self._base = base | |
48 | ||
49 | def __getitem__(self, key): | |
50 | return self._builder.get_object('%s_%s' % (self._base, key)) | |
51 | ||
52 | ||
53 | class GtkGreeterSettingsWindow(Gtk.Window): | |
54 | ||
55 | __gtype_name__ = 'GtkGreeterSettingsWindow' | |
56 | ||
57 | BUILDER_WIDGETS = ('dialog_buttons', 'apply_button', | |
58 | 'gtk_theme_values', 'icons_theme_values') | |
59 | ||
60 | def __new__(cls): | |
61 | builder = Gtk.Builder() | |
62 | builder.add_from_file(helpers.get_data_path('%s.ui' % cls.__name__)) | |
63 | window = builder.get_object("settings_window") | |
64 | window._builder = builder | |
65 | window.__dict__.update(('_' + w, builder.get_object(w)) | |
66 | for w in cls.BUILDER_WIDGETS) | |
67 | builder.connect_signals(window) | |
68 | window._init_window() | |
69 | return window | |
70 | ||
71 | def _init_window(self): | |
72 | self._bindings = {section: {key: BindingValue(cls(BuilderWrapper(self._builder, base_name)), default) | |
73 | for key, (cls, base_name, default) in keys.items()} | |
74 | for section, keys in OPTIONS_BINDINGS.items()} | |
75 | ||
76 | self._config_path = helpers.get_config_path() | |
77 | if not self._has_access_to_write(self._config_path): | |
78 | self._apply_button.props.sensitive = False | |
79 | ||
80 | helpers.show_message(text=_('No permissions to save configuration'), | |
81 | secondary_text=_( | |
82 | 'It seems that you don\'t have permissions to write to file:\n\ | |
83 | %s\n\nTry to run this program using "sudo" or "pkexec"') % self._config_path, | |
84 | message_type=Gtk.MessageType.WARNING) | |
85 | ||
86 | self._config = configparser.RawConfigParser(strict=False, allow_no_value=True) | |
87 | ||
88 | try: | |
89 | if not self._config.read(self._config_path): | |
90 | helpers.show_message(text=_('Failed to read configuration file: %s') % self._config_path, | |
91 | message_type=Gtk.MessageType.ERROR) | |
92 | except (configparser.DuplicateSectionError, configparser.MissingSectionHeaderError): | |
93 | pass | |
94 | ||
95 | for theme in iglob(os.path.join(sys.prefix, 'share', 'themes', '*', 'gtk-3.0')): | |
96 | self._gtk_theme_values.append_text(theme.split(os.path.sep)[-2]) | |
97 | ||
98 | for theme in iglob(os.path.join(sys.prefix, 'share', 'icons', '*', 'index.theme')): | |
99 | self._icons_theme_values.append_text(theme.split(os.path.sep)[-2]) | |
100 | ||
101 | self._read() | |
102 | ||
103 | def _has_access_to_write(self, path): | |
104 | if os.path.exists(path) and os.access(self._config_path, os.W_OK): | |
105 | return True | |
106 | return os.access(os.path.dirname(self._config_path), os.W_OK | os.X_OK) | |
107 | ||
108 | def _read(self): | |
109 | for section, keys in self._bindings.items(): | |
110 | for key, binding in keys.items(): | |
111 | binding.option.value = self._config.get(section, key, fallback=binding.default) | |
112 | ||
113 | def _write(self): | |
114 | for section, keys in self._bindings.items(): | |
115 | if not self._config.has_section(section): | |
116 | self._config.add_section(section) | |
117 | for key, binding in keys.items(): | |
118 | value = binding.option.value | |
119 | if value is None: | |
120 | self._config.remove_option(section, key) | |
121 | else: | |
122 | self._config.set(section, key, value) | |
123 | ||
124 | try: | |
125 | with open(self._config_path + '_', 'w') as file: | |
126 | self._config.write(file) | |
127 | except OSError as e: | |
128 | helpers.show_message(e, Gtk.MessageType.ERROR) | |
129 | ||
130 | def on_destroy(self, *args): | |
131 | Gtk.main_quit() | |
132 | ||
133 | def on_apply_clicked(self, *args): | |
134 | self._write() | |
135 | ||
136 | def on_reset_clicked(self, *args): | |
137 | self._read() | |
138 | ||
139 | def on_close_clicked(self, *args): | |
140 | self.destroy() |
0 | ||
1 | from glob import iglob | |
2 | import os | |
3 | import sys | |
4 | ||
5 | from gi.repository import Gtk | |
6 | from lightdm_gtk_greeter_settings import helpers | |
7 | ||
8 | ||
9 | __all__ = ['IndicatorChooserDialog'] | |
10 | ||
11 | ||
12 | class IndicatorChooserDialog(Gtk.Dialog): | |
13 | ||
14 | __gtype_name__ = 'IndicatorChooserDialog' | |
15 | ||
16 | BUILDER_WIDGETS = ('short_choice', 'short_value', 'short_model', | |
17 | 'path_choice', 'path_value', | |
18 | 'add_button', 'ok_button', 'infobar', 'message') | |
19 | ||
20 | def __new__(cls): | |
21 | builder = Gtk.Builder() | |
22 | builder.add_from_file(helpers.get_data_path('%s.ui' % cls.__name__)) | |
23 | window = builder.get_object('indicator_chooser_dialog') | |
24 | window._builder = builder | |
25 | window.__dict__.update(('_' + w, builder.get_object(w)) | |
26 | for w in cls.BUILDER_WIDGETS) | |
27 | ||
28 | builder.connect_signals(window) | |
29 | window._init_window() | |
30 | return window | |
31 | ||
32 | def _init_window(self): | |
33 | self._add_callback = None | |
34 | self._check_callback = None | |
35 | ||
36 | for path in iglob(os.path.join(sys.prefix, 'share', 'unity', 'indicators', '*')): | |
37 | name = os.path.basename(path) | |
38 | parts = name.rsplit('.', maxsplit=1) | |
39 | if len(parts) == 2 and parts[0] == 'com.canonical.indicator': | |
40 | name = parts[1] | |
41 | self._short_model.append((name,)) | |
42 | ||
43 | for path in iglob(os.path.join(sys.prefix, 'lib', 'indicators3', '7', '*.so')): | |
44 | self._short_model.append((os.path.basename(path),)) | |
45 | ||
46 | def _get_current_value(self): | |
47 | if self._short_choice.props.active: | |
48 | return self._short_value.props.text | |
49 | else: | |
50 | return self._path_value.get_filename() | |
51 | ||
52 | def _update_state(self, force_valid=None): | |
53 | message = None | |
54 | if force_valid is None: | |
55 | valid = False | |
56 | if self._check_callback is not None: | |
57 | check = self._check_callback(self._get_current_value()) | |
58 | if isinstance(check, str): | |
59 | message = check | |
60 | else: | |
61 | valid = bool(check) | |
62 | else: | |
63 | valid = True | |
64 | else: | |
65 | valid = force_valid | |
66 | ||
67 | self._infobar.props.visible = message is not None | |
68 | if message is not None: | |
69 | self._message.props.label = message | |
70 | ||
71 | self._ok_button.props.sensitive = valid | |
72 | self._add_button.props.sensitive = valid | |
73 | ||
74 | def on_short_value_changed(self, widget): | |
75 | if not self._short_choice.props.active: | |
76 | self._short_choice.props.active = True | |
77 | else: | |
78 | self._update_state() | |
79 | ||
80 | def on_path_value_changed(self, widget): | |
81 | self._path_choice.props.active = True | |
82 | self._update_state() | |
83 | ||
84 | def on_short_choice_toggled(self, widget): | |
85 | self._update_state() | |
86 | ||
87 | def on_add_clicked(self, *args): | |
88 | value = self._get_current_value() | |
89 | if value: | |
90 | self._add_callback(value) | |
91 | self._update_state(False) | |
92 | ||
93 | def get_indicator(self, check_callback=None, add_callback=None): | |
94 | self._check_callback = check_callback | |
95 | self._add_callback = add_callback | |
96 | self._add_button.props.visible = add_callback is not None | |
97 | ||
98 | self._update_state() | |
99 | response = self.run() | |
100 | self.hide() | |
101 | if response == Gtk.ResponseType.OK: | |
102 | return self._get_current_value() | |
103 | return None |
0 | ||
1 | from _functools import partial | |
2 | from builtins import isinstance | |
3 | from collections import namedtuple, OrderedDict | |
4 | from itertools import product | |
5 | from locale import gettext as _ | |
6 | import time | |
7 | ||
8 | from gi.repository import Gtk, Gdk, GObject | |
9 | ||
10 | from lightdm_gtk_greeter_settings import IndicatorChooserDialog | |
11 | ||
12 | ||
13 | __all__ = ['BaseEntry', 'BooleanEntry', 'StringEntry', 'ClockFormatEntry', | |
14 | 'BackgroundEntry', 'IconEntry', 'IndicatorsEntry', 'PositionEntry'] | |
15 | ||
16 | ||
17 | class GtkSignalBlocker: | |
18 | def __init__(self, widget, handler): | |
19 | if hasattr(handler, '__call__'): | |
20 | self._block = partial(widget.handler_block_by_func, handler) | |
21 | self._unblock = partial(widget.handler_unblock_by_func, handler) | |
22 | elif isinstance(handler, int): | |
23 | self._block = partial(widget.handler_block, handler) | |
24 | self._unblock = partial(widget.handler_unblock, handler) | |
25 | else: | |
26 | self._block = None | |
27 | self._unblock = None | |
28 | ||
29 | def __enter__(self): | |
30 | if self._block: | |
31 | self._block() | |
32 | return self | |
33 | ||
34 | def __exit__(self, *args): | |
35 | if self._unblock: | |
36 | self._unblock() | |
37 | return False | |
38 | ||
39 | ||
40 | class BaseEntry: | |
41 | ||
42 | @property | |
43 | def value(self): | |
44 | return self._get_value() | |
45 | ||
46 | @value.setter | |
47 | def value(self, value): | |
48 | return self._set_value(value) | |
49 | ||
50 | def __repr__(self): | |
51 | try: | |
52 | value = self._get_value() | |
53 | except NotImplemented: | |
54 | value = '<Undefined>' | |
55 | return '%s(%s)' % (self.__class__.__name__, value) | |
56 | ||
57 | def _get_value(self): | |
58 | raise NotImplementedError(self.__class__) | |
59 | ||
60 | def _set_value(self, value): | |
61 | raise NotImplementedError(self.__class__) | |
62 | ||
63 | ||
64 | class BooleanEntry(BaseEntry): | |
65 | ||
66 | def __init__(self, widgets): | |
67 | self._widget = widgets['value'] | |
68 | ||
69 | def _get_value(self): | |
70 | return 'true' if self._widget.props.active else 'false' | |
71 | ||
72 | def _set_value(self, value): | |
73 | self._widget.props.active = value and value.lower() not in ('false', 'no', '0') | |
74 | ||
75 | ||
76 | class StringEntry(BaseEntry): | |
77 | ||
78 | def __init__(self, widgets): | |
79 | self._widget = widgets['value'] | |
80 | if isinstance(self._widget, Gtk.ComboBox): | |
81 | self._widget = self._widget.get_child() | |
82 | ||
83 | def _get_value(self): | |
84 | return self._widget.props.text | |
85 | ||
86 | def _set_value(self, value): | |
87 | self._widget.props.text = value or '' | |
88 | ||
89 | ||
90 | class ClockFormatEntry(StringEntry): | |
91 | ||
92 | def __init__(self, widgets): | |
93 | super().__init__(widgets) | |
94 | self._preview = widgets['preview'] | |
95 | self._widget.connect('changed', self._on_changed) | |
96 | GObject.timeout_add_seconds(1, self._on_changed, self._widget) | |
97 | ||
98 | def _on_changed(self, entry): | |
99 | self._preview.props.label = time.strftime(self._widget.props.text) | |
100 | return True | |
101 | ||
102 | ||
103 | class BackgroundEntry(BaseEntry): | |
104 | ||
105 | def __init__(self, widgets): | |
106 | self._image_choice = widgets['image_choice'] | |
107 | self._color_choice = widgets['color_choice'] | |
108 | self._image_value = widgets['image_value'] | |
109 | self._color_value = widgets['color_value'] | |
110 | ||
111 | def _get_value(self): | |
112 | if self._image_choice.props.active: | |
113 | return self._image_value.get_filename() | |
114 | else: | |
115 | return self._color_value.props.color.to_string() | |
116 | ||
117 | def _set_value(self, value): | |
118 | if value is None: | |
119 | value = '' | |
120 | ||
121 | color = Gdk.color_parse(value) | |
122 | ||
123 | self._color_choice.props.active = color is not None | |
124 | self._image_choice.props.active = color is None | |
125 | ||
126 | if color is not None: | |
127 | self._color_value.props.color = color | |
128 | self._image_value.unselect_all() | |
129 | else: | |
130 | if value: | |
131 | self._image_value.select_filename(value) | |
132 | else: | |
133 | self._image_value.unselect_all() | |
134 | ||
135 | ||
136 | class FontEntry(BaseEntry): | |
137 | ||
138 | def __init__(self, widgets): | |
139 | self._widget = widgets['value'] | |
140 | ||
141 | def _get_value(self): | |
142 | return self._widget.get_font_name() | |
143 | ||
144 | def _set_value(self, value): | |
145 | self._widget.props.font_name = value or '' | |
146 | ||
147 | ||
148 | class IconEntry(BaseEntry): | |
149 | ||
150 | def __init__(self, widgets): | |
151 | self._image = widgets['image'] | |
152 | self._button = widgets['button'] | |
153 | ||
154 | def _get_value(self): | |
155 | pass | |
156 | ||
157 | def _set_value(self, value): | |
158 | pass | |
159 | ||
160 | ||
161 | class IndicatorsEntry(BaseEntry): | |
162 | NAMES_DELIMITER = ';' | |
163 | # It's the only one place where model columns order defined | |
164 | ModelRow = namedtuple('ModelRow', ('enabled', 'name', 'builtin', 'external')) | |
165 | ||
166 | def __init__(self, widgets): | |
167 | # Map ModelRow fields to self._model_[field-name] = [field-index] | |
168 | for i, field in enumerate(IndicatorsEntry.ModelRow._fields): | |
169 | setattr(self, '_model_' + field, i) | |
170 | ||
171 | self._use = widgets['use'] | |
172 | self._toolbar = widgets['toolbar'] | |
173 | self._treeview = widgets['treeview'] | |
174 | self._selection = widgets['selection'] | |
175 | self._state_renderer = widgets['state_renderer'] | |
176 | self._name_column = widgets['name_column'] | |
177 | self._name_renderer = widgets['name_renderer'] | |
178 | self._add = widgets['add'] | |
179 | self._remove = widgets['remove'] | |
180 | self._up = widgets['up'] | |
181 | self._down = widgets['down'] | |
182 | self._model = widgets['model'] | |
183 | ||
184 | self._initial_items = OrderedDict((item.name, item) | |
185 | for item in map(self.ModelRow._make, self._model)) | |
186 | self._indicators_dialog = None | |
187 | ||
188 | self._treeview.connect("key-press-event", self._on_key_press) | |
189 | self._selection.connect("changed", self._on_selection_changed) | |
190 | self._state_renderer.connect("toggled", self._on_state_toggled) | |
191 | self._name_renderer.connect("edited", self._on_name_edited) | |
192 | self._add.connect("clicked", self._on_add) | |
193 | self._remove.connect("clicked", self._on_remove) | |
194 | self._up.connect("clicked", self._on_up) | |
195 | self._down.connect("clicked", self._on_down) | |
196 | self._use.connect("notify::active", self._on_use_toggled) | |
197 | ||
198 | def _get_value(self): | |
199 | if self._use.props.active: | |
200 | return self.NAMES_DELIMITER.join(item.name for item in map(self.ModelRow._make, self._model) | |
201 | if (item.builtin and item.enabled) or item.external) | |
202 | else: | |
203 | return None | |
204 | ||
205 | def _set_value(self, value): | |
206 | with GtkSignalBlocker(self._use, self._on_use_toggled): | |
207 | self._use.set_active(value is not None) | |
208 | ||
209 | self._model.clear() | |
210 | last_options = self._initial_items.copy() | |
211 | if value: | |
212 | for name in value.split(self.NAMES_DELIMITER): | |
213 | try: | |
214 | self._model.append(last_options.pop(name)._replace(enabled=True)) | |
215 | except KeyError: | |
216 | self._model.append(self.ModelRow(name=name, external=True, | |
217 | builtin=False, enabled=False)) | |
218 | for i in last_options.values(): | |
219 | self._model.append(i) | |
220 | ||
221 | self._toolbar.props.sensitive = value is not None | |
222 | self._treeview.props.sensitive = value is not None | |
223 | ||
224 | self._selection.select_path(0) | |
225 | ||
226 | def _remove_selection(self): | |
227 | model, rowiter = self._selection.get_selected() | |
228 | if rowiter: | |
229 | previter = model.iter_previous(rowiter) | |
230 | model.remove(rowiter) | |
231 | if previter: | |
232 | self._selection.select_iter(previter) | |
233 | ||
234 | def _move_selection(self, move_up): | |
235 | model, rowiter = self._selection.get_selected() | |
236 | if rowiter: | |
237 | if move_up: | |
238 | model.swap(rowiter, model.iter_previous(rowiter)) | |
239 | else: | |
240 | model.swap(rowiter, model.iter_next(rowiter)) | |
241 | self._on_selection_changed(self._selection) | |
242 | ||
243 | def _check_indicator(self, name): | |
244 | ''' Returns True if name is valid, error message or False otherwise ''' | |
245 | if not name: | |
246 | return False | |
247 | else: | |
248 | if any(row[self._model_name] == name for row in self._model): | |
249 | return _('Indicator "%s" is already in the list') % name | |
250 | return True | |
251 | ||
252 | def _add_indicator(self, name): | |
253 | if name: | |
254 | rowiter = self._model.append(self.ModelRow(name=name, external=True, | |
255 | builtin=False, enabled=False)) | |
256 | self._selection.select_iter(rowiter) | |
257 | self._treeview.grab_focus() | |
258 | ||
259 | def _on_key_press(self, treeview, event): | |
260 | if Gdk.keyval_name(event.keyval) == 'Delete': | |
261 | self._remove_selection() | |
262 | elif Gdk.keyval_name(event.keyval) == 'F2': | |
263 | model, rowiter = self._selection.get_selected() | |
264 | if rowiter and model[rowiter][self._model_external]: | |
265 | self._treeview.set_cursor(model.get_path(rowiter), self._name_column, True) | |
266 | else: | |
267 | return False | |
268 | return True | |
269 | ||
270 | def _on_state_toggled(self, renderer, path): | |
271 | self._model[path][self._model_enabled] = not self._model[path][self._model_enabled] | |
272 | ||
273 | def _on_name_edited(self, renderer, path, name): | |
274 | check = self._check_indicator(name) | |
275 | if not isinstance(check, str) and check: | |
276 | self._model[path][self._model_name] = name | |
277 | ||
278 | def _on_use_toggled(self, *args): | |
279 | if self._use.props.active: | |
280 | self._set_value([]) | |
281 | else: | |
282 | self._set_value(None) | |
283 | ||
284 | def _on_selection_changed(self, selection): | |
285 | model, rowiter = selection.get_selected() | |
286 | self._remove.props.sensitive = (rowiter is not None) and self.ModelRow._make(model[rowiter]).external | |
287 | self._down.props.sensitive = (rowiter is not None) and model.iter_next(rowiter) is not None | |
288 | self._up.props.sensitive = (rowiter is not None) and model.iter_previous(rowiter) is not None | |
289 | if rowiter is not None: | |
290 | self._treeview.scroll_to_cell(model.get_path(rowiter)) | |
291 | ||
292 | def _on_add(self, *args): | |
293 | if not self._indicators_dialog: | |
294 | self._indicators_dialog = IndicatorChooserDialog.IndicatorChooserDialog() | |
295 | name = self._indicators_dialog.get_indicator(check_callback=self._check_indicator, | |
296 | add_callback=self._add_indicator) | |
297 | if name: | |
298 | self._add_indicator(name) | |
299 | ||
300 | def _on_remove(self, *args): | |
301 | self._remove_selection() | |
302 | ||
303 | def _on_up(self, *args): | |
304 | self._move_selection(move_up=True) | |
305 | ||
306 | def _on_down(self, *args): | |
307 | self._move_selection(move_up=False) | |
308 | ||
309 | ||
310 | class PositionEntry(BaseEntry): | |
311 | ||
312 | class Dimension: | |
313 | def __init__(self, name, widgets, anchors, on_changed): | |
314 | self.__dict__.update(('_%s' % w, widgets['%s_%s' % (name, w)]) | |
315 | for w in ('value', 'percents', 'mirror', 'adjustment')) | |
316 | self._name = name | |
317 | self._on_changed = on_changed | |
318 | self._anchor = '' | |
319 | ||
320 | self._percents.connect('toggled', self._on_percents_toggled) | |
321 | self._mirror.connect('toggled', self._on_mirror_toggled) | |
322 | self._adjustment.connect('value-changed', self._on_value_changed) | |
323 | ||
324 | for (x, y), widget in anchors.items(): | |
325 | widget.connect('toggled', self._on_anchor_toggled, self, | |
326 | x if self._name == 'x' else y) | |
327 | ||
328 | @property | |
329 | def value(self): | |
330 | return '%s%d%s,%s' % ('-' if self._mirror.props.active else '', | |
331 | int(self._value.props.value), | |
332 | '%' if self._percents.props.active else '', | |
333 | 'start' if self._name == 'x' else 'end') | |
334 | ||
335 | @value.setter | |
336 | def value(self, dim_value): | |
337 | value, _, anchor = dim_value.partition(',') | |
338 | ||
339 | percents = value and value[-1] == '%' | |
340 | if percents: | |
341 | value = value[:-1] | |
342 | ||
343 | try: | |
344 | p = int(value) | |
345 | except ValueError: | |
346 | p = 0 | |
347 | ||
348 | negative = (p < 0) or (p == 0 and value and value[0] == '-') | |
349 | ||
350 | if not anchor or anchor not in ('start', 'center', 'end'): | |
351 | if negative: | |
352 | anchor = 'end' | |
353 | else: | |
354 | anchor = 'start' | |
355 | self._anchor = anchor | |
356 | ||
357 | with GtkSignalBlocker(self._percents, self._on_percents_toggled): | |
358 | self._percents.props.active = percents | |
359 | self._adjustment.props.upper = 100 if self._percents.props.active else 10000 | |
360 | with GtkSignalBlocker(self._mirror, self._on_mirror_toggled): | |
361 | self._mirror.props.active = negative | |
362 | with GtkSignalBlocker(self._adjustment, self._on_value_changed): | |
363 | self._adjustment.props.value = -p if negative else p | |
364 | ||
365 | @property | |
366 | def anchor(self): | |
367 | return self._anchor | |
368 | ||
369 | def get_scaled_position(self, screen, window, scale): | |
370 | screen_size = screen[0] if self._name == 'x' else screen[1] | |
371 | window_size = window[0] if self._name == 'x' else window[1] | |
372 | ||
373 | p = int(self._adjustment.props.value) | |
374 | if self._percents.props.active: | |
375 | p = screen_size * p / 100 | |
376 | else: | |
377 | p *= scale | |
378 | ||
379 | if self._mirror.props.active: | |
380 | p = screen_size - p | |
381 | ||
382 | if self._anchor == 'center': | |
383 | p -= window_size / 2 | |
384 | elif self._anchor == 'end': | |
385 | p -= window_size | |
386 | ||
387 | p = int(p) | |
388 | ||
389 | if p + window_size > screen_size: | |
390 | p = screen_size - window_size | |
391 | if p < 0: | |
392 | p = 0 | |
393 | ||
394 | return p | |
395 | ||
396 | def _on_value_changed(self, widget): | |
397 | self._on_changed(self) | |
398 | ||
399 | def _on_percents_toggled(self, toggle): | |
400 | self._adjustment.props.upper = 100 if toggle.props.active else 10000 | |
401 | self._on_changed(self) | |
402 | ||
403 | def _on_mirror_toggled(self, toggle): | |
404 | self._on_changed(self) | |
405 | ||
406 | def _on_anchor_toggled(self, toggle, dimension, anchor): | |
407 | if dimension == self and toggle.props.active and anchor != self._anchor: | |
408 | self._anchor = anchor | |
409 | self._on_changed(self) | |
410 | ||
411 | REAL_WINDOW_SIZE = 430, 210 | |
412 | ||
413 | def __init__(self, widgets): | |
414 | self._screen = widgets['screen'] | |
415 | self._window = widgets['window'] | |
416 | self._screen_pos = (0, 0) | |
417 | self._screen_size = (0, 0) | |
418 | ||
419 | self._anchors = {(x, y): widgets['base_%s_%s' % (x, y)] | |
420 | for x, y in product(('start', 'center', 'end'), repeat=2)} | |
421 | ||
422 | self._screen.connect('size-allocate', self._on_resize) | |
423 | self._screen.connect('draw', self._on_draw_screen_border) | |
424 | ||
425 | self._x = PositionEntry.Dimension('x', widgets, self._anchors, self._on_dimension_changed) | |
426 | self._y = PositionEntry.Dimension('y', widgets, self._anchors, self._on_dimension_changed) | |
427 | ||
428 | def _on_draw_screen_border(self, widget, cr): | |
429 | width, height = self._screen_size | |
430 | x, y = self._screen_pos | |
431 | line_width = 2 | |
432 | width -= line_width | |
433 | height -= line_width | |
434 | ||
435 | x += line_width / 2 | |
436 | y += line_width / 2 | |
437 | cr.set_source_rgba(0.2, 0.1, 0.2, 0.8) | |
438 | cr.set_line_width(line_width) | |
439 | ||
440 | cr.move_to(x, y) | |
441 | cr.line_to(x + width, y) | |
442 | cr.line_to(x + width, y + height) | |
443 | cr.line_to(x, y + height) | |
444 | cr.line_to(x, y - line_width / 2) | |
445 | cr.stroke_preserve() | |
446 | ||
447 | return False | |
448 | ||
449 | def _get_value(self): | |
450 | return self._x.value + ' ' + self._y.value | |
451 | ||
452 | def _set_value(self, value): | |
453 | if value: | |
454 | x, _, y = value.partition(' ') | |
455 | self._x.value = x | |
456 | self._y.value = y or x | |
457 | self._anchors[self._x.anchor, self._y.anchor].props.active = True | |
458 | self._update_layout() | |
459 | ||
460 | def _update_layout(self): | |
461 | screen = self._screen.get_toplevel().get_screen() | |
462 | geometry = screen.get_monitor_geometry(screen.get_primary_monitor()) | |
463 | window_allocation = self._window.get_allocation() | |
464 | window_size = window_allocation.width, window_allocation.height | |
465 | scale = self._screen_size[0] / geometry.width | |
466 | ||
467 | x = self._screen_pos[0] + self._x.get_scaled_position(self._screen_size, window_size, scale) | |
468 | y = self._screen_pos[1] + self._y.get_scaled_position(self._screen_size, window_size, scale) | |
469 | ||
470 | self._screen.move(self._window, x, y) | |
471 | self._screen.check_resize() | |
472 | ||
473 | def _on_resize(self, widget, allocation): | |
474 | screen = self._screen.get_toplevel().get_screen() | |
475 | geometry = screen.get_monitor_geometry(screen.get_primary_monitor()) | |
476 | screen_scale = geometry.height / geometry.width | |
477 | ||
478 | width = allocation.width | |
479 | height = int(width * screen_scale) | |
480 | ||
481 | if height > allocation.height: | |
482 | height = allocation.height | |
483 | width = min(width, int(height / screen_scale)) | |
484 | self._screen_pos = int((allocation.width - width) / 2), 0 | |
485 | self._screen_size = (width, height) | |
486 | ||
487 | with GtkSignalBlocker(self._screen, self._on_resize): | |
488 | scale = width / geometry.width | |
489 | self._window.set_size_request(PositionEntry.REAL_WINDOW_SIZE[0] * scale, | |
490 | PositionEntry.REAL_WINDOW_SIZE[1] * scale) | |
491 | self._update_layout() | |
492 | ||
493 | def _on_dimension_changed(self, dimension): | |
494 | with GtkSignalBlocker(self._screen, self._on_resize): | |
495 | self._update_layout() |
0 | #!/usr/bin/env python3 | |
1 | ||
2 | ||
3 | def main(): | |
4 | from gi.repository import Gtk | |
5 | from lightdm_gtk_greeter_settings import GtkGreeterSettingsWindow | |
6 | window = GtkGreeterSettingsWindow.GtkGreeterSettingsWindow() | |
7 | window.show() | |
8 | Gtk.main() | |
9 | ||
10 | ||
11 | if __name__ == "__main__": | |
12 | import sys, os | |
13 | sys.path.append(os.path.join(os.path.dirname(__file__), '..')) | |
14 | main() |
0 | ||
1 | import os | |
2 | from gi.repository import Gtk | |
3 | ||
4 | ||
5 | __license__ = 'GPL-3' | |
6 | __version__ = '0.1' | |
7 | __data_directory__ = '../data/' | |
8 | __config_path__ = 'lightdm-gtk-greeter.conf' | |
9 | ||
10 | ||
11 | try: | |
12 | from _override_config import * | |
13 | except ImportError: | |
14 | pass | |
15 | ||
16 | ||
17 | __all__ = ['get_data_path', 'get_config_path', 'show_message'] | |
18 | ||
19 | ||
20 | def get_data_path(*parts): | |
21 | return os.path.abspath(os.path.join(os.path.dirname(__file__), | |
22 | __data_directory__, *parts)) | |
23 | ||
24 | ||
25 | def get_config_path(): | |
26 | return os.path.abspath(__config_path__) | |
27 | ||
28 | ||
29 | def show_message(**kwargs): | |
30 | dialog = Gtk.MessageDialog(buttons=Gtk.ButtonsType.CLOSE, **kwargs) | |
31 | dialog.run() | |
32 | dialog.destroy() |