Codebase list lightdm-gtk-greeter-settings / 88c0636
IndicatorsEntry updated, code cleanup Andrew P. 9 years ago
18 changed file(s) with 2466 addition(s) and 1465 deletion(s). Raw diff Collapse all Expand all
11 <!-- Generated with glade 3.18.3 -->
22 <interface>
33 <requires lib="gtk+" version="3.8"/>
4 <requires lib="" version="3.8"/>
45 <requires lib="gtk_greeter_settings" version="1.0"/>
56 <object class="GtkMenu" id="greeter_default-user-image_menu">
67 <property name="visible">True</property>
2627 <column type="gchararray"/>
2728 <!-- column-name tooltip -->
2829 <column type="gchararray"/>
29 <!-- column-name editable -->
30 <column type="gboolean"/>
3130 <!-- column-name has-state -->
3231 <column type="gboolean"/>
3332 <!-- column-name state -->
3433 <column type="gboolean"/>
34 <!-- column-name options -->
35 <column type="GObject"/>
36 <!-- column-name icon -->
37 <column type="gchararray"/>
38 <!-- column-name markup -->
39 <column type="gchararray"/>
3540 </columns>
36 <data>
37 <row>
38 <col id="0">~host</col>
39 <col id="1" translatable="yes" context="option|greeter|indicators">Host name</col>
40 <col id="2">False</col>
41 <col id="3">True</col>
42 <col id="4">False</col>
43 </row>
44 <row>
45 <col id="0">~clock</col>
46 <col id="1" translatable="yes" context="option|greeter|indicators">Clock</col>
47 <col id="2">False</col>
48 <col id="3">True</col>
49 <col id="4">False</col>
50 </row>
51 <row>
52 <col id="0">~layout</col>
53 <col id="1" translatable="yes" context="option|greeter|indicators">Layout indicator</col>
54 <col id="2">False</col>
55 <col id="3">True</col>
56 <col id="4">False</col>
57 </row>
58 <row>
59 <col id="0">~session</col>
60 <col id="1" translatable="yes" context="option|greeter|indicators">Sessions menu (xfce, unity, gnome etc.)</col>
61 <col id="2">False</col>
62 <col id="3">True</col>
63 <col id="4">False</col>
64 </row>
65 <row>
66 <col id="0">~language</col>
67 <col id="1" translatable="yes" context="option|greeter|indicators">Languages menu</col>
68 <col id="2">False</col>
69 <col id="3">True</col>
70 <col id="4">False</col>
71 </row>
72 <row>
73 <col id="0">~a11y</col>
74 <col id="1" translatable="yes" context="option|greeter|indicators">Accessibility menu</col>
75 <col id="2">False</col>
76 <col id="3">True</col>
77 <col id="4">False</col>
78 </row>
79 <row>
80 <col id="0">~power</col>
81 <col id="1" translatable="yes" context="option|greeter|indicators">Power menu</col>
82 <col id="2">False</col>
83 <col id="3">True</col>
84 <col id="4">False</col>
85 </row>
86 </data>
8741 </object>
8842 <object class="GtkAdjustment" id="greeter_position_x_adjustment">
8943 <property name="upper">10000</property>
12175 <property name="title" translatable="yes">LightDM GTK+ Greeter: settings</property>
12276 <property name="window_position">center</property>
12377 <property name="icon_name">lightdm-gtk-greeter-settings</property>
124 <property name="has_resize_grip">True</property>
12578 <signal name="destroy" handler="on_destroy" swapped="no"/>
12679 <child>
12780 <object class="GtkBox" id="box1">
367320 </packing>
368321 </child>
369322 <child>
370 <object class="GtkRadioButton" id="greeter_background_image_choice">
371 <property name="label" translatable="yes" context="option|greeter|background">Image</property>
372 <property name="visible">True</property>
373 <property name="can_focus">False</property>
374 <property name="receives_default">False</property>
375 <property name="halign">start</property>
376 <property name="margin_left">24</property>
377 <property name="xalign">0.5</property>
378 <property name="active">True</property>
379 <property name="draw_indicator">True</property>
380 </object>
381 <packing>
382 <property name="left_attach">0</property>
383 <property name="top_attach">5</property>
384 </packing>
385 </child>
386 <child>
387323 <object class="GtkFileChooserButton" id="greeter_background_image_value">
388324 <property name="visible">True</property>
389325 <property name="can_focus">False</property>
392328 <packing>
393329 <property name="left_attach">1</property>
394330 <property name="top_attach">5</property>
395 </packing>
396 </child>
397 <child>
398 <object class="GtkRadioButton" id="greeter_background_color_choice">
399 <property name="label" translatable="yes" context="option|greeter|background">Color</property>
400 <property name="visible">True</property>
401 <property name="can_focus">False</property>
402 <property name="receives_default">False</property>
403 <property name="halign">start</property>
404 <property name="margin_left">24</property>
405 <property name="xalign">0.5</property>
406 <property name="active">True</property>
407 <property name="draw_indicator">True</property>
408 <property name="group">greeter_background_image_choice</property>
409 </object>
410 <packing>
411 <property name="left_attach">0</property>
412 <property name="top_attach">6</property>
413331 </packing>
414332 </child>
415333 <child>
706624 <packing>
707625 <property name="left_attach">0</property>
708626 <property name="top_attach">9</property>
627 </packing>
628 </child>
629 <child>
630 <object class="GtkRadioButton" id="greeter_background_color_choice">
631 <property name="label" translatable="yes" context="option|greeter|background">Color</property>
632 <property name="visible">True</property>
633 <property name="can_focus">False</property>
634 <property name="receives_default">False</property>
635 <property name="halign">start</property>
636 <property name="margin_left">24</property>
637 <property name="xalign">0.5</property>
638 <property name="active">True</property>
639 <property name="draw_indicator">True</property>
640 <property name="group">greeter_background_image_choice</property>
641 </object>
642 <packing>
643 <property name="left_attach">0</property>
644 <property name="top_attach">6</property>
645 </packing>
646 </child>
647 <child>
648 <object class="GtkRadioButton" id="greeter_background_image_choice">
649 <property name="label" translatable="yes" context="option|greeter|background">Image</property>
650 <property name="visible">True</property>
651 <property name="can_focus">False</property>
652 <property name="receives_default">False</property>
653 <property name="halign">start</property>
654 <property name="margin_left">24</property>
655 <property name="xalign">0.5</property>
656 <property name="active">True</property>
657 <property name="draw_indicator">True</property>
658 </object>
659 <packing>
660 <property name="left_attach">0</property>
661 <property name="top_attach">5</property>
709662 </packing>
710663 </child>
711664 </object>
809762 <property name="orientation">vertical</property>
810763 <child>
811764 <object class="GtkScrolledWindow" id="scrolledwindow1">
812 <property name="height_request">160</property>
765 <property name="height_request">170</property>
813766 <property name="visible">True</property>
814767 <property name="can_focus">True</property>
815768 <property name="hexpand">True</property>
825778 <property name="headers_visible">False</property>
826779 <property name="headers_clickable">False</property>
827780 <property name="reorderable">True</property>
828 <property name="search_column">0</property>
781 <property name="search_column">3</property>
829782 <property name="tooltip_column">1</property>
830783 <child internal-child="selection">
831784 <object class="GtkTreeSelection" id="greeter_indicators_selection">
834787 </child>
835788 <child>
836789 <object class="GtkTreeViewColumn" id="greeter_indicators_state_column">
790 <property name="visible">False</property>
837791 <property name="sizing">autosize</property>
838792 <property name="title">column</property>
839793 <property name="clickable">True</property>
840794 <child>
841795 <object class="GtkCellRendererToggle" id="greeter_indicators_state_renderer"/>
842796 <attributes>
843 <attribute name="visible">3</attribute>
844 <attribute name="active">4</attribute>
797 <attribute name="visible">2</attribute>
798 <attribute name="active">3</attribute>
845799 </attributes>
846800 </child>
847801 </object>
852806 <property name="title">column</property>
853807 <property name="expand">True</property>
854808 <child>
809 <object class="GtkCellRendererPixbuf" id="greeter_indicators_image_renderer"/>
810 <attributes>
811 <attribute name="icon-name">5</attribute>
812 </attributes>
813 </child>
814 <child>
855815 <object class="GtkCellRendererText" id="greeter_indicators_name_renderer"/>
856816 <attributes>
857 <attribute name="editable">3</attribute>
858 <attribute name="text">0</attribute>
817 <attribute name="markup">6</attribute>
859818 </attributes>
860819 </child>
861820 </object>
928887 <packing>
929888 <property name="expand">False</property>
930889 <property name="homogeneous">True</property>
890 </packing>
891 </child>
892 <child>
893 <object class="GtkToolButton" id="greeter_indicators_tools">
894 <property name="visible">True</property>
895 <property name="can_focus">False</property>
896 <property name="label" translatable="yes" context="option|greeter|indicators">Templates</property>
897 <property name="use_underline">True</property>
898 <property name="icon_name">preferences-other</property>
899 </object>
900 <packing>
901 <property name="expand">False</property>
902 <property name="homogeneous">False</property>
931903 </packing>
932904 </child>
933905 <style>
16461618 </packing>
16471619 </child>
16481620 <child>
1649 <object class="GtkInfoBar" id="no_access_infobar">
1621 <object class="GtkInfoBar" id="infobar">
16501622 <property name="visible">True</property>
16511623 <property name="app_paintable">True</property>
16521624 <property name="can_focus">False</property>
154154 </packing>
155155 </child>
156156 <child>
157 <object class="GtkEntry" id="name">
157 <object class="GtkEntry" id="name_entry">
158158 <property name="visible">True</property>
159159 <property name="can_focus">True</property>
160160 <property name="invisible_char">●</property>
198198 <packing>
199199 <property name="left_attach">1</property>
200200 <property name="top_attach">1</property>
201 </packing>
202 </child>
203 <child>
204 <object class="GtkImage" id="preview">
205 <property name="visible">True</property>
206 <property name="can_focus">False</property>
207 <property name="halign">center</property>
208 <property name="valign">start</property>
209 <property name="stock">gtk-missing-image</property>
210 <property name="pixel_size">32</property>
211 <property name="icon_size">6</property>
212 </object>
213 <packing>
214 <property name="left_attach">2</property>
215 <property name="top_attach">0</property>
216 <property name="height">2</property>
217201 </packing>
218202 </child>
219203 <child>
327311 <property name="top_attach">2</property>
328312 </packing>
329313 </child>
314 <child>
315 <object class="GtkImage" id="preview_image">
316 <property name="width_request">32</property>
317 <property name="height_request">32</property>
318 <property name="visible">True</property>
319 <property name="can_focus">False</property>
320 <property name="halign">center</property>
321 <property name="valign">start</property>
322 <property name="pixel_size">32</property>
323 <property name="icon_size">6</property>
324 </object>
325 <packing>
326 <property name="left_attach">2</property>
327 <property name="top_attach">0</property>
328 <property name="height">2</property>
329 </packing>
330 </child>
330331 </object>
331332 <packing>
332333 <property name="expand">False</property>
+0
-449
data/IndicatorChooserDialog.ui less more
0 <?xml version="1.0" encoding="UTF-8"?>
1 <!-- Generated with glade 3.18.3 -->
2 <interface>
3 <requires lib="gtk+" version="3.8"/>
4 <requires lib="gtk_greeter_settings" version="1.0"/>
5 <object class="GtkImage" id="image1">
6 <property name="visible">True</property>
7 <property name="can_focus">False</property>
8 <property name="xpad">2</property>
9 <property name="icon_name">list-add</property>
10 </object>
11 <object class="GtkImage" id="image2">
12 <property name="visible">True</property>
13 <property name="can_focus">False</property>
14 <property name="xpad">2</property>
15 <property name="icon_name">window-close</property>
16 </object>
17 <object class="GtkImage" id="image3">
18 <property name="visible">True</property>
19 <property name="can_focus">False</property>
20 <property name="xpad">2</property>
21 <property name="icon_name">dialog-ok</property>
22 </object>
23 <object class="GtkFileFilter" id="library_file_filter">
24 <mime-types>
25 <mime-type>application/x-sharedlib</mime-type>
26 </mime-types>
27 </object>
28 <object class="GtkListStore" id="short_model">
29 <columns>
30 <!-- column-name name -->
31 <column type="gchararray"/>
32 </columns>
33 </object>
34 <object class="IndicatorChooserDialog" id="indicator_chooser_dialog">
35 <property name="width_request">400</property>
36 <property name="can_focus">False</property>
37 <property name="title" translatable="yes" context="indicators-dialog">Select indicator</property>
38 <property name="modal">True</property>
39 <property name="window_position">center</property>
40 <property name="destroy_with_parent">True</property>
41 <property name="icon_name">application-x-executable</property>
42 <property name="type_hint">dialog</property>
43 <child internal-child="vbox">
44 <object class="GtkBox" id="dialog-vbox1">
45 <property name="can_focus">False</property>
46 <property name="margin_left">8</property>
47 <property name="margin_right">8</property>
48 <property name="margin_top">8</property>
49 <property name="margin_bottom">8</property>
50 <property name="orientation">vertical</property>
51 <property name="spacing">8</property>
52 <child internal-child="action_area">
53 <object class="GtkButtonBox" id="dialog-action_area1">
54 <property name="can_focus">False</property>
55 <property name="layout_style">end</property>
56 <child>
57 <object class="GtkButton" id="cancel_button">
58 <property name="label" translatable="yes" context="button">_Close</property>
59 <property name="visible">True</property>
60 <property name="can_focus">True</property>
61 <property name="receives_default">True</property>
62 <property name="image">image2</property>
63 <property name="use_underline">True</property>
64 </object>
65 <packing>
66 <property name="expand">False</property>
67 <property name="fill">True</property>
68 <property name="position">0</property>
69 </packing>
70 </child>
71 <child>
72 <object class="GtkButton" id="ok_button">
73 <property name="label" translatable="yes" context="button">_OK</property>
74 <property name="visible">True</property>
75 <property name="can_focus">True</property>
76 <property name="can_default">True</property>
77 <property name="has_default">True</property>
78 <property name="receives_default">True</property>
79 <property name="tooltip_text" translatable="yes">Add selected indicator to the list and close this window</property>
80 <property name="image">image3</property>
81 <property name="use_underline">True</property>
82 </object>
83 <packing>
84 <property name="expand">False</property>
85 <property name="fill">True</property>
86 <property name="position">1</property>
87 </packing>
88 </child>
89 <child>
90 <object class="GtkBox" id="box1">
91 <property name="visible">True</property>
92 <property name="can_focus">False</property>
93 <property name="orientation">vertical</property>
94 <child>
95 <object class="GtkButton" id="add_button">
96 <property name="label" translatable="yes" context="button">_Add</property>
97 <property name="visible">True</property>
98 <property name="can_focus">True</property>
99 <property name="receives_default">True</property>
100 <property name="has_tooltip">True</property>
101 <property name="tooltip_text" translatable="yes">Add selected indicator to the list without closing this window</property>
102 <property name="image">image1</property>
103 <property name="use_underline">True</property>
104 <signal name="clicked" handler="on_add_clicked" swapped="no"/>
105 </object>
106 <packing>
107 <property name="expand">False</property>
108 <property name="fill">True</property>
109 <property name="position">0</property>
110 </packing>
111 </child>
112 </object>
113 <packing>
114 <property name="expand">False</property>
115 <property name="fill">True</property>
116 <property name="position">2</property>
117 <property name="secondary">True</property>
118 <property name="non_homogeneous">True</property>
119 </packing>
120 </child>
121 </object>
122 <packing>
123 <property name="expand">False</property>
124 <property name="fill">True</property>
125 <property name="pack_type">end</property>
126 <property name="position">0</property>
127 </packing>
128 </child>
129 <child>
130 <object class="GtkBox" id="box7">
131 <property name="visible">True</property>
132 <property name="can_focus">False</property>
133 <property name="valign">start</property>
134 <property name="hexpand">True</property>
135 <property name="orientation">vertical</property>
136 <property name="spacing">8</property>
137 <child>
138 <object class="GtkRadioButton" id="short_choice">
139 <property name="visible">True</property>
140 <property name="can_focus">True</property>
141 <property name="receives_default">False</property>
142 <property name="xalign">0</property>
143 <property name="active">True</property>
144 <property name="draw_indicator">True</property>
145 <signal name="toggled" handler="on_short_choice_toggled" swapped="no"/>
146 <child>
147 <object class="GtkLabel" id="label5">
148 <property name="visible">True</property>
149 <property name="can_focus">False</property>
150 <property name="halign">start</property>
151 <property name="hexpand">True</property>
152 <property name="label" translatable="yes" context="indicators-dialog">Short name</property>
153 <attributes>
154 <attribute name="weight" value="semibold"/>
155 </attributes>
156 </object>
157 </child>
158 </object>
159 <packing>
160 <property name="expand">False</property>
161 <property name="fill">True</property>
162 <property name="position">0</property>
163 </packing>
164 </child>
165 <child>
166 <object class="GtkComboBox" id="combobox1">
167 <property name="visible">True</property>
168 <property name="can_focus">False</property>
169 <property name="margin_left">24</property>
170 <property name="model">short_model</property>
171 <property name="has_entry">True</property>
172 <property name="entry_text_column">0</property>
173 <child internal-child="entry">
174 <object class="GtkEntry" id="short_value">
175 <property name="can_focus">True</property>
176 <property name="is_focus">True</property>
177 <signal name="activate" handler="on_short_value_activate" swapped="no"/>
178 <signal name="changed" handler="on_short_value_changed" swapped="no"/>
179 </object>
180 </child>
181 </object>
182 <packing>
183 <property name="expand">False</property>
184 <property name="fill">True</property>
185 <property name="position">1</property>
186 </packing>
187 </child>
188 <child>
189 <object class="GtkLabel" id="label17">
190 <property name="visible">True</property>
191 <property name="can_focus">False</property>
192 <property name="halign">start</property>
193 <property name="margin_left">24</property>
194 <property name="label" translatable="yes" context="indicators-dialog">Indicators that greeter can find without using absolute pathes</property>
195 <attributes>
196 <attribute name="style" value="oblique"/>
197 </attributes>
198 </object>
199 <packing>
200 <property name="expand">False</property>
201 <property name="fill">True</property>
202 <property name="position">2</property>
203 </packing>
204 </child>
205 <child>
206 <object class="GtkRadioButton" id="path_choice">
207 <property name="visible">True</property>
208 <property name="can_focus">True</property>
209 <property name="receives_default">False</property>
210 <property name="xalign">0</property>
211 <property name="draw_indicator">True</property>
212 <property name="group">short_choice</property>
213 <child>
214 <object class="GtkLabel" id="label4">
215 <property name="visible">True</property>
216 <property name="can_focus">False</property>
217 <property name="halign">start</property>
218 <property name="hexpand">True</property>
219 <property name="label" translatable="yes" context="indicators-dialog">Path</property>
220 <attributes>
221 <attribute name="weight" value="semibold"/>
222 </attributes>
223 </object>
224 </child>
225 </object>
226 <packing>
227 <property name="expand">False</property>
228 <property name="fill">True</property>
229 <property name="position">3</property>
230 </packing>
231 </child>
232 <child>
233 <object class="GtkFileChooserButton" id="path_value">
234 <property name="visible">True</property>
235 <property name="can_focus">False</property>
236 <property name="margin_left">24</property>
237 <property name="filter">library_file_filter</property>
238 <signal name="file-set" handler="on_path_value_changed" swapped="no"/>
239 </object>
240 <packing>
241 <property name="expand">False</property>
242 <property name="fill">True</property>
243 <property name="position">4</property>
244 </packing>
245 </child>
246 <child>
247 <object class="GtkLabel" id="label16">
248 <property name="visible">True</property>
249 <property name="can_focus">False</property>
250 <property name="halign">start</property>
251 <property name="margin_left">24</property>
252 <property name="label" translatable="yes" context="indicators-dialog">Absolute path to indicator library file (*.so)</property>
253 <attributes>
254 <attribute name="style" value="oblique"/>
255 </attributes>
256 </object>
257 <packing>
258 <property name="expand">False</property>
259 <property name="fill">True</property>
260 <property name="position">5</property>
261 </packing>
262 </child>
263 <child>
264 <object class="GtkSeparator" id="separator2">
265 <property name="visible">True</property>
266 <property name="can_focus">False</property>
267 </object>
268 <packing>
269 <property name="expand">False</property>
270 <property name="fill">True</property>
271 <property name="position">6</property>
272 </packing>
273 </child>
274 <child>
275 <object class="GtkGrid" id="grid1">
276 <property name="visible">True</property>
277 <property name="can_focus">False</property>
278 <property name="halign">start</property>
279 <property name="row_spacing">4</property>
280 <property name="column_spacing">8</property>
281 <child>
282 <object class="GtkLabel" id="label2">
283 <property name="visible">True</property>
284 <property name="can_focus">False</property>
285 <property name="halign">start</property>
286 <property name="label" translatable="yes" context="indicators-dialog">Fills the maximum available space</property>
287 <attributes>
288 <attribute name="style" value="oblique"/>
289 </attributes>
290 </object>
291 <packing>
292 <property name="left_attach">1</property>
293 <property name="top_attach">0</property>
294 </packing>
295 </child>
296 <child>
297 <object class="GtkRadioButton" id="spacer_choice">
298 <property name="visible">True</property>
299 <property name="can_focus">True</property>
300 <property name="receives_default">False</property>
301 <property name="xalign">0</property>
302 <property name="draw_indicator">True</property>
303 <property name="group">short_choice</property>
304 <child>
305 <object class="GtkLabel" id="label1">
306 <property name="visible">True</property>
307 <property name="can_focus">False</property>
308 <property name="halign">start</property>
309 <property name="hexpand">True</property>
310 <property name="label" translatable="yes" context="indicators-dialog">Spacer</property>
311 <attributes>
312 <attribute name="weight" value="semibold"/>
313 </attributes>
314 </object>
315 </child>
316 </object>
317 <packing>
318 <property name="left_attach">0</property>
319 <property name="top_attach">0</property>
320 </packing>
321 </child>
322 <child>
323 <object class="GtkRadioButton" id="separator_choice">
324 <property name="visible">True</property>
325 <property name="can_focus">True</property>
326 <property name="receives_default">False</property>
327 <property name="xalign">0</property>
328 <property name="draw_indicator">True</property>
329 <property name="group">short_choice</property>
330 <child>
331 <object class="GtkLabel" id="label3">
332 <property name="visible">True</property>
333 <property name="can_focus">False</property>
334 <property name="halign">start</property>
335 <property name="hexpand">True</property>
336 <property name="label" translatable="yes" context="indicators-dialog">Separator</property>
337 <attributes>
338 <attribute name="weight" value="semibold"/>
339 </attributes>
340 </object>
341 </child>
342 </object>
343 <packing>
344 <property name="left_attach">0</property>
345 <property name="top_attach">1</property>
346 </packing>
347 </child>
348 <child>
349 <object class="GtkLabel" id="label6">
350 <property name="visible">True</property>
351 <property name="can_focus">False</property>
352 <property name="halign">start</property>
353 <property name="label" translatable="yes" context="indicators-dialog">Draw a separator</property>
354 <attributes>
355 <attribute name="style" value="oblique"/>
356 </attributes>
357 </object>
358 <packing>
359 <property name="left_attach">1</property>
360 <property name="top_attach">1</property>
361 </packing>
362 </child>
363 </object>
364 <packing>
365 <property name="expand">False</property>
366 <property name="fill">True</property>
367 <property name="position">7</property>
368 </packing>
369 </child>
370 <child>
371 <object class="GtkInfoBar" id="infobar">
372 <property name="visible">True</property>
373 <property name="app_paintable">True</property>
374 <property name="can_focus">False</property>
375 <property name="message_type">warning</property>
376 <child internal-child="action_area">
377 <object class="GtkButtonBox" id="infobar-action_area1">
378 <property name="can_focus">False</property>
379 <property name="border_width">5</property>
380 <property name="orientation">vertical</property>
381 <property name="layout_style">start</property>
382 <child>
383 <placeholder/>
384 </child>
385 </object>
386 <packing>
387 <property name="expand">False</property>
388 <property name="fill">True</property>
389 <property name="position">1</property>
390 </packing>
391 </child>
392 <child internal-child="content_area">
393 <object class="GtkBox" id="infobar-content_area1">
394 <property name="can_focus">False</property>
395 <property name="border_width">8</property>
396 <property name="spacing">16</property>
397 <child>
398 <object class="GtkLabel" id="message">
399 <property name="visible">True</property>
400 <property name="can_focus">False</property>
401 <property name="label">[message]</property>
402 </object>
403 <packing>
404 <property name="expand">True</property>
405 <property name="fill">True</property>
406 <property name="position">0</property>
407 </packing>
408 </child>
409 </object>
410 <packing>
411 <property name="expand">True</property>
412 <property name="fill">True</property>
413 <property name="position">0</property>
414 </packing>
415 </child>
416 </object>
417 <packing>
418 <property name="expand">False</property>
419 <property name="fill">True</property>
420 <property name="position">8</property>
421 </packing>
422 </child>
423 </object>
424 <packing>
425 <property name="expand">True</property>
426 <property name="fill">True</property>
427 <property name="position">1</property>
428 </packing>
429 </child>
430 <child>
431 <object class="GtkSeparator" id="separator1">
432 <property name="visible">True</property>
433 <property name="can_focus">False</property>
434 </object>
435 <packing>
436 <property name="expand">False</property>
437 <property name="fill">True</property>
438 <property name="position">2</property>
439 </packing>
440 </child>
441 </object>
442 </child>
443 <action-widgets>
444 <action-widget response="-7">cancel_button</action-widget>
445 <action-widget response="-5">ok_button</action-widget>
446 </action-widgets>
447 </object>
448 </interface>
0 <?xml version="1.0" encoding="UTF-8"?>
1 <!-- Generated with glade 3.16.1 -->
2 <interface>
3 <requires lib="gtk+" version="3.8"/>
4 <!-- interface-requires gtk_greeter_settings 1.0 -->
5 <object class="GtkImage" id="image1">
6 <property name="visible">True</property>
7 <property name="can_focus">False</property>
8 <property name="xpad">2</property>
9 <property name="icon_name">list-add</property>
10 </object>
11 <object class="GtkImage" id="image2">
12 <property name="visible">True</property>
13 <property name="can_focus">False</property>
14 <property name="xpad">2</property>
15 <property name="icon_name">window-close</property>
16 </object>
17 <object class="GtkImage" id="image3">
18 <property name="visible">True</property>
19 <property name="can_focus">False</property>
20 <property name="xpad">2</property>
21 <property name="icon_name">dialog-ok</property>
22 </object>
23 <object class="GtkFileFilter" id="library_file_filter">
24 <mime-types>
25 <mime-type>application/x-sharedlib</mime-type>
26 </mime-types>
27 </object>
28 <object class="GtkMenu" id="option_image_menu">
29 <property name="visible">True</property>
30 <property name="can_focus">False</property>
31 <child>
32 <object class="GtkMenuItem" id="option_image_default_item">
33 <property name="visible">True</property>
34 <property name="can_focus">False</property>
35 <property name="label">[default]</property>
36 <property name="use_underline">True</property>
37 </object>
38 </child>
39 <child>
40 <object class="GtkMenuItem" id="option_image_icon_item">
41 <property name="visible">True</property>
42 <property name="can_focus">False</property>
43 <property name="label">[icon]</property>
44 </object>
45 </child>
46 <child>
47 <object class="GtkMenuItem" id="option_image_path_item">
48 <property name="visible">True</property>
49 <property name="can_focus">False</property>
50 <property name="label">[path]</property>
51 </object>
52 </child>
53 </object>
54 <object class="GtkListStore" id="option_path_model">
55 <columns>
56 <!-- column-name title -->
57 <column type="gchararray"/>
58 <!-- column-name type -->
59 <column type="gchararray"/>
60 <!-- column-name icon -->
61 <column type="gchararray"/>
62 </columns>
63 <data>
64 <row>
65 <col id="0" translatable="yes" context="option-entry|indicators">Select file...</col>
66 <col id="1">select-path</col>
67 <col id="2">system-search</col>
68 </row>
69 <row>
70 <col id="0">-</col>
71 <col id="1">separator</col>
72 <col id="2">-</col>
73 </row>
74 </data>
75 </object>
76 <object class="IndicatorPropertiesDialog" id="indicator_properties_dialog">
77 <property name="width_request">400</property>
78 <property name="can_focus">False</property>
79 <property name="title" translatable="yes" context="option-entry|indicators">Indicator properties</property>
80 <property name="resizable">False</property>
81 <property name="modal">True</property>
82 <property name="window_position">center</property>
83 <property name="destroy_with_parent">True</property>
84 <property name="icon_name">application-x-executable</property>
85 <property name="type_hint">dialog</property>
86 <child internal-child="vbox">
87 <object class="GtkBox" id="dialog-vbox1">
88 <property name="can_focus">False</property>
89 <property name="margin_left">8</property>
90 <property name="margin_right">8</property>
91 <property name="margin_top">8</property>
92 <property name="margin_bottom">8</property>
93 <property name="orientation">vertical</property>
94 <property name="spacing">8</property>
95 <child>
96 <object class="GtkBox" id="box2">
97 <property name="visible">True</property>
98 <property name="can_focus">False</property>
99 <property name="spacing">6</property>
100 <child>
101 <object class="GtkRadioButton" id="option_type_indicator_choice">
102 <property name="label" translatable="yes" context="option-entry|indicators">Indicator</property>
103 <property name="visible">True</property>
104 <property name="can_focus">True</property>
105 <property name="receives_default">False</property>
106 <property name="xalign">0</property>
107 <property name="active">True</property>
108 <property name="draw_indicator">True</property>
109 </object>
110 <packing>
111 <property name="expand">False</property>
112 <property name="fill">True</property>
113 <property name="position">0</property>
114 </packing>
115 </child>
116 <child>
117 <object class="GtkComboBoxText" id="option_type_types">
118 <property name="visible">True</property>
119 <property name="can_focus">False</property>
120 <signal name="changed" handler="on_option_type_types_changed" swapped="no"/>
121 </object>
122 <packing>
123 <property name="expand">False</property>
124 <property name="fill">True</property>
125 <property name="position">1</property>
126 </packing>
127 </child>
128 </object>
129 <packing>
130 <property name="expand">False</property>
131 <property name="fill">True</property>
132 <property name="position">0</property>
133 </packing>
134 </child>
135 <child internal-child="action_area">
136 <object class="GtkButtonBox" id="dialog-action_area1">
137 <property name="can_focus">False</property>
138 <property name="homogeneous">True</property>
139 <property name="layout_style">end</property>
140 <child>
141 <object class="GtkButton" id="cancel_button">
142 <property name="label" translatable="yes" context="button">_Close</property>
143 <property name="visible">True</property>
144 <property name="can_focus">True</property>
145 <property name="receives_default">True</property>
146 <property name="image">image2</property>
147 <property name="use_underline">True</property>
148 </object>
149 <packing>
150 <property name="expand">False</property>
151 <property name="fill">True</property>
152 <property name="position">0</property>
153 </packing>
154 </child>
155 <child>
156 <object class="GtkButton" id="ok_button">
157 <property name="label" translatable="yes" context="button">_OK</property>
158 <property name="visible">True</property>
159 <property name="can_focus">True</property>
160 <property name="can_default">True</property>
161 <property name="has_default">True</property>
162 <property name="receives_default">True</property>
163 <property name="image">image3</property>
164 <property name="use_underline">True</property>
165 </object>
166 <packing>
167 <property name="expand">False</property>
168 <property name="fill">True</property>
169 <property name="position">1</property>
170 </packing>
171 </child>
172 <child>
173 <object class="GtkBox" id="box1">
174 <property name="visible">True</property>
175 <property name="can_focus">False</property>
176 <property name="orientation">vertical</property>
177 <property name="homogeneous">True</property>
178 <child>
179 <object class="GtkButton" id="add_button">
180 <property name="label" translatable="yes" context="button">_Add</property>
181 <property name="visible">True</property>
182 <property name="can_focus">True</property>
183 <property name="receives_default">True</property>
184 <property name="image">image1</property>
185 <property name="use_underline">True</property>
186 <signal name="clicked" handler="on_add_clicked" swapped="no"/>
187 </object>
188 <packing>
189 <property name="expand">False</property>
190 <property name="fill">True</property>
191 <property name="position">0</property>
192 </packing>
193 </child>
194 </object>
195 <packing>
196 <property name="expand">False</property>
197 <property name="fill">True</property>
198 <property name="position">2</property>
199 <property name="secondary">True</property>
200 <property name="non_homogeneous">True</property>
201 </packing>
202 </child>
203 </object>
204 <packing>
205 <property name="expand">False</property>
206 <property name="fill">True</property>
207 <property name="pack_type">end</property>
208 <property name="position">0</property>
209 </packing>
210 </child>
211 <child>
212 <object class="GtkBox" id="option_type_indicator_box">
213 <property name="visible">True</property>
214 <property name="can_focus">False</property>
215 <property name="margin_left">24</property>
216 <property name="orientation">vertical</property>
217 <property name="spacing">4</property>
218 <child>
219 <object class="GtkGrid" id="grid1">
220 <property name="visible">True</property>
221 <property name="can_focus">False</property>
222 <property name="row_spacing">8</property>
223 <property name="column_spacing">6</property>
224 <child>
225 <object class="GtkCheckButton" id="option_text_use">
226 <property name="label" translatable="yes" context="option-entry|indicators">Display label</property>
227 <property name="visible">True</property>
228 <property name="can_focus">True</property>
229 <property name="receives_default">False</property>
230 <property name="xalign">0</property>
231 <property name="draw_indicator">True</property>
232 </object>
233 <packing>
234 <property name="left_attach">0</property>
235 <property name="top_attach">0</property>
236 <property name="width">1</property>
237 <property name="height">1</property>
238 </packing>
239 </child>
240 <child>
241 <object class="GtkEntry" id="option_text_value">
242 <property name="visible">True</property>
243 <property name="can_focus">True</property>
244 <property name="hexpand">True</property>
245 <property name="placeholder_text" translatable="yes" context="option-entry|indicators">Leave empty to use default value</property>
246 </object>
247 <packing>
248 <property name="left_attach">1</property>
249 <property name="top_attach">0</property>
250 <property name="width">1</property>
251 <property name="height">1</property>
252 </packing>
253 </child>
254 <child>
255 <object class="GtkCheckButton" id="option_image_use">
256 <property name="label" translatable="yes" context="option-entry|indicators">Display image</property>
257 <property name="visible">True</property>
258 <property name="can_focus">True</property>
259 <property name="receives_default">False</property>
260 <property name="xalign">0</property>
261 <property name="draw_indicator">True</property>
262 </object>
263 <packing>
264 <property name="left_attach">0</property>
265 <property name="top_attach">1</property>
266 <property name="width">1</property>
267 <property name="height">1</property>
268 </packing>
269 </child>
270 <child>
271 <object class="GtkMenuButton" id="option_image_button">
272 <property name="visible">True</property>
273 <property name="can_focus">True</property>
274 <property name="receives_default">True</property>
275 <property name="halign">start</property>
276 <property name="popup">option_image_menu</property>
277 <child>
278 <object class="GtkBox" id="box3">
279 <property name="visible">True</property>
280 <property name="can_focus">False</property>
281 <property name="spacing">4</property>
282 <child>
283 <object class="GtkImage" id="option_image_image">
284 <property name="width_request">24</property>
285 <property name="height_request">24</property>
286 <property name="visible">True</property>
287 <property name="can_focus">False</property>
288 <property name="pixel_size">24</property>
289 <property name="icon_name">avatar-default</property>
290 <property name="icon_size">1</property>
291 </object>
292 <packing>
293 <property name="expand">False</property>
294 <property name="fill">True</property>
295 <property name="position">0</property>
296 </packing>
297 </child>
298 <child>
299 <object class="GtkLabel" id="option_image_label">
300 <property name="height_request">24</property>
301 <property name="visible">True</property>
302 <property name="can_focus">False</property>
303 <property name="label">[icon]</property>
304 </object>
305 <packing>
306 <property name="expand">False</property>
307 <property name="fill">True</property>
308 <property name="position">1</property>
309 </packing>
310 </child>
311 </object>
312 </child>
313 </object>
314 <packing>
315 <property name="left_attach">1</property>
316 <property name="top_attach">1</property>
317 <property name="width">1</property>
318 <property name="height">1</property>
319 </packing>
320 </child>
321 </object>
322 <packing>
323 <property name="expand">False</property>
324 <property name="fill">True</property>
325 <property name="position">0</property>
326 </packing>
327 </child>
328 <child>
329 <object class="GtkNotebook" id="custom_options_box">
330 <property name="visible">True</property>
331 <property name="can_focus">False</property>
332 <property name="valign">start</property>
333 <property name="show_tabs">False</property>
334 <property name="show_border">False</property>
335 <child>
336 <object class="GtkBox" id="custom_options_external">
337 <property name="can_focus">False</property>
338 <property name="orientation">vertical</property>
339 <property name="spacing">4</property>
340 <child>
341 <object class="GtkLabel" id="label2">
342 <property name="visible">True</property>
343 <property name="can_focus">False</property>
344 <property name="halign">start</property>
345 <property name="label" translatable="yes" context="option-entry|indicators">Indicator library/service:</property>
346 </object>
347 <packing>
348 <property name="expand">False</property>
349 <property name="fill">True</property>
350 <property name="position">0</property>
351 </packing>
352 </child>
353 <child>
354 <object class="GtkComboBox" id="option_path_combo">
355 <property name="visible">True</property>
356 <property name="can_focus">False</property>
357 <property name="model">option_path_model</property>
358 <property name="has_entry">True</property>
359 <property name="entry_text_column">0</property>
360 <property name="id_column">1</property>
361 <child>
362 <object class="GtkCellRendererPixbuf" id="cellrenderertext1"/>
363 <attributes>
364 <attribute name="icon-name">2</attribute>
365 </attributes>
366 </child>
367 <child>
368 <object class="GtkCellRendererText" id="cellrenderertext2"/>
369 <attributes>
370 <attribute name="text">0</attribute>
371 </attributes>
372 </child>
373 <child internal-child="entry">
374 <object class="GtkEntry" id="option_path_entry">
375 <property name="can_focus">True</property>
376 </object>
377 </child>
378 </object>
379 <packing>
380 <property name="expand">False</property>
381 <property name="fill">True</property>
382 <property name="position">1</property>
383 </packing>
384 </child>
385 </object>
386 </child>
387 <child type="tab">
388 <object class="GtkLabel" id="label3">
389 <property name="visible">True</property>
390 <property name="can_focus">False</property>
391 <property name="label" translatable="yes">page 1</property>
392 </object>
393 <packing>
394 <property name="tab_fill">False</property>
395 </packing>
396 </child>
397 <child>
398 <object class="GtkBox" id="options_power">
399 <property name="can_focus">False</property>
400 <property name="orientation">vertical</property>
401 <child>
402 <object class="GtkCheckButton" id="option_hide_disabled_value">
403 <property name="label" translatable="yes" context="option-entry|indicators">Hide disabled power actions</property>
404 <property name="visible">True</property>
405 <property name="can_focus">True</property>
406 <property name="receives_default">False</property>
407 <property name="xalign">0</property>
408 <property name="draw_indicator">True</property>
409 </object>
410 <packing>
411 <property name="expand">False</property>
412 <property name="fill">True</property>
413 <property name="position">0</property>
414 </packing>
415 </child>
416 </object>
417 <packing>
418 <property name="position">1</property>
419 </packing>
420 </child>
421 <child type="tab">
422 <object class="GtkLabel" id="label1">
423 <property name="visible">True</property>
424 <property name="can_focus">False</property>
425 <property name="label" translatable="yes">page 2</property>
426 </object>
427 <packing>
428 <property name="position">1</property>
429 <property name="tab_fill">False</property>
430 </packing>
431 </child>
432 </object>
433 <packing>
434 <property name="expand">False</property>
435 <property name="fill">True</property>
436 <property name="position">1</property>
437 </packing>
438 </child>
439 </object>
440 <packing>
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="GtkRadioButton" id="option_type_spacer_choice">
448 <property name="label" translatable="yes" context="option-entry|indicators">Spacer - fills the maximum available space</property>
449 <property name="visible">True</property>
450 <property name="can_focus">True</property>
451 <property name="receives_default">False</property>
452 <property name="xalign">0</property>
453 <property name="draw_indicator">True</property>
454 <property name="group">option_type_indicator_choice</property>
455 </object>
456 <packing>
457 <property name="expand">False</property>
458 <property name="fill">True</property>
459 <property name="position">3</property>
460 </packing>
461 </child>
462 <child>
463 <object class="GtkRadioButton" id="option_type_separator_choice">
464 <property name="label" translatable="yes" context="option-entry|indicators">Separator - draw a separator</property>
465 <property name="visible">True</property>
466 <property name="can_focus">True</property>
467 <property name="receives_default">False</property>
468 <property name="xalign">0</property>
469 <property name="draw_indicator">True</property>
470 <property name="group">option_type_indicator_choice</property>
471 </object>
472 <packing>
473 <property name="expand">False</property>
474 <property name="fill">True</property>
475 <property name="position">4</property>
476 </packing>
477 </child>
478 <child>
479 <object class="GtkInfoBar" id="infobar">
480 <property name="app_paintable">True</property>
481 <property name="can_focus">False</property>
482 <property name="message_type">warning</property>
483 <child internal-child="action_area">
484 <object class="GtkButtonBox" id="infobar-action_area">
485 <property name="can_focus">False</property>
486 <property name="orientation">vertical</property>
487 <property name="layout_style">start</property>
488 <child>
489 <placeholder/>
490 </child>
491 </object>
492 <packing>
493 <property name="expand">False</property>
494 <property name="fill">True</property>
495 <property name="position">1</property>
496 </packing>
497 </child>
498 <child internal-child="content_area">
499 <object class="GtkBox" id="infobar-content_area">
500 <property name="can_focus">False</property>
501 <property name="spacing">16</property>
502 <child>
503 <object class="GtkLabel" id="message">
504 <property name="visible">True</property>
505 <property name="can_focus">False</property>
506 <property name="label">[message]</property>
507 </object>
508 <packing>
509 <property name="expand">True</property>
510 <property name="fill">True</property>
511 <property name="position">0</property>
512 </packing>
513 </child>
514 </object>
515 <packing>
516 <property name="expand">True</property>
517 <property name="fill">True</property>
518 <property name="position">0</property>
519 </packing>
520 </child>
521 </object>
522 <packing>
523 <property name="expand">False</property>
524 <property name="fill">True</property>
525 <property name="position">5</property>
526 </packing>
527 </child>
528 <child>
529 <object class="GtkSeparator" id="separator1">
530 <property name="visible">True</property>
531 <property name="can_focus">False</property>
532 </object>
533 <packing>
534 <property name="expand">False</property>
535 <property name="fill">True</property>
536 <property name="position">6</property>
537 </packing>
538 </child>
539 </object>
540 </child>
541 <action-widgets>
542 <action-widget response="-7">cancel_button</action-widget>
543 <action-widget response="-5">ok_button</action-widget>
544 </action-widgets>
545 </object>
546 </interface>
156156 <property name="headers_clickable">False</property>
157157 <property name="search_column">0</property>
158158 <child internal-child="selection">
159 <object class="GtkTreeSelection" id="treeview-selection">
159 <object class="GtkTreeSelection" id="monitors_selection">
160160 <signal name="changed" handler="on_selection_changed" swapped="no"/>
161161 </object>
162162 </child>
33 <glade-widget-class title="Settings Window" name="GtkGreeterSettingsWindow"
44 generic-name="GtkGreeterSettingsWindow" parent="GtkWindow"
55 icon-name="widget-gtk-window"/>
6 <glade-widget-class title="Indicator Chooser Dialog" name="IndicatorChooserDialog"
7 generic-name="IndicatorChooserDialog" parent="GtkDialog"
6 <glade-widget-class title="Indicator Properties Dialog" name="IndicatorPropertiesDialog"
7 generic-name="IndicatorPropertiesDialog" parent="GtkDialog"
88 icon-name="widget-gtk-dialog"/>
99 <glade-widget-class title="Icon Chooser Dialog" name="IconChooserDialog"
1010 generic-name="IconChooserDialog" parent="GtkDialog"
1515 # with this program. If not, see <http://www.gnu.org/licenses/>.
1616
1717
18 import collections
1819 import configparser
1920 import os
2021 import sys
21 from collections import namedtuple
2222 from glob import iglob
2323 from itertools import chain
2424 from locale import gettext as _
25 from gi.repository import Gtk, Gdk
26
27 from lightdm_gtk_greeter_settings import helpers
28 from lightdm_gtk_greeter_settings.helpers import C_, WidgetsWrapper, string2bool
29 from lightdm_gtk_greeter_settings import OptionEntry
25
26 from gi.repository import (
27 Gdk,
28 Gtk)
29
30 from lightdm_gtk_greeter_settings import (
31 helpers,
32 IndicatorsEntry,
33 OptionEntry,
34 PositionEntry)
35 from lightdm_gtk_greeter_settings.helpers import (
36 C_,
37 string2bool,
38 WidgetsWrapper, WidgetsEnum)
39 from lightdm_gtk_greeter_settings.MonitorsGroup import MonitorsGroup
3040 from lightdm_gtk_greeter_settings.OptionGroup import SimpleGroup
31 from lightdm_gtk_greeter_settings.MonitorsGroup import MonitorsGroup
32 from lightdm_gtk_greeter_settings import PositionEntry
3341
3442
3543 __all__ = ['GtkGreeterSettingsWindow']
3644
3745
38 InitialValue = namedtuple('InitialValue', ('value', 'enabled'))
46 InitialValue = collections.namedtuple('InitialValue', ('value', 'enabled'))
3947
4048
4149 class GtkGreeterSettingsWindow(Gtk.Window):
4250
4351 __gtype_name__ = 'GtkGreeterSettingsWindow'
4452
45 BUILDER_WIDGETS = ('apply_button', 'no_access_infobar',
46 'gtk_theme_values', 'icons_theme_values')
53 class Widgets(WidgetsEnum):
54 apply = 'apply_button'
55 infobar = 'infobar'
4756
4857 def __new__(cls):
4958 builder = Gtk.Builder()
5059 builder.add_from_file(helpers.get_data_path('%s.ui' % cls.__name__))
5160 window = builder.get_object('settings_window')
52 window._builder = builder
53 window._widgets = WidgetsWrapper(builder)
54 window.__dict__.update(('_' + w, builder.get_object(w))
55 for w in cls.BUILDER_WIDGETS)
61 window.builder = builder
5662 builder.connect_signals(window)
57 window._init_window()
63 window.init_window()
5864 return window
5965
60 def _init_window(self):
61
62 self._entries_setup = \
63 {
64 ('greeter', 'screensaver-timeout'): self.on_entry_setup_greeter_screensaver_timeout,
65 ('greeter', 'theme-name'): self.on_entry_setup_greeter_theme_name,
66 ('greeter', 'icon-theme-name'): self.on_entry_setup_greeter_icon_theme_name,
67 ('greeter', 'default-user-image'): self.on_entry_setup_greeter_default_user_image,
68 ('greeter', 'background'): self.on_entry_setup_greeter_background,
69 ('greeter', 'allow-debugging'): self.on_entry_setup_greeter_allow_debugging,
70 }
66 builder = None
67
68 entries_setup = {
69 ('greeter', 'allow-debugging'): ('changed',),
70 ('greeter', 'background'): ('changed',),
71 ('greeter', 'default-user-image'): ('changed',),
72 ('greeter', 'screensaver-timeout'): ('setup', 'get', 'set'),
73 ('greeter', 'theme-name'): ('setup', 'changed'),
74 ('greeter', 'icon-theme-name'): ('setup', 'changed')}
75
76 def init_window(self):
77 self._widgets = self.Widgets(builder=self.builder)
7178
7279 self._multihead_dialog = None
7380 self._initial_values = {}
7481 self._changed_entries = None
7582 self._entries = None
76 self._groups = \
77 (
78 SimpleGroup('greeter', self._builder,
79 {
83 self._groups = (
84 SimpleGroup('greeter', self.builder, {
8085 # Appearance
8186 'theme-name': (OptionEntry.StringEntry, None),
8287 'icon-theme-name': (OptionEntry.StringEntry, None),
9196 'default-user-image': (OptionEntry.IconEntry, '#avatar-default'),
9297 # Panel
9398 'clock-format': (OptionEntry.ClockFormatEntry, '%a, %H:%M'),
94 'indicators': (OptionEntry.IndicatorsEntry,
99 'indicators': (IndicatorsEntry.IndicatorsEntry,
95100 '~host;~spacer;~clock;~spacer;~language;~session;~a11y;~power'),
96101 # Position
97102 'position': (PositionEntry.PositionEntry, '50%,center'),
100105 'keyboard': (OptionEntry.StringPathEntry, None),
101106 'reader': (OptionEntry.StringPathEntry, None),
102107 'a11y-states': (OptionEntry.AccessibilityStatesEntry, None),
103 'allow-debugging': (OptionEntry.BooleanEntry, 'false'),
104 }),
105 MonitorsGroup(self._widgets)
106 )
108 'allow-debugging': (OptionEntry.BooleanEntry, 'false'), }),
109 MonitorsGroup(WidgetsWrapper(self.builder)))
107110
108111 for group in self._groups:
109112 group.entry_added.connect(self.on_entry_added)
110113 group.entry_removed.connect(self.on_entry_removed)
111114
112 self._timeout_adjustment = self._widgets['greeter', 'screensaver-timeout', 'adjustment']
113
114115 self._config_path = helpers.get_config_path()
115116 self._allow_edit = self._has_access_to_write(self._config_path)
116 self._no_access_infobar.props.visible = not self._allow_edit
117 self._apply_button.props.visible = self._allow_edit
117 self._widgets.infobar.props.visible = not self._allow_edit
118 self._widgets.apply.props.visible = self._allow_edit
118119 if not self._allow_edit:
119120 helpers.show_message(
120121 text=_('No permissions to save configuration'),
150151 group.read(self._config)
151152
152153 self._initial_values = {entry: InitialValue(entry.value, entry.enabled)
153 for entry in self._initial_values.keys()}
154 for entry in self._initial_values.keys()}
154155 self._changed_entries = set()
155 self._apply_button.props.sensitive = False
156 self._widgets.apply.props.sensitive = False
156157
157158 def _write(self):
158159 for group in self._groups:
162163 self._initial_values[entry] = InitialValue(entry.value, entry.enabled)
163164
164165 self._changed_entries.clear()
165 self._apply_button.props.sensitive = False
166 self._widgets.apply.props.sensitive = False
166167
167168 try:
168169 with open(self._config_path, 'w') as file:
171172 helpers.show_message(e, Gtk.MessageType.ERROR)
172173
173174 def on_entry_added(self, group, entry, key):
174 if isinstance(group, SimpleGroup) and (group.name, key) in self._entries_setup:
175 self._entries_setup[(group.name, key)](entry)
175 if isinstance(group, SimpleGroup) and (group.name, key) in self.entries_setup:
176 for action in self.entries_setup[(group.name, key)]:
177 fname = 'on_entry_%s_%s_%s' % (action, group.name, key)
178 f = getattr(self, fname.replace('-', '_'))
179 if action == 'setup':
180 f(entry)
181 else:
182 entry.connect(action, f)
183
176184 entry.changed.connect(self.on_entry_changed)
177185 self._initial_values[entry] = InitialValue(entry.value, entry.enabled)
178186 self.on_entry_changed(entry, force=True)
183191 return
184192
185193 self._changed_entries.discard(entry)
186 self._apply_button.props.sensitive = self._allow_edit and self._changed_entries
194 self._widgets.apply.props.sensitive = self._allow_edit and self._changed_entries
187195
188196 def on_entry_changed(self, entry, force=False):
189197 if self._changed_entries is None:
190198 return
191199
192 if force or entry.enabled != self._initial_values[entry].enabled or \
193 (entry.enabled and entry.value != self._initial_values[entry].value):
200 initial = self._initial_values[entry]
201 if force or entry.enabled != initial.enabled or \
202 (entry.enabled and entry.value != initial.value):
194203 self._changed_entries.add(entry)
195204 else:
196205 self._changed_entries.discard(entry)
197 self._apply_button.props.sensitive = self._allow_edit and self._changed_entries
198
206
207 self._widgets.apply.props.sensitive = self._allow_edit and self._changed_entries
208
209 # [greeter] screensaver-timeout
199210 def on_entry_setup_greeter_screensaver_timeout(self, entry):
200 timeout_view = entry.widgets['view']
201 timeout_adjustment = entry.widgets['adjustment']
202 timeout_end_label = entry.widgets['end-label']
203 for mark in chain(list(range(10, 61, 10)),
204 list(range(69,
205 int(timeout_adjustment.props.upper),
206 10))):
207 timeout_view.add_mark(mark, Gtk.PositionType.BOTTOM, None)
208 total = int(timeout_adjustment.props.upper - 60) + 1
209 timeout_end_label.props.label = C_('option|greeter|screensaver-timeout', '{count} min').format(count=total)
210
211 timeout_view.connect('format-value', self.on_entry_format_scale_greeter_screensaver_timeout, timeout_adjustment)
212 entry.get.connect(self.on_entry_get_greeter_screensaver_timeout)
213 entry.set.connect(self.on_entry_set_greeter_screensaver_timeout)
214
215 def on_entry_setup_greeter_theme_name(self, entry):
216 values = entry.widgets['values']
217 for theme in sorted(iglob(os.path.join(sys.prefix, 'share', 'themes', '*', 'gtk-3.0'))):
218 values.append_text(theme.split(os.path.sep)[-2])
219 entry.changed.connect(self.on_entry_changed_greeter_theme_name)
220
221 def on_entry_setup_greeter_icon_theme_name(self, entry):
222 values = entry.widgets['values']
223 for theme in sorted(iglob(os.path.join(sys.prefix, 'share', 'icons', '*', 'index.theme'))):
224 values.append_text(theme.split(os.path.sep)[-2])
225 entry.changed.connect(self.on_entry_changed_greeter_icon_theme_name)
226
227 def on_entry_setup_greeter_default_user_image(self, entry):
228 entry.changed.connect(self.on_entry_changed_greeter_default_user_image)
229
230 def on_entry_setup_greeter_background(self, entry):
231 entry.changed.connect(self.on_entry_changed_greeter_background)
232
233 def on_entry_setup_greeter_allow_debugging(self, entry):
234 if (Gtk.MAJOR_VERSION, Gtk.MINOR_VERSION, Gtk.MICRO_VERSION) < (3, 14, 0):
235 entry.changed.connect(self.on_entry_changed_greeter_allow_debugging)
211 view = entry.widgets['view']
212 adjustment = entry.widgets['adjustment']
213 end_label = entry.widgets['end-label']
214 for mark in chain((range(10, 61, 10)),
215 (range(69, int(adjustment.props.upper), 10))):
216 view.add_mark(mark, Gtk.PositionType.BOTTOM, None)
217 total = int(adjustment.props.upper - 60) + 1
218 end_label.props.label = C_('option|greeter|screensaver-timeout',
219 '{count} min').format(count=total)
220
221 view.connect('format-value',
222 self.on_entry_format_scale_greeter_screensaver_timeout,
223 adjustment)
236224
237225 def on_entry_get_greeter_screensaver_timeout(self, entry=None, value=None):
238226 value = int(float(value))
246234 return value // 60 + 59
247235 return value
248236
249 def on_entry_changed_greeter_theme_name(self, entry):
250 if not entry.value or entry.value in (row[0] for row in entry.widgets['values'].props.model):
251 entry.error = None
252 else:
253 entry.error = C_('option|greeter|theme-name', 'Selected theme is not available')
254
255 def on_entry_changed_greeter_icon_theme_name(self, entry):
256 if not entry.value or entry.value in (row[0] for row in entry.widgets['values'].props.model):
257 entry.error = None
258 else:
259 entry.error = C_('option|greeter|icon-theme-name', 'Selected theme is not available')
260
261 def on_entry_changed_greeter_default_user_image(self, entry):
262 value = entry.value
263 if value.startswith('#'):
264 entry.error = None
265 elif not os.path.exists(value):
266 entry.error = C_('option|greeter|default-user-image', 'File not found: {path}'.format(path=value))
267 else:
268 try:
269 if not helpers.file_is_readable_by_greeter(value):
270 entry.error = C_('option|greeter|default-user-image', 'File may be not readable for greeter: {path}'.format(path=value))
271 else:
272 entry.error = None
273 except:
274 entry.error = C_('option|greeter|default-user-image', 'Failed to check permissions for file: {path}'.format(path=value))
275
276 def on_entry_changed_greeter_background(self, entry):
277 value = entry.value
278 if Gdk.RGBA().parse(value):
279 entry.error = None
280 elif not os.path.exists(value):
281 entry.error = C_('option|greeter|background', 'File not found: {path}'.format(path=value))
282 else:
283 try:
284 if not helpers.file_is_readable_by_greeter(value):
285 entry.error = C_('option|greeter|background', 'File may be not readable for greeter: {path}'.format(path=value))
286 else:
287 entry.error = None
288 except:
289 entry.error = C_('option|greeter|background', 'Failed to check permissions for file: {path}'.format(path=value))
290
291 def on_entry_changed_greeter_allow_debugging(self, entry):
292 if string2bool(entry.value):
293 entry.error = C_('option|greeter|allow-debugging',
294 'GtkInspector is not available on your system')
295 else:
296 entry.error = None
297
298237 def on_entry_format_scale_greeter_screensaver_timeout(self, scale, value, adjustment):
299238 if value != adjustment.props.lower and value != adjustment.props.upper:
300239 value = self.on_entry_get_greeter_screensaver_timeout(value=value)
301240 return '%02d:%02d' % (value // 60, value % 60)
302 else:
303 return ''
241 return ''
242
243 # [greeter] theme-name
244 def on_entry_setup_greeter_theme_name(self, entry):
245 values = entry.widgets['values']
246 for theme in sorted(iglob(os.path.join(sys.prefix, 'share', 'themes', '*', 'gtk-3.0'))):
247 values.append_text(theme.split(os.path.sep)[-2])
248
249 def on_entry_changed_greeter_theme_name(self, entry):
250 if not entry.value or \
251 entry.value in (row[0] for row in entry.widgets['values'].props.model):
252 entry.error = None
253 else:
254 entry.error = C_('option|greeter|theme-name', 'Selected theme is not available')
255
256 # [greeter] icon-theme-name
257 def on_entry_setup_greeter_icon_theme_name(self, entry):
258 values = entry.widgets['values']
259 for theme in sorted(iglob(os.path.join(sys.prefix, 'share', 'icons', '*', 'index.theme'))):
260 values.append_text(theme.split(os.path.sep)[-2])
261
262 def on_entry_changed_greeter_icon_theme_name(self, entry):
263 if not entry.value or \
264 entry.value in (row[0] for row in entry.widgets['values'].props.model):
265 entry.error = None
266 else:
267 entry.error = C_('option|greeter|icon-theme-name', 'Selected theme is not available')
268
269 # [greeter] allow-debugging
270 def on_entry_changed_greeter_allow_debugging(self, entry):
271 if (Gtk.MAJOR_VERSION, Gtk.MINOR_VERSION, Gtk.MICRO_VERSION) < (3, 14, 0) and \
272 string2bool(entry.value):
273 entry.error = C_('option|greeter|allow-debugging',
274 'GtkInspector is not available on your system')
275 else:
276 entry.error = None
277
278 # [greeter] default-user-image
279 def on_entry_changed_greeter_default_user_image(self, entry):
280 value = entry.value
281 if value.startswith('#'):
282 entry.error = None
283 else:
284 entry.error = helpers.check_path_accessibility(value)
285
286 # [greeter] background
287 def on_entry_changed_greeter_background(self, entry):
288 value = entry.value
289 if not value or Gdk.RGBA().parse(value):
290 entry.error = None
291 else:
292 entry.error = helpers.check_path_accessibility(value)
304293
305294 def on_destroy(self, *unused):
306295 Gtk.main_quit()
1414 # You should have received a copy of the GNU General Public License along
1515 # with this program. If not, see <http://www.gnu.org/licenses/>.
1616
17 from collections import namedtuple
18 from gi.repository import Gtk, GObject
19 from lightdm_gtk_greeter_settings.helpers import ModelRowEnum
20 from lightdm_gtk_greeter_settings.helpers import get_data_path
21 from lightdm_gtk_greeter_settings.helpers import NC_, C_
17
18 from gi.repository import (
19 GObject,
20 Gtk)
21
22 from lightdm_gtk_greeter_settings.helpers import (
23 get_data_path,
24 NC_,
25 SimpleEnum,
26 WidgetsEnum)
2227
2328
2429 __all__ = ['IconChooserDialog']
2530
26 CONTEXT_ROW = ModelRowEnum('NAME', 'STANDARD', 'TITLE')
27 ICON_ROW = ModelRowEnum('NAME', 'STANDARD', 'CONTEXT')
31
32 class ContextRow(SimpleEnum):
33 Name = ()
34 Standard = ()
35 Title = ()
36
37
38 class IconRow(SimpleEnum):
39 Name = ()
40 Standard = ()
41 Context = ()
2842
2943
3044 class IconChooserDialog(Gtk.Dialog):
3145
3246 __gtype_name__ = 'IconChooserDialog'
33
34 IconsFilterArgs = namedtuple('IconsFilterArgs', ('standard', 'context'))
35
36 BUILDER_WIDGETS = ('name', 'preview', 'standard_toggle', 'spinner',
37 'contexts_view', 'contexts_selection', 'contexts_model',
38 'contexts_filter', 'icons_view', 'icons_selection',
39 'icons_model', 'icons_sorted', 'icons_filter')
4047
4148 def __new__(cls):
4249 builder = Gtk.Builder()
4350 builder.add_from_file(get_data_path('%s.ui' % cls.__name__))
4451 window = builder.get_object('icon_chooser_dialog')
45 window._builder = builder
46 window.__dict__.update(('_' + w, builder.get_object(w))
47 for w in cls.BUILDER_WIDGETS)
48
52 window.builder = builder
4953 builder.connect_signals(window)
50 window._init_window()
54 window.init_window()
5155 return window
5256
53 def _init_window(self):
57 class Widgets(WidgetsEnum):
58 name = 'name_entry'
59 preview = 'preview_image'
60 standard = 'standard_toggle'
61 ok = 'ok_button'
62 cancel = 'cancel_button'
63 contexts_view = 'contexts_view'
64 contexts_selection = 'contexts_selection'
65 contexts_model = 'contexts_model'
66 contexts_filter = 'contexts_filter'
67 icons_view = 'icons_view'
68 icons_selection = 'icons_selection'
69 icons_model = 'icons_model'
70 icons_sorted = 'icons_sorted'
71 icons_filter = 'icons_filter'
72
73 builder = None
74
75 def init_window(self):
76 self._widgets = self.Widgets(builder=self.builder)
77 self._widgets_to_disable = (self._widgets.name, self._widgets.standard,
78 self._widgets.contexts_view, self._widgets.icons_view,
79 self._widgets.ok, self._widgets.cancel)
5480
5581 self._icons_loaded = False
5682 self._icon_to_select = None
57 self._icons_filter_args = None
58
59 self._contexts_view.set_row_separator_func(
60 self._contexts_row_separator_callback, None)
61 self._contexts_filter.set_visible_func(
62 self._contexts_filter_visible_callback)
63
64 self._icons_filter.set_visible_func(
65 self._icons_filter_visible_callback)
66 self._icons_sorted.set_sort_column_id(0, Gtk.SortType.ASCENDING)
83 self._icon_filter_standard = True
84 self._icon_filter_context = None
85
86 self._widgets.contexts_view.set_row_separator_func(self._contexts_separator_callback, None)
87 self._widgets.contexts_filter.set_visible_func(self._contexts_visible_callback)
88
89 self._widgets.icons_filter.set_visible_func(self._icons_filter_visible_callback)
90 self._widgets.icons_sorted.set_sort_column_id(0, Gtk.SortType.ASCENDING)
6791
6892 self._reload()
93
94 def _reload(self):
95 for w in self._widgets_to_disable:
96 w.props.sensitive = False
97 GObject.idle_add(self._read_icons)
6998
7099 def _read_icons(self):
71100 theme = Gtk.IconTheme.get_default()
72 standard_contexts = set(name for name, title in STANDARD_CONTEXTS)
73
74 self._contexts_model.clear()
75 for name, title in STANDARD_CONTEXTS:
76 row = CONTEXT_ROW(NAME=name, STANDARD=True,
77 TITLE=title and C_('icon-dialog', title))
78 self._contexts_model.append(row)
101 standard_contexts = set(name for name, title in StandardContexts)
102
103 self._widgets.contexts_model.clear()
104 for name, title in StandardContexts:
105 row = ContextRow._make(Name=name, Standard=True, Title=title)
106 self._widgets.contexts_model.append(row)
79107
80108 for name in theme.list_contexts():
81109 if name not in standard_contexts:
82 row = CONTEXT_ROW(NAME=name, STANDARD=False, TITLE=name)
83 self._contexts_model.append(row)
84
85 self._icons_model.clear()
110 row = ContextRow._make(Name=name, Standard=False, Title=name)
111 self._widgets.contexts_model.append(row)
112
113 self._widgets.icons_model.clear()
86114 for context in theme.list_contexts():
87115 for icon in theme.list_icons(context):
88 row = ICON_ROW(NAME=icon, CONTEXT=context,
89 STANDARD=icon in STANDARD_ICON_NAMES)
90 self._icons_model.append(row)
116 row = IconRow._make(Name=icon, Standard=icon in StandardIconNames, Context=context)
117 self._widgets.icons_model.append(row)
91118
92119 self._icons_loaded = True
93120 if self._icon_to_select:
94121 self.select_icon(self._icon_to_select)
95122 self._icon_to_select = None
123
124 for w in self._widgets_to_disable:
125 w.props.sensitive = True
126
96127 return False
97128
98 def _reload(self):
99 GObject.idle_add(self._read_icons)
100
101129 def _update_contexts_filter(self):
102 selected_iter = self._contexts_selection.get_selected()[1]
103 selected_path = self._contexts_filter.get_path(
104 selected_iter) if selected_iter else None
105 self._contexts_filter.refilter()
106 if selected_path and \
107 self._contexts_selection.path_is_selected(selected_path):
108 self._update_icons_filter()
130 self._widgets.contexts_filter.refilter()
131 self._update_icons_filter()
109132
110133 def _update_icons_filter(self):
111 model, rowiter = self._contexts_selection.get_selected()
134 self._widgets.icons_view.props.model = None
135 model, rowiter = self._widgets.contexts_selection.get_selected()
112136 if rowiter:
113 self._icons_filter_args = self.IconsFilterArgs(
114 self._standard_toggle.props.active,
115 model[rowiter][CONTEXT_ROW.NAME])
116 else:
117 self._icons_filter_args = None
118 self._icons_view.props.model = None
119 self._icons_filter.refilter()
120 self._icons_view.props.model = self._icons_sorted
121
122 def _contexts_filter_visible_callback(self, model, rowiter, data):
123 if not self._standard_toggle.props.active:
137 self._icon_filter_standard = self._widgets.standard.props.active
138 self._icon_filter_context = model[rowiter][ContextRow.Name]
139 self._widgets.icons_filter.refilter()
140 self._widgets.icons_view.props.model = self._widgets.icons_sorted
141
142 def _contexts_visible_callback(self, model, rowiter, data):
143 if not self._widgets.standard.props.active:
124144 return True
125 return model[rowiter][CONTEXT_ROW.STANDARD]
126
127 def _contexts_row_separator_callback(self, model, rowiter, data):
128 return not model[rowiter][CONTEXT_ROW.NAME] and \
129 not model[rowiter][CONTEXT_ROW.TITLE]
145 return model[rowiter][ContextRow.Standard]
146
147 def _contexts_separator_callback(self, model, rowiter, data):
148 return not model[rowiter][ContextRow.Name] and not model[rowiter][ContextRow.Title]
130149
131150 def _icons_filter_visible_callback(self, model, rowiter, data):
132 if not self._icons_filter_args:
151 if self._icon_filter_standard and not model[rowiter][IconRow.Standard]:
133152 return False
134 if self._icons_filter_args.standard and \
135 not model[rowiter][ICON_ROW.STANDARD]:
136 return False
137 if not self._icons_filter_args.context:
153 if not self._icon_filter_context:
138154 return True
139 return model[rowiter][ICON_ROW.CONTEXT] == \
140 self._icons_filter_args.context
141
142 def run(self):
143 return super().run()
144
145 def get_icon_name(self):
146 return self._name.props.text
155 return model[rowiter][IconRow.Context] == self._icon_filter_context
156
157 def get_selected_icon(self):
158 return self._widgets.name.props.text
147159
148160 def select_icon(self, name):
149161 if not self._icons_loaded:
150162 self._icon_to_select = name
151163 return
152164
153 if not self._icons_filter_args or \
154 self._icons_filter_args.context is not None:
155 if name not in STANDARD_ICON_NAMES:
156 self._standard_toggle.props.active = False
157 self._contexts_selection.select_path(0)
158 for row in self._icons_sorted:
159 if row[ICON_ROW.NAME] == name:
160 self._icons_view.set_cursor(row.path)
161 self._icons_selection.select_path(row.path)
165 if name not in StandardIconNames:
166 self._widgets.standard.props.active = False
167 self._widgets.contexts_selection.select_path(0)
168
169 for row in self._widgets.icons_sorted:
170 if row[IconRow.Name] == name:
171 self._widgets.icons_view.set_cursor(row.path)
172 self._widgets.icons_selection.select_path(row.path)
162173 break
163174 else:
164 self._name.props.text = name
175 self._widgets.name.props.text = name
165176
166177 def on_icons_selection_changed(self, selection):
167 model, rowiter = self._icons_selection.get_selected()
178 model, rowiter = selection.get_selected()
168179 if rowiter:
169 name = model[rowiter][ICON_ROW.NAME]
170 self._name.props.text = name
180 self._widgets.name.props.text = model[rowiter][IconRow.Name]
171181
172182 def on_contexts_selection_changed(self, selection):
173 self._icons_selection.unselect_all()
183 self._widgets.icons_selection.unselect_all()
174184 self._update_icons_filter()
175185
176186 def on_standard_toggled(self, toggle):
180190 name = entry.props.text
181191 if not Gtk.IconTheme.get_default().has_icon(name):
182192 name = ''
183 self._preview.props.icon_name = name
193 self._widgets.preview.props.icon_name = name
184194
185195
186196 # http://standards.freedesktop.org/icon-naming-spec/icon-naming-spec-latest.html
187197
188 STANDARD_CONTEXTS =\
189 (
190 (None, NC_('icon-dialog', 'All contexts')),
191 (None, ''), # separator
192 ('Actions', NC_('icon-dialog', 'Actions')),
193 ('Applications', NC_('icon-dialog', 'Applications')),
194 ('Categories', NC_('icon-dialog', 'Categories')),
195 ('Devices', NC_('icon-dialog', 'Devices')),
196 ('Emblems', NC_('icon-dialog', 'Emblems')),
197 ('Emotes', NC_('icon-dialog', 'Emoticons')),
198 ('International', NC_('icon-dialog', 'International')),
199 ('MimeTypes', NC_('icon-dialog', 'MIME Types')),
200 ('Places', NC_('icon-dialog', 'Places')),
201 ('Status', NC_('icon-dialog', 'Status'))
202 )
203
204 STANDARD_ICON_NAMES = \
205 {
206 # Actions
207 'address-book-new', 'application-exit', 'appointment-new',
208 'call-start', 'call-stop', 'contact-new', 'document-new',
209 'document-open', 'document-open-recent', 'document-page-setup',
210 'document-print', 'document-print-preview', 'document-properties',
211 'document-revert', 'document-save', 'document-save-as',
212 'document-send', 'edit-clear', 'edit-copy', 'edit-cut', 'edit-delete',
213 'edit-find', 'edit-find-replace', 'edit-paste', 'edit-redo',
214 'edit-select-all', 'edit-undo', 'folder-new', 'format-indent-less',
215 'format-indent-more', 'format-justify-center', 'format-justify-fill',
216 'format-justify-left', 'format-justify-right',
217 'format-text-direction-ltr', 'format-text-direction-rtl',
218 'format-text-bold', 'format-text-italic', 'format-text-underline',
219 'format-text-strikethrough', 'go-bottom', 'go-down', 'go-first',
220 'go-home', 'go-jump', 'go-last', 'go-next', 'go-previous', 'go-top',
221 'go-up', 'help-about', 'help-contents', 'help-faq', 'insert-image',
222 'insert-link', 'insert-object', 'insert-text', 'list-add',
223 'list-remove', 'mail-forward', 'mail-mark-important', 'mail-mark-junk',
224 'mail-mark-notjunk', 'mail-mark-read', 'mail-mark-unread',
225 'mail-message-new', 'mail-reply-all', 'mail-reply-sender', 'mail-send',
226 'mail-send-receive', 'media-eject', 'media-playback-pause',
227 'media-playback-start', 'media-playback-stop', 'media-record',
228 'media-seek-backward', 'media-seek-forward', 'media-skip-backward',
229 'media-skip-forward', 'object-flip-horizontal', 'object-flip-vertical',
230 'object-rotate-left', 'object-rotate-right', 'process-stop',
231 'system-lock-screen', 'system-log-out', 'system-run', 'system-search',
232 'system-reboot', 'system-shutdown', 'tools-check-spelling',
233 'view-fullscreen', 'view-refresh', 'view-restore',
234 'view-sort-ascending', 'view-sort-descending', 'window-close',
235 'window-new', 'zoom-fit-best', 'zoom-in', 'zoom-original', 'zoom-out',
236 # StandardApplicationIcons
237 'accessories-calculator', 'accessories-character-map',
238 'accessories-dictionary', 'accessories-text-editor', 'help-browser',
239 'multimedia-volume-control', 'preferences-desktop-accessibility',
240 'preferences-desktop-font', 'preferences-desktop-keyboard',
241 'preferences-desktop-locale', 'preferences-desktop-multimedia',
242 'preferences-desktop-screensaver', 'preferences-desktop-theme',
243 'preferences-desktop-wallpaper', 'system-file-manager',
244 'system-software-install', 'system-software-update',
245 'utilities-system-monitor', 'utilities-terminal',
246 # StandardCategoryIcons
247 'applications-accessories', 'applications-development',
248 'applications-engineering', 'applications-games',
249 'applications-graphics', 'applications-internet',
250 'applications-multimedia', 'applications-office', 'applications-other',
251 'applications-science', 'applications-system',
252 'applications-utilities', 'preferences-desktop',
253 'preferences-desktop-peripherals', 'preferences-desktop-personal',
254 'preferences-other', 'preferences-system',
255 'preferences-system-network', 'system-help',
256 # StandardDeviceIcons
257 'audio-card', 'audio-input-microphone', 'battery', 'camera-photo',
258 'camera-video', 'camera-web', 'computer', 'drive-harddisk',
259 'drive-optical', 'drive-removable-media', 'input-gaming',
260 'input-keyboard', 'input-mouse', 'input-tablet', 'media-flash',
261 'media-floppy', 'media-optical', 'media-tape', 'modem',
262 'multimedia-player', 'network-wired', 'network-wireless', 'pda',
263 'phone', 'printer', 'scanner', 'video-display',
264 # StandardEmblemIcons
265 'emblem-default', 'emblem-documents', 'emblem-downloads',
266 'emblem-favorite', 'emblem-important', 'emblem-mail', 'emblem-photos',
267 'emblem-readonly', 'emblem-shared', 'emblem-symbolic-link',
268 'emblem-synchronized', 'emblem-system', 'emblem-unreadable',
269 # StandardEmotionIcons
270 'face-angel', 'face-angry', 'face-cool', 'face-crying',
271 'face-devilish', 'face-embarrassed', 'face-kiss', 'face-laugh',
272 'face-monkey', 'face-plain', 'face-raspberry', 'face-sad', 'face-sick',
273 'face-smile', 'face-smile-big', 'face-smirk', 'face-surprise',
274 'face-tired', 'face-uncertain', 'face-wink', 'face-worried',
275 # StandardInternationalIcons
276 'flag-aa',
277 # StandardMIMETypeIcons
278 'application-x-executable', 'audio-x-generic', 'font-x-generic',
279 'image-x-generic', 'package-x-generic', 'text-html', 'text-x-generic',
280 'text-x-generic-template', 'text-x-script', 'video-x-generic',
281 'x-office-address-book', 'x-office-calendar', 'x-office-document',
282 'x-office-presentation', 'x-office-spreadsheet',
283 # StandardPlaceIcons
284 'folder', 'folder-remote', 'network-server', 'network-workgroup',
285 'start-here', 'user-bookmarks', 'user-desktop', 'user-home',
286 'user-trash',
287 # StandardStatusIcons
288 'appointment-missed', 'appointment-soon', 'audio-volume-high',
289 'audio-volume-low', 'audio-volume-medium', 'audio-volume-muted',
290 'battery-caution', 'battery-low', 'dialog-error', 'dialog-information',
291 'dialog-password', 'dialog-question', 'dialog-warning',
292 'folder-drag-accept', 'folder-open', 'folder-visiting',
293 'image-loading', 'image-missing', 'mail-attachment', 'mail-unread',
294 'mail-read', 'mail-replied', 'mail-signed', 'mail-signed-verified',
295 'media-playlist-repeat', 'media-playlist-shuffle', 'network-error',
296 'network-idle', 'network-offline', 'network-receive',
297 'network-transmit', 'network-transmit-receive', 'printer-error',
298 'printer-printing', 'security-high', 'security-medium', 'security-low',
299 'software-update-available', 'software-update-urgent', 'sync-error',
300 'sync-synchronizing', 'task-due', 'task-past-due', 'user-available',
301 'user-away', 'user-idle', 'user-offline', 'user-trash-full',
302 'weather-clear', 'weather-clear-night', 'weather-few-clouds',
303 'weather-few-clouds-night', 'weather-fog', 'weather-overcast',
304 'weather-severe-alert', 'weather-showers', 'weather-showers-scattered',
305 'weather-snow', 'weather-storm'
306 }
198 StandardContexts = (
199 (None, NC_('icon-dialog', 'All contexts')),
200 (None, ''), # separator
201 ('Actions', NC_('icon-dialog', 'Actions')),
202 ('Applications', NC_('icon-dialog', 'Applications')),
203 ('Categories', NC_('icon-dialog', 'Categories')),
204 ('Devices', NC_('icon-dialog', 'Devices')),
205 ('Emblems', NC_('icon-dialog', 'Emblems')),
206 ('Emotes', NC_('icon-dialog', 'Emoticons')),
207 ('International', NC_('icon-dialog', 'International')),
208 ('MimeTypes', NC_('icon-dialog', 'MIME Types')),
209 ('Places', NC_('icon-dialog', 'Places')),
210 ('Status', NC_('icon-dialog', 'Status')))
211
212 StandardIconNames = {
213 # Actions
214 'address-book-new', 'application-exit', 'appointment-new',
215 'call-start', 'call-stop', 'contact-new', 'document-new',
216 'document-open', 'document-open-recent', 'document-page-setup',
217 'document-print', 'document-print-preview', 'document-properties',
218 'document-revert', 'document-save', 'document-save-as',
219 'document-send', 'edit-clear', 'edit-copy', 'edit-cut', 'edit-delete',
220 'edit-find', 'edit-find-replace', 'edit-paste', 'edit-redo',
221 'edit-select-all', 'edit-undo', 'folder-new', 'format-indent-less',
222 'format-indent-more', 'format-justify-center', 'format-justify-fill',
223 'format-justify-left', 'format-justify-right',
224 'format-text-direction-ltr', 'format-text-direction-rtl',
225 'format-text-bold', 'format-text-italic', 'format-text-underline',
226 'format-text-strikethrough', 'go-bottom', 'go-down', 'go-first',
227 'go-home', 'go-jump', 'go-last', 'go-next', 'go-previous', 'go-top',
228 'go-up', 'help-about', 'help-contents', 'help-faq', 'insert-image',
229 'insert-link', 'insert-object', 'insert-text', 'list-add',
230 'list-remove', 'mail-forward', 'mail-mark-important', 'mail-mark-junk',
231 'mail-mark-notjunk', 'mail-mark-read', 'mail-mark-unread',
232 'mail-message-new', 'mail-reply-all', 'mail-reply-sender', 'mail-send',
233 'mail-send-receive', 'media-eject', 'media-playback-pause',
234 'media-playback-start', 'media-playback-stop', 'media-record',
235 'media-seek-backward', 'media-seek-forward', 'media-skip-backward',
236 'media-skip-forward', 'object-flip-horizontal', 'object-flip-vertical',
237 'object-rotate-left', 'object-rotate-right', 'process-stop',
238 'system-lock-screen', 'system-log-out', 'system-run', 'system-search',
239 'system-reboot', 'system-shutdown', 'tools-check-spelling',
240 'view-fullscreen', 'view-refresh', 'view-restore',
241 'view-sort-ascending', 'view-sort-descending', 'window-close',
242 'window-new', 'zoom-fit-best', 'zoom-in', 'zoom-original', 'zoom-out',
243 # StandardApplicationIcons
244 'accessories-calculator', 'accessories-character-map',
245 'accessories-dictionary', 'accessories-text-editor', 'help-browser',
246 'multimedia-volume-control', 'preferences-desktop-accessibility',
247 'preferences-desktop-font', 'preferences-desktop-keyboard',
248 'preferences-desktop-locale', 'preferences-desktop-multimedia',
249 'preferences-desktop-screensaver', 'preferences-desktop-theme',
250 'preferences-desktop-wallpaper', 'system-file-manager',
251 'system-software-install', 'system-software-update',
252 'utilities-system-monitor', 'utilities-terminal',
253 # StandardCategoryIcons
254 'applications-accessories', 'applications-development',
255 'applications-engineering', 'applications-games',
256 'applications-graphics', 'applications-internet',
257 'applications-multimedia', 'applications-office', 'applications-other',
258 'applications-science', 'applications-system',
259 'applications-utilities', 'preferences-desktop',
260 'preferences-desktop-peripherals', 'preferences-desktop-personal',
261 'preferences-other', 'preferences-system',
262 'preferences-system-network', 'system-help',
263 # StandardDeviceIcons
264 'audio-card', 'audio-input-microphone', 'battery', 'camera-photo',
265 'camera-video', 'camera-web', 'computer', 'drive-harddisk',
266 'drive-optical', 'drive-removable-media', 'input-gaming',
267 'input-keyboard', 'input-mouse', 'input-tablet', 'media-flash',
268 'media-floppy', 'media-optical', 'media-tape', 'modem',
269 'multimedia-player', 'network-wired', 'network-wireless', 'pda',
270 'phone', 'printer', 'scanner', 'video-display',
271 # StandardEmblemIcons
272 'emblem-default', 'emblem-documents', 'emblem-downloads',
273 'emblem-favorite', 'emblem-important', 'emblem-mail', 'emblem-photos',
274 'emblem-readonly', 'emblem-shared', 'emblem-symbolic-link',
275 'emblem-synchronized', 'emblem-system', 'emblem-unreadable',
276 # StandardEmotionIcons
277 'face-angel', 'face-angry', 'face-cool', 'face-crying',
278 'face-devilish', 'face-embarrassed', 'face-kiss', 'face-laugh',
279 'face-monkey', 'face-plain', 'face-raspberry', 'face-sad', 'face-sick',
280 'face-smile', 'face-smile-big', 'face-smirk', 'face-surprise',
281 'face-tired', 'face-uncertain', 'face-wink', 'face-worried',
282 # StandardInternationalIcons
283 'flag-aa',
284 # StandardMIMETypeIcons
285 'application-x-executable', 'audio-x-generic', 'font-x-generic',
286 'image-x-generic', 'package-x-generic', 'text-html', 'text-x-generic',
287 'text-x-generic-template', 'text-x-script', 'video-x-generic',
288 'x-office-address-book', 'x-office-calendar', 'x-office-document',
289 'x-office-presentation', 'x-office-spreadsheet',
290 # StandardPlaceIcons
291 'folder', 'folder-remote', 'network-server', 'network-workgroup',
292 'start-here', 'user-bookmarks', 'user-desktop', 'user-home',
293 'user-trash',
294 # StandardStatusIcons
295 'appointment-missed', 'appointment-soon', 'audio-volume-high',
296 'audio-volume-low', 'audio-volume-medium', 'audio-volume-muted',
297 'battery-caution', 'battery-low', 'dialog-error', 'dialog-information',
298 'dialog-password', 'dialog-question', 'dialog-warning',
299 'folder-drag-accept', 'folder-open', 'folder-visiting',
300 'image-loading', 'image-missing', 'mail-attachment', 'mail-unread',
301 'mail-read', 'mail-replied', 'mail-signed', 'mail-signed-verified',
302 'media-playlist-repeat', 'media-playlist-shuffle', 'network-error',
303 'network-idle', 'network-offline', 'network-receive',
304 'network-transmit', 'network-transmit-receive', 'printer-error',
305 'printer-printing', 'security-high', 'security-medium', 'security-low',
306 'software-update-available', 'software-update-urgent', 'sync-error',
307 'sync-synchronizing', 'task-due', 'task-past-due', 'user-available',
308 'user-away', 'user-idle', 'user-offline', 'user-trash-full',
309 'weather-clear', 'weather-clear-night', 'weather-few-clouds',
310 'weather-few-clouds-night', 'weather-fog', 'weather-overcast',
311 'weather-severe-alert', 'weather-showers', 'weather-showers-scattered',
312 'weather-snow', 'weather-storm'}
+0
-130
lightdm_gtk_greeter_settings/IndicatorChooserDialog.py less more
0 #!/usr/bin/env python3
1 # -*- Mode: Python; coding: utf-8; indent-tabs-mode: nil; tab-width: 4 -*-
2 # LightDM GTK Greeter Settings
3 # Copyright (C) 2014 Andrew P. <pan.pav.7c5@gmail.com>
4 #
5 # This program is free software: you can redistribute it and/or modify it
6 # under the terms of the GNU General Public License version 3, as published
7 # by the Free Software Foundation.
8 #
9 # This program is distributed in the hope that it will be useful, but
10 # WITHOUT ANY WARRANTY; without even the implied warranties of
11 # MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR
12 # PURPOSE. See the GNU General Public License for more details.
13 #
14 # You should have received a copy of the GNU General Public License along
15 # with this program. If not, see <http://www.gnu.org/licenses/>.
16
17 from glob import iglob
18 import os
19 import sys
20
21 from gi.repository import Gtk
22 from lightdm_gtk_greeter_settings import helpers
23
24
25 __all__ = ['IndicatorChooserDialog']
26
27
28 class IndicatorChooserDialog(Gtk.Dialog):
29
30 __gtype_name__ = 'IndicatorChooserDialog'
31
32 BUILDER_WIDGETS = ('short_choice', 'short_value', 'short_model',
33 'path_choice', 'path_value',
34 'spacer_choice', 'separator_choice',
35 'add_button', 'ok_button', 'infobar', 'message')
36
37 def __new__(cls, check_callback=None, add_callback=None):
38 builder = Gtk.Builder()
39 builder.add_from_file(helpers.get_data_path('%s.ui' % cls.__name__))
40 window = builder.get_object('indicator_chooser_dialog')
41 window._builder = builder
42 window.__dict__.update(('_' + w, builder.get_object(w))
43 for w in cls.BUILDER_WIDGETS)
44
45 builder.connect_signals(window)
46 window._init_window(check_callback, add_callback)
47 return window
48
49 def _init_window(self, check_callback, add_callback):
50 self._check_callback = check_callback
51 self._add_callback = add_callback
52 self._add_button.props.visible = add_callback is not None
53
54 for path in sorted(iglob(os.path.join(sys.prefix, 'share', 'unity',
55 'indicators', '*'))):
56 name = os.path.basename(path)
57 parts = name.rsplit('.', maxsplit=1)
58 if len(parts) == 2 and parts[0] == 'com.canonical.indicator':
59 name = parts[1]
60 self._short_model.append((name,))
61
62 for path in sorted(iglob(os.path.join(sys.prefix, 'lib', 'indicators3',
63 '7', '*.so'))):
64 self._short_model.append((os.path.basename(path),))
65
66 def _get_current_value(self):
67 if self._short_choice.props.active:
68 return self._short_value.props.text
69 elif self._path_choice.props.active:
70 return self._path_value.get_filename()
71 elif self._spacer_choice.props.active:
72 return '~spacer';
73 else:
74 return '~separator';
75
76 def _update_state(self, force_state=None):
77 message = None
78 if force_state is None:
79 valid = False
80 if self._check_callback is not None:
81 check = self._check_callback(self._get_current_value())
82 if isinstance(check, str):
83 message = check
84 else:
85 valid = bool(check)
86 else:
87 valid = True
88 else:
89 valid = force_state
90
91 self._infobar.props.visible = message is not None
92 if message is not None:
93 self._message.props.label = message
94
95 self._ok_button.props.sensitive = valid
96 self._add_button.props.sensitive = valid
97
98 def on_short_value_changed(self, widget):
99 if not self._short_choice.props.active:
100 self._short_choice.props.active = True
101 else:
102 self._update_state()
103
104 def on_path_value_changed(self, widget):
105 self._path_choice.props.active = True
106 self._update_state()
107
108 def on_short_choice_toggled(self, widget):
109 self._update_state()
110
111 def on_add_clicked(self, widget):
112 value = self._get_current_value()
113 if value:
114 self._add_callback(value)
115 self._update_state(False)
116
117 def on_short_value_activate(self, entry):
118 if self._short_choice.props.active and self._ok_button.props.sensitive:
119 self._ok_button.clicked()
120
121 def get_indicator(self):
122 self._short_choice.props.active = True
123 self._update_state()
124 self._short_value.grab_focus()
125 response = self.run()
126 self.hide()
127 if response == Gtk.ResponseType.OK:
128 return self._get_current_value()
129 return None
0 #!/usr/bin/env python3
1 # -*- Mode: Python; coding: utf-8; indent-tabs-mode: nil; tab-width: 4 -*-
2 # LightDM GTK Greeter Settings
3 # Copyright (C) 2014 Andrew P. <pan.pav.7c5@gmail.com>
4 #
5 # This program is free software: you can redistribute it and/or modify it
6 # under the terms of the GNU General Public License version 3, as published
7 # by the Free Software Foundation.
8 #
9 # This program is distributed in the hope that it will be useful, but
10 # WITHOUT ANY WARRANTY; without even the implied warranties of
11 # MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR
12 # PURPOSE. See the GNU General Public License for more details.
13 #
14 # You should have received a copy of the GNU General Public License along
15 # with this program. If not, see <http://www.gnu.org/licenses/>.
16
17
18 import os
19 import sys
20 from copy import deepcopy
21 from glob import iglob
22
23 from gi.repository import Gtk
24
25 from lightdm_gtk_greeter_settings import OptionEntry
26 from lightdm_gtk_greeter_settings.helpers import (
27 C_,
28 bool2string,
29 string2bool,
30 get_data_path,
31 SimpleEnum,
32 WidgetsEnum,
33 WidgetsWrapper)
34 from lightdm_gtk_greeter_settings.IndicatorsEntry import (
35 EmptyIndicators,
36 Indicators,
37 LayoutSet,
38 Option)
39
40
41 __all__ = ['IndicatorPropertiesDialog']
42
43
44 class IndicatorPath(OptionEntry.StringPathEntry):
45
46 class Row(SimpleEnum):
47 Title = ()
48 Type = ()
49 Icon = ()
50
51
52 class IndicatorIconEntry(OptionEntry.IconEntry):
53
54 def __init__(self, widgets):
55 super().__init__(widgets)
56 self._label = widgets['label']
57 self._default_item = widgets['default_item']
58 self._default_item.connect('activate', self._on_select_default)
59
60 def _set_value(self, value):
61 if value is None:
62 self._on_select_default()
63 else:
64 super()._set_value(value)
65
66 def _update(self, icon=None, path=None, failed=False, default=None):
67 super()._update(icon, path, failed)
68 if default:
69 markup = C_('option-entry|indicators', '<b>Using default value</b>')
70 self._default_item.get_child().set_markup(markup)
71 self._button.set_tooltip_markup(markup)
72 self._image.props.visible = False
73 else:
74 self._default_item.get_child().set_markup(
75 C_('option-entry|indicators', 'Use default value...'))
76 self._image.props.visible = True
77 self._label.props.label = self._button.get_tooltip_text()
78
79 def _on_select_default(self, item=None):
80 self._value = None
81 self._image.props.icon_name = ''
82 self._update(default=True)
83 self._emit_changed()
84
85
86 class IndicatorTypeEntry(OptionEntry.BaseEntry):
87
88 def __init__(self, widgets):
89 super().__init__(widgets)
90
91 self._types = widgets['types']
92 self._indicator_choice = widgets['indicator_choice']
93 self._spacer_choice = widgets['spacer_choice']
94 self._separator_choice = widgets['separator_choice']
95
96 self._types.connect('changed', self._emit_changed)
97 self._indicator_choice.connect('toggled', self._on_choice_changed, None,
98 (self._types, widgets['indicator_box']))
99 self._spacer_choice.connect('toggled', self._on_choice_changed, Indicators.Spacer)
100 self._separator_choice.connect('toggled', self._on_choice_changed, Indicators.Separator)
101
102 self._value = None
103
104 def add_type(self, name, title):
105 if name not in EmptyIndicators:
106 self._types.append(name, title or name)
107
108 def _get_value(self):
109 if self._indicator_choice.props.active:
110 return self._types.props.active_id
111 else:
112 return self._value
113
114 def _set_value(self, value):
115 if value == Indicators.Spacer:
116 button = self._spacer_choice
117 elif value == Indicators.Separator:
118 button = self._separator_choice
119 else:
120 button = self._indicator_choice
121
122 self._value = value
123 self._types.set_active_id(value)
124
125 if button.props.active:
126 button.toggled()
127 else:
128 button.props.active = True
129
130 def _on_choice_changed(self, button, value, widgets=[]):
131 for w in widgets:
132 w.props.sensitive = button.props.active
133
134 if button.props.active:
135 self._value = value if value else self._types.props.active_id
136 self._emit_changed()
137
138
139 class IndicatorPropertiesDialog(Gtk.Dialog):
140 __gtype_name__ = 'IndicatorPropertiesDialog'
141
142 class Widgets(WidgetsEnum):
143 add = 'add_button'
144 ok = 'ok_button'
145 infobar = 'infobar'
146 message = 'message'
147 custom_options = 'custom_options_box'
148 path = 'option_path_combo'
149 path_model = 'option_path_model'
150 hide_disabled = 'option_power_hide_disabled'
151
152 def __new__(cls, *args, **kwargs):
153 builder = Gtk.Builder()
154 builder.add_from_file(get_data_path('%s.ui' % cls.__name__))
155 window = builder.get_object('indicator_properties_dialog')
156 window.builder = builder
157 builder.connect_signals(window)
158 window.init_window(*args, **kwargs)
159 return window
160
161 def init_window(self, is_duplicate=None, get_defaults=None, get_name=str):
162 self._widgets = self.Widgets(builder=self.builder)
163 self._get_defaults = get_defaults
164 self._add_indicator = None
165 self._is_duplicate = is_duplicate
166 self._get_name = get_name
167 self._indicator_loaded = False
168 self._name = None
169 self._reversed = False
170
171 self._name2page = {}
172 for i in range(0, self._widgets.custom_options.get_n_pages()):
173 page = self._widgets.custom_options.get_nth_page(i)
174 name = Gtk.Buildable.get_name(page)
175 self._name2page['~' + name.rsplit('_')[-1]] = i
176
177 self._option_type = IndicatorTypeEntry(WidgetsWrapper(self.builder, 'option_type'))
178 self._option_text = OptionEntry.StringEntry(WidgetsWrapper(self.builder, 'option_text'))
179 self._option_image = IndicatorIconEntry(WidgetsWrapper(self.builder, 'option_image'))
180 self._option_path = IndicatorPath(WidgetsWrapper(self.builder, 'option_path'))
181 self._option_hide_disabled = \
182 OptionEntry.BooleanEntry(WidgetsWrapper(self.builder, 'option_hide_disabled'))
183
184 for entry in (self._option_type, self._option_path):
185 entry.changed.connect(self._on_option_changed)
186
187 for name in Indicators:
188 self._option_type.add_type(name, self._get_name(name))
189
190 # Hiding first column created by Gtk.ComboBoxText
191 self._widgets.path.get_cells()[0].props.visible = False
192
193 for path in sorted(iglob(os.path.join(sys.prefix, 'share', 'unity', 'indicators', '*'))):
194 name = os.path.basename(path)
195 parts = name.rsplit('.', maxsplit=1)
196 if len(parts) == 2 and parts[0] == 'com.canonical.indicator':
197 name = parts[1]
198 row = IndicatorPath.Row._make(Type=IndicatorPath.ItemType.Value,
199 Title=name,
200 Icon='application-x-executable')
201 self._widgets.path_model.append(row)
202
203 for path in sorted(iglob(os.path.join(sys.prefix, 'lib', 'indicators3', '7', '*.so'))):
204 row = IndicatorPath.Row._make(Type=IndicatorPath.ItemType.Value,
205 Title=os.path.basename(path),
206 Icon='application-x-executable')
207 self._widgets.path_model.append(row)
208
209 def _on_option_changed(self, entry=None):
210 if not self._indicator_loaded:
211 return
212
213 name = self._option_type.value
214 error = None
215 warning = None
216
217 if name == Indicators.External:
218 if not str(self._option_path.value).strip():
219 error = C_('option-entry|indicators', 'Path/Service field is not filled')
220 elif name != self._name:
221 if self._is_duplicate and self._is_duplicate(name):
222 warning = C_('option-entry|indicators',
223 'Indicator "{name}" is already in the list.\n'
224 'It will be overwritten.').format(name=self._get_name(name, name))
225
226 self._widgets.ok.props.sensitive = error is None
227 self._widgets.add.props.sensitive = error is None
228 self._widgets.infobar.props.visible = error or warning
229 self._widgets.message.props.label = error or warning
230
231 if error:
232 self._widgets.infobar.props.message_type = Gtk.MessageType.WARNING
233 elif warning:
234 self._widgets.infobar.props.message_type = Gtk.MessageType.INFO
235 else:
236 self._widgets.infobar.props.message_type = Gtk.MessageType.OTHER
237
238 def on_option_type_types_changed(self, combo):
239 current = self._widgets.custom_options.props.page
240 if current != -1:
241 self._widgets.custom_options.get_nth_page(current).props.visible = False
242 current = self._name2page.get(combo.props.active_id, -1)
243 if current != -1:
244 self._widgets.custom_options.get_nth_page(current).props.visible = True
245 self._widgets.custom_options.props.page = current
246 if self._indicator_loaded:
247 defaults = self._get_defaults(combo.props.active_id)
248 self._option_text.enabled = Option.Text in defaults
249 self._option_image.enabled = Option.Image in defaults
250
251 def on_add_clicked(self, widget):
252 self._add_callback(self.get_indicator())
253 self._options = deepcopy(self._options)
254 self._on_option_changed()
255
256 @property
257 def add_callback(self):
258 return self._add_callback
259
260 @add_callback.setter
261 def add_callback(self, value):
262 self._add_callback = value
263 self._widgets.add.props.visible = value is not None
264
265 def set_indicator(self, options):
266 self._indicator_loaded = False
267 self._options = deepcopy(options)
268 self._name = options[Option.Name]
269
270 self._option_type.value = options[Option.Name]
271 self._option_path.value = options.get(Option.Path)
272
273 self._option_text.value = options.get(Option.Text, '')
274 self._option_text.enabled = Option.Text in options
275
276 self._option_image.value = options.get(Option.Image)
277 self._option_image.enabled = Option.Image in options
278
279 self._reversed = Option.Layout in options and LayoutSet.Reversed in options[Option.Layout]
280
281 hide_disabled = options.get(Option.HideDisabled, bool2string(False))
282 self._option_hide_disabled.value = hide_disabled or bool2string(True)
283
284 self._indicator_loaded = True
285 self._on_option_changed()
286
287 def get_indicator(self):
288 options = self._options
289
290 name = self._option_type.value
291 options[Option.Name] = name
292
293 options[Option.Layout] = set()
294 if name not in EmptyIndicators:
295 if self._option_text.enabled:
296 options[Option.Text] = self._option_text.value or None
297 options[Option.Layout].add(LayoutSet.Text)
298 if self._option_image.enabled:
299 options[Option.Image] = self._option_image.value or None
300 options[Option.Layout].add(LayoutSet.Image)
301 if self._option_text.enabled and self._option_image.enabled and self._reversed:
302 options[Option.Layout].add(LayoutSet.Reversed)
303
304 if LayoutSet.Text not in options[Option.Layout] and Option.Text in options:
305 del options[Option.Text]
306 if LayoutSet.Image not in options[Option.Layout] and Option.Image in options:
307 del options[Option.Image]
308
309 if name == Indicators.External:
310 options[Option.Path] = self._option_path.value
311 else:
312 options.pop(Option.Path, None)
313
314 if name == Indicators.Power and string2bool(self._option_hide_disabled.value):
315 options[Option.HideDisabled] = None
316 elif Option.HideDisabled in options:
317 options.pop(Option.HideDisabled, None)
318
319 return options
0 # -*- Mode: Python; coding: utf-8; indent-tabs-mode: nil; tab-width: 4 -*-
1 # LightDM GTK Greeter Settings
2 # Copyright (C) 2014 Andrew P. <pan.pav.7c5@gmail.com>
3 #
4 # This program is free software: you can redistribute it and/or modify it
5 # under the terms of the GNU General Public License version 3, as published
6 # by the Free Software Foundation.
7 #
8 # This program is distributed in the hope that it will be useful, but
9 # WITHOUT ANY WARRANTY; without even the implied warranties of
10 # MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR
11 # PURPOSE. See the GNU General Public License for more details.
12 #
13 # You should have received a copy of the GNU General Public License along
14 # with this program. If not, see <http://www.gnu.org/licenses/>.
15
16
17 import operator
18 import platform
19 import string
20 from copy import deepcopy
21
22 from gi.repository import (
23 Gtk,
24 Gdk)
25 from gi.repository.GObject import markup_escape_text as escape_markup
26
27 from lightdm_gtk_greeter_settings.helpers import (
28 C_,
29 get_markup_error,
30 SimpleEnum,
31 TreeStoreDataWrapper)
32 from lightdm_gtk_greeter_settings.OptionEntry import BaseEntry
33
34
35 __all__ = ['BuiltInIndicators',
36 'EmptyIndicators',
37 'Indicators',
38 'IndicatorsEntry',
39 'Option',
40 'SingleIndicators']
41
42
43 class Indicators(SimpleEnum):
44 External = '~external'
45 Spacer = '~spacer'
46 Separator = '~separator'
47 Text = '~text'
48 Host = '~host'
49 Clock = '~clock'
50 Layout = '~layout'
51 Session = '~session'
52 Language = '~language'
53 A11y = '~a11y'
54 Power = '~power'
55
56
57 # Valid builtin indicators
58 BuiltInIndicators = set(Indicators) - {Indicators.External}
59
60 # Special indicators
61 EmptyIndicators = {Indicators.Spacer, Indicators.Separator}
62
63 # These indicators can have only one instance
64 SingleIndicators = set(Indicators) - {Indicators.External, Indicators.Text,
65 Indicators.Spacer, Indicators.Separator}
66
67 # Valid options names
68
69
70 class Option(SimpleEnum):
71 # Common
72 Name = 'name'
73 Layout = 'layout'
74 Text = 'text'
75 Image = 'image'
76 FallbackImage = 'fallback-image'
77 Tooltip = 'tooltip'
78 Markup = 'markup'
79 Expand = 'expand'
80 Align = 'align'
81 # External
82 Path = 'path'
83 # Power
84 HideDisabled = 'hide-disabled'
85
86
87 class Layout(SimpleEnum):
88 Empty = ''
89 Text = 'text'
90 Image = 'image'
91 ImageText = 'image-text'
92 TextImage = 'text-image'
93
94 @classmethod
95 def _to_set(cls, value):
96 return LayoutSet._str2set.get(value, set())
97
98
99 class LayoutSet(SimpleEnum):
100 Text = 'text'
101 Image = 'image'
102 Reversed = 'reversed'
103
104 _str2set = {
105 Layout.Empty: set(),
106 Layout.Text: {Text},
107 Layout.Image: {Image},
108 Layout.ImageText: {Text, Image},
109 Layout.TextImage: {Text, Image, Reversed}}
110
111 @classmethod
112 def _to_string(cls, value):
113 return next((k for k, v in cls._str2set.items() if v == value), '')
114
115
116 class Row(SimpleEnum):
117 Name = ()
118 Tooltip = ()
119 HasState = ()
120 State = ()
121 Options = ()
122 Icon = ()
123 Markup = ()
124
125
126 class IndicatorsEntry(BaseEntry):
127 # Readable names for indicators
128 Names = {
129 Indicators.External: C_('option-entry|indicators|name', 'External library/service'),
130 Indicators.Spacer: C_('option-entry|indicators|name', 'Spacer'),
131 Indicators.Separator: C_('option-entry|indicators|name', 'Separator'),
132 Indicators.Text: C_('option-entry|indicators|name', 'Text'),
133 Indicators.Clock: C_('option-entry|indicators|name', 'Clock'),
134 Indicators.Host: C_('option-entry|indicators|name', 'Host name'),
135 Indicators.Layout: C_('option-entry|indicators|name', 'Keyboard layout'),
136 Indicators.Session: C_('option-entry|indicators|name', 'Sessions menu'),
137 Indicators.Language: C_('option-entry|indicators|name', 'Languages menu'),
138 Indicators.A11y: C_('option-entry|indicators|name', 'Accessibility menu'),
139 Indicators.Power: C_('option-entry|indicators|name', 'Power menu')}
140 # Default icons for indicators to display in treeview
141 Icons = {
142 Indicators.A11y: 'preferences-desktop-accessibility',
143 Indicators.Session: 'document-properties',
144 Indicators.Power: 'system-shutdown'}
145 Tooltips = {
146 Indicators.Spacer: C_('option-entry|indicators|tooltip', 'Spacer'),
147 Indicators.Separator: C_('option-entry|indicators|tooltip', 'Separator'),
148 Indicators.Text: C_('option-entry|indicators|tooltip', 'Custom text or/and image'),
149 Indicators.Host: C_('option-entry|indicators|tooltip', 'Host name'),
150 Indicators.Clock: C_('option-entry|indicators|tooltip', 'Clock'),
151 Indicators.Layout: C_('option-entry|indicators|tooltip', 'Layout indicator'),
152 Indicators.Session: C_('option-entry|indicators|tooltip',
153 'Sessions menu (xfce, unity, gnome etc.)'),
154 Indicators.Language: C_('option-entry|indicators|tooltip', 'Languages menu'),
155 Indicators.A11y: C_('option-entry|indicators|tooltip', 'Accessibility menu'),
156 Indicators.Power: C_('option-entry|indicators|tooltip', 'Power menu')}
157 # Default options for indicators
158 DefaultOptions = {
159 Indicators.External: {Option.Text: None, Option.Image: None},
160 Indicators.Spacer: {Option.Layout: set()},
161 Indicators.Separator: {Option.Layout: set()},
162 Indicators.Text: {Option.Layout: {LayoutSet.Text}, Option.Text: None},
163 Indicators.Host: {Option.Layout: {LayoutSet.Text}, Option.Text: None},
164 Indicators.Clock: {Option.Layout: {LayoutSet.Text}, Option.Text: None},
165 Indicators.Layout: {Option.Layout: {LayoutSet.Text}, Option.Text: None},
166 Indicators.Session: {Option.Layout: {LayoutSet.Text, LayoutSet.Image},
167 Option.Text: None, Option.Image: None},
168 Indicators.Language: {Option.Layout: {LayoutSet.Text}, Option.Text: None},
169 Indicators.A11y: {Option.Layout: {LayoutSet.Image}, Option.Image: None},
170 Indicators.Power: {Option.Layout: {LayoutSet.Image}, Option.Image: None}}
171
172 def __init__(self, widgets):
173 super().__init__(widgets)
174
175 for k, v in self.DefaultOptions.items():
176 v[Option.Name] = k
177
178 self._treeview = widgets['treeview']
179 self._selection = widgets['selection']
180 self._state_renderer = widgets['state_renderer']
181 self._state_column = widgets['state_column']
182 self._add = widgets['add']
183 self._remove = widgets['remove']
184 self._up = widgets['up']
185 self._down = widgets['down']
186 self._tools = widgets['tools']
187 self._model = widgets['model']
188 self._widgets_to_disable = [self._treeview, widgets['toolbar']]
189 self._properties_dialog = None
190 self._row_menu = None
191 self._tools_menu = None
192 self._show_unused = False
193
194 self._treeview.connect('key-press-event', self._on_key_press)
195 self._treeview.connect('row-activated', self._on_row_activated)
196 self._treeview.connect('button-release-event', self._on_button_release)
197 self._selection.connect('changed', self._on_selection_changed)
198 self._state_renderer.connect('toggled', self._on_state_toggled)
199
200 self._add.connect('clicked', self._on_add_clicked)
201 self._remove.connect('clicked', self._on_remove_clicked)
202 self._up.connect('clicked', self._on_up_clicked)
203 self._down.connect('clicked', self._on_down_clicked)
204 self._tools.connect('clicked', self._on_tools_clicked)
205
206 self._on_row_changed_id = self._model.connect('row-changed', self._on_model_changed)
207 self._on_row_deleted_id = self._model.connect('row-deleted', self._on_model_changed)
208 self._on_row_inserted_id = self._model.connect('row-inserted', self._on_model_changed)
209 self._on_rows_reordered_id = self._model.connect('rows-reordered', self._on_model_changed)
210
211 def _on_model_changed(self, *unused):
212 self._emit_changed()
213
214 def _get_value(self):
215 def fix_token(s):
216 s = s.replace('"', r'\"')
217 if any(c in s for c in string.whitespace):
218 s = '"' + s + '"'
219 return s
220
221 items = []
222 for row in self._model:
223 if row[Row.HasState] and not row[Row.State]:
224 continue
225
226 options = deepcopy(row[Row.Options].data)
227 name = options.pop(Option.Name)
228 defaults = deepcopy(self.DefaultOptions[name])
229
230 # text, image, layout=image-text -> text, image
231 if options.get(Option.Layout) == {LayoutSet.Text, LayoutSet.Image}:
232 del options[Option.Layout]
233 # defaults.pop(Option.Image, None)
234 # defaults.pop(Option.Text, None)
235
236 for k in defaults.keys() & options.keys():
237 if defaults[k] == options[k]:
238 del options[k]
239
240 if Option.Layout in options:
241 layout = options[Option.Layout]
242 options[Option.Layout] = LayoutSet._to_string(layout)
243 # text, layout=text -> layout=text
244 if LayoutSet.Text in layout and options.get(Option.Text, self) is None:
245 del options[Option.Text]
246 if LayoutSet.Image in layout and options.get(Option.Image, self) is None:
247 del options[Option.Image]
248
249 # name=~text, text=value -> ~~value
250 if name == Indicators.Text:
251 name = '~~' + (options.pop(Option.Text, None) or '')
252 elif name == Indicators.External:
253 name = options.pop(Option.Path, None) or ''
254
255 if not options:
256 items.append(fix_token(name))
257 else:
258 values = (fix_token(k) + '=' + fix_token(v) if v else fix_token(k)
259 for k, v in sorted(options.items(), key=operator.itemgetter(0)))
260 items.append(fix_token(name) + ': ' + ', '.join(values))
261 return '; '.join(items)
262
263 def _set_value(self, value):
264 with self._model.handler_block(self._on_row_deleted_id):
265 self._model.clear()
266
267 for options in self._read_options_string(value):
268 name = options[Option.Name]
269
270 if name.startswith('~~'):
271 options.setdefault(Option.Text, name[2:])
272 options[Option.Name] = Indicators.Text
273 name = Indicators.Text
274 elif name not in BuiltInIndicators:
275 options.setdefault(Option.Path, name)
276 options[Option.Name] = Indicators.External
277 name = Indicators.External
278
279 defaults = deepcopy(self.DefaultOptions[name])
280
281 if Option.Markup in options:
282 markup = options[Option.Markup]
283 if markup is not None:
284 options[Option.Text] = markup
285 options[Option.Markup] = None
286
287 if Option.Layout in options:
288 options[Option.Layout] = Layout._to_set(options[Option.Layout])
289 else:
290 options[Option.Layout] = defaults.get(Option.Layout) or set()
291
292 if Option.Text in options:
293 options[Option.Layout].add(LayoutSet.Text)
294 elif LayoutSet.Text in options[Option.Layout]:
295 options.setdefault(Option.Text, None)
296 else:
297 defaults.pop(Option.Text, None)
298
299 if Option.Image in options:
300 options[Option.Layout].add(LayoutSet.Image)
301 elif LayoutSet.Image in options[Option.Layout]:
302 options.setdefault(Option.Image, None)
303 else:
304 defaults.pop(Option.Image, None)
305
306 options.update((k, defaults[k])
307 for k in defaults.keys() - options.keys())
308
309 with self._model.handler_block(self._on_row_changed_id), \
310 self._model.handler_block(self._on_row_inserted_id):
311 self._set_row(None, options, select=False)
312
313 if self._show_unused:
314 self._tools_show_unused_toggled()
315
316 self._selection.select_path(0)
317 self._on_model_changed()
318
319 def _read_options_string(self, s):
320
321 while s:
322 name, s = self._next_string_token(s, ':;')
323 if not name:
324 continue
325 options = {Option.Name: name}
326
327 if s.startswith(':'):
328 while s:
329 option, s = self._next_string_token(s[1:], '=,;')
330 if s.startswith('='):
331 value, s = self._next_string_token(s[1:], ',;')
332 else:
333 value = None
334 options[option] = value
335 if not s.startswith(','):
336 break
337
338 yield options
339 s = s[1:]
340
341 def _next_string_token(self, s, delimiters):
342 token = []
343 quoted = False
344
345 for last, c in enumerate(s):
346 if not c.isspace():
347 break
348
349 # Parsing quotes
350 for i, c in enumerate(s[last:], last):
351 if c == '"':
352 if i > last and s[i - 1] == '\\':
353 token.append(s[last:i - 1])
354 token.append('"')
355 else:
356 token.append(s[last:i])
357 quoted = not quoted
358 last = i + 1
359 elif not quoted and c in delimiters:
360 break
361
362 if quoted:
363 return '', ''
364
365 if last != i or last == 0:
366 token.append(s[last: i if c in delimiters else i + 1].rstrip())
367
368 return ''.join(token) if token else None, s[i:]
369
370 def _remove_selection(self):
371 model, rowiter = self._selection.get_selected()
372 if rowiter:
373 if self._show_unused and model[rowiter][Row.HasState]:
374 model[rowiter][Row.State] = False
375 else:
376 model.remove(rowiter)
377 self._on_selection_changed()
378
379 def _move_selection(self, move_up):
380 model, rowiter = self._selection.get_selected()
381 if rowiter:
382 next_iter = model.iter_previous(
383 rowiter) if move_up else model.iter_next(rowiter)
384 if self._show_unused and \
385 (model[rowiter][Row.HasState] and not model[rowiter][Row.State] or
386 model[next_iter][Row.HasState] and not model[next_iter][Row.State]):
387 with self._model.handler_block(self._on_rows_reordered_id):
388 model.swap(rowiter, next_iter)
389 else:
390 model.swap(rowiter, next_iter)
391 self._on_selection_changed()
392
393 def _create_row_tuple(self, options):
394 name = options[Option.Name]
395 error = None
396
397 text = options.get(Option.Text)
398 if Option.Text in options:
399 if text is not None:
400 if Option.Markup in options:
401 error = get_markup_error(text)
402 if error:
403 text = '<i>{text}</i>'.format(text=escape_markup(text))
404 else:
405 text = escape_markup(text)
406 text = '"' + text + '"'
407 elif name == Indicators.Host:
408 text = escape_markup(platform.node())
409
410 display_name = self.Names.get(name, name)
411 if name == Indicators.External:
412 if options.get(Option.Path):
413 title = '{name} ({value})'.format(name=escape_markup(display_name),
414 value=escape_markup(options[Option.Path]))
415 else:
416 title = escape_markup(display_name)
417 else:
418 title = escape_markup(display_name)
419
420 if text:
421 markup = '{name}: {text}'.format(name=title, text=text)
422 else:
423 markup = title
424
425 if Option.Image in options:
426 icon = options[Option.Image]
427 if icon and icon.startswith('#'):
428 icon = icon[1:]
429 elif icon:
430 icon = 'image-x-generic'
431 else:
432 if name in self.Icons:
433 icon = self.Icons[name]
434 elif name in BuiltInIndicators:
435 icon = 'applications-system'
436 else:
437 icon = 'application-x-executable'
438 else:
439 icon = ''
440
441 has_state = name in SingleIndicators
442
443 return Row._make(Name=name,
444 Tooltip=self.Tooltips.get(name),
445 Icon=icon,
446 Markup=markup,
447 HasState=has_state, State=has_state,
448 Options=TreeStoreDataWrapper(options))
449
450 def _set_row(self, rowiter, options, select=True):
451 old_name = self._model[rowiter][Row.Name] if rowiter else None
452 new_name = options.get(
453 Option.Name, '') if options is not None else None
454 old_is_single = old_name in SingleIndicators
455 new_is_single = new_name in SingleIndicators
456
457 if new_name == old_name:
458 # The same row - just update
459 pass
460 elif old_is_single and new_is_single:
461 old_row = next(
462 (row for row in self._model if row[Row.Name] == new_name), None)
463 if old_row:
464 if self._show_unused:
465 # Swap current row with new_row
466 with self._model.handler_block(self._on_rows_reordered_id):
467 self._model.move_before(old_row.iter, rowiter)
468 with self._model.handler_block(self._on_row_changed_id):
469 self._model[rowiter][Row.State] = False
470 rowiter = old_row.iter
471 else:
472 # Replace current row with replace_row
473 with self._model.handler_block(self._on_row_deleted_id):
474 self._model.remove(old_row.iter)
475 elif old_is_single:
476 if self._show_unused:
477 # Uncheck old row and use new instead of it
478 with self._model.handler_block(self._on_row_changed_id):
479 self._model[rowiter][Row.State] = False
480 with self._model.handler_block(self._on_row_inserted_id):
481 new_iter = self._model.insert_after(rowiter)
482 rowiter = new_iter
483 elif new_is_single:
484 old_row = next(
485 (row for row in self._model if row[Row.Name] == new_name), None)
486 if old_row:
487 with self._model.handler_block(self._on_row_deleted_id):
488 self._model.remove(old_row.iter)
489
490 if rowiter and options:
491 with self._model.handler_block(self._on_row_changed_id):
492 self._model[rowiter] = self._create_row_tuple(options)
493 self._model.row_changed(self._model.get_path(rowiter), rowiter)
494 elif options:
495 rowiter = self._model.append(self._create_row_tuple(options))
496
497 if select and rowiter:
498 self._selection.select_iter(rowiter)
499
500 return rowiter
501
502 def _edit_indicator(self, options, add_callback=None):
503 if not self._properties_dialog:
504 from lightdm_gtk_greeter_settings.IndicatorPropertiesDialog \
505 import IndicatorPropertiesDialog as Dialog
506 self._properties_dialog = Dialog(is_duplicate=self._is_duplicate,
507 get_defaults=self.DefaultOptions.get,
508 get_name=self.Names.get)
509 self._properties_dialog.props.transient_for = self._treeview.get_toplevel()
510
511 self._properties_dialog.add_callback = add_callback
512 self._properties_dialog.set_indicator(options)
513 if self._properties_dialog.run() == Gtk.ResponseType.OK:
514 options = self._properties_dialog.get_indicator()
515 else:
516 options = None
517 self._properties_dialog.hide()
518 return options
519
520 def _is_duplicate(self, name):
521 return name in SingleIndicators and any(row[Row.Name] == name
522 for row in self._model if row[Row.State])
523
524 def _add_indicator(self, options):
525 self._set_row(None, options)
526
527 def _on_key_press(self, treeview, event):
528 if Gdk.keyval_name(event.keyval) == 'Delete':
529 self._remove_selection()
530 elif Gdk.keyval_name(event.keyval) == 'F2':
531 model, rowiter = self._selection.get_selected()
532 treeview.row_activated(model.get_path(rowiter), None)
533 else:
534 return False
535 return True
536
537 def _on_row_activated(self, treeview, path, column):
538 if column != self._state_column:
539 options = self._edit_indicator(self._model[path][Row.Options].data)
540 if options:
541 self._set_row(self._model.get_iter(path), options)
542
543 def _on_button_release(self, treeview, event):
544 if event.button != 3:
545 return False
546
547 pos = treeview.get_path_at_pos(int(event.x), int(event.y))
548 if not pos:
549 return False
550
551 row = self._model[pos[0]]
552 if row[Row.HasState] and not row[Row.State]:
553 return False
554
555 if not self._row_menu:
556 self._row_menu = Gtk.Menu()
557 self._row_menu_reset = Gtk.MenuItem(C_('option-entry|indicators',
558 'Reset to _defaults'))
559 self._row_menu_text = Gtk.CheckMenuItem(C_('option-entry|indicators',
560 'Display _label'))
561 self._row_menu_image = Gtk.CheckMenuItem(C_('option-entry|indicators',
562 'Display _image'))
563 self._row_menu_remove = Gtk.MenuItem(
564 C_('option-entry|indicators', '_Remove'))
565
566 self._row_menu_text_id = self._row_menu_text.connect('toggled',
567 self._on_row_menu_toggled,
568 Option.Text)
569 self._row_menu_image_id = self._row_menu_image.connect('toggled',
570 self._on_row_menu_toggled,
571 Option.Image)
572 self._row_menu_reset.connect(
573 'activate', self._on_row_menu_reset_clicked)
574 self._row_menu_remove.connect('activate', self._on_remove_clicked)
575
576 self._row_menu.append(self._row_menu_reset)
577 self._row_menu.append(self._row_menu_text)
578 self._row_menu.append(self._row_menu_image)
579 self._row_menu.append(Gtk.SeparatorMenuItem())
580 self._row_menu.append(self._row_menu_remove)
581
582 for item in self._row_menu:
583 if type(item) is not Gtk.SeparatorMenuItem:
584 item.props.use_underline = True
585 item.props.visible = True
586
587 options = row[Row.Options].data
588
589 with self._row_menu_text.handler_block(self._row_menu_text_id):
590 self._row_menu_text.props.active = Option.Text in options
591 with self._row_menu_image.handler_block(self._row_menu_image_id):
592 self._row_menu_image.props.active = Option.Image in options
593
594 editable = options[Option.Name] not in {
595 Indicators.Spacer, Indicators.Separator}
596 self._row_menu_reset.props.sensitive = editable
597 self._row_menu_text.props.sensitive = editable
598 self._row_menu_image.props.sensitive = editable
599
600 self._row_menu.popup(None, None, None, None, 0,
601 Gtk.get_current_event_time())
602
603 return True
604
605 def _on_row_menu_reset_clicked(self, item):
606 model, rowiter = self._selection.get_selected()
607 if rowiter:
608 name = model[rowiter][Row.Name]
609 options = deepcopy(self.DefaultOptions[name])
610 options[Option.Name] = name
611 with model.handler_block(self._on_row_changed_id):
612 model[rowiter] = self._create_row_tuple(options)
613 model.row_changed(model.get_path(rowiter), rowiter)
614
615 def _on_row_menu_toggled(self, item, option):
616 model, rowiter = self._selection.get_selected()
617 options = model[rowiter][Row.Options].data
618 if item.props.active:
619 options.setdefault(option, None)
620 else:
621 options.pop(option, None)
622 model[rowiter] = self._create_row_tuple(options)
623
624 def _on_state_toggled(self, renderer, path):
625 self._model[path][Row.State] = not self._model[path][Row.State]
626
627 def _on_selection_changed(self, selection=None):
628 model, rowiter = self._selection.get_selected()
629 if rowiter:
630 row = model[rowiter]
631 self._remove.props.sensitive = not row[
632 Row.HasState] or row[Row.State]
633 self._down.props.sensitive = model.iter_next(rowiter)
634 self._up.props.sensitive = model.iter_previous(rowiter)
635 self._treeview.scroll_to_cell(model.get_path(rowiter))
636 else:
637 self._remove.props.sensitive = False
638 self._down.props.sensitive = False
639 self._up.props.sensitive = False
640
641 def _on_add_clicked(self, button=None):
642 options = self._edit_indicator(self.DefaultOptions[Indicators.External],
643 add_callback=self._add_indicator)
644 if options:
645 self._set_row(None, options, select=True)
646
647 def _on_remove_clicked(self, button=None):
648 self._remove_selection()
649
650 def _on_up_clicked(self, button=None):
651 self._move_selection(move_up=True)
652
653 def _on_down_clicked(self, button=None):
654 self._move_selection(move_up=False)
655
656 def _on_tools_clicked(self, button=None):
657 if not self._tools_menu:
658 self._tools_menu = Gtk.Menu()
659 self._tools_menu.attach_to_widget(self._tools)
660
661 unused_item = Gtk.CheckMenuItem(
662 C_('option-entry|indicators', 'Show unused items'))
663 unused_item.connect('toggled', self._tools_show_unused_toggled)
664 self._tools_menu.append(unused_item)
665
666 header_item = Gtk.MenuItem(
667 C_('option-entry|indicators', 'Predefined templates:'))
668 header_item.props.sensitive = False
669 self._tools_menu.append(Gtk.SeparatorMenuItem())
670 self._tools_menu.append(header_item)
671
672 templates = (
673 ('host ~ clock, language, session, power',
674 '~host;~spacer;~language;~session;~power'),
675 ('host ~ clock ~ language, session, a11y, power',
676 '~host;~spacer;~clock;~spacer;~language;~session;~a11y;~power'),
677 ('host, layout, clock ~ language, session, power',
678 '~host;~layout;~clock;~spacer;~language;~session;~power'))
679
680 for title, value in templates:
681 item = Gtk.MenuItem(title)
682 item.connect(
683 'activate', self._on_tools_template_clicked, value)
684 self._tools_menu.append(item)
685
686 self._tools_menu.show_all()
687 self._tools_menu.popup(None, None, None, None, 0,
688 Gtk.get_current_event_time())
689
690 def _on_tools_template_clicked(self, item, value):
691 self._set_value(value)
692
693 def _tools_show_unused_toggled(self, widget=None):
694 if widget:
695 self._show_unused = widget.props.active
696 self._state_column.props.visible = self._show_unused
697
698 used = {row[Row.Name]: row
699 for row in self._model if row[Row.Name] in SingleIndicators}
700 if self._show_unused:
701 for name in SingleIndicators - used.keys():
702 options = deepcopy(self.DefaultOptions[name])
703 options[Option.Name] = name
704 with self._model.handler_block(self._on_row_changed_id),\
705 self._model.handler_block(self._on_row_inserted_id):
706 rowiter = self._set_row(None, options, select=False)
707 self._model[rowiter][Row.State] = False
708 else:
709 for row in used.values():
710 if row[Row.HasState] and not row[Row.State]:
711 with self._model.handler_block(self._on_row_deleted_id):
712 self._model.remove(row.iter)
1717
1818 from gi.repository import Gtk
1919
20 from lightdm_gtk_greeter_settings.helpers import WidgetsWrapper
21 from lightdm_gtk_greeter_settings.MultiheadSetupDialog import MultiheadSetupDialog
2022 from lightdm_gtk_greeter_settings.OptionEntry import BaseEntry
2123 from lightdm_gtk_greeter_settings.OptionGroup import BaseGroup
22 from lightdm_gtk_greeter_settings.helpers import WidgetsWrapper
23 from lightdm_gtk_greeter_settings.MultiheadSetupDialog import MultiheadSetupDialog
2424
2525
2626 __all__ = ['MonitorsGroup']
1515 # with this program. If not, see <http://www.gnu.org/licenses/>.
1616
1717
18 import os
1918 from builtins import max
2019 from locale import gettext as _
21 from gi.repository import Gtk, Gdk, GdkPixbuf
22
23 from lightdm_gtk_greeter_settings import helpers
24 from lightdm_gtk_greeter_settings.helpers import WidgetsWrapper, C_, ModelRowEnum
20
21 from gi.repository import (
22 Gdk,
23 GdkPixbuf,
24 Gtk)
25
26 from lightdm_gtk_greeter_settings.helpers import (
27 C_,
28 check_path_accessibility,
29 get_data_path,
30 SimpleEnum,
31 WidgetsEnum)
2532
2633
2734 __all__ = ['MultiheadSetupDialog']
2835
2936
30 ROW = ModelRowEnum('NAME', 'BACKGROUND',
31 'USER_BG', 'USER_BG_DISABLED',
32 'LAPTOP', 'LAPTOP_DISABLED',
33 'BACKGROUND_PIXBUF', 'BACKGROUND_IS_COLOR',
34 'ERROR_VISIBLE', 'ERROR_TEXT')
35
36 BG_ROW = ModelRowEnum('TEXT', 'TYPE')
37 class Row(SimpleEnum):
38 Name = ()
39 Background = ()
40 UserBg = ()
41 UserBgDisabled = ()
42 Laptop = ()
43 LaptopDisabled = ()
44 BackgroundPixbuf = ()
45 BackgroundIsColor = ()
46 ErrorVisible = ()
47 ErrorText = ()
48
49
50 class BackgroundRow(SimpleEnum):
51 Text = ()
52 Type = ()
3753
3854
3955 class MultiheadSetupDialog(Gtk.Dialog):
40
4156 __gtype_name__ = 'MultiheadSetupDialog'
4257
4358 def __new__(cls):
4459 builder = Gtk.Builder()
45 builder.add_from_file(helpers.get_data_path('%s.ui' % cls.__name__))
60 builder.add_from_file(get_data_path('%s.ui' % cls.__name__))
4661 window = builder.get_object('multihead_setup_dialog')
47 window._widgets = WidgetsWrapper(builder)
62 window.builder = builder
4863 builder.connect_signals(window)
49 window._init_window()
64 window.init_window()
5065 return window
5166
52 def _init_window(self):
67 class Widgets(WidgetsEnum):
68 treeview = 'monitors_treeview'
69 model = 'monitors_model'
70 selection = 'monitors_selection'
71 bg_model = 'background_model'
72 bg_renderer = 'bg_renderer'
73 bg_column = 'background_column'
74 name_column = 'name_column'
75 remove = 'remove_button'
76 available = 'monitors_label'
77
78 builder = None
79
80 def init_window(self):
81 self._widgets = self.Widgets(builder=self.builder)
5382 self._group = None
5483 self._available_monitors = None
5584
56 self._remove_button = self._widgets['remove_button']
57 self._treeview = self._widgets['monitors_treeview']
58 self._model = self._widgets['monitors_model']
59 self._bg_model = self._widgets['background_model']
60 self._selection = self._treeview.get_selection()
61 self._bg_renderer = self._widgets['bg_renderer']
62 self._bg_column = self._widgets['background_column']
63 self._name_column = self._widgets['name_column']
6485 self._file_dialog = None
6586 self._color_dialog = None
6687 self._invalid_name_dialog = None
6788 self._name_exists_dialog = None
6889
69 self._treeview.props.tooltip_column = ROW.ERROR_TEXT
70 self._bg_renderer.set_property('placeholder-text',
71 C_('option|multihead', 'Use default value'))
90 self._widgets.treeview.props.tooltip_column = Row.ErrorText
91 self._widgets.bg_renderer.set_property('placeholder-text',
92 C_('option|multihead', 'Use default value'))
7293
7394 def _update_monitors_label(self):
74 used = set(row[ROW.NAME] for row in self._model)
75 monitors = ['<a href="{name}">{name}</a>'.format(name=name)
76 for name in self._available_monitors if name not in used]
77 label = C_('option|multihead', 'Available monitors: <i>{monitors}</i>')\
78 .format(monitors=', '.join(monitors or ('none',)))
79 self._widgets['monitors_label'].props.label = label
80
95 if not self._available_monitors:
96 self._widgets.available.props.visible = False
97 return
98
99 used = set(row[Row.Name] for row in self._widgets.model)
100 monitors = []
101 for name in self._available_monitors:
102 if name in used:
103 monitors.append(name)
104 else:
105 monitors.append('<i><a href="{name}">{name}</a></i>'.format(name=name))
106
107 label = C_('option|multihead',
108 'Available monitors: {monitors}').format(monitors=', '.join(monitors))
109 self._widgets.available.props.label = label
110 self._widgets.available.props.visible = True
81111
82112 def set_model(self, values):
83 self._model.clear()
113 self._widgets.model.clear()
84114 for name, entry in values.items():
85 rowiter = self._model.append(ROW(NAME=name,
86 BACKGROUND=entry['background'],
87 USER_BG=entry['user-background'],
88 USER_BG_DISABLED=entry['user-background'] is None,
89 LAPTOP=entry['laptop'],
90 LAPTOP_DISABLED=entry['laptop'] is None,
91 BACKGROUND_PIXBUF=None,
92 BACKGROUND_IS_COLOR=False,
93 ERROR_VISIBLE=False,
94 ERROR_TEXT=None))
95 self._update_row_appearance(rowiter)
115 row = Row._make(Name=name,
116 Background=entry['background'],
117 UserBg=entry['user-background'],
118 UserBgDisabled=entry['user-background'] is None,
119 Laptop=entry['laptop'],
120 LaptopDisabled=entry['laptop'] is None,
121 BackgroundPixbuf=None,
122 BackgroundIsColor=False,
123 ErrorVisible=False,
124 ErrorText=None)
125 self._update_row_appearance(self._widgets.model.append(row))
96126 screen = Gdk.Screen.get_default()
97127 self._available_monitors = [screen.get_monitor_plug_name(i)
98128 for i in range(screen.get_n_monitors())]
99129 self._update_monitors_label()
100130
101131 def get_model(self):
102 return {row[ROW.NAME]:
103 {
104 'background': row[ROW.BACKGROUND],
105 'user-background': self._get_toggle_state(row, ROW.USER_BG, ROW.USER_BG_DISABLED),
106 'laptop': self._get_toggle_state(row, ROW.LAPTOP, ROW.LAPTOP_DISABLED)
107 }
108 for row in self._model}
132 return {
133 row[Row.Name]:
134 {
135 'background': row[Row.Background],
136 'user-background': self._get_toggle_state(row, Row.UserBg, Row.UserBgDisabled),
137 'laptop': self._get_toggle_state(row, Row.Laptop, Row.LaptopDisabled)
138 }
139 for row in self._widgets.model}
109140
110141 def _update_row_appearance(self, rowiter):
111 row = self._model[rowiter]
112 bg = row[ROW.BACKGROUND]
142 row = self._widgets.model[rowiter]
143 bg = row[Row.Background]
113144
114145 error = None
115 color = Gdk.color_parse(bg)
116 if color:
117 pixbuf = row[ROW.BACKGROUND_PIXBUF]
146 color = Gdk.RGBA()
147 if color.parse(bg):
148 pixbuf = row[Row.BackgroundPixbuf]
118149 if not pixbuf:
119 pixbuf = GdkPixbuf.Pixbuf.new(GdkPixbuf.Colorspace.RGB,
120 False, 8, 16, 16)
121 row[ROW.BACKGROUND_PIXBUF] = pixbuf
122 value = (int(0xFF / Gdk.Color.MAX_VALUE * color.red) << 24) + \
123 (int(0xFF / Gdk.Color.MAX_VALUE * color.green) << 16) + \
124 (int(0xFF / Gdk.Color.MAX_VALUE * color.blue) << 8)
150 pixbuf = GdkPixbuf.Pixbuf.new(GdkPixbuf.Colorspace.RGB, False, 8, 16, 16)
151 row[Row.BackgroundPixbuf] = pixbuf
152 value = (int(0xFF * color.red) << 24) + \
153 (int(0xFF * color.green) << 16) + \
154 (int(0xFF * color.blue) << 8)
125155 pixbuf.fill(value)
126 row[ROW.BACKGROUND_IS_COLOR] = True
156 row[Row.BackgroundIsColor] = True
127157 else:
128 row[ROW.BACKGROUND_IS_COLOR] = False
129 if not os.path.exists(bg):
130 error = C_('option|multihead', 'File not found: {path}'.format(path=bg))
131 else:
132 try:
133 if not helpers.file_is_readable_by_greeter(bg):
134 error = C_('option|multihead', 'File may be not readable for greeter: {path}'.format(path=bg))
135 except:
136 error = C_('option|multihead', 'Failed to check permissions for file: {path}'.format(path=bg))
137
138 row[ROW.ERROR_VISIBLE] = error is not None
139 row[ROW.ERROR_TEXT] = error
140
141 _TOGGLE_STATES = {None: True, False: None, True: False}
158 row[Row.BackgroundIsColor] = False
159 if bg:
160 error = check_path_accessibility(bg)
161
162 row[Row.ErrorVisible] = error is not None
163 row[Row.ErrorText] = error
164
165 ToggleStatesSeq = {None: True, False: None, True: False}
142166
143167 def _get_toggle_state(self, row, active_column, inconsistent_column):
144168 return None if row[inconsistent_column] else row[active_column]
145169
146170 def _toggle_state(self, row, active_column, inconsistent_column):
147171 state = self._get_toggle_state(row, active_column, inconsistent_column)
148 row[active_column] = self._TOGGLE_STATES[state]
149 row[inconsistent_column] = self._TOGGLE_STATES[state] is None
172 row[active_column] = self.ToggleStatesSeq[state]
173 row[inconsistent_column] = self.ToggleStatesSeq[state] is None
150174
151175 def on_monitors_add_clicked(self, button):
152176 prefix = 'monitor'
153 numbers = (row[ROW.NAME][len(prefix):]
154 for row in self._model if row[ROW.NAME].startswith(prefix))
177 numbers = (row[Row.Name][len(prefix):]
178 for row in self._widgets.model if row[Row.Name].startswith(prefix))
155179 try:
156180 max_number = max(int(v) for v in numbers if v.isdigit())
157181 except ValueError:
158182 max_number = 0
159 rowiter = self._model.append(ROW(NAME='%s%d' % (prefix, max_number + 1),
160 USER_BG=False,
161 USER_BG_ENABLED=False,
162 LAPTOP=True,
163 LAPTOP_ENABLED=False,
164 BACKGROUND='',
165 BACKGROUND_PIXBUF=None,
166 BACKGROUND_IS_COLOR=False,
167 ERROR_VISIBLE=False,
168 ERROR_TEXT=None))
169 self._treeview.set_cursor(self._model.get_path(rowiter), self._name_column, True)
183
184 row = Row._make(Name='%s%d' % (prefix, max_number + 1),
185 UserBg=False,
186 UserBgDisabled=False,
187 Laptop=True,
188 LaptopDisabled=False,
189 Background='',
190 BackgroundPixbuf=None,
191 BackgroundIsColor=False,
192 ErrorVisible=False,
193 ErrorText=None)
194 rowiter = self._widgets.model.append(row)
195 self._widgets.treeview.set_cursor(self._widgets.model.get_path(rowiter),
196 self._widgets.name_column, True)
170197
171198 def on_monitors_remove_clicked(self, button):
172 model, rowiter = self._treeview.get_selection().get_selected()
199 model, rowiter = self._widgets.selection.get_selected()
173200 model.remove(rowiter)
201 self._update_monitors_label()
174202
175203 def on_selection_changed(self, selection):
176 self._remove_button.props.sensitive = selection.get_selected()[1] is not None
204 self._widgets.remove.props.sensitive = all(selection.get_selected())
177205
178206 def on_monitors_label_activate_link(self, label, name):
179 rowiter = self._model.append(ROW(NAME=name,
180 USER_BG=False,
181 USER_BG_ENABLED=False,
182 LAPTOP=True,
183 LAPTOP_ENABLED=False,
184 BACKGROUND='',
185 BACKGROUND_PIXBUF=None,
186 BACKGROUND_IS_COLOR=False))
207 row = Row._make(Name=name,
208 UserBg=False,
209 UserBgDisabled=False,
210 Laptop=True,
211 LaptopDisabled=False,
212 Background='',
213 BackgroundPixbuf=None,
214 BackgroundIsColor=True,
215 ErrorVisible=False,
216 ErrorText=None)
217
218 rowiter = self._widgets.model.append(row)
187219 self._update_row_appearance(rowiter)
188 self._treeview.get_selection().select_iter(rowiter)
220 self._widgets.selection.select_iter(rowiter)
189221 self._update_monitors_label()
190222 return True
191223
193225 combobox.connect('format-entry-text', self.on_bg_combobox_format)
194226
195227 def on_bg_combobox_format(self, combobox, path):
196 model, rowiter = self._selection.get_selected()
197 item_type = combobox.props.model[path][BG_ROW.TYPE]
198 value = model[rowiter][ROW.BACKGROUND]
228 model, rowiter = self._widgets.selection.get_selected()
229 item_type = combobox.props.model[path][BackgroundRow.Type]
230 value = model[rowiter][Row.Background]
231
199232 if item_type == 'path':
200233 if not self._file_dialog:
201234 self._file_dialog = Gtk.FileChooserDialog(
202 parent=self,
203 buttons=(_('_OK'), Gtk.ResponseType.OK,
204 _('_Cancel'), Gtk.ResponseType.CANCEL),
205 title=C_('option|multihead', 'Select background file'))
235 parent=self,
236 buttons=(_('_OK'), Gtk.ResponseType.OK,
237 _('_Cancel'), Gtk.ResponseType.CANCEL),
238 title=C_('option|multihead', 'Select background file'))
206239 self._file_dialog.props.filter = Gtk.FileFilter()
207240 self._file_dialog.props.filter.add_mime_type('image/*')
208241 if self._file_dialog.run() == Gtk.ResponseType.OK:
216249 self._color_dialog.hide()
217250 else:
218251 value = ''
252
219253 combobox.set_active(-1)
220254 return value
221255
222256 def on_bg_renderer_edited(self, renderer, path, new_text):
223 self._model[path][ROW.BACKGROUND] = new_text
224 self._update_row_appearance(self._model.get_iter(path))
257 self._widgets.model[path][Row.Background] = new_text
258 self._update_row_appearance(self._widgets.model.get_iter(path))
225259
226260 def on_name_renderer_edited(self, renderer, path, new_name):
227 old_name = self._model[path][ROW.NAME]
261 old_name = self._widgets.model[path][Row.Name]
228262 invalid_name = not new_name.strip()
229 name_in_use = new_name != old_name and any(new_name == row[ROW.NAME] for row in self._model)
263 name_in_use = new_name != old_name and any(new_name == row[Row.Name]
264 for row in self._widgets.model)
230265 if invalid_name or name_in_use:
231266 if not self._invalid_name_dialog:
232267 self._invalid_name_dialog = Gtk.MessageDialog(parent=self,
233268 buttons=Gtk.ButtonsType.OK)
234269 self._invalid_name_dialog.set_property('text',
235 C_('option|multihead', 'Invalid name: "{name}"')
236 .format(name=new_name))
270 C_('option|multihead',
271 'Invalid name: "{name}"')
272 .format(name=new_name))
237273 if name_in_use:
238274 message = C_('option|multihead', 'This name already in use.')
239275 else:
242278 self._invalid_name_dialog.run()
243279 self._invalid_name_dialog.hide()
244280 else:
245 self._model[path][ROW.NAME] = new_name
281 self._widgets.model[path][Row.Name] = new_name
246282 self._update_monitors_label()
247283
248284 def on_user_bg_renderer_toggled(self, renderer, path):
249 self._toggle_state(self._model[path],
250 ROW.USER_BG, ROW.USER_BG_DISABLED)
285 self._toggle_state(self._widgets.model[path], Row.UserBg, Row.UserBgDisabled)
251286
252287 def on_laptop_renderer_toggled(self, renderer, path):
253 self._toggle_state(self._model[path],
254 ROW.LAPTOP, ROW.LAPTOP_DISABLED)
255
288 self._toggle_state(self._widgets.model[path], Row.Laptop, Row.LaptopDisabled)
1414 # You should have received a copy of the GNU General Public License along
1515 # with this program. If not, see <http://www.gnu.org/licenses/>.
1616
17 from builtins import isinstance
18 from collections import OrderedDict
19 from locale import gettext as _
17
2018 import os
2119 import time
22
23 from gi.repository import Gtk, Gdk, GObject, GLib
20 from locale import gettext as _
21
22 from gi.repository import (
23 Gdk,
24 GLib,
25 GObject,
26 Gtk)
27 from lightdm_gtk_greeter_settings import helpers
28 from lightdm_gtk_greeter_settings.helpers import (
29 C_,
30 bool2string,
31 string2bool, SimpleEnum)
2432 from lightdm_gtk_greeter_settings.IconChooserDialog import IconChooserDialog
25 from lightdm_gtk_greeter_settings.IndicatorChooserDialog import IndicatorChooserDialog
26 from lightdm_gtk_greeter_settings import helpers
27 from lightdm_gtk_greeter_settings.helpers import C_
28 from lightdm_gtk_greeter_settings.helpers import ModelRowEnum
29 from lightdm_gtk_greeter_settings.helpers import string2bool, bool2string
30
31
32 __all__ = ['BaseEntry', 'BooleanEntry', 'InvertedBooleanEntry',
33 'StringEntry', 'StringPathEntry', 'ClockFormatEntry',
34 'BackgroundEntry', 'IconEntry', 'IndicatorsEntry',
35 'AdjustmentEntry', 'ChoiceEntry', 'AccessibilityStatesEntry']
36
37
38 class BuilderWrapper:
39
40 def __init__(self, builder, base):
41 self._builder = builder
42 self._base = base
43
44 def __getitem__(self, key):
45 return self._builder.get_object('%s_%s' % (self._base, key))
33
34
35 __all__ = [
36 'AccessibilityStatesEntry',
37 'AdjustmentEntry',
38 'BackgroundEntry',
39 'BaseEntry',
40 'BooleanEntry',
41 'ChoiceEntry',
42 'ClockFormatEntry',
43 'IconEntry',
44 'InvertedBooleanEntry',
45 'StringEntry',
46 'StringPathEntry']
4647
4748
4849 class BaseEntry(GObject.GObject):
5152 super().__init__()
5253 self._widgets = widgets
5354 self._use = widgets['use']
55 self._widgets_to_disable = []
5456 if self._use:
5557 self._use.connect('notify::active', self._on_use_toggled)
56 self._widgets_to_disable = None
5758 self._error = widgets['error']
5859
5960 @property
99100 pass
100101
101102 @GObject.Signal(flags=GObject.SIGNAL_RUN_CLEANUP)
102 def get(self, value: str) -> str: # @IgnorePep8
103 def get(self, value: str) -> str:
103104 pass
104105
105106 @GObject.Signal(flags=GObject.SIGNAL_RUN_CLEANUP)
106 def set(self, value: str) -> str: # @IgnorePep8
107 def set(self, value: str) -> str:
107108 pass
108109
109110 def __repr__(self):
121122 raise NotImplementedError(self.__class__)
122123
123124 def _get_error(self):
124 raise NotImplementedError(self.__class__)
125 if self._error:
126 return self._error.props.tooltip_text
127 return None
125128
126129 def _set_error(self, text):
127130 if self._error:
147150 super().__init__(widgets)
148151 self._value = widgets['value']
149152 self._value.connect('notify::active', self._emit_changed)
150 self._widgets_to_disable = [self._value]
153 self._widgets_to_disable.append(self._value)
151154
152155 def _get_value(self):
153156 return bool2string(self._value.props.active)
173176 def __init__(self, widgets):
174177 super().__init__(widgets)
175178 self._value = widgets['value']
176 self._widgets_to_disable = [self._value]
179 self._widgets_to_disable.append(self._value)
177180 if isinstance(self._value.props.parent, Gtk.ComboBox):
178181 self._widgets_to_disable += [self._value.props.parent]
179182 self._value.connect('changed', self._emit_changed)
180
183
181184 def _get_value(self):
182185 return self._value.props.text
183186
186189
187190
188191 class StringPathEntry(BaseEntry):
192
193 class Row(helpers.SimpleEnum):
194 Title = ()
195 Type = ()
196
197 class ItemType(SimpleEnum):
198 Select = 'select-path'
199 Value = 'value'
200 Separator = 'separator'
189201
190202 def __init__(self, widgets):
191203 super().__init__(widgets)
194206
195207 self._combo = widgets['combo']
196208 self._entry = widgets['entry']
197 self._widgets_to_disable = [self._combo]
209 self._widgets_to_disable.append(self._combo)
210 self._filters = ()
198211
199212 self._entry.connect('changed', self._emit_changed)
200213 self._combo.connect('format-entry-text', self._on_combobox_format)
201214
202215 self._combo.set_row_separator_func(self._row_separator_callback, None)
203216
217 def add_filter(self, file_filter):
218 if self._file_dialog:
219 self._file_dialog.add_filter(file_filter)
220 elif self._filters:
221 self._filters.append(file_filter)
222 else:
223 self._filters = [file_filter]
224
204225 def _get_value(self):
205226 return self._entry.props.text
206227
208229 self._entry.props.text = value or ''
209230
210231 def _row_separator_callback(self, model, rowiter, data):
211 return model[rowiter][0] == '-'
232 return model[rowiter][self.Row.Type] == self.ItemType.Separator
212233
213234 def _on_combobox_format(self, combobox, path):
214 value = ''
235 value = self._entry.props.text
215236 item_id = combobox.get_active_id()
216 if item_id == 'select-path':
237 if item_id == self.ItemType.Select:
217238 if not self._file_dialog:
218239 self._file_dialog = Gtk.FileChooserDialog(
219 parent=self._combo.get_toplevel(),
220 buttons=(_('_OK'), Gtk.ResponseType.OK,
221 _('_Cancel'), Gtk.ResponseType.CANCEL),
222 title=C_('option|StringPathEntry', 'Select path'))
240 parent=self._combo.get_toplevel(),
241 buttons=(_('_OK'), Gtk.ResponseType.OK,
242 _('_Cancel'), Gtk.ResponseType.CANCEL),
243 title=C_('option|StringPathEntry', 'Select path'))
244 for f in self._filters:
245 self._file_dialog.add_filter(f)
223246 if self._file_dialog.run() == Gtk.ResponseType.OK:
224247 value = self._file_dialog.get_filename()
225 else:
226 value = combobox.get_active_text()
227248 self._file_dialog.hide()
228 elif item_id == 'value':
229 value = combobox.props.model[path][0]
249 elif item_id == self.ItemType.Value:
250 value = combobox.props.model[path][self.Row.Title]
251
230252 combobox.set_active(-1)
253 combobox.grab_focus()
231254 return value
232255
233256
237260 super().__init__(widgets)
238261 self._value = widgets['adjustment']
239262 self._view = widgets['view']
240 self._widgets_to_disable = [self._view]
263 self._widgets_to_disable.append(self._view)
241264 self._value.connect('value-changed', self._emit_changed)
242265
243266 def _get_value(self):
255278 def __init__(self, widgets):
256279 super().__init__(widgets)
257280 self._value = widgets['value']
258 self._widgets_to_disable = [self._value]
281 self._widgets_to_disable.append(self._value)
259282 self._value.connect('changed', self._emit_changed)
260283
261284 def _get_value(self):
338361 def __init__(self, widgets):
339362 super().__init__(widgets)
340363 self._value = widgets['value']
364 self._widgets_to_disable.append(self._value)
341365 self._value.connect('font-set', self._emit_changed)
342366
343367 def _get_value(self):
356380 self._image = widgets['image']
357381 self._icon_item = widgets['icon_item']
358382 self._path_item = widgets['path_item']
383 self._widgets_to_disable.append(self._button)
359384 self._icon_dialog = None
360385 self._path_dialog = None
361386
412437 width, height = image.get_size_request()
413438 if -1 in (width, height):
414439 width, height = 64, 64
415 pixbuf = helpers.new_pixbuf_from_file_scaled_down(path, width, height)
440 pixbuf = helpers.pixbuf_from_file_scaled_down(path, width, height)
416441 image.set_from_pixbuf(pixbuf)
417442 return True
418443 except GLib.Error:
423448 if not self._icon_dialog:
424449 self._icon_dialog = IconChooserDialog()
425450 self._icon_dialog.props.transient_for = self._image.get_toplevel()
426 if self._value.startswith('#'):
451 if self._value and self._value.startswith('#'):
427452 self._icon_dialog.select_icon(self._value[1:])
428453 if self._icon_dialog.run() == Gtk.ResponseType.OK:
429 self._set_icon(self._icon_dialog.get_icon_name())
454 self._set_icon(self._icon_dialog.get_selected_icon())
430455 self._icon_dialog.hide()
431456
432457 def _on_select_path(self, item):
443468 preview.props.pixel_size = preview_size
444469 preview.set_size_request(preview_size, preview_size)
445470
446 self._path_dialog.select_filename(self._value)
471 if self._value:
472 self._path_dialog.select_filename(self._value)
447473 if self._path_dialog.run() == Gtk.ResponseType.OK:
448474 self._set_path(self._path_dialog.get_filename())
449475 self._path_dialog.hide()
452478 self._set_image_from_path(chooser.props.preview_widget, chooser.get_filename())
453479
454480
455 class IndicatorsEntry(BaseEntry):
456 ROW = ModelRowEnum('NAME', 'TOOLTIP', 'EDITABLE', 'HAS_STATE', 'STATE')
457 NAMES_DELIMITER = ';'
458 DEFAULT_TOOLTIPS = {'~spacer': C_('option-entry|indicators', 'Spacer'),
459 '~separator': C_('option-entry|indicators', 'Separator')}
460
461 def __init__(self, widgets):
462 super().__init__(widgets)
463 self._toolbar = widgets['toolbar']
464 self._treeview = widgets['treeview']
465 self._selection = widgets['selection']
466 self._state_renderer = widgets['state_renderer']
467 self._name_column = widgets['name_column']
468 self._name_renderer = widgets['name_renderer']
469 self._add = widgets['add']
470 self._remove = widgets['remove']
471 self._up = widgets['up']
472 self._down = widgets['down']
473 self._model = widgets['model']
474 self._widgets_to_disable = [self._treeview, self._toolbar]
475 self._indicators_dialog = None
476 self._initial_items = OrderedDict((item.NAME, item)
477 for item in map(self.ROW, self._model))
478
479 self._treeview.connect('key-press-event', self._on_key_press)
480 self._selection.connect('changed', self._on_selection_changed)
481 self._state_renderer.connect('toggled', self._on_state_toggled)
482 self._name_renderer.connect('edited', self._on_name_edited)
483 self._add.connect('clicked', self._on_add)
484 self._remove.connect('clicked', self._on_remove)
485 self._up.connect('clicked', self._on_up)
486 self._down.connect('clicked', self._on_down)
487
488 self._model.connect('row-changed', self._on_model_changed)
489 self._model.connect('row-deleted', self._on_model_changed)
490 self._model.connect('row-inserted', self._on_model_changed)
491 self._model.connect('rows-reordered', self._on_model_changed)
492
493 def _on_model_changed(self, *unused):
494 self._emit_changed()
495
496 def _get_value(self):
497 names = (row[self.ROW.NAME] for row in self._model
498 if not row[self.ROW.HAS_STATE] or row[self.ROW.STATE])
499 return self.NAMES_DELIMITER.join(names)
500
501 def _set_value(self, value):
502 self._model.clear()
503 last_options = self._initial_items.copy()
504 if value:
505 for name in value.split(self.NAMES_DELIMITER):
506 try:
507 self._model[self._model.append(last_options.pop(name))]\
508 [self.ROW.STATE] = True
509 except KeyError:
510 self._model.append(self._get_indicator_tuple(name))
511
512 for item in list(last_options.values()):
513 self._model.append(item)
514
515 self._selection.select_path(0)
516
517 def _remove_selection(self):
518 model, rowiter = self._selection.get_selected()
519 if rowiter:
520 previter = model.iter_previous(rowiter)
521 model.remove(rowiter)
522 if previter:
523 self._selection.select_iter(previter)
524
525 def _move_selection(self, move_up):
526 model, rowiter = self._selection.get_selected()
527 if rowiter:
528 if move_up:
529 model.swap(rowiter, model.iter_previous(rowiter))
530 else:
531 model.swap(rowiter, model.iter_next(rowiter))
532 self._on_selection_changed(self._selection)
533
534 def _check_indicator(self, name):
535 ''' Returns True if name is valid, error message or False otherwise '''
536 if not name:
537 return False
538 elif name not in ('~spacer', '~separator'):
539 if any(row[self.ROW.NAME] == name for row in self._model):
540 return C_('option-entry|indicators',
541 'Indicator "{indicator}" is already in the list')\
542 .format(indicator=name)
543 return True
544
545 def _add_indicator(self, name):
546 if name:
547 rowiter = self._model.append(self._get_indicator_tuple(name))
548 self._selection.select_iter(rowiter)
549 self._treeview.grab_focus()
550
551 def _get_indicator_tuple(self, name):
552 tooltip = self.DEFAULT_TOOLTIPS.get(name,
553 C_('option-entry|indicators', 'Indicator: {name}')
554 .format(name=name))
555 editable = name not in ('~spacer', '~separator')
556 return self.ROW(NAME=name, TOOLTIP=tooltip, EDITABLE=editable,
557 HAS_STATE=False, STATE=False)
558
559 def _on_key_press(self, treeview, event):
560 if Gdk.keyval_name(event.keyval) == 'Delete':
561 self._remove_selection()
562 elif Gdk.keyval_name(event.keyval) == 'F2':
563 model, rowiter = self._selection.get_selected()
564 if rowiter and model[rowiter][self.COLUMN.EDITABLE]:
565 self._treeview.set_cursor(
566 model.get_path(rowiter), self.COLUMN.NAME, True)
567 else:
568 return False
569 return True
570
571 def _on_state_toggled(self, renderer, path):
572 self._model[path][self.ROW.STATE] = not self._model[path][self.ROW.STATE]
573
574 def _on_name_edited(self, renderer, path, name):
575 check = self._check_indicator(name)
576 if not isinstance(check, str) and check:
577 self._model[path][self.ROW.NAME] = name
578
579 def _on_selection_changed(self, selection):
580 model, rowiter = selection.get_selected()
581 has_selection = rowiter is not None
582 self._remove.props.sensitive = has_selection and \
583 not model[rowiter][self.ROW.HAS_STATE]
584 self._down.props.sensitive = has_selection and model.iter_next(
585 rowiter) is not None
586 self._up.props.sensitive = has_selection and model.iter_previous(
587 rowiter) is not None
588 if has_selection:
589 self._treeview.scroll_to_cell(model.get_path(rowiter))
590
591 def _on_add(self, *args):
592 if not self._indicators_dialog:
593 self._indicators_dialog = IndicatorChooserDialog(
594 check_callback=self._check_indicator,
595 add_callback=self._add_indicator)
596 self._indicators_dialog.props.transient_for = \
597 self._treeview.get_toplevel()
598 name = self._indicators_dialog.get_indicator()
599 if name:
600 self._add_indicator(name)
601
602 def _on_remove(self, *args):
603 self._remove_selection()
604
605 def _on_up(self, *args):
606 self._move_selection(move_up=True)
607
608 def _on_down(self, *args):
609 self._move_selection(move_up=False)
610
611
612481 class AccessibilityStatesEntry(BaseEntry):
613482
614 OPTIONS = {'keyboard', 'reader', 'contrast', 'font'}
615
616 def __init__(self, widgets):
617 super().__init__(widgets)
618
619 self._states = {name: widgets[name] for name in self.OPTIONS}
483 Options = {'keyboard', 'reader', 'contrast', 'font'}
484
485 def __init__(self, widgets):
486 super().__init__(widgets)
487
488 self._states = {name: widgets[name] for name in self.Options}
620489
621490 for w in self._states.values():
622491 w.connect('changed', self._emit_changed)
623492
624493 def _get_value(self):
625 states = {name: widget.props.active_id for (name, widget) in self._states.items()}
494 states = {name: widget.props.active_id
495 for (name, widget) in self._states.items()}
626496 return ';'.join(state + name
627497 for (name, state) in states.items() if state not in {None, '-'})
628498
632502 for v in value.split(';') if v)
633503 else:
634504 states = {}
635 for name in self.OPTIONS:
505 for name in self.Options:
636506 self._states[name].props.active_id = states.get(name, '-')
637
1616
1717
1818 from gi.repository import GObject
19
20 from lightdm_gtk_greeter_settings.helpers import WidgetsWrapper
1921 from lightdm_gtk_greeter_settings.OptionEntry import BaseEntry
20 from lightdm_gtk_greeter_settings.helpers import WidgetsWrapper
2122
2223
23 __all__ = ['BaseGroup', 'SimpleGroup']
24 __all__ = [
25 'BaseGroup',
26 'SimpleGroup']
2427
2528
2629 # Broken solution - too complex
27
2830 class BaseGroup(GObject.GObject):
2931
3032 def __init__(self, widgets):
8890 config.set(self._name, key, entry.value)
8991 else:
9092 config.remove_option(self._name, key)
91
1414 # with this program. If not, see <http://www.gnu.org/licenses/>.
1515
1616
17 from itertools import product
18
1719 from gi.repository import Gtk
18 from itertools import product
20
21 from lightdm_gtk_greeter_settings.helpers import WidgetsWrapper
1922 from lightdm_gtk_greeter_settings.OptionEntry import BaseEntry
20 from lightdm_gtk_greeter_settings.helpers import WidgetsWrapper
2123
2224
2325 __all__ = ['PositionEntry']
2628 class PositionEntry(BaseEntry):
2729
2830 class Dimension:
31
2932 def __init__(self, widgets, on_changed):
3033 self._entry = widgets['entry']
3134 self._percents = widgets['percents']
104107 def _on_value_changed(self, widget):
105108 self._on_changed(self)
106109
107
108110 ASSUMED_WINDOW_SIZE = 430, 240
109111
110112 def __init__(self, widgets):
128130 for (left, x_anchor), (top, y_anchor), w in anchors:
129131 w.props.halign = anchors_align[left]
130132 w.props.valign = anchors_align[top]
131 w.props.group = anchors[0][-1]
133 if w != anchors[0][-1]:
134 w.props.group = anchors[0][-1]
132135 w.connect('toggled', self._on_anchor_toggled, x_anchor, y_anchor)
133136 self._grid.attach(w, left, top, 1, 1)
134137 self._anchors[x_anchor, y_anchor] = w
140143
141144 self._on_gdk_screen_changed()
142145
143 self._screen_overlay.connect('get-child-position', self._on_screen_overlay_get_child_position)
146 self._screen_overlay.connect(
147 'get-child-position', self._on_screen_overlay_get_child_position)
144148 self._screen_overlay.connect('screen-changed', self._on_gdk_screen_changed)
145149
146150 def _get_value(self):
178182 def _on_screen_overlay_get_child_position(self, overlay, child, allocation):
179183 screen = overlay.get_allocation()
180184
181 if self._last_window_allocation and self._last_overlay_size == (screen.width, screen.height):
182 allocation.x, allocation.y, allocation.width, allocation.height = self._last_window_allocation
185 if self._last_window_allocation and \
186 self._last_overlay_size == (screen.width, screen.height):
187 (allocation.x, allocation.y,
188 allocation.width, allocation.height) = self._last_window_allocation
183189 return True
184190 self._last_overlay_size = screen.width, screen.height
185191
193199 # And check what actually we have now
194200 width, height = child.size_request().width, child.size_request().height
195201
196 x = self._get_corrected_position(int(self._x.get_value_for_screen(self._screen_size[0]) * scale),
197 screen.width, width, self._x.anchor)
198 y = self._get_corrected_position(int(self._y.get_value_for_screen(self._screen_size[1]) * scale),
199 screen.height, height, self._y.anchor)
202 x = int(self._x.get_value_for_screen(self._screen_size[0]) * scale)
203 y = int(self._y.get_value_for_screen(self._screen_size[1]) * scale)
204
205 x = self._get_corrected_position(x, screen.width, width, self._x.anchor)
206 y = self._get_corrected_position(y, screen.height, height, self._y.anchor)
200207
201208 self._last_window_allocation = x, y, width, height
202209 allocation.x, allocation.y, allocation.width, allocation.height = x, y, width, height
212219 self._screen_overlay.queue_resize()
213220 self._emit_changed()
214221
215 def _on_gdk_screen_changed(self, widget = None, prev_screen = None):
222 def _on_gdk_screen_changed(self, widget=None, prev_screen=None):
216223 screen = self._screen_overlay.get_toplevel().get_screen()
217224 geometry = screen.get_monitor_geometry(screen.get_primary_monitor())
218225 self._screen_size = geometry.width, geometry.height
219226 self._screen_frame.props.ratio = geometry.width / geometry.height
220
221
1313 #
1414 # You should have received a copy of the GNU General Public License along
1515 # with this program. If not, see <http://www.gnu.org/licenses/>.
16
1617
1718 def main():
1819 from gi.repository import Gtk
1414 # You should have received a copy of the GNU General Public License along
1515 # with this program. If not, see <http://www.gnu.org/licenses/>.
1616
17 from collections import namedtuple
18 from itertools import chain
17
1918 import configparser
2019 import glob
2120 import locale
21 import os
2222 import pwd
23 import os
2423 import stat
2524
26 from gi.repository import Gtk, GdkPixbuf
25 from collections import (
26 namedtuple,
27 OrderedDict,
28 defaultdict)
29 from itertools import (
30 chain,
31 accumulate)
32 from locale import gettext as _
33
34 from gi.repository import (
35 GdkPixbuf,
36 GLib,
37 GObject,
38 Gtk,
39 Pango)
2740
2841
2942 __license__ = 'GPL-3'
3346
3447
3548 try:
36 from . installation_config import *
49 from . installation_config import (
50 __version__,
51 __data_directory__,
52 __config_path__)
3753 except ImportError:
3854 pass
3955
4056
41 __all__ = ['C_', 'NC_',
42 'get_data_path', 'get_config_path', 'show_message',
43 'bool2string', 'string2bool', 'new_pixbuf_from_file_scaled_down',
44 'file_is_readable_by_greeter',
45 'ModelRowEnum', 'WidgetsWrapper']
57 __all__ = [
58 'bool2string',
59 'C_',
60 'check_path_accessibility',
61 'DefaultValueDict',
62 'file_is_readable_by_greeter',
63 'get_config_path',
64 'get_data_path',
65 'get_markup_error',
66 'get_version',
67 'ModelRowEnum',
68 'NC_',
69 'pixbuf_from_file_scaled_down',
70 'show_message',
71 'SimpleEnum',
72 'string2bool',
73 'TreeStoreDataWrapper',
74 'WidgetsEnum',
75 'WidgetsWrapper']
4676
4777
4878 def C_(context, message):
49 CONTEXT_SEPARATOR = '\x04'
50 message_with_context = '{}{}{}'.format(context, CONTEXT_SEPARATOR, message)
79 separator = '\x04'
80 message_with_context = '{}{}{}'.format(context, separator, message)
5181 result = locale.gettext(message_with_context)
52 if CONTEXT_SEPARATOR in result:
82 if separator in result:
5383 result = message
5484 return result
5585
6797 return os.path.abspath(__config_path__)
6898
6999
100 def get_version():
101 return __version__
102
103
104 def bool2string(value):
105 return 'true' if value else 'false'
106
107
108 def string2bool(value):
109 return value and value.lower() in ('true', 'yes', '1')
110
111
70112 def show_message(**kwargs):
71 dialog = Gtk.MessageDialog(parent=Gtk.Window.list_toplevels()[0], buttons=Gtk.ButtonsType.CLOSE, **kwargs)
113 dialog = Gtk.MessageDialog(parent=Gtk.Window.list_toplevels()[0],
114 buttons=Gtk.ButtonsType.CLOSE, **kwargs)
72115 dialog.run()
73116 dialog.destroy()
74117
75 def get_version():
76 return __version__
77
78
79 def bool2string(value):
80 return 'true' if value else 'false'
81
82
83 def string2bool(value):
84 return value and value.lower() in ('true', 'yes', '1')
85
86
87 def new_pixbuf_from_file_scaled_down(path: str, width: int, height: int):
118
119 def pixbuf_from_file_scaled_down(path, width, height):
88120 pixbuf = GdkPixbuf.Pixbuf.new_from_file(path)
89121 scale = max(pixbuf.props.width / width, pixbuf.props.height / height)
90122
96128 return pixbuf
97129
98130
99 def file_is_readable_by_greeter(path):
131 def check_path_accessibility(path):
132 """Return None if file is readable by greeter and error message otherwise"""
133
134 if not os.path.exists(path):
135 return _('File not found: {path}').format(path=path)
136
100137 try:
101 uid, groups = file_is_readable_by_greeter.id_cached_data
138 uid, gids = check_path_accessibility.id_cached_data
102139 except AttributeError:
103140 files = glob.glob('/etc/lightdm/lightdm.d/*.conf')
104141 files += ['/etc/lightdm/lightdm.conf']
108145
109146 pw = pwd.getpwnam(username)
110147 uid = pw.pw_uid
111 groups = set(os.getgrouplist(username, pw.pw_gid))
112 file_is_readable_by_greeter.id_cached_data = uid, groups
148 gids = set(os.getgrouplist(username, pw.pw_gid))
149 check_path_accessibility.id_cached_data = uid, gids
113150
114151 parts = os.path.normpath(path).split(os.path.sep)
115152 if not parts[0]:
116153 parts[0] = os.path.sep
117154
118 def readable(st, uid, gids):
155 def check(p):
156 try:
157 st = os.stat(p)
158 except OSError as e:
159 return _('Failed to check permissions: {error}'.format(error=e.strerror))
160
119161 if stat.S_ISDIR(st.st_mode) and not stat.S_IREAD:
120 return False
162 return _('Directory is not readable: {path}'.format(path=p))
121163 if st.st_uid == uid:
122 return bool(st.st_mode & stat.S_IRUSR)
123 if st.st_gid in groups:
124 return bool(st.st_mode & stat.S_IRGRP)
125 return bool(st.st_mode & stat.S_IROTH)
126
127 return all(readable(os.stat(os.path.join(*parts[:i+1])), uid, groups)
128 for i in range(len(parts)))
129
130
131 class ModelRowEnum:
132
133 def __init__(self, *names):
134 self.__keys = tuple(names)
135 self.__values = {name: i for i, name in enumerate(names)}
136 self.__dict__.update(self.__values)
137 self.__RowTuple = namedtuple('ModelRowEnumTuple', names)
138
139 def __call__(self, *args, **kwargs):
164 return not (st.st_mode & stat.S_IRUSR) and \
165 _('LighDM do not have permissions to read path: {path}'.format(path=p))
166 if st.st_gid in gids:
167 return not (st.st_mode & stat.S_IRGRP) and \
168 _('LighDM do not have permissions to read path: {path}'.format(path=p))
169 return not (st.st_mode & stat.S_IROTH) and \
170 _('LighDM do not have permissions to read path: {path}'.format(path=p))
171
172 errors = (check(p) for p in accumulate(parts, os.path.join))
173 return next((error for error in errors if error), None)
174
175
176 def get_markup_error(markup):
177 try:
178 Pango.parse_markup(markup, -1, '\0')
179 except GLib.Error as e:
180 return e.message
181 return None
182
183
184 class DefaultValueDict(defaultdict):
185
186 def __init__(self, *items, default=None, factory=None, source=None):
187 super().__init__(None, source or items)
188 self._value = default
189 self._factory = factory
190
191 def __missing__(self, key):
192 return self._factory(key) if self._factory else self._value
193
194
195 class SimpleEnumMeta(type):
196
197 @classmethod
198 def __prepare__(mcs, *args, **kwargs):
199 return OrderedDict()
200
201 def __new__(self, cls, bases, classdict):
202 obj = super().__new__(self, cls, bases, classdict)
203 obj._dict = OrderedDict((k, v)
204 for k, v in classdict.items() if obj._accept_member_(k, v))
205 obj._tuple_type = namedtuple(obj.__class__.__name__ + 'Tuple', obj._dict.keys())
206 keys = list(obj._dict.keys())
207 for i in range(len(keys)):
208 if obj._dict[keys[i]] is ():
209 v = 0 if i == 0 else obj._dict[keys[i - 1]] + 1
210 setattr(obj, keys[i], v)
211 obj._dict[keys[i]] = v
212 return obj
213
214 def __contains__(self, value):
215 return value in self._dict.values()
216
217 def __iter__(self):
218 return iter(self._dict.values())
219
220 def _make(self, *args, **kwargs):
221 return self._tuple_type._make(self._imake(*args, **kwargs))
222
223 def _imake(self, *args, **kwargs):
140224 if args:
141 return self.__RowTuple._make(chain.from_iterable(args))
225 return args
226 elif kwargs:
227 return (kwargs.get(k, v) for k, v in self._dict.items())
142228 else:
143 return self.__RowTuple._make(kwargs.get(name, i) for i, name in enumerate(self.__keys))
229 return self._dict.values()
230
231
232 class SimpleEnum(metaclass=SimpleEnumMeta):
233 _dict = None
234
235 def __init__(self, *args, **kwargs):
236 if kwargs:
237 self.__dict__.update(kwargs)
238 else:
239 self.__dict__.update((k, args[i]) for i, k in enumerate(self._dict))
240
241 def __iter__(self):
242 return (self.__dict__[k] for k in self._dict)
243
244 @classmethod
245 def _accept_member_(cls, name, value):
246 return not name.startswith('_') and not name.endswith('_')
247
248
249 class WidgetsEnum(SimpleEnum):
250
251 def __init__(self, wrapper=None, builder=None):
252 getter = wrapper.__getitem__ if wrapper else builder.get_object
253 for k, v in self._dict.items():
254 if isinstance(v, type) and issubclass(v, WidgetsEnum):
255 self.__dict__[k] = v(WidgetsWrapper(wrapper or builder, k))
256 else:
257 self.__dict__[k] = getter(v or k)
144258
145259
146260 class WidgetsWrapper:
261 _builder = None
262 _prefixes = None
147263
148264 def __init__(self, source, *prefixes):
149265 if isinstance(source, Gtk.Builder):
159275 if not isinstance(args, tuple):
160276 args = (args,)
161277 return self._builder.get_object('_'.join(chain(self._prefixes, args)))
278
279
280 class TreeStoreDataWrapper(GObject.Object):
281
282 def __init__(self, data):
283 super().__init__()
284 self.data = data