How Do I Disable Alt-Drag Selection on an NSTextView? - cocoa

I do not know, and cannot find, the standard technical term for the drag-select functionality when the alt/option button is pressed over an NSTextView.
When alt is pressed, the crosshair appears and text can be selected/highlighted in columns/vertically, as in Xcode.
I would like to disable this functionality in my NSTextViews, how can I do that please?

Digging into the disassembly of AppKit quickly reveals a hook which tells NSTextView whether the ALT+drag interaction should be enabled. It's the private method called _allowsMultipleTextSelectionByMouse. Altering its behavior does the trick but it is achievable only via private API. In this case, it should be pretty safe to do so.
There are following two ways for altering the aforementioned method:
Approach 1: NSUserDefaults
The method internally accesses the NSProhibitMultipleTextSelectionByMouse of NSUserDefaults. You can control the boolean value for this key on the app level. The downsides of this approach are:
It can possibly be reenabled by the users of your app (e.g. via the defaults command line tool).
It affects all instances of NSTextView in your app even those you don't necessarily want to alter.
Approach 2: Swizzling
The approach I decided to go with is a simple swizzle of this method in my subclass of NSTextView.
internal final class MyTextView: NSTextView {
internal static func initializeClass() {
try? MyTextView.swizzle(
Selector(("_allowsMultipleTextSelectionByMouse")),
with: #selector(getter: swizzled_allowsMultipleTextSelectionByMouse)
)
}
#objc dynamic internal var swizzled_allowsMultipleTextSelectionByMouse: Bool {
return false
}
}
And somewhere in the app bootstrapping code like the main function you have to trigger the class initialization code by MyTextView.initializeClass().
The code snippet above uses my own wrapper around the swizzling API but there are surely some libraries out there to use or you can follow the advices from this article by PSPDFKit.

Related

Make "Close All" (cmd+option+W) apply only to one type of document windows

I have an application that manages different types of NSDocument subclasses (along with matching NSWindow subclasses).
For instance, it's possible that the app has one window of type A open, and two windows of type B.
Now, if a window of type B is active, and the user chooses "Close All" or hits cmd+option+W, all my app's windows are sent the close message.
But I only want all of the active window type's windows closed instead, i.e. only the two type B, not the type A window. How do I accomplish this?
I currently have no explicit menu entry for "Close All". Instead, macOS provides that automagically. If there perhaps a way to intercept a "closeAll" message? Can't find one, though.
AppKit will add the Close All menu item if there isn't one. Add an alternate menu item item with key equivalent cmd+option+W below the Close menu and connect it to your own action method.
You might succeed with overriding your document's canClose(withDelegate:,shouldClose:,contextInfo:) to return whether a document should be closed.
If this doesn't behave the way you want, you can create a subclass of NSDocumentController (if you don't have one already). Details on how to do that vary, but usually you have main (menu) XIB or main Storyboard, which has a "Document Controller" object: set its class to your custom class.
Then override closeAllDocuments(withDelegate:,didCloseAllSelector:,contextInfo:) and implement your custom logic.
Note that you should detect whether your app is about to quit and then really do close all you documents (unless you really want to prevent the app quit, e.g. because a document is dirty).
After some digging I figured out where the auto-generated "Close All" menu item sends its action to: To the closeAll: selector of the application target:
Thus my solution is to subclass NSApplication and implement the handler there, which then simply closes all windows that are of the same type (this assumes that I use specific subclasses for my different types of windows):
- (IBAction)closeAll:(id)sender {
Class windowClass = self.keyWindow.class;
for (NSWindow *w in self.windows) {
if (windowClass == w.class) {
[w performClose:sender];
}
}
}
Caution: If you adopt this pattern be aware that:
The closeAll: selector is not documented nor mentioned in the header files, meaning that Apple might feel free to change in a future SDK, though I find that unlikely. It will probably not break anything if that happens, but instead your custom handler won't be called any more.
The code simply tells all windows to close, ignoring the fact that one might reject to be closed, e.g. by user interaction. In that case you may want to stop the loop instead of continuing to close more windows (though I know of no easy way to accomplish that).

