Capturing undo and redo edit text groups in NSTextView - objective-c

I'm writing a plugin for an existing app and I need to capture the modified text and range affected by the undo and redo action. I am able to access the NSUndoManager and NSTextView the app has created and I am able to register for notifications. Is there a way to leverage these elements to grab the group of text that is undone/redone?

I haven't done this, so I'm only going by the docs/knowledge.
Because you have access to the textview you can become a textview delegate. You'll then receive useful messages...
Before the text changes:
textView:shouldChangeTextInRange:replacementString:
textView:shouldChangeTextInRanges:replacementStrings:
textView:shouldChangeTypingAttributes:toAttributes:
After the text changes:
textViewDidChangeTypingAttributes:
I don't know if you'll receive these changes (does UndoManager bypass this stuff?), but you may. In any event you can query selection settings while handling the previous messages.
Before the selection changes:
textView:willChangeSelectionFromCharacterRange:toCharacterRange:
textView:willChangeSelectionFromCharacterRanges:toCharacterRanges:
After:
textViewDidChangeSelection:
UndoManager should tell you that it is in the process of performing an Undo, which mean that you can differentiate ordinary changes from undo-based changes.
This seems like enough to go on, I hope it helps.

Related

NSDocument isDocumentEdited and undo

In a document-based application, for every user action I add an entry in the UndoManager, including selection, meaning that an 'undo' will restore the previous selection.
Some times a user will open a document, view some items by selecting them and then close the document, but even if the user didn't 'alter' anything, the user is asked to save changes, this is annoying and can be misleading to the user.
It seems that the document's dirty flag (isDocumentEdited) is automatically set when registering undo actions, but is there a way I can prevent this for some particualr undoable actions, such as selection change?
Thanks!
You should be able to call -setActionIsDiscardable:YES on the undoManager when you register actions that don’t need to be saved.

How to update a NSTextField Value during an action?

