1__all__ = ('object_property', 'bool_property',
2        'array_property', 'set_property', 'dict_property')
3
4from objc import ivar, selector, _C_ID, _C_NSBOOL, _C_BOOL, NULL, _C_NSUInteger
5from objc import lookUpClass
6import collections
7from copy import copy as copy_func
8import sys
9
10NSSet = lookUpClass('NSSet')
11NSObject = lookUpClass('NSObject')
12
13
14def attrsetter(prop, name, copy):
15    if copy:
16        def func(self, value):
17            if isinstance(value, NSObject):
18                setattr(self, name, value.copy())
19            else:
20                setattr(self, name, copy_func(value))
21    else:
22        def func(self, value):
23            setattr(self, name, value)
24    return func
25
26def attrgetter(name):
27    def func(self):
28        return getattr(self, name)
29    return func
30
31def _return_value(value):
32    def func(self):
33        return value
34
35    return func
36
37def _dynamic_getter(name):
38    def getter(object):
39        m = getattr(object.pyobjc_instanceMethods, name)
40        return m()
41    getter.__name__ = name
42    return getter
43
44def _dynamic_setter(name):
45    def setter(object, value):
46        m = getattr(object.pyobjc_instanceMethods, name)
47        return m(value)
48    setter.__name__ = name
49    return setter
50
51class object_property (object):
52    def __init__(self, name=None,
53            read_only=False, copy=False, dynamic=False,
54            ivar=None, typestr=_C_ID, depends_on=None):
55        self.__created = False
56        self.__inherit = False
57        self._name = name
58        self._typestr = typestr
59        self._ro = read_only
60        self._copy = copy
61        self._dynamic = dynamic
62        self._ivar = ivar
63        self._getter = None
64        self._setter = None
65        self._validate = None
66        if depends_on is None:
67            self._depends_on = ()
68        else:
69            self._depends_on = list(depends_on)
70
71        self.__getprop = None
72        self.__setprop = None
73        self.__parent = None
74
75    def _clone(self):
76        v = type(self)(name=self._name,
77                read_only=self._ro, copy=self._copy, dynamic=self._dynamic,
78                ivar=self._ivar, typestr=self._typestr, depends_on=None)
79        v.__inherit = True
80
81        v.__getprop = self.__getprop
82        v.__setprop = self.__setprop
83        v.__parent = self
84
85        return v
86
87    def __pyobjc_class_setup__(self, name, class_dict, instance_methods, class_methods):
88        self.__created = True
89        if self._name is None:
90            self._name = name
91
92        if self._ivar is not NULL:
93            if self._ivar is None:
94                ivname = '_' + self._name
95            else:
96                ivname = self._ivar
97
98            if self.__parent is None:
99                ivar_ref = ivar(name=ivname, type=self._typestr)
100                class_dict[ivname] = ivar_ref
101
102        if self._ro:
103            self._setter = None
104
105        else:
106            setterName = b'set' + name[0].upper().encode('latin1') + name[1:].encode('latin1') + b':'
107            signature = b'v@:' + self._typestr
108            if self._setter is None:
109                if self.__inherit:
110                    pass
111
112                elif self._dynamic:
113                    dynSetterName = 'set' + name[0].upper() + name[1:] + '_'
114                    self.__setprop = _dynamic_setter(dynSetterName)
115                    instance_methods.add(setterName)
116
117                else:
118
119                    if self._ivar is NULL:
120                        raise ValueError(
121                            "Cannot create default setter for property "
122                            "without ivar")
123
124                    self.__setprop = selector(
125                        attrsetter(self._name, ivname, self._copy),
126                        selector=setterName,
127                        signature=signature
128                    )
129                    self.__setprop.isHidden = True
130                    instance_methods.add(self.__setprop)
131            else:
132                self.__setprop = selector(
133                    self._setter,
134                    selector=setterName,
135                    signature=signature
136                )
137                self.__setprop.isHidden = True
138                instance_methods.add(self.__setprop)
139
140        if self._typestr in (_C_NSBOOL, _C_BOOL):
141            getterName = b'is' + name[0].upper().encode('latin1') + name[:1].encode('latin1')
142        else:
143            getterName = self._name.encode('latin1')
144
145        if self._getter is None:
146            if self.__inherit:
147                pass
148
149            elif self._dynamic:
150                if self._typestr in (_C_NSBOOL, _C_BOOL):
151                    dynGetterName = 'is' + name[0].upper() + name[:1]
152                else:
153                    dynGetterName = self._name
154
155                self.__getprop = _dynamic_getter(dynGetterName)
156                instance_methods.add(getterName)
157
158            else:
159                if self._ivar is NULL:
160                    raise ValueError(
161                        "Cannot create default getter for property without ivar")
162
163                self.__getprop = selector(
164                        attrgetter(ivname),
165                        selector=getterName,
166                        signature=self._typestr + b'@:')
167                self.__getprop.isHidden=True
168                instance_methods.add(self.__getprop)
169
170        else:
171            self.__getprop = selector(
172                    self._getter,
173                    selector=getterName,
174                    signature=self._typestr + b'@:')
175            self.__getprop.isHidden=True
176            instance_methods.add(self.__getprop)
177
178        if self._validate is not None:
179            selName = b'validate' + self._name[0].upper().encode('latin') + self._name[1:].encode('latin') + b':error:'
180            signature = _C_NSBOOL + b'@:N^@o^@'
181            validate = selector(
182                    self._validate,
183                    selector=selName,
184                    signature=signature)
185            validate.isHidden = True
186            instance_methods.add(validate)
187
188        if self._depends_on:
189            if self.__parent is not None:
190                if self.__parent._depends_on:
191                    self._depends_on.update(self.__parent._depends_on.copy())
192
193            self._depends_on = self._depends_on
194
195            affecting = selector(
196                    _return_value(NSSet.setWithArray_(list(self._depends_on))),
197                    selector = b'keyPathsForValuesAffecting' + self._name[0].upper().encode('latin1') + self._name[1:].encode('latin1'),
198                    signature = b'@@:',
199                    isClassMethod=True)
200            affecting.isHidden = True
201            class_dict[affecting.selector] = affecting
202            class_methods.add(affecting)
203
204
205    def __get__(self, object, owner):
206        if object is None:
207            return self
208        return self.__getprop(object)
209
210    def __set__(self, object, value):
211        if self.__setprop is None:
212            raise ValueError("setting read-only property " + self._name)
213
214        return self.__setprop(object, value)
215
216    def __delete__(self, object):
217        raise TypeError("cannot delete property " + self._name)
218
219    def depends_on(self, keypath):
220        if self._depends_on is None:
221            self._depends_on = set()
222        self._depends_on.add(keypath)
223
224    def getter(self, function):
225        if self.__created:
226            v = self._clone()
227            v._getter = function
228            return v
229
230        self._getter = function
231        return self
232
233    def setter(self, function):
234
235        if self.__created:
236            v = self._clone()
237            v._ro = False
238            v._setter = function
239            return v
240
241        if self._ro:
242            raise ValueError("Defining settter for read-only property")
243
244        self._setter = function
245        return self
246
247    def validate(self, function):
248        if self._ro:
249            raise ValueError("Defining validator for read-only property")
250
251        if self.__created:
252            v = self._clone()
253            v._validate = function
254            return v
255
256        self._validate = function
257        return self
258
259class bool_property (object_property):
260    def __init__(self, name=None,
261            read_only=False, copy=False, dynamic=False,
262            ivar=None, typestr=_C_NSBOOL):
263        super(bool_property, self).__init__(
264                name, read_only, copy, dynamic, ivar, typestr)
265
266
267
268def _id(value):
269    return value
270
271NSIndexSet = lookUpClass('NSIndexSet')
272NSMutableIndexSet = lookUpClass('NSMutableIndexSet')
273NSKeyValueChangeSetting = 1
274NSKeyValueChangeInsertion = 2
275NSKeyValueChangeRemoval = 3
276NSKeyValueChangeReplacement = 4
277
278# FIXME: split into two: array_proxy and mutable_array_proxy
279class array_proxy (collections.MutableSequence):
280    # XXX: The implemenation should be complete, but is currently not
281    # tested.
282    __slots__ = ('_name', '_parent', '__wrapped', '_ro')
283
284    def __init__(self, name, parent, wrapped, read_only):
285        self._name = name
286        self._parent = parent
287        self._ro = read_only
288        self.__wrapped = wrapped
289
290    @property
291    def _wrapped(self):
292        return self.__wrapped.__getvalue__(self._parent)
293
294    @_wrapped.setter
295    def _wrapped(self, value):
296        setattr(self._parent, self._name, value)
297
298    def __indexSetForIndex(self, index):
299        if isinstance(index, slice):
300            result = NSMutableIndexSet.alloc().init()
301            start, stop, step = index.indices(len(self._wrapped))
302            for i in xrange(start, stop, step):
303                result.addIndex_(i)
304
305            return result
306
307        else:
308            if index < 0:
309                v = len(self) + index
310                if v < 0:
311                    raise IndexError(index)
312                return NSIndexSet.alloc().initWithIndex_(v)
313
314            else:
315                return NSIndexSet.alloc().initWithIndex_(index)
316
317
318
319    def __repr__(self):
320        return '<array proxy for property ' + self._name + ' ' + repr(self._wrapped) + '>'
321
322    def __reduce__(self):
323        # Ensure that the proxy itself doesn't get stored
324        # in pickles.
325        return _id, self._wrapped
326
327    def __getattr__(self, name):
328        # Default: just defer to wrapped list
329        return getattr(self._wrapped, name)
330
331    def __len__(self):
332        return self._wrapped.__len__()
333
334    def __getitem__(self, index):
335        return self._wrapped[index]
336
337    def __setitem__(self, index, value):
338        if self._ro:
339            raise ValueError("Property '%s' is read-only"%(self._name,))
340
341        indexes = self.__indexSetForIndex(index)
342        self._parent.willChange_valuesAtIndexes_forKey_(
343                NSKeyValueChangeSetting,
344                indexes, self._name)
345        try:
346            self._wrapped[index] = value
347        finally:
348            self._parent.didChange_valuesAtIndexes_forKey_(
349                NSKeyValueChangeReplacement,
350                indexes, self._name)
351
352    def __delitem__(self, index):
353        if self._ro:
354            raise ValueError("Property '%s' is read-only"%(self._name,))
355
356        indexes = self.__indexSetForIndex(index)
357        self._parent.willChange_valuesAtIndexes_forKey_(
358                NSKeyValueChangeRemoval,
359                indexes, self._name)
360        try:
361            del self._wrapped[index]
362        finally:
363            self._parent.didChange_valuesAtIndexes_forKey_(
364                NSKeyValueChangeRemoval,
365                indexes, self._name)
366
367    def append(self, value):
368        if self._ro:
369            raise ValueError("Property '%s' is read-only"%(self._name,))
370
371        index = len(self)
372        indexes = NSIndexSet.alloc().initWithIndex_(index)
373        self._parent.willChange_valuesAtIndexes_forKey_(
374                NSKeyValueChangeInsertion,
375                indexes, self._name)
376        try:
377            self._wrapped.append(value)
378        finally:
379            self._parent.didChange_valuesAtIndexes_forKey_(
380                NSKeyValueChangeInsertion,
381                indexes, self._name)
382
383    def insert(self, index, value):
384        if self._ro:
385            raise ValueError("Property '%s' is read-only"%(self._name,))
386
387        if isinstance(index, slice):
388            raise ValueError("insert argument 1 is a slice")
389
390        indexes = self.__indexSetForIndex(index)
391        self._parent.willChange_valuesAtIndexes_forKey_(
392                NSKeyValueChangeInsertion,
393                indexes, self._name)
394        try:
395            self._wrapped.insert(index, value)
396        finally:
397            self._parent.didChange_valuesAtIndexes_forKey_(
398                NSKeyValueChangeInsertion,
399                indexes, self._name)
400
401    def pop(self, index=-1):
402        if self._ro:
403            raise ValueError("Property '%s' is read-only"%(self._name,))
404
405        if isinstance(index, slice):
406            raise ValueError("insert argument 1 is a slice")
407
408        indexes = self.__indexSetForIndex(index)
409        self._parent.willChange_valuesAtIndexes_forKey_(
410                NSKeyValueChangeRemoval,
411                indexes, self._name)
412        try:
413            return self._wrapped.pop(index)
414        finally:
415            self._parent.didChange_valuesAtIndexes_forKey_(
416                NSKeyValueChangeRemoval,
417                indexes, self._name)
418
419    def extend(self, values):
420        # XXX: This is suboptimal but correct
421        if self._ro:
422            raise ValueError("Property '%s' is read-only"%(self._name,))
423
424        values = list(values)
425
426        indexes = NSIndexSet.alloc().initWithIndexesInRange_((len(self), len(values)))
427
428        self._parent.willChange_valuesAtIndexes_forKey_(
429                NSKeyValueChangeInsertion,
430                indexes, self._name)
431        try:
432            for item in values:
433                self._wrapped.append(item)
434        finally:
435            self._parent.didChange_valuesAtIndexes_forKey_(
436                NSKeyValueChangeInsertion,
437                indexes, self._name)
438
439    def __iadd__(self, value):
440        return self._wrapped + value
441
442# This causes a internal python error:
443#        self._wrapped.extend(value)
444#        return self
445
446    def __imul__(self, count):
447        return self._wrapped * count
448
449# This causes an error I don't quite get yet:
450#        if self._ro:
451#            raise ValueError("Property '%s' is read-only"%(self._name,))
452#        if not isinstance(count, (int, long)):
453#            raise ValueError(count)
454#
455#        indexes = NSIndexSet.alloc().initWithIndexesInRange_((len(self), len(self)*count))
456#        self._parent.willChange_valuesAtIndexes_forKey_(
457#                NSKeyValueChangeInsertion,
458#                indexes, self._name)
459#        try:
460#            self._wrapped *= count
461#        finally:
462#            self._parent.didChange_valuesAtIndexes_forKey_(
463#                NSKeyValueChangeInsertion,
464#                indexes, self._name)
465#
466#        return self
467
468
469    def __eq__(self, other):
470        if isinstance(other, array_proxy):
471            return self._wrapped == other._wrapped
472
473        else:
474            return self._wrapped == other
475
476    def __ne__(self, other):
477        if isinstance(other, array_proxy):
478            return self._wrapped != other._wrapped
479
480        else:
481            return self._wrapped != other
482
483    def __lt__(self, other):
484        if isinstance(other, array_proxy):
485            return self._wrapped < other._wrapped
486
487        else:
488            return self._wrapped < other
489
490    def __le__(self, other):
491        if isinstance(other, array_proxy):
492            return self._wrapped <= other._wrapped
493
494        else:
495            return self._wrapped <= other
496
497    def __gt__(self, other):
498        if isinstance(other, array_proxy):
499            return self._wrapped > other._wrapped
500
501        else:
502            return self._wrapped > other
503
504    def __ge__(self, other):
505        if isinstance(other, array_proxy):
506            return self._wrapped >= other._wrapped
507
508        else:
509            return self._wrapped >= other
510
511
512    if sys.version_info[0] == 2:
513        def __cmp__(self, other):
514            if isinstance(other, array_proxy):
515                return cmp(self._wrapped, other._wrapped)
516
517            else:
518                return cmp(self._wrapped, other)
519
520    if sys.version_info[0] == 2:
521        def sort(self, cmp=None, key=None, reverse=False):
522            if self._ro:
523                raise ValueError("Property '%s' is read-only"%(self._name,))
524
525            indexes = NSIndexSet.alloc().initWithIndexesInRange_(
526                    (0, len(self._wrapped)))
527            self._parent.willChange_valuesAtIndexes_forKey_(
528                    NSKeyValueChangeReplacement,
529                    indexes, self._name)
530            try:
531                self._wrapped.sort(cmp=cmp, key=key, reverse=reverse)
532            finally:
533                self._parent.didChange_valuesAtIndexes_forKey_(
534                    NSKeyValueChangeReplacement,
535                    indexes, self._name)
536
537    else:
538        def sort(self, key=None, reverse=False):
539            if self._ro:
540                raise ValueError("Property '%s' is read-only"%(self._name,))
541
542            indexes = NSIndexSet.alloc().initWithIndexesInRange_(
543                    (0, len(self._wrapped)))
544            self._parent.willChange_valuesAtIndexes_forKey_(
545                    NSKeyValueChangeReplacement,
546                    indexes, self._name)
547            try:
548                self._wrapped.sort(key=key, reverse=reverse)
549            finally:
550                self._parent.didChange_valuesAtIndexes_forKey_(
551                    NSKeyValueChangeReplacement,
552                    indexes, self._name)
553
554    def reverse(self):
555        if self._ro:
556            raise ValueError("Property '%s' is read-only"%(self._name,))
557
558        indexes = NSIndexSet.alloc().initWithIndexesInRange_(
559                (0, len(self._wrapped)))
560        self._parent.willChange_valuesAtIndexes_forKey_(
561                NSKeyValueChangeReplacement,
562                indexes, self._name)
563        try:
564            self._wrapped.reverse()
565        finally:
566            self._parent.didChange_valuesAtIndexes_forKey_(
567                NSKeyValueChangeReplacement,
568                indexes, self._name)
569
570def makeArrayAccessors(name):
571
572    def countOf(self):
573        return len(getattr(self, name))
574
575    def objectIn(self, idx):
576        return getattr(self, name)[idx]
577
578    def insert(self, value, idx):
579        getattr(self, name).insert(idx, value)
580
581    def replace(self, idx, value):
582        getattr(self, name)[idx] = value
583
584    def remove(self, idx):
585        del getattr(self, name)[idx]
586
587    return countOf, objectIn, insert, remove, replace
588
589class array_property (object_property):
590    def __init__(self, name=None,
591            read_only=False, copy=True, dynamic=False,
592            ivar=None, depends_on=None):
593        super(array_property, self).__init__(name,
594                read_only=read_only,
595                copy=copy, dynamic=dynamic,
596                ivar=ivar, depends_on=depends_on)
597
598    def __pyobjc_class_setup__(self, name, class_dict, instance_methods, class_methods):
599        super(array_property, self).__pyobjc_class_setup__(name, class_dict, instance_methods, class_methods)
600
601
602        # Insert (Mutable) Indexed Accessors
603        # FIXME: should only do the mutable bits when we're actually a mutable property
604
605        name = self._name
606        Name = name[0].upper() + name[1:]
607
608        countOf, objectIn, insert, remove, replace = makeArrayAccessors(self._name)
609
610        countOf = selector(countOf,
611                selector  = ('countOf%s'%(Name,)).encode('latin1'),
612                signature = _C_NSUInteger + b'@:',
613        )
614        countOf.isHidden = True
615        instance_methods.add(countOf)
616
617        objectIn = selector(objectIn,
618                selector  = ('objectIn%sAtIndex:'%(Name,)).encode('latin1'),
619                signature = b'@@:' + _C_NSUInteger,
620        )
621        objectIn.isHidden = True
622        instance_methods.add(objectIn)
623
624        insert = selector(insert,
625                selector  = ('insertObject:in%sAtIndex:'%(Name,)).encode('latin1'),
626                signature = b'v@:@' + _C_NSUInteger,
627        )
628        insert.isHidden = True
629        instance_methods.add(insert)
630
631        remove = selector(remove,
632                selector  = ('removeObjectFrom%sAtIndex:'%(Name,)).encode('latin1'),
633                signature = b'v@:' + _C_NSUInteger,
634        )
635        remove.isHidden = True
636        instance_methods.add(remove)
637
638        replace = selector(replace,
639                selector  = ('replaceObjectIn%sAtIndex:withObject:'%(Name,)).encode('latin1'),
640                signature = b'v@:' + _C_NSUInteger + b'@',
641        )
642        replace.isHidden = True
643        instance_methods.add(replace)
644
645
646    def __set__(self, object, value):
647        if isinstance(value, array_property):
648            value = list(value)
649
650        super(array_property, self).__set__(object, value)
651
652    def __get__(self, object, owner):
653        v = object_property.__get__(self, object, owner)
654        if v is None:
655            v = list()
656            object_property.__set__(self, object, v)
657        return array_proxy(self._name, object, self, self._ro)
658
659    def __getvalue__(self, object):
660        v = object_property.__get__(self, object, None)
661        if v is None:
662            v = list()
663            object_property.__set__(self, object, v)
664        return v
665
666
667NSKeyValueUnionSetMutation = 1
668NSKeyValueMinusSetMutation = 2
669NSKeyValueIntersectSetMutation = 3
670NSKeyValueSetSetMutation = 4
671
672
673class set_proxy (collections.MutableSet):
674    __slots__ = ('_name', '__wrapped', '_parent', '_ro')
675
676    def __init__(self, name, parent, wrapped, read_only):
677        self._name = name
678        self._parent = parent
679        self._ro = read_only
680        self.__wrapped = wrapped
681
682    def __repr__(self):
683        return '<set proxy for property ' + self._name + ' ' + repr(self._wrapped) + '>'
684
685    @property
686    def _wrapped(self):
687        return self.__wrapped.__getvalue__(self._parent)
688
689    @_wrapped.setter
690    def _wrapped(self, value):
691        setattr(self._parent, self._name, value)
692
693    def __getattr__(self, attr):
694        return getattr(self._wrapped, attr)
695
696
697    def __contains__(self, value):
698        return self._wrapped.__contains__(value)
699
700    def __iter__(self):
701        return self._wrapped.__iter__()
702
703    def __len__(self):
704        return self._wrapped.__len__()
705
706
707    def __eq__(self, other):
708        if isinstance(other, set_proxy):
709            return self._wrapped == other._wrapped
710
711        else:
712            return self._wrapped == other
713
714    def __ne__(self, other):
715        if isinstance(other, set_proxy):
716            return self._wrapped != other._wrapped
717
718        else:
719            return self._wrapped != other
720
721    def __lt__(self, other):
722        if isinstance(other, set_proxy):
723            return self._wrapped < other._wrapped
724
725        else:
726            return self._wrapped < other
727
728    def __le__(self, other):
729        if isinstance(other, set_proxy):
730            return self._wrapped <= other._wrapped
731
732        else:
733            return self._wrapped <= other
734
735    def __gt__(self, other):
736        if isinstance(other, set_proxy):
737            return self._wrapped > other._wrapped
738
739        else:
740            return self._wrapped > other
741
742    def __ge__(self, other):
743        if isinstance(other, set_proxy):
744            return self._wrapped >= other._wrapped
745
746        else:
747            return self._wrapped >= other
748
749
750    if sys.version_info[0] == 2:
751        def __cmp__(self, other):
752            if isinstance(other, set_proxy):
753                return cmp(self._wrapped, other._wrapped)
754
755            else:
756                return cmp(self._wrapped, other)
757
758    def add(self, item):
759        if self._ro:
760            raise ValueError("Property '%s' is read-only"%(self._name,))
761
762        self._parent.willChangeValueForKey_withSetMutation_usingObjects_(
763                self._name,
764                NSKeyValueUnionSetMutation,
765                set([item]),
766        )
767        try:
768            self._wrapped.add(item)
769        finally:
770            self._parent.didChangeValueForKey_withSetMutation_usingObjects_(
771                self._name,
772                NSKeyValueUnionSetMutation,
773                set([item]),
774            )
775
776    def clear(self):
777        if self._ro:
778            raise ValueError("Property '%s' is read-only"%(self._name,))
779
780        object = set(self._wrapped)
781        self._parent.willChangeValueForKey_withSetMutation_usingObjects_(
782                self._name,
783                NSKeyValueMinusSetMutation,
784                object
785        )
786        try:
787            self._wrapped.clear()
788        finally:
789            self._parent.didChangeValueForKey_withSetMutation_usingObjects_(
790                self._name,
791                NSKeyValueMinusSetMutation,
792                object
793            )
794
795    def difference_update(self, *others):
796        if self._ro:
797            raise ValueError("Property '%s' is read-only"%(self._name,))
798
799        s = set()
800        s.update(*others)
801        self._parent.willChangeValueForKey_withSetMutation_usingObjects_(
802                self._name,
803                NSKeyValueMinusSetMutation,
804                s
805        )
806        try:
807            self._wrapped.difference_update(s)
808
809        finally:
810            self._parent.didChangeValueForKey_withSetMutation_usingObjects_(
811                self._name,
812                NSKeyValueMinusSetMutation,
813                s
814            )
815
816
817    def discard(self, item):
818        if self._ro:
819            raise ValueError("Property '%s' is read-only"%(self._name,))
820
821        self._parent.willChangeValueForKey_withSetMutation_usingObjects_(
822                self._name,
823                NSKeyValueMinusSetMutation,
824                set([item])
825        )
826        try:
827            self._wrapped.discard(item)
828
829        finally:
830            self._parent.didChangeValueForKey_withSetMutation_usingObjects_(
831                self._name,
832                NSKeyValueMinusSetMutation,
833                set([item])
834            )
835
836    def intersection_update(self, other):
837        if self._ro:
838            raise ValueError("Property '%s' is read-only"%(self._name,))
839
840        self._parent.willChangeValueForKey_withSetMutation_usingObjects_(
841                self._name,
842                NSKeyValueIntersectSetMutation,
843                set([item])
844        )
845        try:
846            self._wrapped.intersection_update(s)
847
848        finally:
849            self._parent.didChangeValueForKey_withSetMutation_usingObjects_(
850                self._name,
851                NSKeyValueIntersectSetMutation,
852                set([item])
853            )
854
855    def pop(self):
856        if self._ro:
857            raise ValueError("Property '%s' is read-only"%(self._name,))
858
859        try:
860            v = iter(self).next()
861        except KeyError:
862            raise KeyError("Empty set")
863
864        self.remove(v)
865
866
867    def remove(self, item):
868        if self._ro:
869            raise ValueError("Property '%s' is read-only"%(self._name,))
870
871        self._parent.willChangeValueForKey_withSetMutation_usingObjects_(
872                self._name,
873                NSKeyValueMinusSetMutation,
874                set([item])
875        )
876        try:
877            self._wrapped.remove(item)
878
879        finally:
880            self._parent.didChangeValueForKey_withSetMutation_usingObjects_(
881                self._name,
882                NSKeyValueMinusSetMutation,
883                set([item])
884            )
885
886    def symmetric_difference_update(self, other):
887        # NOTE: This method does not call the corresponding method
888        # of the wrapped set to ensure that we generate the right
889        # notifications.
890        if self._ro:
891            raise ValueError("Property '%s' is read-only"%(self._name,))
892
893        other = set(other)
894
895        to_add = set()
896        to_remove = set()
897        for o in other:
898            if o in self:
899                to_remove.add(o)
900            else:
901                to_add.add(o)
902
903        self._parent.willChangeValueForKey_withSetMutation_usingObjects_(
904                self._name,
905                NSKeyValueMinusSetMutation,
906                to_remove
907        )
908        try:
909            self._wrapped.difference_update(to_remove)
910
911        finally:
912            self._parent.didChangeValueForKey_withSetMutation_usingObjects_(
913                    self._name,
914                    NSKeyValueMinusSetMutation,
915                    to_remove
916            )
917
918        self._parent.willChangeValueForKey_withSetMutation_usingObjects_(
919                self._name,
920                NSKeyValueUnionSetMutation,
921                to_add
922        )
923        try:
924            self._wrapped.update(to_add)
925
926        finally:
927            self._parent.didChangeValueForKey_withSetMutation_usingObjects_(
928                    self._name,
929                    NSKeyValueUnionSetMutation,
930                    to_add
931            )
932
933    def update(self, *others):
934        if self._ro:
935            raise ValueError("Property '%s' is read-only"%(self._name,))
936
937        s = set()
938        s.update(*others)
939
940        self._parent.willChangeValueForKey_withSetMutation_usingObjects_(
941                self._name,
942                NSKeyValueUnionSetMutation,
943                s
944        )
945        try:
946            self._wrapped.update(s)
947
948        finally:
949            self._parent.didChangeValueForKey_withSetMutation_usingObjects_(
950                self._name,
951                NSKeyValueUnionSetMutation,
952                s
953            )
954
955    def __or__(self, other):
956        return self._wrapped | other
957
958    def __and__(self, other):
959        return self._wrapped & other
960
961    def __xor__(self, other):
962        return self._wrapped ^ other
963
964    def __sub__(self, other):
965        return self._wrapped - other
966
967    def __ior__(self, other):
968        if self._ro:
969            raise ValueError("Property '%s' is read-only"%(self._name,))
970
971        return self|other
972
973    def __isub__(self, other):
974        if self._ro:
975            raise ValueError("Property '%s' is read-only"%(self._name,))
976
977        return self-other
978
979    def __ixor__(self, other):
980        if self._ro:
981            raise ValueError("Property '%s' is read-only"%(self._name,))
982
983        return self^other
984
985    def __iand__(self, other):
986        if self._ro:
987            raise ValueError("Property '%s' is read-only"%(self._name,))
988
989        return self&other
990
991def makeSetAccessors(name):
992    def countOf(self):
993        return len(getattr(self, name))
994
995    def enumeratorOf(self):
996        return iter(getattr(self, name))
997
998    def memberOf(self, value):
999        collection =  getattr(self, name)
1000        if value not in collection:
1001            return None
1002
1003        for item in collection:
1004            if item == value:
1005                return item
1006
1007    def add(self, value):
1008        getattr(self, name).add(value)
1009
1010    def remove(self, value):
1011        getattr(self, name).discard(value)
1012
1013    return countOf, enumeratorOf, memberOf, add, remove
1014
1015
1016class set_property (object_property):
1017    def __init__(self, name=None,
1018            read_only=False, copy=True, dynamic=False,
1019            ivar=None, depends_on=None):
1020        super(set_property, self).__init__(name,
1021                read_only=read_only,
1022                copy=copy, dynamic=dynamic,
1023                ivar=ivar, depends_on=depends_on)
1024
1025    def __get__(self, object, owner):
1026        v = object_property.__get__(self, object, owner)
1027        if v is None:
1028            v = set()
1029            object_property.__set__(self, object, v)
1030        return set_proxy(self._name, object, self, self._ro)
1031
1032    def __getvalue__(self, object):
1033        v = object_property.__get__(self, object, None)
1034        if v is None:
1035            v = set()
1036            object_property.__set__(self, object, v)
1037        return v
1038
1039    def __pyobjc_class_setup__(self, name, class_dict, instance_methods, class_methods):
1040        super(set_property, self).__pyobjc_class_setup__(name, class_dict, instance_methods, class_methods)
1041
1042        # (Mutable) Unordered Accessors
1043        # FIXME: should only do the mutable bits when we're actually a mutable property
1044
1045        name = self._name
1046        Name = name[0].upper() + name[1:]
1047
1048        countOf, enumeratorOf, memberOf, add, remove = makeSetAccessors(self._name)
1049
1050        countOf = selector(countOf,
1051                selector  = ('countOf%s'%(Name,)).encode('latin1'),
1052                signature = _C_NSUInteger + b'@:',
1053        )
1054        countOf.isHidden = True
1055        instance_methods.add(countOf)
1056
1057        enumeratorOf = selector(enumeratorOf,
1058                selector  = ('enumeratorOf%s'%(Name,)).encode('latin1'),
1059                signature = b'@@:',
1060        )
1061        enumeratorOf.isHidden = True
1062        instance_methods.add(enumeratorOf)
1063
1064        memberOf = selector(memberOf,
1065                selector  = ('memberOf%s:'%(Name,)).encode('latin'),
1066                signature = b'@@:@',
1067        )
1068        memberOf.isHidden = True
1069        instance_methods.add(memberOf)
1070
1071        add1 = selector(add,
1072                selector  = ('add%s:'%(Name,)).encode('latin'),
1073                signature = b'v@:@',
1074        )
1075        add1.isHidden = True
1076        instance_methods.add(add1)
1077
1078        add2 = selector(add,
1079                selector  = ('add%sObject:'%(Name,)).encode('latin1'),
1080                signature = b'v@:@',
1081        )
1082        add2.isHidden = True
1083        instance_methods.add(add2)
1084
1085        remove1 = selector(remove,
1086                selector  = ('remove%s:'%(Name,)).encode('latin1'),
1087                signature = b'v@:@',
1088        )
1089        remove1.isHidden = True
1090        instance_methods.add(remove1)
1091
1092        remove2 = selector(remove,
1093                selector  = ('remove%sObject:'%(Name,)).encode('latin'),
1094                signature = b'v@:@',
1095        )
1096        remove2.isHidden = True
1097        instance_methods.add(remove2)
1098
1099
1100NSMutableDictionary = lookUpClass('NSMutableDictionary')
1101
1102class dict_property (object_property):
1103    def __get__(self, object, owner):
1104        v = object_property.__get__(self, object, owner)
1105        if v is None:
1106            v = NSMutableDictionary.alloc().init()
1107            object_property.__set__(self, object, v)
1108        return object_property.__get__(self, object, owner)
1109
1110