pyobjc icon indicating copy to clipboard operation
pyobjc copied to clipboard

Ability to override method on object instance

Open ktprograms opened this issue 3 years ago • 2 comments

Is your feature request related to a problem? Please describe. The project I am working on uses PyQt for its GUI. I want to make acceptsFirstMouse_ return False (it currently doesn't deep inside PyQt platform-specific code). Due to the architecture, I can't figure out how the "normal" way of subclassing NSView would work since the view is already initialized by PyQt, so I'm trying to set a function for the acceptsFirstMouse_ instance "variable".

Here's the code I'm using:

def acceptsFirstMouse_(self, event):
    return False

from objc import objc_object
from AppKit import NSView
view = objc_object(c_void_p=c_void_p(int(self.winId())))
view.acceptsFirstMouse_ = acceptsFirstMouse_.__get__(view, NSView)

I'm getting this error when running:

AttributeError: 'QNSView' object attribute 'acceptsFirstMouse_' is read-only

Describe the solution you'd like Ability to override methods in this way without subclassing.

Describe alternatives you've considered I can't think of any. Is it possible to use a subclass of NSView somehow with PyQt (I'd like the whole window to have acceptsFirstMouse_ as False).

ktprograms avatar Jul 30 '22 02:07 ktprograms

It is not possible to implement this. The method (acceptsFirstMouse:) is called in Objective-C, with method resolution in the Objective-C runtime. That method resolution only looks at methods in the class and does not know about (Python) instance attributes.

In theory it could be possible to implement a hack for this, but that would affect all instances of the class and would be fragile at best. I won't implement this.

In your case Qt's WindowTransparentForInput flag might help, although that affects both mouse and keyboard input.

In general it is possible to override a method on the class using something like the code below, although that likely won't work in your case:

QNSView = objc.lookUpClass("QNSView")

# Store a reference to the current method implementation:
orig_imp = QNSView. instanceMethodForSelector_(b"acceptsFirstMouse:")

# New implementation:
def acceptsFirstMouse_(self, event):
     if hasattr(self, "overrideAcceptsFirstMouse_"):
         return self.overrideAcceptsFirstMouse_(event)

     # Fall back to the original implementation
     return orig_imp(self, event)

# Replace method on the class:
QNSView. acceptsFirstMouse_ = acceptsFirstMouse_

This doesn't work because instances of QNSView should not have a __dict__ attribute, meaning it is not possible to add the overrideAcceptsFirstMouse_ attribute. Adding a __dict__ for arbitrary Objective-C objects is not possible, this would require adding instance attributes to the Objective-C class to do this reliably.

The pattern could work if you have some other way to check if self is a widget for which you want to override the return value.

ronaldoussoren avatar Jul 30 '22 20:07 ronaldoussoren

Thanks for the detailed explanation.

I'll just have to wait then, as according to this it looks like it'll be possible in QT 6.5.

ktprograms avatar Jul 31 '22 00:07 ktprograms