How to handle NSTextView preferences (spelling and grammar, substitutions,...) - cocoa

The NSTextView class allows the user to dis-/enable features like "spelling while typing" with the context menu (right click). But when I use a NSTextView in my own app, those preferences are not saved automatically by the text view itself, which means that I have to save them separately - right?
Now I also want to allow the user to change those settings in my app preferences (like in TextEdit). What I do is to save the text view preferences in the user defaults, which means that every time the user changes the setting in the app preferences, I apply those settings and save them. It's pretty easy to accomplish that except the one case where the user changes the text view setting with the context menu and not through the app preferences.
My question now: How can I get notified when the settings of a NSTextView is changed, so I can save them?

I've done a project where I have subclassed NSTextView and I can easily catch any setting changes that have been made by the user.
So, for you to do this, simply create a new .h & .m file and declare it like this:
(in the .h file)
#interface BrutellaTextView : NSTextView
#end
(in the .m file)
#interface BrutellaTextView
- (void)setContinuousSpellCheckingEnabled:(BOOL)flag
{
// here I am just setting user defaults... you may choose
// to have some other place to save the settings
NSUserDefaults * userDefaults = [NSUserDefaults standardUserDefaults];
if(userDefaults)
{
[userDefaults setBool: flag forKey: #"continuousSpellCheckingEnabled"];
}
// and to get the functionality to actually happen,
// call the superclass
[super setContinuousSpellCheckingEnabled: flag];
}
#end
(and you can override other NSTextView methods to capture when other settings change, such as setRulerVisible:).
Now, when you are in your XIB file, make sure to set the CustomClass of your text view to be BrutellaTextView and you'll be all set!
There are no notifications that you can register to get NSTextView settings changes, so as far as I'm concerned, this is the best way to do what you're trying to do.
I hope this answer helps you out!

You could also use NSTextViews method
toggleAutomaticSpellingCorrection:
This method is only called, when the user changes the setting via menu/contextMenu and not if the programmer changes the setting programatically (e.g. temporarily disabling when inserting text).

Related

How to detach NSTextView's undo manager from NSDocument?

I have an NSDocument that is in a non-directly editable format, it's a description of something in XML. The NSWindow has a settings view controller associated with it which manipulates the data in the document just fine, undo works as expected, save, etc. Now also in the NSWindow is an NSTextView, which the user can enter some text into, but is not part of the content of the document, it's used only as temporary text. Of course I want to support undo for this text too, so I have the "Undo" checkmark enabled in Interface Builder for this NSTextView, and undo works just fine.
Now comes the rub: the NSDocument is getting marked as dirty when the NSTextView is modified. Because this is temporary text, I don't want the user to be nagged to save changes to the document, that really are not part of the document.
How do I detach the NSTextView from the responder chain leading up to the NSDocument's undo manager instance? Simply providing a new instance of NSUndoManager doesn't solve it, because it just goes up the responder chain to NSDocument as well.
extension InputViewController: NSTextViewDelegate {
func undoManager(for view: NSTextView) -> UndoManager? {
return myUndoManager
}
}
I'm pretty sure you'd need to override the undoManager property of the window's view controller, not the text field's delegate.
However, to simply make your document/window permanently non-editable all you need to do is override either the documentEdited property so it always returns false, or override updateChangeCount so it ignores requests to record changes.
Thanks to James Bucanek's comment about overriding updateChangeCount, I was able to do something like this in my NSDocument subclass:
override func updateChangeCount(_ change: NSDocument.ChangeType) {
if !suppressChangeCount {
super.updateChangeCount(change)
} else {
suppressChangeCount = false
}
}
Thus allowing undo/redo to work in my InputViewController keycode handler without dirtying the document.

NSDocument subclass instance apparently not in responder chain

I'm creating my first NSDocument based application. I'm able to create new documents, both from scratch and by importing legacy files.
This app will allow multiple windows per document, so I am overriding makeWindowControllers. This method is currently very simple:
- (void) makeWindowControllers
{
if (documentDatabase == nil) return;
DataSheetWindowController * dswc = [[DataSheetWindowController alloc] initWithDatabase:documentDatabase];
[self addWindowController: dswc];
}
The window appears as expected, however, the Save, Revert to Save, and other document enabled menus are disabled, as if the document was not in the responder chain.
As an experiment, I tried adding this method to my NSWindowController class:
- (void)saveDocument:(id)sender {
[[self document] saveDocument:sender];
}
With this method in place, the Save menu item is enabled, and selecting it causes the document's save methods to be invoked.
From reading the documentation and other questions on Stack Overflow, it's clear that something is wrong -- I should NOT have to put this method in the NSWindowController class. I'm sure I've overlooked something silly, but for the life of me I cannot figure out what it is, or any other mention of this problem here or elsewhere on the web.
Some additional information that may be useful -- in IB, the window's owner and delegate are set to the NSWindowController. I created a method to display the responder chain (see How to inspect the responder chain?) and the document was not listed. Here is the output of the responder chain (however, since NSDocument is not a subclass of NSResponder, I'm not sure if it is supposed to be listed anyway).
RESPONDER CHAIN:
<NSClipView: 0x102344350>
<NSScrollView: 0x102344480>
<NSView: 0x102345040>
<NSWindow: 0x10234e090>
Since the saveDocument method I put into the NSWindowController class does work, that indicates to me that the window controller does know that it is associated with the document.
So -- any thoughts as to why the document is behaving as if it is not in the responder chain?
Updated info: After setting up a new document, the initWithType method includes this temporary line to make sure that the document status is edited:
[self updateChangeCount:NSChangeDone];
I have verified that isDocumentEdited returns true.
I'm going to suggest that the solution is the one pointed to here:
https://stackoverflow.com/a/9349636/341994
In the nib containing the window that the window controller will be loading, the File's Owner proxy needs to be of the window controller's class (select the File's Owner proxy and examine the Identity inspector to confirm / configure that), and its window outlet must be hooked to the window and the window's delegate outlet must be hooked to the File's Owner proxy (select the File's Owner proxy and examine the Connections inspector to confirm that).

