Codebase list lightdm-gtk-greeter-settings / 151f20e
Multihead setup: each option is Entry now, no cancel button; delay before saving config in embedded mode (750ms) Andrew P. 9 years ago
9 changed file(s) with 780 addition(s) and 543 deletion(s). Raw diff Collapse all Expand all
1616 <property name="visible">True</property>
1717 <property name="can_focus">False</property>
1818 <property name="icon_name">add</property>
19 </object>
20 <object class="GtkImage" id="image2">
21 <property name="visible">True</property>
22 <property name="can_focus">False</property>
23 <property name="xpad">2</property>
24 <property name="icon_name">window-close</property>
2519 </object>
2620 <object class="GtkImage" id="image3">
2721 <property name="visible">True</property>
5549 <object class="GtkButtonBox" id="dialog-action_area">
5650 <property name="can_focus">False</property>
5751 <property name="layout_style">end</property>
58 <child>
59 <object class="GtkButton" id="cancel_button">
60 <property name="label" translatable="yes" context="button">_Cancel</property>
61 <property name="visible">True</property>
62 <property name="can_focus">True</property>
63 <property name="receives_default">True</property>
64 <property name="image">image2</property>
65 <property name="use_underline">True</property>
66 </object>
67 <packing>
68 <property name="expand">False</property>
69 <property name="fill">True</property>
70 <property name="position">0</property>
71 </packing>
72 </child>
7352 <child>
7453 <object class="GtkButton" id="ok_button">
7554 <property name="label" translatable="yes" context="button">_OK</property>
175154 </packing>
176155 </child>
177156 <child>
178 <object class="GtkGrid" id="editor_page">
157 <object class="GtkBox" id="editor_page">
179158 <property name="visible">True</property>
180159 <property name="can_focus">False</property>
181160 <property name="margin_left">8</property>
182161 <property name="margin_right">8</property>
183162 <property name="margin_top">8</property>
184163 <property name="margin_bottom">8</property>
185 <property name="row_spacing">6</property>
186 <property name="column_spacing">12</property>
187 <child>
188 <object class="GtkRadioButton" id="background_image_choice">
189 <property name="label" translatable="yes" context="option|greeter|background">Image</property>
190 <property name="visible">True</property>
191 <property name="can_focus">True</property>
192 <property name="receives_default">False</property>
193 <property name="halign">start</property>
194 <property name="margin_left">24</property>
195 <property name="xalign">0</property>
196 <property name="draw_indicator">True</property>
197 <property name="group">background_color_choice</property>
198 </object>
199 <packing>
200 <property name="left_attach">0</property>
201 <property name="top_attach">2</property>
202 </packing>
203 </child>
204 <child>
205 <object class="GtkRadioButton" id="background_color_choice">
206 <property name="label" translatable="yes" context="option|greeter|background">Color</property>
207 <property name="visible">True</property>
208 <property name="can_focus">True</property>
209 <property name="receives_default">False</property>
210 <property name="halign">start</property>
211 <property name="margin_left">24</property>
212 <property name="xalign">0</property>
213 <property name="active">True</property>
214 <property name="draw_indicator">True</property>
215 </object>
216 <packing>
217 <property name="left_attach">0</property>
218 <property name="top_attach">3</property>
219 </packing>
220 </child>
221 <child>
222 <object class="GtkFileChooserButton" id="background_image_value">
223 <property name="visible">True</property>
224 <property name="can_focus">False</property>
225 <property name="halign">start</property>
226 <property name="hexpand">True</property>
227 <property name="create_folders">False</property>
228 <property name="local_only">False</property>
229 </object>
230 <packing>
231 <property name="left_attach">1</property>
232 <property name="top_attach">2</property>
233 </packing>
234 </child>
235 <child>
236 <object class="GtkColorButton" id="background_color_value">
237 <property name="visible">True</property>
238 <property name="can_focus">True</property>
239 <property name="receives_default">True</property>
240 <property name="halign">start</property>
241 <property name="hexpand">True</property>
242 </object>
243 <packing>
244 <property name="left_attach">1</property>
245 <property name="top_attach">3</property>
246 </packing>
247 </child>
248 <child>
249 <object class="GtkCheckButton" id="laptop_value">
250 <property name="label" translatable="yes" context="option|greeter|laptop">This monitor is laptop display (detect lid closing)</property>
251 <property name="visible">True</property>
252 <property name="can_focus">True</property>
253 <property name="receives_default">False</property>
254 <property name="margin_left">24</property>
255 <property name="hexpand">True</property>
256 <property name="xalign">0</property>
257 <property name="image_position">right</property>
258 <property name="draw_indicator">True</property>
259 </object>
260 <packing>
261 <property name="left_attach">0</property>
262 <property name="top_attach">7</property>
263 <property name="width">2</property>
264 </packing>
265 </child>
266 <child>
267 <object class="GtkCheckButton" id="user-background_use">
268 <property name="label" translatable="yes" context="option|multihead">Overwrite default "user-background" option</property>
269 <property name="visible">True</property>
270 <property name="can_focus">True</property>
271 <property name="receives_default">False</property>
272 <property name="margin_top">2</property>
273 <property name="xalign">0</property>
274 <property name="draw_indicator">True</property>
275 </object>
276 <packing>
277 <property name="left_attach">0</property>
278 <property name="top_attach">4</property>
279 <property name="width">2</property>
280 </packing>
281 </child>
282 <child>
283 <object class="GtkCheckButton" id="laptop_use">
284 <property name="label" translatable="yes" context="option|multihead">Overwrite default "laptop" option</property>
285 <property name="visible">True</property>
286 <property name="can_focus">True</property>
287 <property name="receives_default">False</property>
288 <property name="margin_top">2</property>
289 <property name="xalign">0</property>
290 <property name="draw_indicator">True</property>
291 </object>
292 <packing>
293 <property name="left_attach">0</property>
294 <property name="top_attach">6</property>
295 <property name="width">2</property>
296 </packing>
297 </child>
164 <property name="orientation">vertical</property>
165 <property name="spacing">6</property>
298166 <child>
299167 <object class="GtkBox" id="monitor_name_box">
300168 <property name="visible">True</property>
301169 <property name="can_focus">False</property>
302170 <property name="hexpand">True</property>
171 <property name="spacing">4</property>
303172 <child>
304173 <object class="GtkLabel" id="label1">
305174 <property name="visible">True</property>
316185 <object class="GtkComboBoxText" id="name_combo">
317186 <property name="visible">True</property>
318187 <property name="can_focus">False</property>
319 <property name="margin_left">4</property>
320 <property name="margin_right">4</property>
321188 <property name="has_entry">True</property>
322189 <child internal-child="entry">
323190 <object class="GtkEntry" id="name_value">
386253 </child>
387254 </object>
388255 <packing>
389 <property name="left_attach">0</property>
390 <property name="top_attach">0</property>
391 <property name="width">2</property>
256 <property name="expand">False</property>
257 <property name="fill">True</property>
258 <property name="position">0</property>
392259 </packing>
393260 </child>
394261 <child>
395 <object class="GtkBox" id="box1">
262 <object class="GtkEventBox" id="background_label_holder">
396263 <property name="visible">True</property>
397264 <property name="can_focus">False</property>
398265 <child>
399 <object class="GtkCheckButton" id="background_use">
400 <property name="label" translatable="yes" context="option|multihead">Overwrite default "background" option</property>
401 <property name="visible">True</property>
402 <property name="can_focus">True</property>
403 <property name="receives_default">False</property>
404 <property name="xalign">0</property>
405 <property name="draw_indicator">True</property>
406 </object>
407 <packing>
408 <property name="expand">False</property>
409 <property name="fill">True</property>
410 <property name="position">0</property>
411 </packing>
412 </child>
413 <child>
414 <object class="GtkImage" id="background_error">
266 <object class="GtkBox" id="box3">
267 <property name="visible">True</property>
415268 <property name="can_focus">False</property>
416 <property name="yalign">0</property>
417 <property name="pixel_size">12</property>
418 <property name="icon_name">dialog-warning</property>
419 <property name="icon_size">1</property>
420 </object>
421 <packing>
422 <property name="expand">False</property>
423 <property name="fill">True</property>
424 <property name="position">1</property>
425 </packing>
269 <property name="orientation">vertical</property>
270 <property name="spacing">6</property>
271 <child>
272 <object class="GtkBox" id="box1">
273 <property name="visible">True</property>
274 <property name="can_focus">False</property>
275 <child>
276 <object class="GtkCheckButton" id="background_use">
277 <property name="label" translatable="yes" context="option|multihead">Overwrite default "background" option</property>
278 <property name="visible">True</property>
279 <property name="can_focus">True</property>
280 <property name="receives_default">False</property>
281 <property name="xalign">0</property>
282 <property name="draw_indicator">True</property>
283 </object>
284 <packing>
285 <property name="expand">False</property>
286 <property name="fill">True</property>
287 <property name="position">0</property>
288 </packing>
289 </child>
290 <child>
291 <object class="GtkImage" id="background_error">
292 <property name="can_focus">False</property>
293 <property name="yalign">0</property>
294 <property name="pixel_size">12</property>
295 <property name="icon_name">dialog-warning</property>
296 <property name="icon_size">1</property>
297 </object>
298 <packing>
299 <property name="expand">False</property>
300 <property name="fill">True</property>
301 <property name="position">1</property>
302 </packing>
303 </child>
304 </object>
305 <packing>
306 <property name="expand">False</property>
307 <property name="fill">True</property>
308 <property name="position">0</property>
309 </packing>
310 </child>
311 <child>
312 <object class="GtkGrid" id="grid1">
313 <property name="visible">True</property>
314 <property name="can_focus">False</property>
315 <property name="margin_left">24</property>
316 <property name="row_spacing">6</property>
317 <property name="column_spacing">12</property>
318 <child>
319 <object class="GtkRadioButton" id="background_image_choice">
320 <property name="label" translatable="yes" context="option|greeter|background">Image</property>
321 <property name="visible">True</property>
322 <property name="can_focus">True</property>
323 <property name="receives_default">False</property>
324 <property name="halign">start</property>
325 <property name="xalign">0</property>
326 <property name="draw_indicator">True</property>
327 <property name="group">background_color_choice</property>
328 </object>
329 <packing>
330 <property name="left_attach">0</property>
331 <property name="top_attach">0</property>
332 </packing>
333 </child>
334 <child>
335 <object class="GtkRadioButton" id="background_color_choice">
336 <property name="label" translatable="yes" context="option|greeter|background">Color</property>
337 <property name="visible">True</property>
338 <property name="can_focus">True</property>
339 <property name="receives_default">False</property>
340 <property name="halign">start</property>
341 <property name="xalign">0</property>
342 <property name="active">True</property>
343 <property name="draw_indicator">True</property>
344 </object>
345 <packing>
346 <property name="left_attach">0</property>
347 <property name="top_attach">1</property>
348 </packing>
349 </child>
350 <child>
351 <object class="GtkFileChooserButton" id="background_image_value">
352 <property name="visible">True</property>
353 <property name="can_focus">False</property>
354 <property name="halign">start</property>
355 <property name="hexpand">True</property>
356 <property name="create_folders">False</property>
357 <property name="local_only">False</property>
358 </object>
359 <packing>
360 <property name="left_attach">1</property>
361 <property name="top_attach">0</property>
362 </packing>
363 </child>
364 <child>
365 <object class="GtkColorButton" id="background_color_value">
366 <property name="visible">True</property>
367 <property name="can_focus">True</property>
368 <property name="receives_default">True</property>
369 <property name="halign">start</property>
370 <property name="hexpand">True</property>
371 </object>
372 <packing>
373 <property name="left_attach">1</property>
374 <property name="top_attach">1</property>
375 </packing>
376 </child>
377 </object>
378 <packing>
379 <property name="expand">False</property>
380 <property name="fill">True</property>
381 <property name="position">1</property>
382 </packing>
383 </child>
384 </object>
426385 </child>
427386 </object>
428387 <packing>
429 <property name="left_attach">0</property>
430 <property name="top_attach">1</property>
431 <property name="width">2</property>
388 <property name="expand">False</property>
389 <property name="fill">True</property>
390 <property name="position">1</property>
432391 </packing>
433392 </child>
434393 <child>
435 <object class="GtkCheckButton" id="user-background_value">
436 <property name="label" translatable="yes" context="option|greeter|user-background">Use user wallpaper if available</property>
394 <object class="GtkEventBox" id="user-background_label_holder">
437395 <property name="visible">True</property>
438 <property name="can_focus">True</property>
439 <property name="receives_default">False</property>
440 <property name="margin_left">24</property>
441 <property name="hexpand">True</property>
442 <property name="xalign">0</property>
443 <property name="image_position">right</property>
444 <property name="draw_indicator">True</property>
396 <property name="can_focus">False</property>
397 <child>
398 <object class="GtkBox" id="box4">
399 <property name="visible">True</property>
400 <property name="can_focus">False</property>
401 <property name="orientation">vertical</property>
402 <property name="spacing">6</property>
403 <child>
404 <object class="GtkCheckButton" id="user-background_use">
405 <property name="label" translatable="yes" context="option|multihead">Overwrite default "user-background" option</property>
406 <property name="visible">True</property>
407 <property name="can_focus">True</property>
408 <property name="receives_default">False</property>
409 <property name="margin_top">2</property>
410 <property name="xalign">0</property>
411 <property name="draw_indicator">True</property>
412 </object>
413 <packing>
414 <property name="expand">False</property>
415 <property name="fill">True</property>
416 <property name="position">0</property>
417 </packing>
418 </child>
419 <child>
420 <object class="GtkCheckButton" id="user-background_value">
421 <property name="label" translatable="yes" context="option|greeter|user-background">Use user wallpaper if available</property>
422 <property name="visible">True</property>
423 <property name="can_focus">True</property>
424 <property name="receives_default">False</property>
425 <property name="margin_left">24</property>
426 <property name="hexpand">True</property>
427 <property name="xalign">0</property>
428 <property name="image_position">right</property>
429 <property name="draw_indicator">True</property>
430 </object>
431 <packing>
432 <property name="expand">False</property>
433 <property name="fill">True</property>
434 <property name="position">1</property>
435 </packing>
436 </child>
437 </object>
438 </child>
445439 </object>
446440 <packing>
447 <property name="left_attach">0</property>
448 <property name="top_attach">5</property>
449 <property name="width">2</property>
441 <property name="expand">False</property>
442 <property name="fill">True</property>
443 <property name="position">2</property>
444 </packing>
445 </child>
446 <child>
447 <object class="GtkEventBox" id="laptop_label_holder">
448 <property name="visible">True</property>
449 <property name="can_focus">False</property>
450 <child>
451 <object class="GtkBox" id="box5">
452 <property name="visible">True</property>
453 <property name="can_focus">False</property>
454 <property name="orientation">vertical</property>
455 <property name="spacing">6</property>
456 <child>
457 <object class="GtkCheckButton" id="laptop_use">
458 <property name="label" translatable="yes" context="option|multihead">Overwrite default "laptop" option</property>
459 <property name="visible">True</property>
460 <property name="can_focus">True</property>
461 <property name="receives_default">False</property>
462 <property name="margin_top">2</property>
463 <property name="xalign">0</property>
464 <property name="draw_indicator">True</property>
465 </object>
466 <packing>
467 <property name="expand">False</property>
468 <property name="fill">True</property>
469 <property name="position">0</property>
470 </packing>
471 </child>
472 <child>
473 <object class="GtkCheckButton" id="laptop_value">
474 <property name="label" translatable="yes" context="option|greeter|laptop">This monitor is laptop display (detect lid closing)</property>
475 <property name="visible">True</property>
476 <property name="can_focus">True</property>
477 <property name="receives_default">False</property>
478 <property name="margin_left">24</property>
479 <property name="hexpand">True</property>
480 <property name="xalign">0</property>
481 <property name="image_position">right</property>
482 <property name="draw_indicator">True</property>
483 </object>
484 <packing>
485 <property name="expand">False</property>
486 <property name="fill">True</property>
487 <property name="position">1</property>
488 </packing>
489 </child>
490 </object>
491 </child>
492 </object>
493 <packing>
494 <property name="expand">False</property>
495 <property name="fill">True</property>
496 <property name="position">3</property>
450497 </packing>
451498 </child>
452499 </object>
503550 </object>
504551 </child>
505552 <action-widgets>
506 <action-widget response="-7">cancel_button</action-widget>
507553 <action-widget response="-5">ok_button</action-widget>
508554 </action-widgets>
509555 </object>
2020 from glob import iglob
2121
2222 from gi.repository import GLib
23
2324 from lightdm_gtk_greeter_settings import helpers
2425
2526
7475 if not values:
7576 del self._items[item]
7677
77 def __init__(self):
78 def __init__(self, base_dir='lightdm', base_name='lightdm-gtk-greeter.conf'):
79 self._base_dir = base_dir
80 self._base_name = base_name
7881 self._output_path = helpers.get_config_path()
7982 self._groups = OrderedDict()
8083 self._key_values = helpers.SimpleDictWrapper(getter=self._get_key_values)
8992
9093 files = []
9194 for path in pathes:
92 files += sorted(iglob(os.path.join(path, 'lightdm',
93 'lightdm-gtk-greeter.conf.d', '*.conf')))
94 files.append(os.path.join(path, 'lightdm', 'lightdm-gtk-greeter.conf'))
95 files += sorted(iglob(os.path.join(path, self._base_dir,
96 self._base_name + '.d', '*.conf')))
97 files.append(os.path.join(path, self._base_dir, self._base_name))
9598
9699 for path in filter(os.path.isfile, files):
97100 config_file = configparser.RawConfigParser(strict=False)
180183 def __setitem__(self, item, value):
181184 if isinstance(item, tuple):
182185 if not item[0] in self._groups:
183 self._groups = Config.ConfigGroup(self)
186 self._groups[item[0]] = Config.ConfigGroup(self)
184187 self._groups[item[0]][item[1]] = value
185188
186189 def __delitem__(self, item):
2626
2727 from gi.repository import (
2828 Gdk,
29 GLib,
2930 Gtk)
3031 from gi.repository import Pango
3132 from gi.repository.GObject import markup_escape_text as escape_markup
4142 C_,
4243 string2bool,
4344 SimpleEnum,
44 WidgetsEnum)
45 WidgetsEnum,
46 WidgetsWrapper)
4547 from lightdm_gtk_greeter_settings.MonitorsGroup import MonitorsGroup
4648 from lightdm_gtk_greeter_settings.OptionGroup import SimpleGroup
4749
8688 builder = None
8789 mode = WindowMode.Default
8890
91 GreeterGroupSetup = {
92 # Appearance
93 'theme-name': (OptionEntry.StringEntry, ''),
94 'icon-theme-name': (OptionEntry.StringEntry, ''),
95 'font-name': (OptionEntry.FontEntry, 'Sans 10'),
96 'xft-antialias': (OptionEntry.BooleanEntry, None),
97 'xft-dpi': (OptionEntry.StringEntry, None),
98 'xft-rgba': (OptionEntry.ChoiceEntry, None),
99 'xft-hintstyle': (OptionEntry.ChoiceEntry, None),
100 'background': (OptionEntry.BackgroundEntry, '#000000'),
101 'user-background': (OptionEntry.BooleanEntry, 'true'),
102 'hide-user-image': (OptionEntry.InvertedBooleanEntry, 'false'),
103 'default-user-image': (IconEntry.IconEntry, '#avatar-default'),
104 # Panel
105 'clock-format': (OptionEntry.ClockFormatEntry, '%a, %H:%M'),
106 'indicators': (IndicatorsEntry.IndicatorsEntry,
107 '~host;~spacer;~clock;~spacer;~language;~session;~a11y;~power'),
108 # Position
109 'position': (PositionEntry.PositionEntry, '50%,center'),
110 # Misc
111 'screensaver-timeout': (OptionEntry.AdjustmentEntry, '60'),
112 'keyboard': (OptionEntry.StringPathEntry, ''),
113 'reader': (OptionEntry.StringPathEntry, ''),
114 'a11y-states': (OptionEntry.AccessibilityStatesEntry, ''),
115 'allow-debugging': (OptionEntry.BooleanEntry, 'false'), }
116
89117 entries_setup = {
90118 ('greeter', 'allow-debugging'): ('changed',),
91119 ('greeter', 'background'): ('changed',),
98126
99127 def init_window(self):
100128 self._widgets = self.Widgets(builder=self.builder)
101 self._multihead_dialog = None
129
130 if self.mode == WindowMode.Embedded:
131 self.on_entry_removed = self.on_entry_removed_embedded
132 self.on_entry_changed = self.on_entry_changed_embedded
133 self._write = self._write_embedded
134
135 self._widgets.buttons.hide()
136 self._widgets.content.reorder_child(self._widgets.infobar, 0)
137 self._widgets.content.connect('destroy', self.on_destroy, True)
138 # Socket/Plug focus issues workaround
139 self._widgets.multihead_label.connect('button-press-event', self.on_multihead_click)
140 elif self.mode == WindowMode.GtkHeader:
141 for button in (self._widgets.apply, self._widgets.reload):
142 self._widgets.buttons.remove(button)
143 button.set_label('')
144 button.set_always_show_image(True)
145 self._widgets.buttons.hide()
146
147 header = Gtk.HeaderBar()
148 header.set_show_close_button(True)
149 header.props.title = self.get_title()
150 header.pack_start(self._widgets.reload)
151 header.pack_start(self._widgets.apply)
152 header.show_all()
153
154 self.set_titlebar(header)
155
156 self._config = Config.Config()
157
102158 self._entry_menu = None
103159 self._initial_values = {}
160 self._entries = None
161
104162 self._changed_entries = None
105 self._entries = None
106
107 self._group_greeter = SimpleGroup('greeter', self.builder, {
108 # Appearance
109 'theme-name': (OptionEntry.StringEntry, ''),
110 'icon-theme-name': (OptionEntry.StringEntry, ''),
111 'font-name': (OptionEntry.FontEntry, 'Sans 10'),
112 'xft-antialias': (OptionEntry.BooleanEntry, None),
113 'xft-dpi': (OptionEntry.StringEntry, None),
114 'xft-rgba': (OptionEntry.ChoiceEntry, None),
115 'xft-hintstyle': (OptionEntry.ChoiceEntry, None),
116 'background': (OptionEntry.BackgroundEntry, '#000000'),
117 'user-background': (OptionEntry.BooleanEntry, 'true'),
118 'hide-user-image': (OptionEntry.InvertedBooleanEntry, 'false'),
119 'default-user-image': (IconEntry.IconEntry, '#avatar-default'),
120 # Panel
121 'clock-format': (OptionEntry.ClockFormatEntry, '%a, %H:%M'),
122 'indicators': (IndicatorsEntry.IndicatorsEntry,
123 '~host;~spacer;~clock;~spacer;~language;~session;~a11y;~power'),
124 # Position
125 'position': (PositionEntry.PositionEntry, '50%,center'),
126 # Misc
127 'screensaver-timeout': (OptionEntry.AdjustmentEntry, '60'),
128 'keyboard': (OptionEntry.StringPathEntry, ''),
129 'reader': (OptionEntry.StringPathEntry, ''),
130 'a11y-states': (OptionEntry.AccessibilityStatesEntry, ''),
131 'allow-debugging': (OptionEntry.BooleanEntry, 'false'), })
132 self._group_monitors = MonitorsGroup(self.builder, self._get_default_monitors_options)
133
134 self._groups = self._group_greeter, self._group_monitors
163 self._new_entries = None
164 self._removed_entries = None
165
166 self._groups = (
167 SimpleGroup('greeter', WidgetsWrapper(self.builder, 'greeter'), self.GreeterGroupSetup),
168 MonitorsGroup(self.builder))
135169
136170 for group in self._groups:
137171 group.entry_added.connect(self.on_entry_added)
138172 group.entry_removed.connect(self.on_entry_removed)
139173
140 self._config = Config.Config()
141
142174 self._allow_edit = self._config.is_writable()
143 self._widgets.apply.props.visible = self._allow_edit
175 self._update_apply_button()
144176
145177 if not self._allow_edit:
146178 self._set_message(_('You don\'t have permissions to change greeter configuration'),
154186 'or "pkexec"').format(path=helpers.get_config_path()),
155187 message_type=Gtk.MessageType.WARNING)
156188
157 if self.mode == WindowMode.Embedded:
158 self.on_entry_changed = self.on_entry_changed_embedded
159 self._widgets.buttons.hide()
160 self._widgets.content.reorder_child(self._widgets.infobar, 0)
161 # Socket/Plug focus issues workaround
162 self._widgets.multihead_label.connect('button-press-event', self.on_multihead_click)
163 elif self.mode == WindowMode.GtkHeader:
164 for button in (self._widgets.apply, self._widgets.reload):
165 self._widgets.buttons.remove(button)
166 button.set_label('')
167 button.set_always_show_image(True)
168 self._widgets.buttons.hide()
169
170 header = Gtk.HeaderBar()
171 header.set_show_close_button(True)
172 header.props.title = self.get_title()
173 header.pack_start(self._widgets.reload)
174 header.pack_start(self._widgets.apply)
175 header.show_all()
176
177 self.set_titlebar(header)
178
179189 self._read()
180190
181191 def _set_message(self, message, type_=Gtk.MessageType.INFO):
186196 self._widgets.infobar_label.props.label = message
187197 self._widgets.infobar.show()
188198
199 def _update_apply_button(self):
200 allow = (self._allow_edit and
201 (self._changed_entries or self._new_entries or self._removed_entries))
202 self._widgets.apply.props.sensitive = allow
203
189204 def _read(self):
190205 self._config.read()
191206 self._changed_entries = None
207 self._new_entries = None
208 self._removed_entries = None
192209
193210 for group in self._groups:
194211 group.read(self._config)
195212
196213 self._initial_values = {entry: InitialValue(entry.value, entry.enabled)
197214 for entry in self._initial_values.keys()}
215
198216 self._changed_entries = set()
199 self._widgets.apply.props.sensitive = False
217 self._new_entries = set()
218 self._removed_entries = set()
219
220 self._update_apply_button()
200221
201222 def _write(self):
223 changed = self._changed_entries | self._new_entries
202224 for group in self._groups:
203 group.write(self._config, self._changed_entries.__contains__)
225 group.write(self._config, changed.__contains__)
204226
205227 if self.mode != WindowMode.Embedded:
206 for entry in self._changed_entries:
228 for entry in changed:
207229 self._initial_values[entry] = InitialValue(entry.value, entry.enabled)
208230
209 self._changed_entries.clear()
210 self._widgets.apply.props.sensitive = False
231 self._changed_entries.clear()
232 self._new_entries.clear()
233 self._removed_entries.clear()
211234
212235 try:
213236 self._config.write()
214237 except OSError as e:
215238 helpers.show_message(e, Gtk.MessageType.ERROR)
216239
217 def _get_default_monitors_options(self):
218 return {key: self._group_greeter.entries[key].value or self._group_greeter.defaults[key]
219 for key in ('background', 'user-background')}
220
221 def on_entry_added(self, group, entry, key):
222 if isinstance(group, SimpleGroup) and (group.name, key) in self.entries_setup:
223 for action in self.entries_setup[(group.name, key)]:
224 fname = 'on_entry_%s_%s_%s' % (action, group.name, key)
240 self._update_apply_button()
241
242 _write_timeout_id = None
243
244 def _write_embedded(self, delay=750):
245 if self._write_timeout_id:
246 GLib.Source.remove(self._write_timeout_id)
247 if delay:
248 self._write_timeout_id = GLib.timeout_add(delay, self.on_write_timeout)
249 else:
250 self._write_timeout_id = None
251 self.on_write_timeout()
252
253 def on_write_timeout(self):
254 self._write_timeout_id = None
255 self.__class__._write(self)
256 return False
257
258 def on_entry_added(self, group, source, entry, key):
259 if isinstance(source, SimpleGroup) and (source.name, key) in self.entries_setup:
260 for action in self.entries_setup[(source.name, key)]:
261 fname = 'on_entry_%s_%s_%s' % (action, source.name, key)
225262 f = getattr(self, fname.replace('-', '_'))
226263 if action == 'setup':
227264 f(entry)
228265 else:
229266 entry.connect(action, f)
230267
231 label_holder = entry.widgets['label_holder']
232 if label_holder and isinstance(group, SimpleGroup):
233 label_holder.connect('button-press-event', self.on_entry_label_clicked,
234 entry, group, key)
235
268 entry.show_menu.connect(self.on_show_menu, source, key)
236269 entry.changed.connect(self.on_entry_changed)
270
237271 self._initial_values[entry] = InitialValue(entry.value, entry.enabled)
238 self.on_entry_changed(entry, force=True)
239
240 def on_entry_removed(self, group, entry, key):
241 self._initial_values.pop(entry)
242
272 self.on_entry_changed(entry, forced=True)
273
274 if self._new_entries is not None:
275 self._new_entries.add(entry)
276
277 def on_entry_removed(self, source, group, entry, key):
243278 if self._changed_entries is None:
244279 return
245280
246 self._changed_entries.discard(entry)
247 self._widgets.apply.props.sensitive = self._allow_edit and self._changed_entries
248
249 def on_entry_changed(self, entry, force=False):
281 self._initial_values.pop(entry, None)
282 if entry in self._new_entries:
283 self._new_entries.discard(entry)
284 self._changed_entries.discard(entry)
285 else:
286 self._removed_entries.add(entry)
287
288 self._update_apply_button()
289
290 def on_entry_removed_embedded(self, source, group, entry, key):
291 if self._removed_entries is None:
292 return
293
294 self._initial_values.pop(entry, None)
295 self._removed_entries.add(entry)
296 if self._allow_edit:
297 self._write()
298
299 def on_entry_changed(self, entry, forced=False):
250300 if self._changed_entries is None:
251301 return
252302
253303 initial = self._initial_values[entry]
254 if force or entry.enabled != initial.enabled or \
304 if forced or entry.enabled != initial.enabled or \
255305 (entry.enabled and entry.value != initial.value):
256306 self._changed_entries.add(entry)
257307 else:
258308 self._changed_entries.discard(entry)
259309
260 self._widgets.apply.props.sensitive = self._allow_edit and self._changed_entries
261
262 def on_entry_changed_embedded(self, entry, force=False):
263 if self._changed_entries is None:
264 return
265
266 initial = self._initial_values[entry]
267 if force or entry.enabled != initial.enabled or \
268 (entry.enabled and entry.value != initial.value):
310 self._update_apply_button()
311
312 def on_entry_changed_embedded(self, entry, forced=False):
313 if self._changed_entries is not None:
269314 self._changed_entries.add(entry)
270 else:
271 self._changed_entries.discard(entry)
272
273 if self._allow_edit:
274 self._write()
315 if self._allow_edit:
316 self._write()
275317
276318 def on_entry_reset_clicked(self, item):
277319 entry, value, enabled = item._reset_entry_data
284326 entry, action = item._fix_entry_data
285327 action(entry)
286328
287 def on_entry_label_clicked(self, widget, event, entry, group, key):
288 if event.button != 3:
289 return
329 def on_show_menu(self, entry, group, key):
290330
291331 def new_item(activate=None, width=90):
292332 item = Gtk.MenuItem('')
303343 if not self._entry_menu:
304344 class EntryMenu:
305345 menu = Gtk.Menu()
346 group = new_item()
306347 value = new_item()
307348 file = new_item()
308349 error_separator = Gtk.SeparatorMenuItem()
313354 default = new_item(self.on_entry_reset_clicked)
314355 other = []
315356
357 menu.append(group)
316358 menu.append(value)
317359 menu.append(file)
318360 menu.append(error_separator)
337379
338380 menu = self._entry_menu
339381
340 menu.value.props.label = '{key} = {value}'.format(
341 group=group.name,
342 key=key,
343 value=format_value(value=entry.value, enabled=entry.enabled))
344
382 # [group]
383 if group.name:
384 menu.group.props.label = '[{group}]'.format(group=group.name)
385 menu.group.show()
386 else:
387 menu.group.hide()
388
389 # key = value
390 if entry.enabled:
391 menu.value.props.label = '{key} = {value}'.format(
392 key=key, value=format_value(value=entry.value, enabled=entry.enabled))
393 else:
394 menu.value.props.label = '# {key} ='.format(key=key)
395
396 # File with key definition
345397 config_values = self._config.key_values[group.name, key]
346
347398 if entry not in self._changed_entries and \
348399 config_values and config_values[-1][0] != helpers.get_config_path():
349400 menu.file.props.label = _('Value defined in file: {path}')\
353404 else:
354405 menu.file.hide()
355406
407 # Error message
356408 error = entry.error
357409 error_action = None
358410 if error:
370422 menu.error_action.props.visible = error_action is not None
371423 menu.error_separator.props.visible = error_action is not None
372424
373 if entry in self._changed_entries:
374 initial = self._initial_values[entry]
375
425 # Reset to initial value
426 initial = self._initial_values[entry]
427 if initial.enabled != entry.enabled or initial.value != entry.value:
376428 if entry.enabled != initial.enabled and not initial.enabled:
377429 menu.initial._reset_entry_data = entry, None, initial.enabled
378430 else:
386438 else:
387439 menu.initial.props.visible = False
388440
441 # Reset to default value
389442 default = group.defaults[key]
390443 if default is not None and entry.value != default:
391444 value = format_value(value=default)
397450 else:
398451 menu.default.props.visible = False
399452
453 # Reset to values from all other (.conf
400454 item_idx = 0
401455 if config_values and len(config_values) > 1:
402456 values = {None, default, self._initial_values[entry].value, entry.value}
545599 return True
546600 return False
547601
548 def on_destroy(self, *unused):
602 def on_destroy(self, widget, write=False):
603 if write and self._write_timeout_id:
604 self._write_embedded(delay=None)
549605 Gtk.main_quit()
550606
551607 def on_apply_clicked(self, *unused):
1515 # with this program. If not, see <http://www.gnu.org/licenses/>.
1616
1717
18 from collections import OrderedDict
19
20 from gi.repository import Gtk
21
18 from lightdm_gtk_greeter_settings.MultiheadSetupDialog import MultiheadSetupDialog
19 from lightdm_gtk_greeter_settings import (
20 helpers,
21 OptionEntry,
22 OptionGroup)
2223 from lightdm_gtk_greeter_settings.helpers import (
23 bool2string,
2424 WidgetsWrapper)
25 from lightdm_gtk_greeter_settings.MultiheadSetupDialog import MultiheadSetupDialog
26 from lightdm_gtk_greeter_settings.OptionEntry import BaseEntry
27 from lightdm_gtk_greeter_settings.OptionGroup import BaseGroup
2825
2926
3027 __all__ = ['MonitorsGroup']
3128
3229
33 class MonitorsGroup(BaseGroup):
34 GROUP_PREFIX = 'monitor:'
30 class MonitorsGroup(OptionGroup.BaseGroup):
3531
36 def __init__(self, widgets, defaults_callback=None):
32 GroupPrefix = 'monitor:'
33 EntriesSetup = (('name', OptionEntry.StringEntry),
34 ('background', OptionEntry.BackgroundEntry),
35 ('user-background', OptionEntry.BooleanEntry),
36 ('laptop', OptionEntry.BooleanEntry))
37
38 def __init__(self, widgets):
3739 super().__init__(widgets)
38 self._entries = OrderedDict()
39 self._widgets = WidgetsWrapper(widgets, 'multihead')
40 self._widgets['label'].connect('activate-link', self._on_label_link_activate)
40 self._widgets = helpers.WidgetsWrapper(widgets)
41 self._groups = []
42 self._adapters = None
4143 self._dialog = None
42 self._get_defaults_callback = defaults_callback
44
45 self._groups_wrapper = helpers.SimpleDictWrapper(
46 deleter=self._remove_group,
47 add=self._add_group,
48 itergetter=lambda: iter(self._groups))
49
50 self._widgets['multihead_label'].connect('activate-link', self._on_label_link_activate)
4351
4452 def read(self, config):
45 self._entries.clear()
46 for name, group in config.items():
47 if not name.startswith(self.GROUP_PREFIX):
53 for group in self._groups:
54 group.clear()
55 self._groups.clear()
56
57 for groupname in config:
58 if not groupname.startswith(MonitorsGroup.GroupPrefix):
4859 continue
49 name = name[len(self.GROUP_PREFIX):].strip()
50 entry = MonitorEntry(self._widgets)
51 entry['background'] = group['background']
52 entry['user-background'] = bool2string(group['user-background'], True)
53 entry['laptop'] = bool2string(group['laptop'], True)
54 self._entries[name] = entry
55 self.entry_added.emit(entry, name)
60 if not self._adapters:
61 self._adapters = {key: OptionGroup.OneToManyEntryAdapter()
62 for key, __ in self.EntriesSetup}
5663
57 def write(self, config, changed=None):
58 groups = set(name for name, __ in self._entries.items())
64 monitor = groupname[len(MonitorsGroup.GroupPrefix):].strip()
65 self._add_group(monitor, groupname, config)
66
67 def write(self, config, is_changed=None):
68 groups = set(group.entries['name'].value for group in self._groups)
5969 groups_to_remove = tuple(name for name in config
60 if (name.startswith(self.GROUP_PREFIX) and
61 name[len(self.GROUP_PREFIX):].strip() not in groups))
70 if (name.startswith(self.GroupPrefix) and
71 name[len(self.GroupPrefix):].strip() not in groups))
6272
63 for name, entry in self._entries.items():
64 if changed and not changed(entry):
65 continue
66 groupname = '{prefix} {name}'.format(prefix=self.GROUP_PREFIX, name=name.strip())
67 group = config.add_group(groupname)
68 for key, value in entry:
69 group[key] = value
73 for group in self._groups:
74 name = group.entries['name']
75 new_name = self.GroupPrefix + ' ' + name.value
76
77 if group.name == new_name:
78 def changed_(entry):
79 if entry == name:
80 return False
81 return not is_changed or is_changed(entry)
82 else:
83 def changed_(entry):
84 return entry != name
85
86 group.name = new_name
87 group.write(config, is_changed=changed_)
7088
7189 for name in groups_to_remove:
72 del config[name]
90 config_group = config[name]
91 for key, *__ in self.EntriesSetup:
92 del config_group[key]
93
94 def clear(self):
95 if not self._groups and not self._adapters:
96 return
97 for group in self._groups:
98 group.clear()
99 self._groups.clear()
100
101 def activate(self, key, entry):
102 self._adapters[key].activate(entry)
103
104 @property
105 def groups(self):
106 return self._groups_wrapper
107
108 def _add_group(self, monitor='', groupname='', config=None):
109 group = OptionGroup.SimpleGroup(groupname, self._widgets)
110
111 group.entry_added.connect(lambda g, s, e, k: self.entry_added.emit(s, e, k))
112 group.entry_removed.connect(lambda g, s, e, k: self.entry_removed.emit(s, e, k))
113
114 group.options = {key: (adapter.new_entry, None)
115 for key, adapter in self._adapters.items()}
116
117 if config:
118 group.read(config)
119
120 name = group.entries['name']
121 name.enabled = True
122 name.value = monitor
123
124 self._groups.append(group)
125 return group
126
127 def _remove_group(self, group):
128 group.clear()
129 self._groups.remove(group)
73130
74131 def _on_label_link_activate(self, label, uri):
75132 if not self._dialog:
76 self._dialog = MultiheadSetupDialog()
77 self._dialog.props.transient_for = self._widgets['label'].get_toplevel()
133 self._dialog = MultiheadSetupDialog(self)
134 self._dialog.props.transient_for = self._widgets['multihead_label'].get_toplevel()
78135
79 if self._get_defaults_callback:
80 self._dialog.set_defaults(self._get_defaults_callback())
136 for key, klass in self.EntriesSetup:
137 self._adapters[key].base_entry = klass(WidgetsWrapper(self._dialog.builder, key))
81138
82 self._dialog.set_model(self._entries)
83
84 if self._dialog.run() == Gtk.ResponseType.OK:
85 current_names = set(self._entries.keys())
86 for name, values in self._dialog.get_model():
87 if name in self._entries:
88 self._entries[name].assign(values)
89 current_names.discard(name)
90 else:
91 entry = MonitorEntry(self._widgets, values)
92 self._entries[name] = entry
93 self.entry_added.emit(entry, name)
94 for name in current_names:
95 self.entry_added.emit(self._entries.pop(name), name)
139 self._dialog.run()
96140 self._dialog.hide()
97141 return True
98
99
100 class MonitorEntry(BaseEntry):
101
102 def __init__(self, widgets, values=None):
103 super().__init__(widgets)
104 self._values = values or {}
105
106 def _get_value(self):
107 return self._values.copy()
108
109 def _set_value(self, value):
110 self._values = value.copy()
111
112 def __getitem__(self, key):
113 return self._values[key]
114
115 def __setitem__(self, key, value):
116 self._values[key] = value
117
118 def __iter__(self):
119 return iter(self._values.items())
120
121 def assign(self, values):
122 if not self._values == values:
123 self._values.update(values)
124 self._emit_changed()
1515 # with this program. If not, see <http://www.gnu.org/licenses/>.
1616
1717
18 from collections import defaultdict
19
1820 from gi.repository import (
1921 Gdk,
2022 Gtk)
2123
2224 import lightdm_gtk_greeter_settings.helpers
23
2425 from lightdm_gtk_greeter_settings.helpers import (
2526 check_path_accessibility,
2627 get_data_path,
27 WidgetsEnum,
28 WidgetsWrapper)
29 from lightdm_gtk_greeter_settings import OptionEntry
28 WidgetsEnum)
3029
3130 from gi.overrides import GLib
3231
3433 __all__ = ['MultiheadSetupDialog']
3534
3635 C_ = lambda t: lightdm_gtk_greeter_settings.helpers.C_('option|multihead', t)
37
38
39 class MonitorConfig:
40 name = None
41 background = None
42 background_disabled = None
43 user_background = None
44 user_background_disabled = None
45 laptop = None
46 laptop_disabled = None
47 label = None
4836
4937
5038 class MultiheadSetupDialog(Gtk.Dialog):
5139 __gtype_name__ = 'MultiheadSetupDialog'
5240
53 def __new__(cls):
41 def __new__(cls, monitors):
5442 builder = Gtk.Builder()
5543 builder.add_from_file(get_data_path('%s.ui' % cls.__gtype_name__))
5644 window = builder.get_object('multihead_setup_dialog')
5745 window.builder = builder
46 window.monitors = monitors
5847 builder.connect_signals(window)
5948 window.init_window()
6049 return window
7261 empty_add_button = 'empty_add_button'
7362 empty_add_menu_button = 'empty_add_menu_button'
7463
64 class PageData:
65 group = None
66 holder = None
67 label = None
68 ids = None
69 # Entries
70 name = None
71 background = None
72
7573 builder = None
74 monitors = None
7675
7776 def init_window(self):
7877 self._widgets = self.Widgets(builder=self.builder)
8483
8584 self._current_page = None
8685 self._defaults = {}
87 self._configs = {}
88
89 self._option_name = OptionEntry.StringEntry(WidgetsWrapper(self.builder, 'name'))
90 self._option_bg = OptionEntry.BackgroundEntry(WidgetsWrapper(self.builder, 'background'))
91 self._option_user_bg = OptionEntry.BooleanEntry(WidgetsWrapper(self.builder,
92 'user-background'))
93 self._option_laptop = OptionEntry.BooleanEntry(WidgetsWrapper(self.builder, 'laptop'))
94
95 self._option_name.changed.connect(self._on_name_changed)
96 for entry in (self._option_bg, self._option_user_bg, self._option_laptop):
97 entry.changed.connect(self._on_option_changed)
86 self._page_to_data = {}
9887
9988 screen = Gdk.Screen.get_default()
10089 self._available_monitors = [(screen.get_monitor_plug_name(i),
11099 self._widgets.available_menu.append(item)
111100 item.connect('activate', self.on_add_button_clicked, name)
112101
113 def set_model(self, values):
102 def run(self):
103 editor_parent = self._widgets.editor.get_parent()
104 if editor_parent:
105 editor_parent.remove(self._widgets.editor)
106
114107 self._widgets.notebook.handler_block_by_func(self.on_switch_page)
115108 for page in self._widgets.notebook.get_children():
116 self._remove_page(page, update=False)
109 if page != self._widgets.empty:
110 self._widgets.notebook.remove_page(self._widgets.notebook.page_num(page))
117111 self._widgets.notebook.handler_unblock_by_func(self.on_switch_page)
118112
119 self._configs.clear()
120 for name, entry in values.items():
121 config = MonitorConfig()
122 config.name = name
123 config.background = entry['background']
124 config.user_background = entry['user-background']
125 config.laptop = entry['laptop']
126
127 config.background_disabled = self._get_first_not_none(
128 config.background, self._defaults.get('background'), '')
129 config.user_background_disabled = self._get_first_not_none(
130 config.user_background, self._defaults.get('user-background'), 'true')
131 config.laptop_disabled = self._get_first_not_none(
132 config.laptop, self._defaults.get('laptop'), 'false')
133
134 self._add_page(config)
135
136 self._widgets.empty.props.visible = not self._configs
137
113 for data in self._page_to_data.values():
114 for entry, ids in data.ids.items():
115 for id_ in ids:
116 entry.disconnect(id_)
117
118 self._page_to_data.clear()
119
120 for group in self.monitors.groups:
121 self._add_page(group)
122
123 self._widgets.empty.props.visible = not self._page_to_data
138124 self._update_monitors_list()
139125
140 def get_model(self):
141 sections = []
142 for page in self._widgets.notebook.get_children():
143 config = self._configs.get(page)
144 if not config or not config.name:
145 continue
146 sections.append((config.name,
147 {'background': config.background,
148 'user-background': config.user_background,
149 'laptop': config.laptop}))
150 return sections
151
152 def set_defaults(self, values):
153 self._defaults = values.copy()
154
155 def _get_first_not_none(self, *values, fallback=None):
156 return next((v for v in values if v is not None), fallback)
157
158 def _add_page(self, config):
159 holder = Gtk.Box()
160 holder.show()
161
162 config.label = Gtk.Label(config.name)
163 config.error_image = Gtk.Image.new_from_icon_name('dialog-warning', Gtk.IconSize.INVALID)
164 config.error_image.set_pixel_size(16)
165 config.error_image.set_no_show_all(True)
126 super().run()
127
128 def _add_page(self, group):
129 data = self.PageData()
130 data.group = group
131
132 data.holder = Gtk.Box()
133 data.holder.show()
134 data.label = Gtk.Label(group.entries['name'].value)
166135
167136 close_button = Gtk.Button()
168137 close_button.set_focus_on_click(False)
169138 close_button.set_relief(Gtk.ReliefStyle.NONE)
170 close_button.connect('clicked', lambda w, p: self._remove_page(p), holder)
139 close_button.connect('clicked', lambda w, p: self._remove_page(p), data.holder)
171140 close_image = Gtk.Image.new_from_icon_name('stock_close', Gtk.IconSize.INVALID)
172141 close_image.set_pixel_size(16)
173142 close_button.add(close_image)
174143
175144 label_box = Gtk.Box(Gtk.Orientation.HORIZONTAL)
176 label_box.pack_start(config.error_image, False, False, 1)
177 label_box.pack_start(config.label, False, False, 3)
145 label_box.pack_start(data.label, False, False, 3)
178146 label_box.pack_start(close_button, False, False, 0)
179147
180148 label_eventbox = Gtk.EventBox()
181149 label_eventbox.add(label_box)
182150 label_eventbox.show_all()
183151
184 self._configs[holder] = config
152 data.name = group.entries['name']
153 data.background = group.entries['background']
154
155 data.ids = defaultdict(list)
156 data.ids[data.name].append(data.name.changed.connect(self._on_name_changed, data))
157 data.ids[data.background].append(
158 data.background.changed.connect(self._on_background_changed, data))
159
160 self._on_name_changed(data.name, data)
161 self._on_background_changed(data.background, data)
162
163 self._page_to_data[data.holder] = data
185164
186165 if self._widgets.empty.get_parent():
187166 self._widgets.empty.hide()
188167
189168 current_idx = self._widgets.notebook.get_current_page()
190 current_idx = self._widgets.notebook.insert_page(holder, label_eventbox, current_idx + 1)
169 current_idx = self._widgets.notebook.insert_page(data.holder, label_eventbox,
170 current_idx + 1)
191171
192172 return current_idx
193173
194 def _remove_page(self, page, update=True):
195 if not self._configs.pop(page, None):
196 return
197
174 def _remove_page(self, page):
198175 if page == self._widgets.editor.props.parent:
199176 page.remove(self._widgets.editor)
200177
201 if update:
202 self._update_monitors_list()
203 if not self._configs:
204 self._widgets.empty.show()
205
206178 self._widgets.notebook.remove_page(self._widgets.notebook.page_num(page))
207179
180 del self.monitors.groups[self._page_to_data[page].group]
181 del self._page_to_data[page]
182
183 self._update_monitors_list()
184 if not self._page_to_data:
185 self._widgets.empty.show()
186
208187 def _update_monitors_list(self):
209 configs = set(config.name for config in self._configs.values())
188 configs = set(group.entries['name'].value for group in self.monitors.groups)
210189 used_count = 0
211190 self._widgets.name_combo.get_model().clear()
212191 for name, item in self._available_monitors:
223202 self._widgets.editor_add_menu_button.props.visible = show_button
224203 self._widgets.empty_add_menu_button.props.visible = show_button
225204
226 def _on_option_changed(self, entry=None):
227 config = self._configs.get(self._current_page)
228 config.background_disabled = self._option_bg.value
229 config.background = config.background_disabled if self._option_bg.enabled else None
230 config.user_background_disabled = self._option_user_bg.value
231 config.user_background = (config.user_background_disabled
232 if self._option_user_bg.enabled else None)
233 config.laptop_disabled = self._option_laptop.value
234 config.laptop = config.laptop_disabled if self._option_laptop.enabled else None
235
236 if not config.background or Gdk.RGBA().parse(config.background):
237 self._option_bg.error = None
238 else:
239 self._option_bg.error = check_path_accessibility(config.background)
240
241 def _on_name_changed(self, entry=None):
242 config = self._configs[self._current_page]
243 config.name = self._option_name.value.strip()
244 self._update_monitors_list()
245
205 def _on_name_changed(self, entry, data):
206 value = entry.value
246207 markup = None
247208 error = None
248209
249 if not config.name:
210 if not value:
250211 markup = C_('<i>No name</i>')
251212 error = C_('The name can\'t be empty. Configuration will not be saved.')
252 elif any(config.name == o.name and p != self._current_page
253 for p, o in self._configs.items()):
213 elif any(data.name != entry and data.name.value == value
214 for data in self._page_to_data.values()):
254215 error = (C_('"{name}" is already defined. Only last configuration will be saved.')
255 .format(name=config.name))
216 .format(name=value))
256217
257218 if markup:
258 config.label.set_markup(markup)
219 data.label.set_markup(markup)
259220 else:
260 config.label.set_label(config.name)
261
262 self._option_name.error = error
221 data.label.set_label(value)
222
223 data.name.error = error
224 self._update_monitors_list()
225
226 def _on_background_changed(self, entry, data):
227 value = entry.value
228 if not value or Gdk.RGBA().parse(value):
229 entry.error = None
230 else:
231 entry.error = check_path_accessibility(value)
263232
264233 def _focus_name_entry(self):
265234 self._widgets.name.grab_focus()
266235 self._widgets.name.set_position(0)
267236
268 def on_add_button_clicked(self, widget, name=""):
269 config = MonitorConfig()
270 config.name = name
271
272 config.background_disabled = self._get_first_not_none(
273 self._defaults.get('background'), '')
274 config.user_background_disabled = self._get_first_not_none(
275 self._defaults.get('user-background'), 'true')
276 config.laptop_disabled = self._get_first_not_none(self._defaults.get('laptop'), 'false')
277
278 self._widgets.notebook.set_current_page(self._add_page(config))
237 def on_add_button_clicked(self, widget, name=''):
238 group = self.monitors.groups.add(name)
239 page_idx = self._add_page(group)
240
241 self._widgets.empty.props.visible = not self._page_to_data
242 self._widgets.notebook.set_current_page(page_idx)
279243 if name:
280244 self._update_monitors_list()
281245
282 def aaa(self, *args):
283 print(self._widgets.notebook.get_current_page(), args)
284
285246 def on_switch_page(self, notebook, page, page_idx):
286247 if page == self._widgets.empty:
287 self._current_page = None
288248 buttons = self._widgets.editor_add_menu_button, self._widgets.empty_add_menu_button
289249 else:
290 self._current_page = page
291250 buttons = self._widgets.empty_add_menu_button, self._widgets.editor_add_menu_button
292251
293252 old_parent = self._widgets.editor.get_parent()
295254 old_parent.remove(self._widgets.editor)
296255 page.add(self._widgets.editor)
297256
298 config = self._configs[page]
299 for entry, value, fallback in \
300 ((self._option_bg, config.background, config.background_disabled),
301 (self._option_user_bg, config.user_background, config.user_background_disabled),
302 (self._option_laptop, config.laptop, config.laptop_disabled)):
303 entry.handler_block_by_func(self._on_option_changed)
304 entry.value = fallback if value is None else value
305 entry.enabled = value is not None
306 entry.handler_unblock_by_func(self._on_option_changed)
307
308 self._option_name.value = config.name or ''
309 self._on_option_changed()
310 self._on_name_changed()
257 data = self._page_to_data[page]
258 for key, *__ in self.monitors.EntriesSetup:
259 self.monitors.activate(key, data.group.entries[key])
311260
312261 GLib.idle_add(self._focus_name_entry)
313262
5454 if self.__use:
5555 self.__use.connect('notify::active', self.__on_use_toggled)
5656
57 label_holder = widgets['label_holder']
58 if label_holder:
59 label_holder.connect('button-press-event', self.__on_label_clicked)
60
5761 self.__error = widgets['error']
5862
5963 @property
100104
101105 @GObject.Signal(flags=GObject.SIGNAL_RUN_CLEANUP)
102106 def set(self, value: str) -> str:
107 pass
108
109 @GObject.Signal('show-menu')
110 def show_menu(self):
103111 pass
104112
105113 def __repr__(self):
147155
148156 def __on_use_toggled(self, toggle, *unused):
149157 self._set_enabled(self.__use.props.active)
158
159 def __on_label_clicked(self, widget, event):
160 if event.button == 3:
161 self.show_menu.emit()
150162
151163
152164 class BooleanEntry(BaseEntry):
2424
2525 __all__ = [
2626 'BaseGroup',
27 'OneToManyEntryAdapter',
2728 'SimpleGroup']
2829
2930
3940 '''Read group content from specified GreeterConfig object'''
4041 raise NotImplementedError(self.__class__)
4142
42 def write(self, config, changed=None):
43 def write(self, config, is_changed=None):
4344 '''Writes content of this group to specified GreeterConfig object'''
45 raise NotImplementedError(self.__class__)
46
47 def clear(self):
48 '''Removes all entries'''
4449 raise NotImplementedError(self.__class__)
4550
4651 @property
6065 raise NotImplementedError(self.__class__)
6166
6267 @GObject.Signal
63 def entry_added(self, entry: BaseEntry, key: str):
68 def entry_added(self, source: object, entry: BaseEntry, key: str):
6469 '''New entry has been added to this group'''
6570 pass
6671
6772 @GObject.Signal
68 def entry_removed(self, entry: BaseEntry, key: str):
73 def entry_removed(self, source: object, entry: BaseEntry, key: str):
6974 '''Entry has been removed from this group'''
7075 pass
7176
7277
7378 class SimpleGroup(BaseGroup):
7479
75 def __init__(self, name, widgets, options):
80 def __init__(self, name, widgets, options=None):
7681 super().__init__(widgets)
7782 self._name = name
78 self._widgets = WidgetsWrapper(widgets, name)
79 self._options = options
83 self._options_to_init = options
84 self._widgets = WidgetsWrapper(widgets)
8085 self._entries = {}
8186 self._defaults = {}
8287
8489 def name(self):
8590 return self._name
8691
92 @name.setter
93 def name(self, value):
94 self._name = value
95
96 @property
97 def options(self):
98 pass
99
100 @options.setter
101 def options(self, options):
102 self.clear()
103
104 for key, (klass, default) in options.items():
105 entry = klass(WidgetsWrapper(self._widgets, key))
106 if default is not None:
107 entry.value = default
108 self._entries[key] = entry
109 self._defaults[key] = default
110 self.entry_added.emit(self, entry, key)
111
87112 def read(self, config):
88 if not self._entries:
89 for key, (klass, default) in self._options.items():
90 entry = klass(WidgetsWrapper(self._widgets, key))
91 self._entries[key] = entry
92 self._defaults[key] = default
93 self.entry_added.emit(entry, key)
113 if self._options_to_init is not None:
114 self.options = self._options_to_init
115 self._options_to_init = None
94116
95117 for key, entry in self._entries.items():
96118 value = config[self._name, key]
97119 entry.value = value if value is not None else self._defaults[key]
98120 entry.enabled = value is not None
99121
100 def write(self, config, changed=None):
122 def write(self, config, is_changed=None):
101123 for key, entry in self._entries.items():
102 if changed and not changed(entry):
124 if is_changed and not is_changed(entry):
103125 continue
104126 config[self._name, key] = entry.value if entry.enabled else None, self._get_default(key)
105127
128 def clear(self):
129 if not self._entries:
130 return
131 for key, entry in self._entries.items():
132 self.entry_removed.emit(self, entry, key)
133 self._entries.clear()
134
106135 def _get_entry(self, key):
107136 return self._entries.get(key)
108137
109138 def _get_default(self, key):
110139 return self._defaults.get(key)
140
141
142 class OneToManyEntryAdapter:
143
144 class Error(Exception):
145 pass
146
147 class InvalidEntry(Error):
148 pass
149
150 class WrongAdapter(Error):
151 pass
152
153 class NoBaseEntryError(Error):
154 pass
155
156 class EntryWrapper(BaseEntry):
157
158 def __init__(self, adapter):
159 super().__init__(helpers.WidgetsWrapper(None))
160 self._adapter = adapter
161 self._value = None
162 self._error = None
163 self._enabled = False
164
165 def _get_value(self):
166 return self._value
167
168 def _set_value(self, value):
169 self._value = value
170 if self._adapter._active == self and self._adapter._base_entry:
171 self._adapter._base_entry._set_value(value)
172
173 def _get_error(self):
174 return self._error
175
176 def _set_error(self, text):
177 self._error = text
178 if self._adapter._active == self and self._adapter._base_entry:
179 self._adapter._base_entry._set_error(text)
180
181 def _get_enabled(self):
182 return self._enabled
183
184 def _set_enabled(self, value):
185 self._enabled = value
186 if self._adapter._active == self and self._adapter._base_entry:
187 self._adapter._base_entry._set_enabled(value)
188
189 def __init__(self, entry=None):
190 self._base_entry = None
191 self._active = None
192
193 self._on_changed_id = None
194 self._on_menu_id = None
195
196 self.base_entry = entry
197
198 @property
199 def base_entry(self):
200 return self._base_entry
201
202 @base_entry.setter
203 def base_entry(self, entry):
204 if self._base_entry:
205 self._base_entry.disconnect(self._on_changed_id)
206 self._base_entry.disconnect(self._on_menu_id)
207 self._on_changed_id = None
208 self._on_menu_id = None
209 self._base_entry = entry
210 if entry:
211 self._on_changed_id = entry.changed.connect(self._on_changed)
212 self._on_menu_id = entry.show_menu.connect(self._on_show_menu)
213
214 def new_entry(self, widgets=None):
215 return OneToManyEntryAdapter.EntryWrapper(self)
216
217 def activate(self, entry):
218 if not isinstance(entry, OneToManyEntryAdapter.EntryWrapper):
219 raise OneToManyEntryAdapter.InvalidEntry()
220 if not entry._adapter == self:
221 raise OneToManyEntryAdapter.WrongAdapter()
222 if not self._base_entry:
223 raise OneToManyEntryAdapter.NoBaseEntryError()
224
225 self._active = entry
226 with self._base_entry.handler_block(self._on_changed_id):
227 self._base_entry._set_value(entry._value)
228 self._base_entry._set_enabled(entry._enabled)
229 self._base_entry._set_error(entry._error)
230
231 def _on_changed(self, entry):
232 if self._active:
233 self._active._enabled = entry._get_enabled()
234 self._active._value = entry._get_value()
235 self._active._emit_changed()
236
237 def _on_show_menu(self, base_entry):
238 if self._active:
239 self._active.show_menu.emit()
3636
3737 if args.test_socket:
3838 w = Gtk.Window()
39 w.props.title = 'Testing embedded mode'
3940 socket = Gtk.Socket.new()
4041 w.add(socket)
4142 w.show_all()
330330 _prefixes = None
331331
332332 def __init__(self, source, *prefixes):
333 if source is None:
334 return
333335 if isinstance(source, Gtk.Builder):
334336 self._builder = source
335337 self._prefixes = tuple(prefixes)
340342 raise TypeError(source)
341343
342344 def __getitem__(self, args):
345 if not self._builder:
346 return None
343347 if not isinstance(args, tuple):
344348 args = (args,)
345349 return self._builder.get_object('_'.join(chain(self._prefixes, args)))
346350
351 @property
352 def path(self):
353 return '_'.join(self._prefixes) if self._prefixes else ''
354
347355
348356 class TreeStoreDataWrapper(GObject.Object):
349357
354362
355363 class SimpleDictWrapper:
356364
357 def __init__(self, getter):
365 def __init__(self, getter=None, setter=None, add=None, deleter=None, itergetter=None):
358366 self._getter = getter
367 self._setter = setter
368 self._deleter = deleter
369 self._itergetter = itergetter
370 self._add = add
359371
360372 def __getitem__(self, key):
361373 return self._getter(key)
374
375 def __setitem__(self, key, value):
376 return self._setter(key, value)
377
378 def __delitem__(self, key):
379 return self._deleter(key)
380
381 def __iter__(self):
382 return self._itergetter()
383
384 def add(self, value):
385 return self._add(value)