Cocoa Data Binding - One view object bound to two 'enabled' properties

Using Cocoa Data Binding I have bound a button's 'enabled' property to two separate 'Model Key Paths' (Enabled and Enabled2).
The idea is that the button will become enabled if either of these properties is true.
Unfortunately, it only works if both become true.
Can anyone help me change this logic from AND to OR?
Cool question-- I'm going to make a shot in the dark, I can't confirm it for you until tomorrow, but try this maybe?
Create a new property, enabledOrEnabled2 (maybe with a better name).
Override the getter (-(BOOL)enabledOrEnabled2) for this property,
to just return (Enabled || Enabled2)
Set the Key Path to be enabledOrEnabled2
I'm a little skeptical if this will work because Cocoa Bindings work by using KVC and KVO. So atm, your view controller is observing your Enabled and Enabled2 properties. What happens during runtime is that Cocoa will override your properties w/ their own KVO properties that look and act just like you think they would. Except the setters have been overridden, to send out notifications to observers.
I think the problem with my solution is that -(void)setEnabledOrEnabled2 will never be called, because you will only be setting your Enabled and Enabled2 properties. Therefore, the Cocoa overridden -(void)setEnabledOrEnabled2 will never notify your observing view controller
EDIT: Just read #stevesilva's comment, and didn't even know that dependent keys was a thing. Looks like you could definitely implement it this way if you wanted to
Actually, a first-thought hack would be to switch from overriding the getter (like I first recommended), and instead override the setters: -(void)setEnabled and -(void)setEnabled2, and add a line, something like: _enabledOrEnabled2 = _Enabled || _Enabled2. That way anytime you set your two BOOL properties, it will update enabledOrEnabled2, which will then send a notification out to it's observer, your view controller.
But now that I've written that out, I was wondering about the second part of Cocoa Bindings, KVC to modify the model whenever the view sees changes.
And because you are actually using Bindings on an Enabled state-- I actually don't think you should be using Bindings. This is because the view can't actually be modified (i.e, you can't modify the enabled state of a button). So you wouldn't ever need to use KVC to change enabledOrEnabled2, you only need your button to observe BOOLs to know if it should be enabled or not.
So scrap everything I said thusfar--
What you should do is still modify the setters ( -(void)setEnabled and -(void)setEnabled2 ), and before setting the ivar you should add a line:
[self.button setEnabled:(_Enabled || _Enabled2)];
That should do the trick for ya :)
Sorry for rambling a bit, I feel like the info I wrote initially may be helpful so I decided not to delete it

Calling `print` inside NSView opens print dialog

This is bizarre. I have a simple storyboard placeholder with GridView for the class name attribute.
class GridView: NSView {
required init?(coder: NSCoder) {
super.init(coder: coder)
print("coder: \(coder)")
}
override func drawRect(dirtyRect: NSRect) {
let rect = NSBezierPath(rect: dirtyRect)
NSColor.redColor().setFill()
rect.fill()
}
}
This worked as expected with just drawRect implemented, but after I added the initialiser it started opening the print dialog every time I run the app.
Why does this happen and how can I properly reimplement the storyboard initialiser for a custom view?
Calling print() does something different as it should - more precisely: something different as you would expect. It calls NSView's print(sender: AnyObject?) instead of the logging print. You could consider this as a bug or at least as quite unexpected behavior since the Swift.print(...) is generally much more used.
This action method opens the Print panel, and if the user chooses an option other than canceling, prints the receiver and all its subviews to the device specified in the Print panel.
Take a look at this post in the apple dev forum.
In fact it is not a bug since calling the print which is "closer" in the current context is certainly the correct way. Calling the parent's print is a lot more reasonable than calling some arbitrary other print. Only the fact that you normally use the other print is the confusing point here since in general you do not worry in what scope the logging print is located - it just works. If you think the other way around and would want to use the printing print of your parent it would be a lot more confusing having to explicitly state that you want to use the parents print and not the Swift.print(...).
The only "solution" would be to use different names for the two functions which is probably not going to happen.
The reason it's calling the print dialog is that Swift 2 apparently has two methods with the same signature.
This is a pretty old thread, but I must add here that you can write:
Swift.print("something")
and you would be using the "log print" function instead of view's one.

