@@ -31,6 +31,18 @@ def __str__(self):
31
31
def __repr__ (self ):
32
32
return repr (self ._data )
33
33
34
+ def __reversed__ (self ):
35
+ return reversed (self ._data )
36
+
37
+ def index (self , value ):
38
+ return self ._data .index (value )
39
+
40
+ def count (self , value ):
41
+ return self ._data .count (value )
42
+
43
+
44
+ Sequence .register (ImmutableList )
45
+
34
46
35
47
class Choices :
36
48
"A class to define allowable data types for a property"
@@ -108,24 +120,13 @@ def __init__(
108
120
:param integer: Are integers allowed as values?
109
121
:param number: Are numbers allowed as values?
110
122
:param color: Are colors allowed as values?
111
- :param initial: The initial value for the property.
123
+ :param initial: The initial value for the property. If the property has not been
124
+ explicitly set, this is what is returned when it's accessed.
112
125
"""
113
126
self .choices = Choices (
114
127
* constants , string = string , integer = integer , number = number , color = color
115
128
)
116
- self .initial = None
117
-
118
- try :
119
- # If an initial value has been provided, it must be consistent with
120
- # the choices specified.
121
- if initial is not None :
122
- self .initial = self .validate (initial )
123
- except ValueError :
124
- # Unfortunately, __set_name__ hasn't been called yet, so we don't know the
125
- # property's name.
126
- raise ValueError (
127
- f"Invalid initial value { initial !r} . Available choices: { self .choices } "
128
- )
129
+ self .initial = None if initial is None else self .validate (initial )
129
130
130
131
def __set_name__ (self , owner , name ):
131
132
self .name = name
@@ -153,7 +154,15 @@ def __set__(self, obj, value):
153
154
154
155
value = self .validate (value )
155
156
156
- if value != getattr (obj , f"_{ self .name } " , self .initial ):
157
+ if (current := getattr (obj , f"_{ self .name } " , None )) is None :
158
+ # If the value has not been explicitly set already, then we always want to
159
+ # assign to the attribute -- even if the value being assigned is identical
160
+ # to the initial value.
161
+ setattr (obj , f"_{ self .name } " , value )
162
+ if value != self .initial :
163
+ obj .apply (self .name , value )
164
+
165
+ elif value != current :
157
166
setattr (obj , f"_{ self .name } " , value )
158
167
obj .apply (self .name , value )
159
168
@@ -166,8 +175,8 @@ def __delete__(self, obj):
166
175
obj .apply (self .name , self .initial )
167
176
168
177
@property
169
- def _name_if_set (self , default = "" ):
170
- return f" { self .name } " if hasattr (self , "name" ) else default
178
+ def _name_if_set (self ):
179
+ return f" { self .name } " if hasattr (self , "name" ) else ""
171
180
172
181
def validate (self , value ):
173
182
try :
@@ -230,20 +239,19 @@ def __init__(self, name_format):
230
239
:param name_format: The format from which to generate subproperties. "{}" will
231
240
be replaced with "_top", etc.
232
241
"""
233
- self .name_format = name_format
242
+ self .property_names = [
243
+ name_format .format (f"_{ direction } " ) for direction in self .DIRECTIONS
244
+ ]
234
245
235
246
def __set_name__ (self , owner , name ):
236
247
self .name = name
237
248
owner ._BASE_ALL_PROPERTIES [owner ].add (self .name )
238
249
239
- def format (self , direction ):
240
- return self .name_format .format (f"_{ direction } " )
241
-
242
250
def __get__ (self , obj , objtype = None ):
243
251
if obj is None :
244
252
return self
245
253
246
- return tuple (obj [self . format ( direction ) ] for direction in self .DIRECTIONS )
254
+ return tuple (obj [name ] for name in self .property_names )
247
255
248
256
def __set__ (self , obj , value ):
249
257
if value is self :
@@ -255,22 +263,20 @@ def __set__(self, obj, value):
255
263
value = (value ,)
256
264
257
265
if order := self .ASSIGNMENT_SCHEMES .get (len (value )):
258
- for direction , index in zip (self .DIRECTIONS , order ):
259
- obj [self . format ( direction ) ] = value [index ]
266
+ for name , index in zip (self .property_names , order ):
267
+ obj [name ] = value [index ]
260
268
else :
261
269
raise ValueError (
262
270
f"Invalid value for '{ self .name } '; value must be a number, or a 1-4 "
263
271
f"tuple."
264
272
)
265
273
266
274
def __delete__ (self , obj ):
267
- for direction in self .DIRECTIONS :
268
- del obj [self . format ( direction ) ]
275
+ for name in self .property_names :
276
+ del obj [name ]
269
277
270
278
def is_set_on (self , obj ):
271
- return any (
272
- hasattr (obj , self .format (direction )) for direction in self .DIRECTIONS
273
- )
279
+ return any (hasattr (obj , name ) for name in self .property_names )
274
280
275
281
276
282
class BaseStyle :
@@ -297,8 +303,8 @@ def _ALL_PROPERTIES(self):
297
303
298
304
# Fallback in case subclass isn't decorated as subclass (probably from using
299
305
# previous API) or for pre-3.10, before kw_only argument existed.
300
- def __init__ (self , ** style ):
301
- self .update (** style )
306
+ def __init__ (self , ** properties ):
307
+ self .update (** properties )
302
308
303
309
@property
304
310
def _applicator (self ):
@@ -324,6 +330,34 @@ def _applicator(self, value):
324
330
stacklevel = 2 ,
325
331
)
326
332
333
+ def reapply (self ):
334
+ for name in self ._PROPERTIES :
335
+ self .apply (name , self [name ])
336
+
337
+ def copy (self , applicator = None ):
338
+ """Create a duplicate of this style declaration."""
339
+ dup = self .__class__ ()
340
+ dup .update (** self )
341
+
342
+ ######################################################################
343
+ # 10-2024: Backwards compatibility for Toga <= 0.4.8
344
+ ######################################################################
345
+
346
+ if applicator is not None :
347
+ warn (
348
+ "Providing an applicator to BaseStyle.copy() is deprecated. Set "
349
+ "applicator afterward on the returned copy." ,
350
+ DeprecationWarning ,
351
+ stacklevel = 2 ,
352
+ )
353
+ dup ._applicator = applicator
354
+
355
+ ######################################################################
356
+ # End backwards compatibility
357
+ ######################################################################
358
+
359
+ return dup
360
+
327
361
######################################################################
328
362
# Interface that style declarations must define
329
363
######################################################################
@@ -342,35 +376,15 @@ def layout(self, viewport):
342
376
# Provide a dict-like interface
343
377
######################################################################
344
378
345
- def reapply (self ):
346
- for name in self ._PROPERTIES :
347
- self .apply (name , self [name ])
348
-
349
- def update (self , ** styles ):
350
- "Set multiple styles on the style definition."
351
- for name , value in styles .items ():
379
+ def update (self , ** properties ):
380
+ """Set multiple styles on the style definition."""
381
+ for name , value in properties .items ():
352
382
name = name .replace ("-" , "_" )
353
383
if name not in self ._ALL_PROPERTIES :
354
384
raise NameError (f"Unknown style '{ name } '" )
355
385
356
386
self [name ] = value
357
387
358
- def copy (self , applicator = None ):
359
- "Create a duplicate of this style declaration."
360
- dup = self .__class__ ()
361
- dup .update (** self )
362
-
363
- if applicator is not None :
364
- warn (
365
- "Providing an applicator to BaseStyle.copy() is deprecated. Set "
366
- "applicator afterward on the returned copy." ,
367
- DeprecationWarning ,
368
- stacklevel = 2 ,
369
- )
370
- dup ._applicator = applicator
371
-
372
- return dup
373
-
374
388
def __getitem__ (self , name ):
375
389
name = name .replace ("-" , "_" )
376
390
if name in self ._ALL_PROPERTIES :
@@ -392,13 +406,13 @@ def __delitem__(self, name):
392
406
raise KeyError (name )
393
407
394
408
def keys (self ):
395
- return {name for name in self . _PROPERTIES if name in self }
409
+ return {name for name in self }
396
410
397
411
def items (self ):
398
- return [(name , self [name ]) for name in self . _PROPERTIES if name in self ]
412
+ return [(name , self [name ]) for name in self ]
399
413
400
414
def __len__ (self ):
401
- return sum (1 for name in self . _PROPERTIES if name in self )
415
+ return sum (1 for _ in self )
402
416
403
417
def __contains__ (self , name ):
404
418
return name in self ._ALL_PROPERTIES and (
@@ -432,6 +446,7 @@ def __ior__(self, other):
432
446
######################################################################
433
447
# Get the rendered form of the style declaration
434
448
######################################################################
449
+
435
450
def __str__ (self ):
436
451
return "; " .join (
437
452
f"{ name .replace ('_' , '-' )} : { value } " for name , value in sorted (self .items ())
0 commit comments