Esempio n. 1
0
class ABPerson(objc.Category(AddressBook.ABPerson)):
    # Pull first and last name, organization, and record flags
    # If the entry is a company, display the organization name instead
    def displayName(self):
        firstName = self.valueForProperty_(AddressBook.kABFirstNameProperty)
        lastName = self.valueForProperty_(AddressBook.kABLastNameProperty)
        companyName = self.valueForProperty_(AddressBook.kABOrganizationProperty)
        flags = self.valueForProperty_(AddressBook.kABPersonFlags)
        if flags is None:
            flags = 0

        if (flags & AddressBook.kABShowAsMask) == AddressBook.kABShowAsCompany:
            if len(companyName):
                return companyName

        lastNameFirst = (
            flags & AddressBook.kABNameOrderingMask
        ) == AddressBook.kABLastNameFirst
        hasFirstName = firstName is not None
        hasLastName = lastName is not None

        if hasLastName and hasFirstName:
            if lastNameFirst:
                return Cocoa.NSString.stringWithString_("%s %s" % (lastName, firstName))
            else:
                return Cocoa.NSString.stringWithString_("%s %s" % (firstName, lastName))

        if hasLastName:
            return lastName

        return firstName

    def compareDisplayNames_(self, person):
        return self.displayName().localizedCaseInsensitiveCompare_(person.displayName())
    class NSAnimationContext(objc.Category(NSAnimationContext)):
        @classmethod
        def __enter__(cls):
            cls.beginGrouping()

        @classmethod
        def __exit__(cls, exc_type, exc_value, exc_tb):
            cls.endGrouping()
            return False
Esempio n. 3
0
        class Methods(objc.Category(Methods)):
            def categoryMethod(self):
                return True

            def categoryMethod2(self):
                return False

            def anotherClassMethod(self):
                return "hello"

            anotherClassMethod = classmethod(anotherClassMethod)
Esempio n. 4
0
    class SDL_QuartzWindow(objc.Category(SDL_QuartzWindow)):
        def canBecomeKeyWindow(self):
            return True

        def canBecomeMainWindow(self):
            return True

        def acceptsFirstResponder(self):
            return True

        def becomeFirstResponder(self):
            return True

        def resignFirstResponder(self):
            return True
Esempio n. 5
0
 class NSObjectCat(objc.Category(NSObjectCat)):
     """
     This is a docstring
     """
     def withDocStringMethod(self):
         return 42
Esempio n. 6
0
 class BaseClassRedef(objc.Category(BaseClassRedef)):
     def foo(self):
         return 2
Esempio n. 7
0
 class list(objc.Category(list)):
     pass
Esempio n. 8
0
 class NSFoo(objc.Category(NSObject)):
     pass
Esempio n. 9
0
 class NSObject(objc.Category(NSObject), object):
     pass
Esempio n. 10
0
class NSGraphicsContext(objc.Category(NSGraphicsContext)):
    @classmethod
    def savedGraphicsState(self):
        return _ctxHelper()
Esempio n. 11
0
class NSColor(objc.Category(Cocoa.NSColor)):
    @classmethod
    def randomColor(self):
        return Cocoa.NSColor.colorWithCalibratedRed_green_blue_alpha_(
            random.uniform(0, 1), random.uniform(0, 1), random.uniform(0, 1),
            1)
Esempio n. 12
0
def Category(classname):
    return objc.Category(objc.lookUpClass(classname))
Esempio n. 13
0
 class NSObjectCat2(objc.Category(NSObjectCat2)):
     @classmethod
     def aClassMethod(cls):
         return 1