How do I test if my custom callback is set as the current procedure for a given window?

We have a kiosk mode application for Windows Mobile 5 that was going strong for a year at least in production. It uses window subclassing through the SetWindowLong windows API function to override the behavior of the taskbar to prevent users from ever leaving our application or other explicitly allowed applications.
My callback overrides the handling of a few window messages and calls the default handler for other messages. This is done by storing the previous function pointer returned by SetWindowLong, and using it inside of the new function by calling CallWindowProc on it.
Then we had to update the application to be compatible with Windows Mobile 6.5.3, and started having a bunch of problems. I followed this article to disable the bottom menu buttons which are new on 6.5. On a single application, it works fine and the menu respects my callback. As soon as the user opens up another application through ours, that window seems to be recreated, and I had to devise a mechanism to detect foreground window changes and then "resubclass" the window again.
In my code, I don't have control of when exactly this window is recreated, so my first attempt was to use the GetWindowLong function to obtain the current callback address and test it against my own function address. I learned the hard way that I can't just compare the values like that, since it does not always return the function pointer.
Because I'm not able to test if my method is the current handler that way, I end up sometimes setting my method as the handler, and the previous handler is also my own method (in this case, the window was not recreated and thus already had my method set). This results in an infinite loop, since my callback ends up calling itself indefinitely.
How can I know if my custom function is the one being used by a certain window, so that I can avoid this infinite recursion?
Don't use SetWindowLong(GWL_WNDPROC) to subclass a window. Use SetWindowSubClass() instead (like Raymond Chen said):
Subclassing Controls
Safer subclassing
Amongst providing safer subclassing semantics (such as allowing multiple subclasses at a time), it also allows you to associate user-defined data with each subclass. So you could use GetWindowSubclass() to check if you have already subclassed the window or not.
Or, you could simply keep track of whether or not you have already subclassed the window. A simple boolean variable will suffice. Once your subclass is in place, you MUST remove the subclass before the window is fully destroyed. So, for instance, when the subclass receives the WM_NCDESTROY message, you can remove the subclass and clear your boolean at the same time, and then the next time you see the window again your boolean will tell you that you need to subclass the window.
Send the custom message to window. Handle this message in your custom function to return some value that will indicate that it was your custom handler.. and so on.
if(SendMessage(hwnd, mymsg, 0, 0) != myvalue)
;// It's not your handler

CGWindowID from AXUIElement

I'm trying to automate a foreign OSX application using the accessibility API. Some of
the state of the application isn't available through the API, so I acquire it through
screen scraping. To do this, I need to get CGWindowID for an accessibility object with
a 'Window Role'.
Is there any direct way of acquiring CGWindowID of a 'Window Role' accessibility object?
I can get it heuristically, by matching various attributes of the window, such as
the size, title and location, but this is really hacky, and I'd feel better if my
application would also support the corner cases, even if they are unlikely.
There is a function from at least Leopard and up (and still around as of 10.7.3):
extern "C" AXError _AXUIElementGetWindow(AXUIElementRef, CGWindowID* out);
Usual caveats about using something like this apply though. It may change as soon as the next OS update!
There’s no way to do that; the accessibility hierarchy is completely decoupled from the actual window/view hierarchy. I think your matching will work best.
The correct current declaration in a Swift Bridging Header is:
#import <AppKit/AppKit.h>
AXError _AXUIElementGetWindow(AXUIElementRef element, uint32_t *identifier);
As used here: https://github.com/rxhanson/Rectangle/blob/master/Rectangle/Rectangle-Bridging-Header.h

Resources