I am running a lengthly task in an Action and I would like to have a display of where I am at. For that I created a Text Field and I tried it with setStringValue:
[textField setStingValue: [NSSting stringWithFormat:#"%ld",currentValue]]
The code works but unfortunately it is not updating the NSTextField after every iteration but rather when the whole Action is done.
What am I doing wrong?
This is because applications with the Cocoa framework use an event loop to perform operations, and events occur in a completely serial fashion.
An event is basically any kind of action that the framework designer could not predict or found convenient to have run in a delayed manner. Since you can't predict when clicks will be performed, they need to be considered events; and for efficiency reasons (since you don't want to repaint a component multiple times if you don't need to), the repaint actions are events too.
Your action runs in response to a user event (for instance, a click on a button is an event) and therefore blocks all other events waiting in the queue until it's complete. However, components are repainted in response to a different, framework-triggered event, and as such the text field must wait until your action completes to repaint itself. This is why you cannot visually change the value of a text field from inside an action.
In order to notify your user of the progress of your task, you'll need to run it on a different thread. There's a lot to say about threads, so you should probably read some about them. I'm also sure that there are plenty of examples of how to run a long action in a background thread and update the UI accordingly for Cocoa all over the Internet.
When you click on a UI component, and it enters the Action block, the code is running on the main thread, the same thread that is painting the UI. If you run a long running operation in that block, it isn't going to paint until you are done because it is busy doing whatever you have it doing - you have hijacked the paint thread.
As said elsewhere, you need to spawn another thread, and then have the new thread perform the long running operation, and occasionally send messages to have the UI be updated by the main thread.
As a next step, go read the Apple documentation on NSThread, specifically:
+ (void)detachNewThreadSelector:(SEL)aSelector toTarget:(id)aTarget withObject:(id)anArgument
Be aware that threading is a non-trivial domain area, and be ready for some wierd behavior if you aren't careful.

Domain object "Changed" event fires multiple times?

I have a custom window to display various objects from the input tree. Once an object is checked on the input tree and displayed in the window, I subscribe to the object's "Changed" event. I am absolutely sure that I did not subscribe to the event more than once. The problem I'm seeing is when I make changes to the object, such as color, the event fires 3 times.
pseudocode:
- Draw a borehole in a custom window<br />
- borehole.Changed += borehole_Changed<br />
- Change the color of the borehole<br />
- See event fire 3 times (I just added debug prints)
Edit:
I have noticed that just opening the settings and clicking "ok" without changing anything causes the 3 events to be fired. So now I assume it actually has nothing to do with changing the color.
I have also tried checking the DomainObjectChangeEventArgs PropertyNames property, but that is always empty.
It looks like the Changed event is being phased out in favor of ColorInfo.ColorChanged, ImageInfo.ImageChanged, etc. In fact, the Changed event is not fired anymore as of 2011 for color changes. Turns out that there were other things underlying that caused the event to fire.
Anyways, to make a long story short, don't use the Changed event.
I can't confirm this behavior, I only get one event - can you please tell us which version you are using? And - are you changing the color via code or via the settings page?
In my case I got a single callback in both cases.
Thanks
I am getting one event also. I am using 2011.1 and the ColorChanged event from the ColorInfo for the Borehole.
In other cases I do see multiple events, but these happen when the data changed triggers changes to other Borehole related data. For example, changing the KB will cause lots of underlying calculations and result in multiple event triggers.

QInputDialog like thing in Cocoa/Xcode?

I'm fairly new to Xcode and Cocoa/Objective-C and I'm trying to implement something as simple as a QInputDialog that can be re-used throughout the program - with a unique message to the user each time it is launched and return a string as a result.
I have searched the web and found multiple methods but nothing seems to be quite clear or concise - well enough for me to understand anyway.
Is there anything out there as simple as:
Create/Launch a window from a method with a new message label to the user in the form of a string.
Has an NSTextField to receive the users input.
Close the window and return the string from the text field (if accepted) to the calling method.
??
Modal prompts for input are very un-Mac-like. It's like smashing the user's face in with a cricket bat and yelling “TELL ME THE ANSWER NOW!”
The correct solution is to put your text field into a non-modal window, so that the value is already ready when the user invokes whatever action needs the value. Beep and show the “hey, you forgot this” icon if the user hasn't filled in the field and you need a value there. If the field is not relevant in the window the user starts the action from, or if you're going to need several facts as input, then show another window, non-modally, with its own window controller, to take in all the input you'll need for the action.
A separate, non-modal window will also enable the user to fill out and/or perform multiple such actions in parallel.
If you must demand the value with a modal dialog, you can and should make it a sheet, but you'll still need to build the panel and its contents from scratch in IB or code.
See also the Sheet Programming Guide and the chapter on windows in the Human Interface Guidelines.

How can I tell if a Balloon tip in the system tray has closed?

I have an application that uses a NotifyIcon in the tray to hide/restore the application, as well as pop up notices to the user of application events. My application has a notification queue, and I use the NotificationIcon.BalloonTipClosed event to determine when to reset the balloon and show the next notification (if there's one in the queue).
This method seems to work great in both usual causes (user lets the balloon close itself when it times out, and user clicks "X" in balloon to force it to close), but there's a third case where BalloonTipClosed doesn't get called:
Notification balloon pops up
While it's visible, user right-clicks on notification icon to bring up context menu, causing the balloon to disappear
The BalloonTipClosed event doesn't get triggered in this instance - I figure it's a bug in the framework (I'm using 2.0), but does anybody have an idea around this? If I don't get this event, my application always thinks there's a balloon visible (I have a boolean that prevents it from displaying multiple balloons at once), and it will never show another icon again, as long as it's running.
This belongs as a comment to Aarons answer, but I am not allowed to comment yet.
If you handle the BalloonTipClicked and MouseClick events on the NotifyIcon (as well as the BalloonTipClosed) then you can capture all the ways the balloon can close. The only thing you have to be aware of is that several scenerios will trigger multiple events, so be sure to code around that (something like isClosed = true, and then reset that when a new balloon is displayed).
In the event handler for the BalloonTipClicked Event, I would check to see if the right mouse button was clicked, and if it was set the boolean to false.
Here's what I ended up doing, though I don't particularly like this solution. I added a second timer to the form and set it for 10 seconds. When a notification pops up (when I pop one), I start the timer, and then in BalloonTipClosed, I stop it. If the timer ticks (meaning that BalloonTipClosed hasn't run yet), I display the next tip manually.
The result is that if it hasn't fired yet, I take care of it. However, I'm open to better solutions if anybody has one.
I think this post from Raymond Chen about balloon notifications may help you:
http://blogs.msdn.com/oldnewthing/archive/2009/05/04/9585032.aspx