Esempio n. 14
0
class EditingMessageWebView(objc.Category(objc.runtime.EditingMessageWebView)):
    @swizzle(objc.runtime.EditingMessageWebView, 'decreaseIndentation:')
    def decreaseIndentation_(self, original, sender):
        # Call the original Mail.app decreaseIndentation: selector for rich
        # text messages.

        if self.contentElement().className() != 'ApplePlainTextBody':
            return original(self, sender)

        # If we have a selection, remove indentation on all lines which
        # overlap it. Otherwise, remove indentation on the line containing
        # the cursor. Combine the operation into a single undo group for UI
        # purposes.

        self.undoManager().beginUndoGrouping()
        affinity = self.selectionAffinity()
        selection = self.selectedDOMRange()
        if self.selectedRange().length == 0:
            self.moveToBeginningOfParagraph_(None)
            self.moveToEndOfParagraphAndModifySelection_(None)
            if not self.selectedText().strip():
                if self.selectedText():
                    self.deleteBackward_(None)
            elif self.selectedText()[:self._indentWidth].isspace():
                self.moveToBeginningOfParagraph_(None)
                for _ in xrange(self._indentWidth):
                    self.deleteForward_(None)
        else:
            last = self.selectedRange().length
            self.moveToEndOfDocumentAndModifySelection_(None)
            last = self.selectedRange().length - last
            while self.selectedRange().length > last:
                self.moveToBeginningOfParagraph_(None)
                self.moveToEndOfParagraphAndModifySelection_(None)
                if not self.selectedText().strip():
                    if self.selectedText():
                        self.deleteBackward_(None)
                elif self.selectedText()[:self._indentWidth].isspace():
                    self.moveToBeginningOfParagraph_(None)
                    for _ in xrange(self._indentWidth):
                        self.deleteForward_(None)
                self.moveToEndOfParagraph_(None)
                self.moveForward_(None)
                self.moveToEndOfDocumentAndModifySelection_(None)
            self.moveBackward_(None)
        self.setSelectedDOMRange_affinity_(selection, affinity)
        self.undoManager().endUndoGrouping()

    def fillParagraph(self):
        # Note the quote level of the current paragraph and the location of
        # the end of the message to avoid attempts to move beyond it.

        self.moveToEndOfDocumentAndModifySelection_(None)
        last = self.selectedRange().location + self.selectedRange().length

        self.moveToBeginningOfParagraph_(None)
        self.selectParagraph_(None)
        level = self.quoteLevelAtStartOfSelection()

        # If we are on a blank line, move down to the start of the next
        # paragraph block and finish.

        if not self.selectedText().strip():
            while True:
                self.moveDown_(None)
                self.selectParagraph_(None)
                location = self.selectedRange().location
                if location + self.selectedRange().length >= last:
                    self.moveToEndOfParagraph_(None)
                    return
                if self.selectedText().strip():
                    self.moveToBeginningOfParagraph_(None)
                    return

        # Otherwise move to the start of this paragraph block, working
        # upward until we hit the start of the message, a blank line or a
        # change in quote level.

        while self.selectedRange().location > 0:
            self.moveUp_(None)
            if self.quoteLevelAtStartOfSelection() != level:
                self.moveDown_(None)
                break
            self.selectParagraph_(None)
            if not self.selectedText().strip():
                self.moveDown_(None)
                break
        self.moveToBeginningOfParagraph_(None)

        # Insert a temporary placeholder space character to avoid any
        # assumptions about Mail.app's strange and somewhat unpredictable
        # handling of newlines between block elements.

        self.insertText_(' ')
        self.moveToEndOfParagraphAndModifySelection_(None)

        # Now extend the selection forward line-by-line until we hit a blank
        # line, a change in quote level or the end of the message.

        affinity = self.selectionAffinity()
        selection = self.selectedDOMRange()
        while True:
            location = self.selectedRange().location
            if location + self.selectedRange().length >= last:
                break
            self.moveDown_(None)
            self.moveToEndOfParagraphAndModifySelection_(None)
            if self.quoteLevelAtStartOfSelection() != level:
                break
            if not self.selectedText().strip():
                break
            selection.setEnd__(self.selectedDOMRange().endContainer(),
                               self.selectedDOMRange().endOffset())
        self.setSelectedDOMRange_affinity_(selection, affinity)

        # Finally, extend the selection forward to encompass any blank lines
        # following the paragraph block, regardless of quote level. Store
        # the minimum quote level of this paragraph block and the next.

        while True:
            location = self.selectedRange().location
            if location + self.selectedRange().length >= last:
                minimum = 0
                break
            self.moveDown_(None)
            self.moveToBeginningOfParagraph_(None)
            self.moveToEndOfParagraphAndModifySelection_(None)
            if self.selectedText().strip():
                minimum = min(self.quoteLevelAtStartOfSelection(), level)
                break
            selection.setEnd__(self.selectedDOMRange().endContainer(),
                               self.selectedDOMRange().endOffset())
        self.setSelectedDOMRange_affinity_(selection, affinity)

        # Re-fill the text allowing for quote level and retaining block
        # indentation, then insert it to replace the selection.

        text = fill(self.selectedText().expandtabs(), level) + '\n'
        self.insertTextWithoutReplacement_(text)

        # Reduce the quote level of the trailing blank line if necessary,
        # then remove the placeholder character and position the cursor at
        # the start of the next paragraph block.

        item = NSMenuItem.alloc().initWithTitle_action_keyEquivalent_(
            'Decrease', 'changeQuoteLevel:', '')
        item.setTag_(-1)
        for _ in xrange(level - minimum):
            self.changeQuoteLevel_(item)

        selection = self.selectedDOMRange()
        for _ in xrange(text.count('\n')):
            self.moveUp_(None)
            self.moveToBeginningOfParagraph_(None)
        self.deleteForward_(None)
        self.setSelectedDOMRange_affinity_(selection, affinity)
        self.moveForward_(None)

    def fillText(self):
        # MailWrap only works correctly on plain text messages, so ignore
        # any requests to format paragraphs in rich-text/HTML messages.

        if self.contentElement().className() != 'ApplePlainTextBody':
            return

        # If we have a selection, format all paragraph blocks which overlap
        # it. Otherwise, format the paragraph block containing the cursor.
        # Combine the operation into a single undo group for UI purposes.

        self.undoManager().beginUndoGrouping()
        if self.selectedRange().length == 0:
            self.fillParagraph()
            self.moveToEndOfDocumentAndModifySelection_(None)
        else:
            last = self.selectedRange().length
            self.moveToEndOfDocumentAndModifySelection_(None)
            last = self.selectedRange().length - last
            while self.selectedRange().length > last:
                self.fillParagraph()
                self.moveToEndOfDocumentAndModifySelection_(None)
        if self.selectedRange().length > 0:
            self.moveBackward_(None)
        else:
            self.deleteBackward_(None)
        self.undoManager().endUndoGrouping()

    @swizzle(objc.runtime.EditingMessageWebView, 'increaseIndentation:')
    def increaseIndentation_(self, original, sender):
        # Call the original Mail.app increaseIndentation: selector for rich
        # text messages.

        if self.contentElement().className() != 'ApplePlainTextBody':
            return original(self, sender)

        # If we have a selection, indent all lines which overlap it.
        # Otherwise, indent the line containing the cursor. Combine the
        # operation into a single undo group for UI purposes.

        self.undoManager().beginUndoGrouping()
        affinity = self.selectionAffinity()
        selection = self.selectedDOMRange()
        if self.selectedRange().length == 0:
            self.moveToBeginningOfParagraph_(None)
            self.insertText_(' ' * self._indentWidth)
            self.moveToBeginningOfParagraph_(None)
            self.moveToEndOfParagraphAndModifySelection_(None)
            if not self.selectedText().strip():
                self.deleteBackward_(None)
        else:
            last = self.selectedRange().length
            self.moveToEndOfDocumentAndModifySelection_(None)
            last = self.selectedRange().length - last
            while self.selectedRange().length > last:
                self.moveToBeginningOfParagraph_(None)
                self.insertText_(' ' * self._indentWidth)
                self.moveToBeginningOfParagraph_(None)
                self.moveToEndOfParagraphAndModifySelection_(None)
                if self.selectedText().strip():
                    self.moveForward_(None)
                else:
                    self.deleteBackward_(None)
                self.moveForward_(None)
                self.moveToEndOfDocumentAndModifySelection_(None)
        self.moveBackward_(None)
        self.setSelectedDOMRange_affinity_(selection, affinity)
        self.undoManager().endUndoGrouping()

    def insertTextWithoutReplacement_(self, text):
        if self.isAutomaticTextReplacementEnabled():
            self.setAutomaticTextReplacementEnabled_(False)
            self.insertText_(text)
            self.setAutomaticTextReplacementEnabled_(True)
        else:
            self.insertText_(text)

    def quoteLevelAtStartOfSelection(self):
        return self.selectedDOMRange().startContainer().quoteLevel()

    def selectedText(self):
        return self.selectedDOMRange().stringValue() or ''

    def wrapLine(self):
        # Select the current line, and break it into lines of the correct
        # width, allowing for quoting overhead and retaining the
        # indentation.

        self.moveToBeginningOfParagraph_(None)
        self.moveToEndOfParagraphAndModifySelection_(None)

        # Wrap the line allowing for quote level and retaining indentation,
        # then insert it to replace the selection.

        level = self.quoteLevelAtStartOfSelection()
        text = fill(self.selectedText().expandtabs(), level)

        # Insert the wrapped text to replace the selection, temporarily
        # disabling text replacement to avoid unintended substitutions.
        # Finally, position the cursor at the start of the next line.

        self.insertTextWithoutReplacement_(text)
        self.moveForward_(None)

    def wrapText(self):
        # MailWrap only works correctly on plain text messages, so ignore
        # any requests to format paragraphs in rich-text/HTML messages.

        if self.contentElement().className() != 'ApplePlainTextBody':
            return

        # If we have a selection, wrap all lines which overlap it.
        # Otherwise, wrap the line containing the cursor. Combine the
        # operation into a single undo group for UI purposes.

        self.undoManager().beginUndoGrouping()
        if self.selectedRange().length == 0:
            self.wrapLine()
        else:
            last = self.selectedRange().length
            self.moveToEndOfDocumentAndModifySelection_(None)
            last = self.selectedRange().length - last
            while self.selectedRange().length > last:
                self.wrapLine()
                self.moveToEndOfDocumentAndModifySelection_(None)
            if self.selectedRange().length > 0:
                self.moveBackward_(None)
        self.undoManager().endUndoGrouping()