Prevent save prompt when closing NSWindow

I have a document-based Cocoa app that uses a secondary NSWindow for a preview mode (with shouldCloseDocument set to NO).
If the document is dirty (edited without saving) and I close the secondary NSWindow, a "Do you want to save the changes made to the document" prompt appears.
How can I avoid this prompt on the secondary NSWindow?
I couldn't find a way to do this. I expected to find a NSWindowDelegate or NSWindow method called before the save prompt but none of the obvious candidates (windowWillClose, close, performClose, windowShouldClose) are.
As a workaround, instead of setting NSWindowController.document I'm using a custom property to pass the document. With document set to NIL, the save prompt is not shown anymore.
specifically you can do this inside your viewController subclass:
make sure delegate is set from the window to the owner class:
//-------------------------------------------------------
- (void)windowControllerDidLoadNib:(NSWindowController *)aController
{
NSLog (#"windowControllerDidLoadNib");
aController.document = nil;
[super windowControllerDidLoadNib:aController];
............. etc.......

Cocoa screen saver config panel floating freely

I'm writing a screen saver using Cocoa's ScreenSaver API. It's compiled for 64-bit arch and I'm running it on Lion.
In order to enable configuration, I have added the following to the main view:
- (BOOL)hasConfigureSheet
{
return YES;
}
- (NSWindow*)configureSheet
{
if (configureSheet == nil) {
if (![NSBundle loadNibNamed: #"WTConfigureSheet" owner: self]) {
NSLog(#"Failed to load config sheet");
return nil;
}
}
ScreenSaverDefaults *defaults =
[ScreenSaverDefaults defaultsForModuleWithName: WTModuleName];
backgroundColorWell.color = [defaults objectForKey: #"BackgroundColor"];
lightLetterColorWell.color = [defaults objectForKey: #"LightLetterColor"];
darkLetterColorWell.color = [defaults objectForKey: #"DarkLetterColor"];
return configureSheet;
}
After installing the saver freshly, clicking "Options" makes the config sheet appear not as a sheet, but floating freely on the screen, without a border. Otherwise, it works correctly and disappears after being dismissed.
When I click "Options" a second time, the config sheet appears again, this time correctly as a sheet of the preferences window. It then immediately freezes, so that I can't click any of its controls.
Does anyone have an idea what causes this behavior?
I had the same problem as you today and it took me quite some time to figure this one out, so here's my solution:
I discovered that the NSWindow appears as soon as you call loadNibNamed:owner:. So there had to be some sort of mechanism to automatically open windows from nibs.
So I re-checked the nib and saw that there is an option called "Visible At Launch" on the attribute inspector pane which is checked by default.
The solution is very simple: just uncheck that checkbox and it works as expected.
I find it easy to overlook since you expect the window to open, but it actually opens twice (once automatically and a second time because System Preferences.app shows it as a sheet) which leads to the glitches.
Another problem that could happen, depending on how you defined the ivar / property on your class is that after the first close and re-open of the window it just freezes.
This is because per default the window releases itself when closed.
So be sure to also uncheck "Release When Closed" in interface builder.
For this code to work as written, you need to create an IBOutlet of type NSWindow* named configureSheet in your main view's header file, save that file so Interface Builder can see the change, then load WTConfigureSheet.xib in Interface Builder and connect up the toplevel window component to Files Owner -> configureSheet.

-shortcutRecorder:keyComboDidChange: isn’t executed

I am writing a Cocoa application and I'd like to implement a global hotkey function.
I implemented the ShortcutRecorder.framework from Waffle Software and I added a customView to my xib. Then I subclassed the CustomView to SRRecorderControl. Now I see the Recorder in my Window, but how can I get the KeyCombo and how can I react on that?
I implemented the keyComboDidChange method with no luck to get the keycode. What am I doing wrong?
Here is my code for getting the keycode:
- (void)shortcutRecorder:(SRRecorderControl *)aRecorder keyComboDidChange:(KeyCombo)newKeyCombo
{
if (aRecorder == shortcutRecorder)
{
NSLog{"We got a new Key Combo");
}
}
shortcutrecorder is my IBOutlet btw.
Do I have to implement a protocol or setDelegate:self or something like that?
Edited to add
Actually I have declared my shortcutRecorder outlet in my Preferences.h. Then in the Identity Inspector I put "Preferences" as Custom Class for Files Owner in and I connect the delegate to my Shortcut Recorder... but the keyComboDidChange is never called ... I don't understand why.
Let me explain the steps I took to get it working:
Create a window xib
The file's owner of this class is, in my case, PreferencesWindowController
Create a referencing outlet from the window to the file's owner, by right clicking the window and dragging it to the file's owner
Add the custom view to your window
You have to connect the delegate of the ShortcutRecorder to the "File's owner". To do this, right-click the SRRecorderControl and drag the delegate to the "File's owner" on your left.
After this: the ShortcutRecorder only records the hotkey and leaves it to you what to do with it. You need to use the PTHotKeyCenter (which is shipped with ShortcutRecorder) or you could implement the shortcut handling yourself.
The ShortcutRecorder contains a great demo which demonstrates the use of the ShortcutRecorder in combination with the PTHotKeyCenter. It works like this:
Listen to events from the ShortcutRecorder (which you already do, but without setting the delegate)
Check if the globalHotKey variable is set
If so, unload the previous hotkey
Init a new hotkey with the settings from the ShortcutRecorder
Set the target and action to actually capture the hotkey, once pressed
Save the hotkey to the shared center (from this moment on, the hotkey will work)
Little sample, from their source:
if (globalHotKey != nil)
{
[[PTHotKeyCenter sharedCenter] unregisterHotKey: globalHotKey];
[globalHotKey release];
globalHotKey = nil;
}
globalHotKey = [[PTHotKey alloc] initWithIdentifier:#"SRTest"
keyCombo:[PTKeyCombo keyComboWithKeyCode:[shortcutRecorder keyCombo].code
modifiers:[shortcutRecorder cocoaToCarbonFlags: [shortcutRecorder keyCombo].flags]]];
[globalHotKey setTarget: self];
[globalHotKey setAction: #selector(hitHotKey:)];
[[PTHotKeyCenter sharedCenter] registerHotKey: globalHotKey];
The only thing left to do is the hotkey handler:
- (void)hitHotKey:(PTHotKey *)hotKey
{
NSLog(#"Hotkey pressed!");
}
You could easily save the hotkey settings to the UserDefaults to load them every time you application starts.
Actually I have declared my shortcutRecorder outlet in my Preferences.h. Then in the Identity Inspector I put "Preferences" as Custom Class for Files Owner in and I connect the delegate to my Shortcut Recorder... but the keyComboDidChange is never called ... I don't understand why -.-

Resources