Multihead setup: each option is Entry now, no cancel button; delay before saving config in embedded mode (750ms)
Andrew P.
9 years ago
16 | 16 | <property name="visible">True</property> |
17 | 17 | <property name="can_focus">False</property> |
18 | 18 | <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> | |
25 | 19 | </object> |
26 | 20 | <object class="GtkImage" id="image3"> |
27 | 21 | <property name="visible">True</property> |
55 | 49 | <object class="GtkButtonBox" id="dialog-action_area"> |
56 | 50 | <property name="can_focus">False</property> |
57 | 51 | <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> | |
73 | 52 | <child> |
74 | 53 | <object class="GtkButton" id="ok_button"> |
75 | 54 | <property name="label" translatable="yes" context="button">_OK</property> |
175 | 154 | </packing> |
176 | 155 | </child> |
177 | 156 | <child> |
178 | <object class="GtkGrid" id="editor_page"> | |
157 | <object class="GtkBox" id="editor_page"> | |
179 | 158 | <property name="visible">True</property> |
180 | 159 | <property name="can_focus">False</property> |
181 | 160 | <property name="margin_left">8</property> |
182 | 161 | <property name="margin_right">8</property> |
183 | 162 | <property name="margin_top">8</property> |
184 | 163 | <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> | |
298 | 166 | <child> |
299 | 167 | <object class="GtkBox" id="monitor_name_box"> |
300 | 168 | <property name="visible">True</property> |
301 | 169 | <property name="can_focus">False</property> |
302 | 170 | <property name="hexpand">True</property> |
171 | <property name="spacing">4</property> | |
303 | 172 | <child> |
304 | 173 | <object class="GtkLabel" id="label1"> |
305 | 174 | <property name="visible">True</property> |
316 | 185 | <object class="GtkComboBoxText" id="name_combo"> |
317 | 186 | <property name="visible">True</property> |
318 | 187 | <property name="can_focus">False</property> |
319 | <property name="margin_left">4</property> | |
320 | <property name="margin_right">4</property> | |
321 | 188 | <property name="has_entry">True</property> |
322 | 189 | <child internal-child="entry"> |
323 | 190 | <object class="GtkEntry" id="name_value"> |
386 | 253 | </child> |
387 | 254 | </object> |
388 | 255 | <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> | |
392 | 259 | </packing> |
393 | 260 | </child> |
394 | 261 | <child> |
395 | <object class="GtkBox" id="box1"> | |
262 | <object class="GtkEventBox" id="background_label_holder"> | |
396 | 263 | <property name="visible">True</property> |
397 | 264 | <property name="can_focus">False</property> |
398 | 265 | <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> | |
415 | 268 | <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> | |
426 | 385 | </child> |
427 | 386 | </object> |
428 | 387 | <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> | |
432 | 391 | </packing> |
433 | 392 | </child> |
434 | 393 | <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"> | |
437 | 395 | <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> | |
445 | 439 | </object> |
446 | 440 | <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> | |
450 | 497 | </packing> |
451 | 498 | </child> |
452 | 499 | </object> |
503 | 550 | </object> |
504 | 551 | </child> |
505 | 552 | <action-widgets> |
506 | <action-widget response="-7">cancel_button</action-widget> | |
507 | 553 | <action-widget response="-5">ok_button</action-widget> |
508 | 554 | </action-widgets> |
509 | 555 | </object> |
20 | 20 | from glob import iglob |
21 | 21 | |
22 | 22 | from gi.repository import GLib |
23 | ||
23 | 24 | from lightdm_gtk_greeter_settings import helpers |
24 | 25 | |
25 | 26 | |
74 | 75 | if not values: |
75 | 76 | del self._items[item] |
76 | 77 | |
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 | |
78 | 81 | self._output_path = helpers.get_config_path() |
79 | 82 | self._groups = OrderedDict() |
80 | 83 | self._key_values = helpers.SimpleDictWrapper(getter=self._get_key_values) |
89 | 92 | |
90 | 93 | files = [] |
91 | 94 | 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)) | |
95 | 98 | |
96 | 99 | for path in filter(os.path.isfile, files): |
97 | 100 | config_file = configparser.RawConfigParser(strict=False) |
180 | 183 | def __setitem__(self, item, value): |
181 | 184 | if isinstance(item, tuple): |
182 | 185 | if not item[0] in self._groups: |
183 | self._groups = Config.ConfigGroup(self) | |
186 | self._groups[item[0]] = Config.ConfigGroup(self) | |
184 | 187 | self._groups[item[0]][item[1]] = value |
185 | 188 | |
186 | 189 | def __delitem__(self, item): |
26 | 26 | |
27 | 27 | from gi.repository import ( |
28 | 28 | Gdk, |
29 | GLib, | |
29 | 30 | Gtk) |
30 | 31 | from gi.repository import Pango |
31 | 32 | from gi.repository.GObject import markup_escape_text as escape_markup |
41 | 42 | C_, |
42 | 43 | string2bool, |
43 | 44 | SimpleEnum, |
44 | WidgetsEnum) | |
45 | WidgetsEnum, | |
46 | WidgetsWrapper) | |
45 | 47 | from lightdm_gtk_greeter_settings.MonitorsGroup import MonitorsGroup |
46 | 48 | from lightdm_gtk_greeter_settings.OptionGroup import SimpleGroup |
47 | 49 | |
86 | 88 | builder = None |
87 | 89 | mode = WindowMode.Default |
88 | 90 | |
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 | ||
89 | 117 | entries_setup = { |
90 | 118 | ('greeter', 'allow-debugging'): ('changed',), |
91 | 119 | ('greeter', 'background'): ('changed',), |
98 | 126 | |
99 | 127 | def init_window(self): |
100 | 128 | 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 | ||
102 | 158 | self._entry_menu = None |
103 | 159 | self._initial_values = {} |
160 | self._entries = None | |
161 | ||
104 | 162 | 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)) | |
135 | 169 | |
136 | 170 | for group in self._groups: |
137 | 171 | group.entry_added.connect(self.on_entry_added) |
138 | 172 | group.entry_removed.connect(self.on_entry_removed) |
139 | 173 | |
140 | self._config = Config.Config() | |
141 | ||
142 | 174 | self._allow_edit = self._config.is_writable() |
143 | self._widgets.apply.props.visible = self._allow_edit | |
175 | self._update_apply_button() | |
144 | 176 | |
145 | 177 | if not self._allow_edit: |
146 | 178 | self._set_message(_('You don\'t have permissions to change greeter configuration'), |
154 | 186 | 'or "pkexec"').format(path=helpers.get_config_path()), |
155 | 187 | message_type=Gtk.MessageType.WARNING) |
156 | 188 | |
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 | ||
179 | 189 | self._read() |
180 | 190 | |
181 | 191 | def _set_message(self, message, type_=Gtk.MessageType.INFO): |
186 | 196 | self._widgets.infobar_label.props.label = message |
187 | 197 | self._widgets.infobar.show() |
188 | 198 | |
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 | ||
189 | 204 | def _read(self): |
190 | 205 | self._config.read() |
191 | 206 | self._changed_entries = None |
207 | self._new_entries = None | |
208 | self._removed_entries = None | |
192 | 209 | |
193 | 210 | for group in self._groups: |
194 | 211 | group.read(self._config) |
195 | 212 | |
196 | 213 | self._initial_values = {entry: InitialValue(entry.value, entry.enabled) |
197 | 214 | for entry in self._initial_values.keys()} |
215 | ||
198 | 216 | 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() | |
200 | 221 | |
201 | 222 | def _write(self): |
223 | changed = self._changed_entries | self._new_entries | |
202 | 224 | for group in self._groups: |
203 | group.write(self._config, self._changed_entries.__contains__) | |
225 | group.write(self._config, changed.__contains__) | |
204 | 226 | |
205 | 227 | if self.mode != WindowMode.Embedded: |
206 | for entry in self._changed_entries: | |
228 | for entry in changed: | |
207 | 229 | self._initial_values[entry] = InitialValue(entry.value, entry.enabled) |
208 | 230 | |
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() | |
211 | 234 | |
212 | 235 | try: |
213 | 236 | self._config.write() |
214 | 237 | except OSError as e: |
215 | 238 | helpers.show_message(e, Gtk.MessageType.ERROR) |
216 | 239 | |
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) | |
225 | 262 | f = getattr(self, fname.replace('-', '_')) |
226 | 263 | if action == 'setup': |
227 | 264 | f(entry) |
228 | 265 | else: |
229 | 266 | entry.connect(action, f) |
230 | 267 | |
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) | |
236 | 269 | entry.changed.connect(self.on_entry_changed) |
270 | ||
237 | 271 | 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): | |
243 | 278 | if self._changed_entries is None: |
244 | 279 | return |
245 | 280 | |
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): | |
250 | 300 | if self._changed_entries is None: |
251 | 301 | return |
252 | 302 | |
253 | 303 | initial = self._initial_values[entry] |
254 | if force or entry.enabled != initial.enabled or \ | |
304 | if forced or entry.enabled != initial.enabled or \ | |
255 | 305 | (entry.enabled and entry.value != initial.value): |
256 | 306 | self._changed_entries.add(entry) |
257 | 307 | else: |
258 | 308 | self._changed_entries.discard(entry) |
259 | 309 | |
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: | |
269 | 314 | 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() | |
275 | 317 | |
276 | 318 | def on_entry_reset_clicked(self, item): |
277 | 319 | entry, value, enabled = item._reset_entry_data |
284 | 326 | entry, action = item._fix_entry_data |
285 | 327 | action(entry) |
286 | 328 | |
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): | |
290 | 330 | |
291 | 331 | def new_item(activate=None, width=90): |
292 | 332 | item = Gtk.MenuItem('') |
303 | 343 | if not self._entry_menu: |
304 | 344 | class EntryMenu: |
305 | 345 | menu = Gtk.Menu() |
346 | group = new_item() | |
306 | 347 | value = new_item() |
307 | 348 | file = new_item() |
308 | 349 | error_separator = Gtk.SeparatorMenuItem() |
313 | 354 | default = new_item(self.on_entry_reset_clicked) |
314 | 355 | other = [] |
315 | 356 | |
357 | menu.append(group) | |
316 | 358 | menu.append(value) |
317 | 359 | menu.append(file) |
318 | 360 | menu.append(error_separator) |
337 | 379 | |
338 | 380 | menu = self._entry_menu |
339 | 381 | |
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 | |
345 | 397 | config_values = self._config.key_values[group.name, key] |
346 | ||
347 | 398 | if entry not in self._changed_entries and \ |
348 | 399 | config_values and config_values[-1][0] != helpers.get_config_path(): |
349 | 400 | menu.file.props.label = _('Value defined in file: {path}')\ |
353 | 404 | else: |
354 | 405 | menu.file.hide() |
355 | 406 | |
407 | # Error message | |
356 | 408 | error = entry.error |
357 | 409 | error_action = None |
358 | 410 | if error: |
370 | 422 | menu.error_action.props.visible = error_action is not None |
371 | 423 | menu.error_separator.props.visible = error_action is not None |
372 | 424 | |
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: | |
376 | 428 | if entry.enabled != initial.enabled and not initial.enabled: |
377 | 429 | menu.initial._reset_entry_data = entry, None, initial.enabled |
378 | 430 | else: |
386 | 438 | else: |
387 | 439 | menu.initial.props.visible = False |
388 | 440 | |
441 | # Reset to default value | |
389 | 442 | default = group.defaults[key] |
390 | 443 | if default is not None and entry.value != default: |
391 | 444 | value = format_value(value=default) |
397 | 450 | else: |
398 | 451 | menu.default.props.visible = False |
399 | 452 | |
453 | # Reset to values from all other (.conf | |
400 | 454 | item_idx = 0 |
401 | 455 | if config_values and len(config_values) > 1: |
402 | 456 | values = {None, default, self._initial_values[entry].value, entry.value} |
545 | 599 | return True |
546 | 600 | return False |
547 | 601 | |
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) | |
549 | 605 | Gtk.main_quit() |
550 | 606 | |
551 | 607 | def on_apply_clicked(self, *unused): |
15 | 15 | # with this program. If not, see <http://www.gnu.org/licenses/>. |
16 | 16 | |
17 | 17 | |
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) | |
22 | 23 | from lightdm_gtk_greeter_settings.helpers import ( |
23 | bool2string, | |
24 | 24 | 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 | |
28 | 25 | |
29 | 26 | |
30 | 27 | __all__ = ['MonitorsGroup'] |
31 | 28 | |
32 | 29 | |
33 | class MonitorsGroup(BaseGroup): | |
34 | GROUP_PREFIX = 'monitor:' | |
30 | class MonitorsGroup(OptionGroup.BaseGroup): | |
35 | 31 | |
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): | |
37 | 39 | 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 | |
41 | 43 | 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) | |
43 | 51 | |
44 | 52 | 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): | |
48 | 59 | 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} | |
56 | 63 | |
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) | |
59 | 69 | 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)) | |
62 | 72 | |
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_) | |
70 | 88 | |
71 | 89 | 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) | |
73 | 130 | |
74 | 131 | def _on_label_link_activate(self, label, uri): |
75 | 132 | 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() | |
78 | 135 | |
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)) | |
81 | 138 | |
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() | |
96 | 140 | self._dialog.hide() |
97 | 141 | 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() |
15 | 15 | # with this program. If not, see <http://www.gnu.org/licenses/>. |
16 | 16 | |
17 | 17 | |
18 | from collections import defaultdict | |
19 | ||
18 | 20 | from gi.repository import ( |
19 | 21 | Gdk, |
20 | 22 | Gtk) |
21 | 23 | |
22 | 24 | import lightdm_gtk_greeter_settings.helpers |
23 | ||
24 | 25 | from lightdm_gtk_greeter_settings.helpers import ( |
25 | 26 | check_path_accessibility, |
26 | 27 | get_data_path, |
27 | WidgetsEnum, | |
28 | WidgetsWrapper) | |
29 | from lightdm_gtk_greeter_settings import OptionEntry | |
28 | WidgetsEnum) | |
30 | 29 | |
31 | 30 | from gi.overrides import GLib |
32 | 31 | |
34 | 33 | __all__ = ['MultiheadSetupDialog'] |
35 | 34 | |
36 | 35 | 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 | |
48 | 36 | |
49 | 37 | |
50 | 38 | class MultiheadSetupDialog(Gtk.Dialog): |
51 | 39 | __gtype_name__ = 'MultiheadSetupDialog' |
52 | 40 | |
53 | def __new__(cls): | |
41 | def __new__(cls, monitors): | |
54 | 42 | builder = Gtk.Builder() |
55 | 43 | builder.add_from_file(get_data_path('%s.ui' % cls.__gtype_name__)) |
56 | 44 | window = builder.get_object('multihead_setup_dialog') |
57 | 45 | window.builder = builder |
46 | window.monitors = monitors | |
58 | 47 | builder.connect_signals(window) |
59 | 48 | window.init_window() |
60 | 49 | return window |
72 | 61 | empty_add_button = 'empty_add_button' |
73 | 62 | empty_add_menu_button = 'empty_add_menu_button' |
74 | 63 | |
64 | class PageData: | |
65 | group = None | |
66 | holder = None | |
67 | label = None | |
68 | ids = None | |
69 | # Entries | |
70 | name = None | |
71 | background = None | |
72 | ||
75 | 73 | builder = None |
74 | monitors = None | |
76 | 75 | |
77 | 76 | def init_window(self): |
78 | 77 | self._widgets = self.Widgets(builder=self.builder) |
84 | 83 | |
85 | 84 | self._current_page = None |
86 | 85 | 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 = {} | |
98 | 87 | |
99 | 88 | screen = Gdk.Screen.get_default() |
100 | 89 | self._available_monitors = [(screen.get_monitor_plug_name(i), |
110 | 99 | self._widgets.available_menu.append(item) |
111 | 100 | item.connect('activate', self.on_add_button_clicked, name) |
112 | 101 | |
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 | ||
114 | 107 | self._widgets.notebook.handler_block_by_func(self.on_switch_page) |
115 | 108 | 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)) | |
117 | 111 | self._widgets.notebook.handler_unblock_by_func(self.on_switch_page) |
118 | 112 | |
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 | |
138 | 124 | self._update_monitors_list() |
139 | 125 | |
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) | |
166 | 135 | |
167 | 136 | close_button = Gtk.Button() |
168 | 137 | close_button.set_focus_on_click(False) |
169 | 138 | 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) | |
171 | 140 | close_image = Gtk.Image.new_from_icon_name('stock_close', Gtk.IconSize.INVALID) |
172 | 141 | close_image.set_pixel_size(16) |
173 | 142 | close_button.add(close_image) |
174 | 143 | |
175 | 144 | 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) | |
178 | 146 | label_box.pack_start(close_button, False, False, 0) |
179 | 147 | |
180 | 148 | label_eventbox = Gtk.EventBox() |
181 | 149 | label_eventbox.add(label_box) |
182 | 150 | label_eventbox.show_all() |
183 | 151 | |
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 | |
185 | 164 | |
186 | 165 | if self._widgets.empty.get_parent(): |
187 | 166 | self._widgets.empty.hide() |
188 | 167 | |
189 | 168 | 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) | |
191 | 171 | |
192 | 172 | return current_idx |
193 | 173 | |
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): | |
198 | 175 | if page == self._widgets.editor.props.parent: |
199 | 176 | page.remove(self._widgets.editor) |
200 | 177 | |
201 | if update: | |
202 | self._update_monitors_list() | |
203 | if not self._configs: | |
204 | self._widgets.empty.show() | |
205 | ||
206 | 178 | self._widgets.notebook.remove_page(self._widgets.notebook.page_num(page)) |
207 | 179 | |
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 | ||
208 | 187 | 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) | |
210 | 189 | used_count = 0 |
211 | 190 | self._widgets.name_combo.get_model().clear() |
212 | 191 | for name, item in self._available_monitors: |
223 | 202 | self._widgets.editor_add_menu_button.props.visible = show_button |
224 | 203 | self._widgets.empty_add_menu_button.props.visible = show_button |
225 | 204 | |
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 | |
246 | 207 | markup = None |
247 | 208 | error = None |
248 | 209 | |
249 | if not config.name: | |
210 | if not value: | |
250 | 211 | markup = C_('<i>No name</i>') |
251 | 212 | 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()): | |
254 | 215 | error = (C_('"{name}" is already defined. Only last configuration will be saved.') |
255 | .format(name=config.name)) | |
216 | .format(name=value)) | |
256 | 217 | |
257 | 218 | if markup: |
258 | config.label.set_markup(markup) | |
219 | data.label.set_markup(markup) | |
259 | 220 | 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) | |
263 | 232 | |
264 | 233 | def _focus_name_entry(self): |
265 | 234 | self._widgets.name.grab_focus() |
266 | 235 | self._widgets.name.set_position(0) |
267 | 236 | |
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) | |
279 | 243 | if name: |
280 | 244 | self._update_monitors_list() |
281 | 245 | |
282 | def aaa(self, *args): | |
283 | print(self._widgets.notebook.get_current_page(), args) | |
284 | ||
285 | 246 | def on_switch_page(self, notebook, page, page_idx): |
286 | 247 | if page == self._widgets.empty: |
287 | self._current_page = None | |
288 | 248 | buttons = self._widgets.editor_add_menu_button, self._widgets.empty_add_menu_button |
289 | 249 | else: |
290 | self._current_page = page | |
291 | 250 | buttons = self._widgets.empty_add_menu_button, self._widgets.editor_add_menu_button |
292 | 251 | |
293 | 252 | old_parent = self._widgets.editor.get_parent() |
295 | 254 | old_parent.remove(self._widgets.editor) |
296 | 255 | page.add(self._widgets.editor) |
297 | 256 | |
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]) | |
311 | 260 | |
312 | 261 | GLib.idle_add(self._focus_name_entry) |
313 | 262 |
54 | 54 | if self.__use: |
55 | 55 | self.__use.connect('notify::active', self.__on_use_toggled) |
56 | 56 | |
57 | label_holder = widgets['label_holder'] | |
58 | if label_holder: | |
59 | label_holder.connect('button-press-event', self.__on_label_clicked) | |
60 | ||
57 | 61 | self.__error = widgets['error'] |
58 | 62 | |
59 | 63 | @property |
100 | 104 | |
101 | 105 | @GObject.Signal(flags=GObject.SIGNAL_RUN_CLEANUP) |
102 | 106 | def set(self, value: str) -> str: |
107 | pass | |
108 | ||
109 | @GObject.Signal('show-menu') | |
110 | def show_menu(self): | |
103 | 111 | pass |
104 | 112 | |
105 | 113 | def __repr__(self): |
147 | 155 | |
148 | 156 | def __on_use_toggled(self, toggle, *unused): |
149 | 157 | 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() | |
150 | 162 | |
151 | 163 | |
152 | 164 | class BooleanEntry(BaseEntry): |
24 | 24 | |
25 | 25 | __all__ = [ |
26 | 26 | 'BaseGroup', |
27 | 'OneToManyEntryAdapter', | |
27 | 28 | 'SimpleGroup'] |
28 | 29 | |
29 | 30 | |
39 | 40 | '''Read group content from specified GreeterConfig object''' |
40 | 41 | raise NotImplementedError(self.__class__) |
41 | 42 | |
42 | def write(self, config, changed=None): | |
43 | def write(self, config, is_changed=None): | |
43 | 44 | '''Writes content of this group to specified GreeterConfig object''' |
45 | raise NotImplementedError(self.__class__) | |
46 | ||
47 | def clear(self): | |
48 | '''Removes all entries''' | |
44 | 49 | raise NotImplementedError(self.__class__) |
45 | 50 | |
46 | 51 | @property |
60 | 65 | raise NotImplementedError(self.__class__) |
61 | 66 | |
62 | 67 | @GObject.Signal |
63 | def entry_added(self, entry: BaseEntry, key: str): | |
68 | def entry_added(self, source: object, entry: BaseEntry, key: str): | |
64 | 69 | '''New entry has been added to this group''' |
65 | 70 | pass |
66 | 71 | |
67 | 72 | @GObject.Signal |
68 | def entry_removed(self, entry: BaseEntry, key: str): | |
73 | def entry_removed(self, source: object, entry: BaseEntry, key: str): | |
69 | 74 | '''Entry has been removed from this group''' |
70 | 75 | pass |
71 | 76 | |
72 | 77 | |
73 | 78 | class SimpleGroup(BaseGroup): |
74 | 79 | |
75 | def __init__(self, name, widgets, options): | |
80 | def __init__(self, name, widgets, options=None): | |
76 | 81 | super().__init__(widgets) |
77 | 82 | self._name = name |
78 | self._widgets = WidgetsWrapper(widgets, name) | |
79 | self._options = options | |
83 | self._options_to_init = options | |
84 | self._widgets = WidgetsWrapper(widgets) | |
80 | 85 | self._entries = {} |
81 | 86 | self._defaults = {} |
82 | 87 | |
84 | 89 | def name(self): |
85 | 90 | return self._name |
86 | 91 | |
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 | ||
87 | 112 | 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 | |
94 | 116 | |
95 | 117 | for key, entry in self._entries.items(): |
96 | 118 | value = config[self._name, key] |
97 | 119 | entry.value = value if value is not None else self._defaults[key] |
98 | 120 | entry.enabled = value is not None |
99 | 121 | |
100 | def write(self, config, changed=None): | |
122 | def write(self, config, is_changed=None): | |
101 | 123 | for key, entry in self._entries.items(): |
102 | if changed and not changed(entry): | |
124 | if is_changed and not is_changed(entry): | |
103 | 125 | continue |
104 | 126 | config[self._name, key] = entry.value if entry.enabled else None, self._get_default(key) |
105 | 127 | |
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 | ||
106 | 135 | def _get_entry(self, key): |
107 | 136 | return self._entries.get(key) |
108 | 137 | |
109 | 138 | def _get_default(self, key): |
110 | 139 | 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() |
36 | 36 | |
37 | 37 | if args.test_socket: |
38 | 38 | w = Gtk.Window() |
39 | w.props.title = 'Testing embedded mode' | |
39 | 40 | socket = Gtk.Socket.new() |
40 | 41 | w.add(socket) |
41 | 42 | w.show_all() |
330 | 330 | _prefixes = None |
331 | 331 | |
332 | 332 | def __init__(self, source, *prefixes): |
333 | if source is None: | |
334 | return | |
333 | 335 | if isinstance(source, Gtk.Builder): |
334 | 336 | self._builder = source |
335 | 337 | self._prefixes = tuple(prefixes) |
340 | 342 | raise TypeError(source) |
341 | 343 | |
342 | 344 | def __getitem__(self, args): |
345 | if not self._builder: | |
346 | return None | |
343 | 347 | if not isinstance(args, tuple): |
344 | 348 | args = (args,) |
345 | 349 | return self._builder.get_object('_'.join(chain(self._prefixes, args))) |
346 | 350 | |
351 | @property | |
352 | def path(self): | |
353 | return '_'.join(self._prefixes) if self._prefixes else '' | |
354 | ||
347 | 355 | |
348 | 356 | class TreeStoreDataWrapper(GObject.Object): |
349 | 357 | |
354 | 362 | |
355 | 363 | class SimpleDictWrapper: |
356 | 364 | |
357 | def __init__(self, getter): | |
365 | def __init__(self, getter=None, setter=None, add=None, deleter=None, itergetter=None): | |
358 | 366 | self._getter = getter |
367 | self._setter = setter | |
368 | self._deleter = deleter | |
369 | self._itergetter = itergetter | |
370 | self._add = add | |
359 | 371 | |
360 | 372 | def __getitem__(self, key): |
361 | 373 | 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) |