Esempio n. 15
0
class DocumentEditor(objc.Category(objc.runtime.DocumentEditor)):
    @swizzle(objc.runtime.DocumentEditor, 'finishLoadingEditor')
    def finishLoadingEditor(self, original):
        # Let Mail.app complete its own preparation of the new message and
        # the document editor before we do our own cleanups.

        original(self)

        if self.messageType() in [1, 2]:
            # We only modify messages resulting from a reply or reply-to-all
            # action. Begin by stripping stray blank lines at the beginning
            # of the message body and around cited text.

            view = self.webView()
            document = view.mainFrame().DOMDocument()

            view.contentElement().removeStrayLinefeeds()
            blockquotes = document.getElementsByTagName_('BLOCKQUOTE')
            for index in xrange(blockquotes.length()):
                if blockquotes.item_(index):
                    blockquotes.item_(index).removeStrayLinefeeds()

            # If we are configured to fix the attribution string, remove the
            # 'On DATE, at TIME, ' prefix from the first line, which will
            # always be the attribution in an unmodified reply. Also ensure
            # that the attribution and the blank line following it are not
            # incorrectly quoted by a bug in Mail.app 8.0.

            if self._fixAttribution:
                view.moveToBeginningOfDocument_(None)
                view.moveToEndOfParagraphAndModifySelection_(None)
                view.moveForwardAndModifySelection_(None)
                item = NSMenuItem.alloc().initWithTitle_action_keyEquivalent_(
                    'Decrease', 'changeQuoteLevel:', '')
                item.setTag_(-1)
                view.changeQuoteLevel_(item)

                attribution = view.selectedDOMRange().stringValue()
                attribution = attribution.rsplit(',', 1)[-1].lstrip()
                if view.isAutomaticTextReplacementEnabled():
                    view.setAutomaticTextReplacementEnabled_(False)
                    view.insertText_(attribution)
                    view.setAutomaticTextReplacementEnabled_(True)
                else:
                    view.insertText_(attribution)

            if self._placeCursorAtEnd:
                # Place the cursor at the end of the quoted text but before
                # the signature if present, separated from the quoted text by
                # a blank line.

                signature = document.getElementById_('AppleMailSignature')
                if signature:
                    range = document.createRange()
                    range.selectNode_(signature)
                    view.setSelectedDOMRange_affinity_(range, 0)
                    view.moveUp_(None)
                else:
                    view.moveToEndOfDocument_(None)

                view.insertParagraphSeparator_(None)
                view.insertParagraphSeparator_(None)
            else:
                view.moveToBeginningOfDocument_(None)
                view.insertParagraphSeparator_(None)
                view.insertParagraphSeparator_(None)
                view.moveToBeginningOfDocument_(None)

            view.undoManager().removeAllActions()
            self.backEnd().setHasChanges_(False)
Esempio n. 16
0
 class list(objc.Category(list)):  # noqa: A001
     pass
class NSURLRequest(objc.Category(NSURLRequest)):
    @classmethod
    def allowsAnyHTTPSCertificateForHost_(cls, host):
        # Use setting?
        settings = SIPSimpleSettings()
        return not settings.tls.verify_server
Esempio n. 18
0
        class NSObjectCat3(objc.Category(NSObjectCat3)):
            classValue = "aap"

            def getClassValue(self):
                return self.classValue
Esempio n. 19
0
 class Methods(objc.Category(Methods)):
     outlet = objc.IBOutlet()
Esempio n. 20
0
class NSEvent(objc.Category(Cocoa.NSEvent)):
    def locationInView_(self, view):
        return view.convertPoint_fromView_(self.locationInWindow(), None)
Esempio n. 21
0
        class NSObject(objc.Category(NSObject)):
            def myCategoryMethod(self):
                return True

            def myCategoryMethod2(self):
                return False
Esempio n. 22
0
class NSObject(objc.Category(NSObject)):
    @objc.namedSelector(b"_pyobjc_performOnThread:")
    def _pyobjc_performOnThread_(self, callinfo):
        try:
            sel, arg = callinfo
            # XXX: PyObjC's methodForSelector implementation doesn't work
            # with Python methods, using getattr instead
            # m = self.methodForSelector_(sel)
            m = getattr(self, _str(sel))
            m(arg)
        except:
            import traceback

            traceback.print_exc(file=sys.stderr)

    @objc.namedSelector(b"_pyobjc_performOnThreadWithResult:")
    def _pyobjc_performOnThreadWithResult_(self, callinfo):
        try:
            sel, arg, result = callinfo
            # m = self.methodForSelector_(sel)
            m = getattr(self, _str(sel))
            r = m(arg)
            result.append((True, r))
        except:
            result.append((False, sys.exc_info()))

    if hasattr(NSObject, "performSelector_onThread_withObject_waitUntilDone_"):

        @objc.namedSelector(
            b"pyobjc_performSelector:onThread:withObject:waitUntilDone:"
        )
        def pyobjc_performSelector_onThread_withObject_waitUntilDone_(
            self, aSelector, thread, arg, wait
        ):
            """
            A version of performSelector:onThread:withObject:waitUntilDone: that
            will log exceptions in the called method (instead of aborting the
            NSRunLoop on the other thread).
            """
            self.performSelector_onThread_withObject_waitUntilDone_(
                b"_pyobjc_performOnThread:", thread, (aSelector, arg), wait
            )

        @objc.namedSelector(
            b"pyobjc_performSelector:onThread:withObject:waitUntilDone:modes:"
        )
        def pyobjc_performSelector_onThread_withObject_waitUntilDone_modes_(
            self, aSelector, thread, arg, wait, modes
        ):
            """
            A version of performSelector:onThread:withObject:waitUntilDone:modes:
            that will log exceptions in the called method (instead of aborting the
            NSRunLoop on the other thread).
            """
            self.performSelector_onThread_withObject_waitUntilDone_modes_(
                b"_pyobjc_performOnThread:", thread, (aSelector, arg), wait, modes
            )

    @objc.namedSelector(b"pyobjc_performSelector:withObject:afterDelay:")
    def pyobjc_performSelector_withObject_afterDelay_(self, aSelector, arg, delay):
        """
        A version of performSelector:withObject:afterDelay:
        that will log exceptions in the called method (instead of aborting the
        NSRunLoop).
        """
        self.performSelector_withObject_afterDelay_(
            b"_pyobjc_performOnThread:", (aSelector, arg), delay
        )

    @objc.namedSelector(b"pyobjc_performSelector:withObject:afterDelay:inModes:")
    def pyobjc_performSelector_withObject_afterDelay_inModes_(
        self, aSelector, arg, delay, modes
    ):
        """
        A version of performSelector:withObject:afterDelay:inModes:
        that will log exceptions in the called method (instead of aborting the
        NSRunLoop).
        """
        self.performSelector_withObject_afterDelay_inModes_(
            b"_pyobjc_performOnThread:", (aSelector, arg), delay, modes
        )

    if hasattr(NSObject, "performSelectorInBackground_withObject_"):

        @objc.namedSelector(b"pyobjc_performSelectorInBackground:withObject:")
        def pyobjc_performSelectorInBackground_withObject_(self, aSelector, arg):
            """
            A version of performSelectorInBackground:withObject:
            that will log exceptions in the called method (instead of aborting the
            NSRunLoop).
            """
            self.performSelectorInBackground_withObject_(
                b"_pyobjc_performOnThread:", (aSelector, arg)
            )

    @objc.namedSelector(b"pyobjc_performSelectorOnMainThread:withObject:waitUntilDone:")
    def pyobjc_performSelectorOnMainThread_withObject_waitUntilDone_(
        self, aSelector, arg, wait
    ):
        """
        A version of performSelectorOnMainThread:withObject:waitUntilDone:
        that will log exceptions in the called method (instead of aborting the
        NSRunLoop in the main thread).
        """
        self.performSelectorOnMainThread_withObject_waitUntilDone_(
            b"_pyobjc_performOnThread:", (aSelector, arg), wait
        )

    @objc.namedSelector(
        b"pyobjc_performSelectorOnMainThread:withObject:waitUntilDone:modes:"
    )
    def pyobjc_performSelectorOnMainThread_withObject_waitUntilDone_modes_(
        self, aSelector, arg, wait, modes
    ):
        """
        A version of performSelectorOnMainThread:withObject:waitUntilDone:modes:
        that will log exceptions in the called method (instead of aborting the
        NSRunLoop in the main thread).
        """
        self.performSelectorOnMainThread_withObject_waitUntilDone_modes_(
            b"_pyobjc_performOnThread:", (aSelector, arg), wait, modes
        )

    # And some a some versions that return results

    @objc.namedSelector(b"pyobjc_performSelectorOnMainThread:withObject:modes:")
    def pyobjc_performSelectorOnMainThread_withObject_modes_(
        self, aSelector, arg, modes
    ):
        """
        Simular to performSelectorOnMainThread:withObject:waitUntilDone:modes:,
        but:

        - always waits until done
        - returns the return value of the called method
        - if the called method raises an exception, this will raise the same
           exception
        """
        result = []
        self.performSelectorOnMainThread_withObject_waitUntilDone_modes_(
            b"_pyobjc_performOnThreadWithResult:", (aSelector, arg, result), True, modes
        )
        isOK, result = result[0]

        if isOK:
            return result
        else:
            exc_type, exc_value, exc_trace = result
            _raise(exc_type, exc_value, exc_trace)

    @objc.namedSelector(b"pyobjc_performSelectorOnMainThread:withObject:")
    def pyobjc_performSelectorOnMainThread_withObject_(self, aSelector, arg):
        result = []
        self.performSelectorOnMainThread_withObject_waitUntilDone_(
            b"_pyobjc_performOnThreadWithResult:", (aSelector, arg, result), True
        )
        isOK, result = result[0]

        if isOK:
            return result
        else:
            exc_type, exc_value, exc_trace = result
            _raise(exc_type, exc_value, exc_trace)

    if hasattr(NSObject, "performSelector_onThread_withObject_waitUntilDone_"):
        # These methods require Leopard, don't define them if the
        # platform functionality isn't present.

        @objc.namedSelector(b"pyobjc_performSelector:onThread:withObject:modes:")
        def pyobjc_performSelector_onThread_withObject_modes_(
            self, aSelector, thread, arg, modes
        ):
            result = []
            self.performSelector_onThread_withObject_waitUntilDone_modes_(
                b"_pyobjc_performOnThreadWithResult:",
                thread,
                (aSelector, arg, result),
                True,
                modes,
            )
            isOK, result = result[0]

            if isOK:
                return result
            else:
                exc_type, exc_value, exc_trace = result
                _raise(exc_type, exc_value, exc_trace)

        @objc.namedSelector(b"pyobjc_performSelector:onThread:withObject:")
        def pyobjc_performSelector_onThread_withObject_(self, aSelector, thread, arg):
            result = []
            self.performSelector_onThread_withObject_waitUntilDone_(
                b"_pyobjc_performOnThreadWithResult:",
                thread,
                (aSelector, arg, result),
                True,
            )
            isOK, result = result[0]

            if isOK:
                return result
            else:
                exc_type, exc_value, exc_trace = result
                _raise(exc_type, exc_value, exc_trace)
Esempio n. 23
0
 class NSBundle(objc.Category(NS.Bundle)):
     @objc.typedSelector(NS.Bundle.bundleIdentifier.signature)
     def uitoolsBundleIdentifier(self):
         if self == NSBundle.mainBundle():
             return bundle_id
         return self.uitoolsBundleIdentifier()